diff --git a/.github/boring-cyborg.yml b/.github/boring-cyborg.yml index 6a22b5bf5..e627b9232 100644 --- a/.github/boring-cyborg.yml +++ b/.github/boring-cyborg.yml @@ -21,9 +21,6 @@ labelPRBasedOnFilePath: area/batch: - libraries/src/AWS.Lambda.Powertools.Batch/* - libraries/src/AWS.Lambda.Powertools.Batch/**/* - area/metrics-aspnetcore: - - libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/* - - libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/**/* documentation: - docs/* diff --git a/.gitignore b/.gitignore index 76b1dfce6..3e593c9af 100644 --- a/.gitignore +++ b/.gitignore @@ -12,8 +12,6 @@ deploy/** .vs/ .aws-sam -node_modules/* - examples/SimpleLambda/.aws-sam examples/SimpleLambda/samconfig.toml @@ -21,13 +19,7 @@ AWS.Lambda.Powertools.sln.DotSettings.user [Oo]bj/** [Bb]in/** .DS_Store -.cache dist/ site/ -samconfig.toml - -.kiro -.claude -.amazonq -.github/instructions +samconfig.toml \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 7f7def166..da8dca0c5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,4 @@ -# 6.0-bullseye-slim -FROM mcr.microsoft.com/dotnet/sdk@sha256:fc71510497ce2ec3575359068b9c7b1b9f449cfdb0371b5c71a939963a2fedfd AS build-image +FROM mcr.microsoft.com/dotnet/sdk:6.0-bullseye-slim AS build-image ARG FUNCTION_DIR="/build" ARG SAM_BUILD_MODE="run" @@ -20,7 +19,7 @@ WORKDIR $FUNCTION_DIR/examples/SimpleLambda/src/HelloWorld/ RUN if [ "$SAM_BUILD_MODE" = "debug" ]; then dotnet lambda package --configuration Debug; else dotnet lambda package --configuration Release; fi RUN if [ "$SAM_BUILD_MODE" = "debug" ]; then cp -r /bin/Debug/net6.0/publish/* /build/build_artifacts; else cp -r bin/Release/net6.0/publish/* /build/build_artifacts; fi -FROM public.ecr.aws/lambda/dotnet@sha256:ec61a7f638e2a0c86d75204117cc7710bcdc70222ffc777e3fc1458287b09834 +FROM public.ecr.aws/lambda/dotnet:6 COPY --from=build-image /build/build_artifacts/ /var/task/ # Command can be overwritten by providing a different command in the template directly. diff --git a/apidocs/docfx.json b/apidocs/docfx.json index 7c1c0c1fe..4576b299c 100644 --- a/apidocs/docfx.json +++ b/apidocs/docfx.json @@ -9,7 +9,7 @@ ], "dest": "api", "properties": { - "TargetFramework": "net8.0" + "TargetFramework": "net6.0" }, "disableGitFeatures": false, "disableDefaultFilter": false @@ -48,9 +48,6 @@ "noLangKeyword": false, "keepFileLink": false, "cleanupCacheHistory": false, - "disableGitFeatures": false, - "sitemap": { - "baseUrl": "https://docs.powertools.aws.dev/lambda/dotnet/api/api" - } + "disableGitFeatures": false } } diff --git a/docs/Dockerfile b/docs/Dockerfile index aea0f721d..4f576cc60 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -1,5 +1,3 @@ # v9.1.18 -FROM squidfunk/mkdocs-material@sha256:209b62dd9530163cc5cf9a49853b5bb8570ffb3f3b5fe4eadc1d319bbda5ce2f - -COPY requirements.txt /tmp/ -RUN pip install --require-hashes -r /tmp/requirements.txt +FROM squidfunk/mkdocs-material@sha256:c62453b1ba229982c6325a71165c1a3007c11bd3dd470e7a1446c5783bd145b4 +RUN pip install mkdocs-git-revision-date-plugin diff --git a/docs/requirements.in b/docs/requirements.in deleted file mode 100644 index 2b9323e7b..000000000 --- a/docs/requirements.in +++ /dev/null @@ -1,2 +0,0 @@ -mkdocs-git-revision-date-plugin==0.3.2 -mkdocs-llmstxt==0.2.0 diff --git a/docs/requirements.txt b/docs/requirements.txt deleted file mode 100644 index 6f492883d..000000000 --- a/docs/requirements.txt +++ /dev/null @@ -1,302 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.10 -# by the following command: -# -# pip-compile --generate-hashes --output-file=requirements.txt requirements.in -# -beautifulsoup4==4.13.4 \ - --hash=sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b \ - --hash=sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195 - # via - # markdownify - # mkdocs-llmstxt -click==8.1.8 \ - --hash=sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2 \ - --hash=sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a - # via mkdocs -ghp-import==2.1.0 \ - --hash=sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619 \ - --hash=sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343 - # via mkdocs -gitdb==4.0.12 \ - --hash=sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571 \ - --hash=sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf - # via gitpython -gitpython==3.1.44 \ - --hash=sha256:9e0e10cda9bed1ee64bc9a6de50e7e38a9c9943241cd7f585f6df3ed28011110 \ - --hash=sha256:c87e30b26253bf5418b01b0660f818967f3c503193838337fe5e573331249269 - # via mkdocs-git-revision-date-plugin -jinja2==3.1.6 \ - --hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \ - --hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67 - # via - # mkdocs - # mkdocs-git-revision-date-plugin -markdown==3.7 \ - --hash=sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2 \ - --hash=sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803 - # via mkdocs -markdown-it-py==3.0.0 \ - --hash=sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1 \ - --hash=sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb - # via mdformat -markdownify==1.1.0 \ - --hash=sha256:32a5a08e9af02c8a6528942224c91b933b4bd2c7d078f9012943776fc313eeef \ - --hash=sha256:449c0bbbf1401c5112379619524f33b63490a8fa479456d41de9dc9e37560ebd - # via mkdocs-llmstxt -markupsafe==3.0.2 \ - --hash=sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4 \ - --hash=sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30 \ - --hash=sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0 \ - --hash=sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9 \ - --hash=sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396 \ - --hash=sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13 \ - --hash=sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028 \ - --hash=sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca \ - --hash=sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557 \ - --hash=sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832 \ - --hash=sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0 \ - --hash=sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b \ - --hash=sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579 \ - --hash=sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a \ - --hash=sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c \ - --hash=sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff \ - --hash=sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c \ - --hash=sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22 \ - --hash=sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094 \ - --hash=sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb \ - --hash=sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e \ - --hash=sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5 \ - --hash=sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a \ - --hash=sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d \ - --hash=sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a \ - --hash=sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b \ - --hash=sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8 \ - --hash=sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225 \ - --hash=sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c \ - --hash=sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144 \ - --hash=sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f \ - --hash=sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87 \ - --hash=sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d \ - --hash=sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93 \ - --hash=sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf \ - --hash=sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158 \ - --hash=sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84 \ - --hash=sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb \ - --hash=sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48 \ - --hash=sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171 \ - --hash=sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c \ - --hash=sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6 \ - --hash=sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd \ - --hash=sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d \ - --hash=sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1 \ - --hash=sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d \ - --hash=sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca \ - --hash=sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a \ - --hash=sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29 \ - --hash=sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe \ - --hash=sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798 \ - --hash=sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c \ - --hash=sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8 \ - --hash=sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f \ - --hash=sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f \ - --hash=sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a \ - --hash=sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178 \ - --hash=sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0 \ - --hash=sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79 \ - --hash=sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430 \ - --hash=sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50 - # via - # jinja2 - # mkdocs -mdformat==0.7.22 \ - --hash=sha256:61122637c9e1d9be1329054f3fa216559f0d1f722b7919b060a8c2a4ae1850e5 \ - --hash=sha256:eef84fa8f233d3162734683c2a8a6222227a229b9206872e6139658d99acb1ea - # via mkdocs-llmstxt -mdurl==0.1.2 \ - --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ - --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba - # via markdown-it-py -mergedeep==1.3.4 \ - --hash=sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8 \ - --hash=sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307 - # via - # mkdocs - # mkdocs-get-deps -mkdocs==1.6.1 \ - --hash=sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2 \ - --hash=sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e - # via mkdocs-git-revision-date-plugin -mkdocs-get-deps==0.2.0 \ - --hash=sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c \ - --hash=sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134 - # via mkdocs -mkdocs-git-revision-date-plugin==0.3.2 \ - --hash=sha256:2e67956cb01823dd2418e2833f3623dee8604cdf223bddd005fe36226a56f6ef - # via -r requirements.in -mkdocs-llmstxt==0.2.0 \ - --hash=sha256:104f10b8101167d6baf7761942b4743869be3d8f8a8d909f4e9e0b63307f709e \ - --hash=sha256:907de892e0c8be74002e8b4d553820c2b5bbcf03cc303b95c8bca48fb49c1a29 - # via -r requirements.in -packaging==24.2 \ - --hash=sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759 \ - --hash=sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f - # via mkdocs -pathspec==0.12.1 \ - --hash=sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08 \ - --hash=sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712 - # via mkdocs -platformdirs==4.3.6 \ - --hash=sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907 \ - --hash=sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb - # via mkdocs-get-deps -python-dateutil==2.9.0.post0 \ - --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ - --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 - # via ghp-import -pyyaml==6.0.2 \ - --hash=sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff \ - --hash=sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48 \ - --hash=sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086 \ - --hash=sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e \ - --hash=sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133 \ - --hash=sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5 \ - --hash=sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484 \ - --hash=sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee \ - --hash=sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5 \ - --hash=sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68 \ - --hash=sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a \ - --hash=sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf \ - --hash=sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99 \ - --hash=sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8 \ - --hash=sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85 \ - --hash=sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19 \ - --hash=sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc \ - --hash=sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a \ - --hash=sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1 \ - --hash=sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317 \ - --hash=sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c \ - --hash=sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631 \ - --hash=sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d \ - --hash=sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652 \ - --hash=sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5 \ - --hash=sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e \ - --hash=sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b \ - --hash=sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8 \ - --hash=sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476 \ - --hash=sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706 \ - --hash=sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563 \ - --hash=sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237 \ - --hash=sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b \ - --hash=sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083 \ - --hash=sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180 \ - --hash=sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425 \ - --hash=sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e \ - --hash=sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f \ - --hash=sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725 \ - --hash=sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183 \ - --hash=sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab \ - --hash=sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774 \ - --hash=sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725 \ - --hash=sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e \ - --hash=sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5 \ - --hash=sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d \ - --hash=sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290 \ - --hash=sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44 \ - --hash=sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed \ - --hash=sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4 \ - --hash=sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba \ - --hash=sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12 \ - --hash=sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4 - # via - # mkdocs - # mkdocs-get-deps - # pyyaml-env-tag -pyyaml-env-tag==0.1 \ - --hash=sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb \ - --hash=sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069 - # via mkdocs -six==1.17.0 \ - --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \ - --hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81 - # via - # markdownify - # python-dateutil -smmap==5.0.2 \ - --hash=sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5 \ - --hash=sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e - # via gitdb -soupsieve==2.7 \ - --hash=sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4 \ - --hash=sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a - # via beautifulsoup4 -tomli==2.2.1 \ - --hash=sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6 \ - --hash=sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd \ - --hash=sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c \ - --hash=sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b \ - --hash=sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8 \ - --hash=sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6 \ - --hash=sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77 \ - --hash=sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff \ - --hash=sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea \ - --hash=sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192 \ - --hash=sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249 \ - --hash=sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee \ - --hash=sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4 \ - --hash=sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98 \ - --hash=sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8 \ - --hash=sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4 \ - --hash=sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281 \ - --hash=sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744 \ - --hash=sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69 \ - --hash=sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13 \ - --hash=sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140 \ - --hash=sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e \ - --hash=sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e \ - --hash=sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc \ - --hash=sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff \ - --hash=sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec \ - --hash=sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2 \ - --hash=sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222 \ - --hash=sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106 \ - --hash=sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272 \ - --hash=sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a \ - --hash=sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7 - # via mdformat -typing-extensions==4.13.2 \ - --hash=sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c \ - --hash=sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef - # via beautifulsoup4 -watchdog==6.0.0 \ - --hash=sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a \ - --hash=sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2 \ - --hash=sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f \ - --hash=sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c \ - --hash=sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c \ - --hash=sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c \ - --hash=sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0 \ - --hash=sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13 \ - --hash=sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134 \ - --hash=sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa \ - --hash=sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e \ - --hash=sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379 \ - --hash=sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a \ - --hash=sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11 \ - --hash=sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282 \ - --hash=sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b \ - --hash=sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f \ - --hash=sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c \ - --hash=sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112 \ - --hash=sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948 \ - --hash=sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881 \ - --hash=sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860 \ - --hash=sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3 \ - --hash=sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680 \ - --hash=sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26 \ - --hash=sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26 \ - --hash=sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e \ - --hash=sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8 \ - --hash=sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c \ - --hash=sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2 - # via mkdocs diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index 93b397f56..f24b32faa 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -33,9 +33,3 @@ [data-md-color-scheme="slate"] { --md-typeset-a-color: rgb(28, 152, 152) } - -/*.md-nav__link[for] {*/ -/* font-weight: bold*/ -/*}*/ -.md-nav__link[for] { color: var(--md-default-fg-color) !important; } - diff --git a/dotnet6-deprecation-only.patch b/dotnet6-deprecation-only.patch new file mode 100644 index 000000000..971f6f8f3 --- /dev/null +++ b/dotnet6-deprecation-only.patch @@ -0,0 +1,56215 @@ +diff --git a/.github/boring-cyborg.yml b/.github/boring-cyborg.yml +index 6a22b5bf..e627b923 100644 +--- a/.github/boring-cyborg.yml ++++ b/.github/boring-cyborg.yml +@@ -21,9 +21,6 @@ labelPRBasedOnFilePath: + area/batch: + - libraries/src/AWS.Lambda.Powertools.Batch/* + - libraries/src/AWS.Lambda.Powertools.Batch/**/* +- area/metrics-aspnetcore: +- - libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/* +- - libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/**/* + + documentation: + - docs/* +diff --git a/.gitignore b/.gitignore +index 76b1dfce..3e593c9a 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -12,8 +12,6 @@ deploy/** + .vs/ + .aws-sam + +-node_modules/* +- + examples/SimpleLambda/.aws-sam + examples/SimpleLambda/samconfig.toml + +@@ -21,13 +19,7 @@ AWS.Lambda.Powertools.sln.DotSettings.user + [Oo]bj/** + [Bb]in/** + .DS_Store +-.cache + + dist/ + site/ +-samconfig.toml +- +-.kiro +-.claude +-.amazonq +-.github/instructions ++samconfig.toml +\ No newline at end of file +diff --git a/Dockerfile b/Dockerfile +index 7f7def16..da8dca0c 100644 +--- a/Dockerfile ++++ b/Dockerfile +@@ -1,5 +1,4 @@ +-# 6.0-bullseye-slim +-FROM mcr.microsoft.com/dotnet/sdk@sha256:fc71510497ce2ec3575359068b9c7b1b9f449cfdb0371b5c71a939963a2fedfd AS build-image ++FROM mcr.microsoft.com/dotnet/sdk:6.0-bullseye-slim AS build-image + + ARG FUNCTION_DIR="/build" + ARG SAM_BUILD_MODE="run" +@@ -20,7 +19,7 @@ WORKDIR $FUNCTION_DIR/examples/SimpleLambda/src/HelloWorld/ + RUN if [ "$SAM_BUILD_MODE" = "debug" ]; then dotnet lambda package --configuration Debug; else dotnet lambda package --configuration Release; fi + RUN if [ "$SAM_BUILD_MODE" = "debug" ]; then cp -r /bin/Debug/net6.0/publish/* /build/build_artifacts; else cp -r bin/Release/net6.0/publish/* /build/build_artifacts; fi + +-FROM public.ecr.aws/lambda/dotnet@sha256:ec61a7f638e2a0c86d75204117cc7710bcdc70222ffc777e3fc1458287b09834 ++FROM public.ecr.aws/lambda/dotnet:6 + + COPY --from=build-image /build/build_artifacts/ /var/task/ + # Command can be overwritten by providing a different command in the template directly. +diff --git a/apidocs/docfx.json b/apidocs/docfx.json +index 7c1c0c1f..4576b299 100644 +--- a/apidocs/docfx.json ++++ b/apidocs/docfx.json +@@ -9,7 +9,7 @@ + ], + "dest": "api", + "properties": { +- "TargetFramework": "net8.0" ++ "TargetFramework": "net6.0" + }, + "disableGitFeatures": false, + "disableDefaultFilter": false +@@ -48,9 +48,6 @@ + "noLangKeyword": false, + "keepFileLink": false, + "cleanupCacheHistory": false, +- "disableGitFeatures": false, +- "sitemap": { +- "baseUrl": "https://docs.powertools.aws.dev/lambda/dotnet/api/api" +- } ++ "disableGitFeatures": false + } + } +diff --git a/docs/Dockerfile b/docs/Dockerfile +index aea0f721..4f576cc6 100644 +--- a/docs/Dockerfile ++++ b/docs/Dockerfile +@@ -1,5 +1,3 @@ + # v9.1.18 +-FROM squidfunk/mkdocs-material@sha256:209b62dd9530163cc5cf9a49853b5bb8570ffb3f3b5fe4eadc1d319bbda5ce2f +- +-COPY requirements.txt /tmp/ +-RUN pip install --require-hashes -r /tmp/requirements.txt ++FROM squidfunk/mkdocs-material@sha256:c62453b1ba229982c6325a71165c1a3007c11bd3dd470e7a1446c5783bd145b4 ++RUN pip install mkdocs-git-revision-date-plugin +diff --git a/docs/requirements.in b/docs/requirements.in +deleted file mode 100644 +index 2b9323e7..00000000 +--- a/docs/requirements.in ++++ /dev/null +@@ -1,2 +0,0 @@ +-mkdocs-git-revision-date-plugin==0.3.2 +-mkdocs-llmstxt==0.2.0 +diff --git a/docs/requirements.txt b/docs/requirements.txt +deleted file mode 100644 +index 6f492883..00000000 +--- a/docs/requirements.txt ++++ /dev/null +@@ -1,302 +0,0 @@ +-# +-# This file is autogenerated by pip-compile with Python 3.10 +-# by the following command: +-# +-# pip-compile --generate-hashes --output-file=requirements.txt requirements.in +-# +-beautifulsoup4==4.13.4 \ +- --hash=sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b \ +- --hash=sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195 +- # via +- # markdownify +- # mkdocs-llmstxt +-click==8.1.8 \ +- --hash=sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2 \ +- --hash=sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a +- # via mkdocs +-ghp-import==2.1.0 \ +- --hash=sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619 \ +- --hash=sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343 +- # via mkdocs +-gitdb==4.0.12 \ +- --hash=sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571 \ +- --hash=sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf +- # via gitpython +-gitpython==3.1.44 \ +- --hash=sha256:9e0e10cda9bed1ee64bc9a6de50e7e38a9c9943241cd7f585f6df3ed28011110 \ +- --hash=sha256:c87e30b26253bf5418b01b0660f818967f3c503193838337fe5e573331249269 +- # via mkdocs-git-revision-date-plugin +-jinja2==3.1.6 \ +- --hash=sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d \ +- --hash=sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67 +- # via +- # mkdocs +- # mkdocs-git-revision-date-plugin +-markdown==3.7 \ +- --hash=sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2 \ +- --hash=sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803 +- # via mkdocs +-markdown-it-py==3.0.0 \ +- --hash=sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1 \ +- --hash=sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb +- # via mdformat +-markdownify==1.1.0 \ +- --hash=sha256:32a5a08e9af02c8a6528942224c91b933b4bd2c7d078f9012943776fc313eeef \ +- --hash=sha256:449c0bbbf1401c5112379619524f33b63490a8fa479456d41de9dc9e37560ebd +- # via mkdocs-llmstxt +-markupsafe==3.0.2 \ +- --hash=sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4 \ +- --hash=sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30 \ +- --hash=sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0 \ +- --hash=sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9 \ +- --hash=sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396 \ +- --hash=sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13 \ +- --hash=sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028 \ +- --hash=sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca \ +- --hash=sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557 \ +- --hash=sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832 \ +- --hash=sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0 \ +- --hash=sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b \ +- --hash=sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579 \ +- --hash=sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a \ +- --hash=sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c \ +- --hash=sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff \ +- --hash=sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c \ +- --hash=sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22 \ +- --hash=sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094 \ +- --hash=sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb \ +- --hash=sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e \ +- --hash=sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5 \ +- --hash=sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a \ +- --hash=sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d \ +- --hash=sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a \ +- --hash=sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b \ +- --hash=sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8 \ +- --hash=sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225 \ +- --hash=sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c \ +- --hash=sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144 \ +- --hash=sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f \ +- --hash=sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87 \ +- --hash=sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d \ +- --hash=sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93 \ +- --hash=sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf \ +- --hash=sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158 \ +- --hash=sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84 \ +- --hash=sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb \ +- --hash=sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48 \ +- --hash=sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171 \ +- --hash=sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c \ +- --hash=sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6 \ +- --hash=sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd \ +- --hash=sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d \ +- --hash=sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1 \ +- --hash=sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d \ +- --hash=sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca \ +- --hash=sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a \ +- --hash=sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29 \ +- --hash=sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe \ +- --hash=sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798 \ +- --hash=sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c \ +- --hash=sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8 \ +- --hash=sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f \ +- --hash=sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f \ +- --hash=sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a \ +- --hash=sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178 \ +- --hash=sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0 \ +- --hash=sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79 \ +- --hash=sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430 \ +- --hash=sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50 +- # via +- # jinja2 +- # mkdocs +-mdformat==0.7.22 \ +- --hash=sha256:61122637c9e1d9be1329054f3fa216559f0d1f722b7919b060a8c2a4ae1850e5 \ +- --hash=sha256:eef84fa8f233d3162734683c2a8a6222227a229b9206872e6139658d99acb1ea +- # via mkdocs-llmstxt +-mdurl==0.1.2 \ +- --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ +- --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba +- # via markdown-it-py +-mergedeep==1.3.4 \ +- --hash=sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8 \ +- --hash=sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307 +- # via +- # mkdocs +- # mkdocs-get-deps +-mkdocs==1.6.1 \ +- --hash=sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2 \ +- --hash=sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e +- # via mkdocs-git-revision-date-plugin +-mkdocs-get-deps==0.2.0 \ +- --hash=sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c \ +- --hash=sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134 +- # via mkdocs +-mkdocs-git-revision-date-plugin==0.3.2 \ +- --hash=sha256:2e67956cb01823dd2418e2833f3623dee8604cdf223bddd005fe36226a56f6ef +- # via -r requirements.in +-mkdocs-llmstxt==0.2.0 \ +- --hash=sha256:104f10b8101167d6baf7761942b4743869be3d8f8a8d909f4e9e0b63307f709e \ +- --hash=sha256:907de892e0c8be74002e8b4d553820c2b5bbcf03cc303b95c8bca48fb49c1a29 +- # via -r requirements.in +-packaging==24.2 \ +- --hash=sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759 \ +- --hash=sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f +- # via mkdocs +-pathspec==0.12.1 \ +- --hash=sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08 \ +- --hash=sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712 +- # via mkdocs +-platformdirs==4.3.6 \ +- --hash=sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907 \ +- --hash=sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb +- # via mkdocs-get-deps +-python-dateutil==2.9.0.post0 \ +- --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ +- --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 +- # via ghp-import +-pyyaml==6.0.2 \ +- --hash=sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff \ +- --hash=sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48 \ +- --hash=sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086 \ +- --hash=sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e \ +- --hash=sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133 \ +- --hash=sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5 \ +- --hash=sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484 \ +- --hash=sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee \ +- --hash=sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5 \ +- --hash=sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68 \ +- --hash=sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a \ +- --hash=sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf \ +- --hash=sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99 \ +- --hash=sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8 \ +- --hash=sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85 \ +- --hash=sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19 \ +- --hash=sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc \ +- --hash=sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a \ +- --hash=sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1 \ +- --hash=sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317 \ +- --hash=sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c \ +- --hash=sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631 \ +- --hash=sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d \ +- --hash=sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652 \ +- --hash=sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5 \ +- --hash=sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e \ +- --hash=sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b \ +- --hash=sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8 \ +- --hash=sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476 \ +- --hash=sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706 \ +- --hash=sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563 \ +- --hash=sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237 \ +- --hash=sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b \ +- --hash=sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083 \ +- --hash=sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180 \ +- --hash=sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425 \ +- --hash=sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e \ +- --hash=sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f \ +- --hash=sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725 \ +- --hash=sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183 \ +- --hash=sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab \ +- --hash=sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774 \ +- --hash=sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725 \ +- --hash=sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e \ +- --hash=sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5 \ +- --hash=sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d \ +- --hash=sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290 \ +- --hash=sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44 \ +- --hash=sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed \ +- --hash=sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4 \ +- --hash=sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba \ +- --hash=sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12 \ +- --hash=sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4 +- # via +- # mkdocs +- # mkdocs-get-deps +- # pyyaml-env-tag +-pyyaml-env-tag==0.1 \ +- --hash=sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb \ +- --hash=sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069 +- # via mkdocs +-six==1.17.0 \ +- --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \ +- --hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81 +- # via +- # markdownify +- # python-dateutil +-smmap==5.0.2 \ +- --hash=sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5 \ +- --hash=sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e +- # via gitdb +-soupsieve==2.7 \ +- --hash=sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4 \ +- --hash=sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a +- # via beautifulsoup4 +-tomli==2.2.1 \ +- --hash=sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6 \ +- --hash=sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd \ +- --hash=sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c \ +- --hash=sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b \ +- --hash=sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8 \ +- --hash=sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6 \ +- --hash=sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77 \ +- --hash=sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff \ +- --hash=sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea \ +- --hash=sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192 \ +- --hash=sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249 \ +- --hash=sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee \ +- --hash=sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4 \ +- --hash=sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98 \ +- --hash=sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8 \ +- --hash=sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4 \ +- --hash=sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281 \ +- --hash=sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744 \ +- --hash=sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69 \ +- --hash=sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13 \ +- --hash=sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140 \ +- --hash=sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e \ +- --hash=sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e \ +- --hash=sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc \ +- --hash=sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff \ +- --hash=sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec \ +- --hash=sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2 \ +- --hash=sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222 \ +- --hash=sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106 \ +- --hash=sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272 \ +- --hash=sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a \ +- --hash=sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7 +- # via mdformat +-typing-extensions==4.13.2 \ +- --hash=sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c \ +- --hash=sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef +- # via beautifulsoup4 +-watchdog==6.0.0 \ +- --hash=sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a \ +- --hash=sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2 \ +- --hash=sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f \ +- --hash=sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c \ +- --hash=sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c \ +- --hash=sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c \ +- --hash=sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0 \ +- --hash=sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13 \ +- --hash=sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134 \ +- --hash=sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa \ +- --hash=sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e \ +- --hash=sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379 \ +- --hash=sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a \ +- --hash=sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11 \ +- --hash=sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282 \ +- --hash=sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b \ +- --hash=sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f \ +- --hash=sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c \ +- --hash=sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112 \ +- --hash=sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948 \ +- --hash=sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881 \ +- --hash=sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860 \ +- --hash=sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3 \ +- --hash=sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680 \ +- --hash=sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26 \ +- --hash=sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26 \ +- --hash=sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e \ +- --hash=sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8 \ +- --hash=sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c \ +- --hash=sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2 +- # via mkdocs +diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css +index 93b397f5..f24b32fa 100644 +--- a/docs/stylesheets/extra.css ++++ b/docs/stylesheets/extra.css +@@ -33,9 +33,3 @@ + [data-md-color-scheme="slate"] { + --md-typeset-a-color: rgb(28, 152, 152) + } +- +-/*.md-nav__link[for] {*/ +-/* font-weight: bold*/ +-/*}*/ +-.md-nav__link[for] { color: var(--md-default-fg-color) !important; } +- +diff --git a/examples/AOT/AOT_Logging/src/AOT_Logging/AOT_Logging.csproj b/examples/AOT/AOT_Logging/src/AOT_Logging/AOT_Logging.csproj +index d06a0a53..97cc041c 100644 +--- a/examples/AOT/AOT_Logging/src/AOT_Logging/AOT_Logging.csproj ++++ b/examples/AOT/AOT_Logging/src/AOT_Logging/AOT_Logging.csproj +@@ -18,8 +18,8 @@ + + + +- +- +- ++ ++ ++ + + +diff --git a/examples/AOT/AOT_Logging/test/AOT_Logging.Tests/AOT_Logging.Tests.csproj b/examples/AOT/AOT_Logging/test/AOT_Logging.Tests/AOT_Logging.Tests.csproj +index cba0ba03..3d996e24 100644 +--- a/examples/AOT/AOT_Logging/test/AOT_Logging.Tests/AOT_Logging.Tests.csproj ++++ b/examples/AOT/AOT_Logging/test/AOT_Logging.Tests/AOT_Logging.Tests.csproj +@@ -6,7 +6,7 @@ + true + + +- ++ + + + +diff --git a/examples/AOT/AOT_Metrics/src/AOT_Metrics/AOT_Metrics.csproj b/examples/AOT/AOT_Metrics/src/AOT_Metrics/AOT_Metrics.csproj +index 74caf11d..ec08ac52 100644 +--- a/examples/AOT/AOT_Metrics/src/AOT_Metrics/AOT_Metrics.csproj ++++ b/examples/AOT/AOT_Metrics/src/AOT_Metrics/AOT_Metrics.csproj +@@ -18,8 +18,8 @@ + + + +- +- +- ++ ++ ++ + + +\ No newline at end of file +diff --git a/examples/AOT/AOT_Metrics/test/AOT_Metrics.Tests/AOT_Metrics.Tests.csproj b/examples/AOT/AOT_Metrics/test/AOT_Metrics.Tests/AOT_Metrics.Tests.csproj +index fb935a9a..34fa6d4c 100644 +--- a/examples/AOT/AOT_Metrics/test/AOT_Metrics.Tests/AOT_Metrics.Tests.csproj ++++ b/examples/AOT/AOT_Metrics/test/AOT_Metrics.Tests/AOT_Metrics.Tests.csproj +@@ -6,7 +6,7 @@ + true + + +- ++ + + + +diff --git a/examples/AOT/AOT_Tracing/src/AOT_Tracing/AOT_Tracing.csproj b/examples/AOT/AOT_Tracing/src/AOT_Tracing/AOT_Tracing.csproj +index 6e92d331..20100166 100644 +--- a/examples/AOT/AOT_Tracing/src/AOT_Tracing/AOT_Tracing.csproj ++++ b/examples/AOT/AOT_Tracing/src/AOT_Tracing/AOT_Tracing.csproj +@@ -18,8 +18,8 @@ + + + +- +- +- ++ ++ ++ + + +\ No newline at end of file +diff --git a/examples/AOT/AOT_Tracing/test/AOT_Tracing.Tests/AOT_Tracing.Tests.csproj b/examples/AOT/AOT_Tracing/test/AOT_Tracing.Tests/AOT_Tracing.Tests.csproj +index b62601e6..2bdc9557 100644 +--- a/examples/AOT/AOT_Tracing/test/AOT_Tracing.Tests/AOT_Tracing.Tests.csproj ++++ b/examples/AOT/AOT_Tracing/test/AOT_Tracing.Tests/AOT_Tracing.Tests.csproj +@@ -6,7 +6,7 @@ + true + + +- ++ + + + +diff --git a/examples/BatchProcessing/events/event.json b/examples/BatchProcessing/events/event.json +index f98443d8..6b9c1b91 100644 +--- a/examples/BatchProcessing/events/event.json ++++ b/examples/BatchProcessing/events/event.json +@@ -7,7 +7,7 @@ + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1545082649183", +- "SenderId": "SENDER_ID", ++ "SenderId": "AIDAIENQZJOLO23YVJ4VO", + "ApproximateFirstReceiveTimestamp": "1545082649185" + }, + "messageAttributes": {}, +@@ -23,7 +23,7 @@ + "attributes": { + "ApproximateReceiveCount": "1", + "SentTimestamp": "1545082649183", +- "SenderId": "SENDER_ID", ++ "SenderId": "AIDAIENQZJOLO23YVJ4VO", + "ApproximateFirstReceiveTimestamp": "1545082649185" + }, + "messageAttributes": {}, +diff --git a/examples/BatchProcessing/events/typed-dynamodb-event.json b/examples/BatchProcessing/events/typed-dynamodb-event.json +deleted file mode 100644 +index 858454d6..00000000 +--- a/examples/BatchProcessing/events/typed-dynamodb-event.json ++++ /dev/null +@@ -1,82 +0,0 @@ +-{ +- "Records": [ +- { +- "eventID": "1", +- "eventVersion": "1.0", +- "dynamodb": { +- "Keys": { +- "customerId": { +- "S": "CUST-123" +- } +- }, +- "NewImage": { +- "customerId": { +- "S": "CUST-123" +- }, +- "name": { +- "S": "John Doe" +- }, +- "email": { +- "S": "john.doe@example.com" +- }, +- "createdAt": { +- "S": "2024-01-15T10:30:00Z" +- } +- }, +- "StreamViewType": "NEW_AND_OLD_IMAGES", +- "SequenceNumber": "111", +- "SizeBytes": 26 +- }, +- "awsRegion": "us-west-2", +- "eventName": "INSERT", +- "eventSourceARN": "arn:aws:dynamodb:us-west-2:123456789012:table/customers/stream/2015-06-27T00:48:05.899", +- "eventSource": "aws:dynamodb" +- }, +- { +- "eventID": "2", +- "eventVersion": "1.0", +- "dynamodb": { +- "Keys": { +- "customerId": { +- "S": "CUST-124" +- } +- }, +- "NewImage": { +- "customerId": { +- "S": "CUST-124" +- }, +- "name": { +- "S": "Jane Smith" +- }, +- "email": { +- "S": "jane.smith@example.com" +- }, +- "createdAt": { +- "S": "2024-01-15T10:35:00Z" +- } +- }, +- "OldImage": { +- "customerId": { +- "S": "CUST-124" +- }, +- "name": { +- "S": "Jane Doe" +- }, +- "email": { +- "S": "jane.doe@example.com" +- }, +- "createdAt": { +- "S": "2024-01-15T10:35:00Z" +- } +- }, +- "StreamViewType": "NEW_AND_OLD_IMAGES", +- "SequenceNumber": "222", +- "SizeBytes": 59 +- }, +- "awsRegion": "us-west-2", +- "eventName": "MODIFY", +- "eventSourceARN": "arn:aws:dynamodb:us-west-2:123456789012:table/customers/stream/2015-06-27T00:48:05.899", +- "eventSource": "aws:dynamodb" +- } +- ] +-} +\ No newline at end of file +diff --git a/examples/BatchProcessing/events/typed-kinesis-event.json b/examples/BatchProcessing/events/typed-kinesis-event.json +deleted file mode 100644 +index c9d66d8e..00000000 +--- a/examples/BatchProcessing/events/typed-kinesis-event.json ++++ /dev/null +@@ -1,36 +0,0 @@ +-{ +- "Records": [ +- { +- "kinesis": { +- "kinesisSchemaVersion": "1.0", +- "partitionKey": "order-1", +- "sequenceNumber": "49590338271490256608559692538361571095921575989136588898", +- "data": "eyJvcmRlcklkIjogIk9SRC0xMjMiLCAib3JkZXJEYXRlIjogIjIwMjQtMDEtMTVUMTA6MzA6MDBaIiwgImN1c3RvbWVySWQiOiAiQ1VTVC0xMjMiLCAiaXRlbXMiOiBbeyJpZCI6IDEsICJuYW1lIjogIkxhcHRvcCIsICJwcmljZSI6IDk5OS45OX0sIHsiaWQiOiAyLCAibmFtZSI6ICJNb3VzZSIsICJwcmljZSI6IDI5Ljk5fV0sICJ0b3RhbEFtb3VudCI6IDEwMjkuOTgsICJzdGF0dXMiOiAiUGVuZGluZyJ9", +- "approximateArrivalTimestamp": 1545084650.987 +- }, +- "eventSource": "aws:kinesis", +- "eventVersion": "1.0", +- "eventID": "shardId-000000000006:49590338271490256608559692538361571095921575989136588898", +- "eventName": "aws:kinesis:record", +- "invokeIdentityArn": "arn:aws:iam::123456789012:role/lambda-kinesis-role", +- "awsRegion": "us-east-1", +- "eventSourceARN": "arn:aws:kinesis:us-east-1:123456789012:stream/lambda-stream" +- }, +- { +- "kinesis": { +- "kinesisSchemaVersion": "1.0", +- "partitionKey": "order-2", +- "sequenceNumber": "49590338271490256608559692538361571095921575989136588899", +- "data": "eyJvcmRlcklkIjogIk9SRC0xMjQiLCAib3JkZXJEYXRlIjogIjIwMjQtMDEtMTVUMTA6MzU6MDBaIiwgImN1c3RvbWVySWQiOiAiQ1VTVC0xMjQiLCAiaXRlbXMiOiBbeyJpZCI6IDMsICJuYW1lIjogIktleWJvYXJkIiwgInByaWNlIjogNzkuOTl9XSwgInRvdGFsQW1vdW50IjogNzkuOTksICJzdGF0dXMiOiAiUGVuZGluZyJ9", +- "approximateArrivalTimestamp": 1545084651.987 +- }, +- "eventSource": "aws:kinesis", +- "eventVersion": "1.0", +- "eventID": "shardId-000000000006:49590338271490256608559692538361571095921575989136588899", +- "eventName": "aws:kinesis:record", +- "invokeIdentityArn": "arn:aws:iam::123456789012:role/lambda-kinesis-role", +- "awsRegion": "us-east-1", +- "eventSourceARN": "arn:aws:kinesis:us-east-1:123456789012:stream/lambda-stream" +- } +- ] +-} +\ No newline at end of file +diff --git a/examples/BatchProcessing/events/typed-sqs-event.json b/examples/BatchProcessing/events/typed-sqs-event.json +deleted file mode 100644 +index 864180c0..00000000 +--- a/examples/BatchProcessing/events/typed-sqs-event.json ++++ /dev/null +@@ -1,52 +0,0 @@ +-{ +- "Records": [ +- { +- "messageId": "19dd0b57-b21e-4ac1-bd88-01bbb068cb78", +- "receiptHandle": "MessageReceiptHandle", +- "body": "{\"id\": 1, \"name\": \"Laptop Computer\", \"price\": 999.99}", +- "attributes": { +- "ApproximateReceiveCount": "1", +- "SentTimestamp": "1523232000000", +- "SenderId": "123456789012", +- "ApproximateFirstReceiveTimestamp": "1523232000001" +- }, +- "messageAttributes": {}, +- "md5OfBody": "7b270e59b47ff90a553787216d55d91d", +- "eventSource": "aws:sqs", +- "eventSourceARN": "arn:aws:sqs:us-east-1:123456789012:MyQueue", +- "awsRegion": "us-east-1" +- }, +- { +- "messageId": "19dd0b57-b21e-4ac1-bd88-01bbb068cb79", +- "receiptHandle": "MessageReceiptHandle2", +- "body": "{\"id\": 2, \"name\": \"Wireless Mouse\", \"price\": 29.99}", +- "attributes": { +- "ApproximateReceiveCount": "1", +- "SentTimestamp": "1523232000000", +- "SenderId": "123456789012", +- "ApproximateFirstReceiveTimestamp": "1523232000001" +- }, +- "messageAttributes": {}, +- "md5OfBody": "7b270e59b47ff90a553787216d55d92e", +- "eventSource": "aws:sqs", +- "eventSourceARN": "arn:aws:sqs:us-east-1:123456789012:MyQueue", +- "awsRegion": "us-east-1" +- }, +- { +- "messageId": "19dd0b57-b21e-4ac1-bd88-01bbb068cb80", +- "receiptHandle": "MessageReceiptHandle3", +- "body": "{\"id\": 999, \"name\": \"Invalid Product\", \"price\": -10.00}", +- "attributes": { +- "ApproximateReceiveCount": "1", +- "SentTimestamp": "1523232000000", +- "SenderId": "123456789012", +- "ApproximateFirstReceiveTimestamp": "1523232000001" +- }, +- "messageAttributes": {}, +- "md5OfBody": "7b270e59b47ff90a553787216d55d93f", +- "eventSource": "aws:sqs", +- "eventSourceARN": "arn:aws:sqs:us-east-1:123456789012:MyQueue", +- "awsRegion": "us-east-1" +- } +- ] +-} +\ No newline at end of file +diff --git a/examples/BatchProcessing/src/HelloWorld/Data/Order.cs b/examples/BatchProcessing/src/HelloWorld/Data/Order.cs +deleted file mode 100644 +index 89703a92..00000000 +--- a/examples/BatchProcessing/src/HelloWorld/Data/Order.cs ++++ /dev/null +@@ -1,50 +0,0 @@ +-/* +- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +- * +- * Licensed under the Apache License, Version 2.0 (the "License"). +- * You may not use this file except in compliance with the License. +- * A copy of the License is located at +- * +- * http://aws.amazon.com/apache2.0 +- * +- * or in the "license" file accompanying this file. This file is distributed +- * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +- * express or implied. See the License for the specific language governing +- * permissions and limitations under the License. +- */ +- +-using System; +-using System.Collections.Generic; +-using System.Text.Json.Serialization; +- +-namespace HelloWorld.Data; +- +-public class Order +-{ +- public string? OrderId { get; set; } +- public DateTime OrderDate { get; set; } +- public string? CustomerId { get; set; } +- public List Items { get; set; } = new(); +- public decimal TotalAmount { get; set; } +- public string? Status { get; set; } +-} +- +-public class Customer +-{ +- public string? CustomerId { get; set; } +- public string? Name { get; set; } +- public string? Email { get; set; } +- public DateTime CreatedAt { get; set; } +-} +- +-/// +-/// JsonSerializerContext for AOT compatibility +-/// +-[JsonSerializable(typeof(Product))] +-[JsonSerializable(typeof(Order))] +-[JsonSerializable(typeof(Customer))] +-[JsonSerializable(typeof(List))] +-[JsonSerializable(typeof(List))] +-public partial class ExampleJsonSerializerContext : JsonSerializerContext +-{ +-} +\ No newline at end of file +diff --git a/examples/BatchProcessing/src/HelloWorld/Dockerfile b/examples/BatchProcessing/src/HelloWorld/Dockerfile +index 0b025f36..eb6d0e0b 100644 +--- a/examples/BatchProcessing/src/HelloWorld/Dockerfile ++++ b/examples/BatchProcessing/src/HelloWorld/Dockerfile +@@ -1,4 +1,4 @@ +-FROM mcr.microsoft.com/dotnet/sdk@sha256:c8fdd06e430de9f4ddd066b475ea350d771f341b77dd5ff4c2fafa748e3f2ef2 AS build-image ++FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-image + + ARG FUNCTION_DIR="/build" + ARG SAM_BUILD_MODE="run" +@@ -15,7 +15,7 @@ RUN mkdir -p build_artifacts + RUN if [ "$SAM_BUILD_MODE" = "debug" ]; then dotnet lambda package --configuration Debug; else dotnet lambda package --configuration Release; fi + RUN if [ "$SAM_BUILD_MODE" = "debug" ]; then cp -r /build/bin/Debug/net6.0/publish/* /build/build_artifacts; else cp -r /build/bin/Release/net6.0/publish/* /build/build_artifacts; fi + +-FROM public.ecr.aws/lambda/dotnet@sha256:ec61a7f638e2a0c86d75204117cc7710bcdc70222ffc777e3fc1458287b09834 ++FROM public.ecr.aws/lambda/dotnet:6 + + COPY --from=build-image /build/build_artifacts/ /var/task/ + # Command can be overwritten by providing a different command in the template directly. +diff --git a/examples/BatchProcessing/src/HelloWorld/HelloWorld.csproj b/examples/BatchProcessing/src/HelloWorld/HelloWorld.csproj +index 7d3263e2..d34e3e63 100644 +--- a/examples/BatchProcessing/src/HelloWorld/HelloWorld.csproj ++++ b/examples/BatchProcessing/src/HelloWorld/HelloWorld.csproj +@@ -5,10 +5,10 @@ + enable + + +- +- ++ ++ + +- +- ++ ++ + + +diff --git a/examples/BatchProcessing/test/HelloWorld.Test/HelloWorld.Tests.csproj b/examples/BatchProcessing/test/HelloWorld.Test/HelloWorld.Tests.csproj +index 3990c011..903aee7d 100644 +--- a/examples/BatchProcessing/test/HelloWorld.Test/HelloWorld.Tests.csproj ++++ b/examples/BatchProcessing/test/HelloWorld.Test/HelloWorld.Tests.csproj +@@ -3,12 +3,12 @@ + net8.0 + + +- ++ + + + + +- ++ + + + +diff --git a/examples/Event Handler/BedrockAgentFunction/infra/.gitignore b/examples/Event Handler/BedrockAgentFunction/infra/.gitignore +deleted file mode 100644 +index f60797b6..00000000 +--- a/examples/Event Handler/BedrockAgentFunction/infra/.gitignore ++++ /dev/null +@@ -1,8 +0,0 @@ +-*.js +-!jest.config.js +-*.d.ts +-node_modules +- +-# CDK asset staging directory +-.cdk.staging +-cdk.out +diff --git a/examples/Event Handler/BedrockAgentFunction/infra/.npmignore b/examples/Event Handler/BedrockAgentFunction/infra/.npmignore +deleted file mode 100644 +index c1d6d45d..00000000 +--- a/examples/Event Handler/BedrockAgentFunction/infra/.npmignore ++++ /dev/null +@@ -1,6 +0,0 @@ +-*.ts +-!*.d.ts +- +-# CDK asset staging directory +-.cdk.staging +-cdk.out +diff --git a/examples/Event Handler/BedrockAgentFunction/infra/cdk.json b/examples/Event Handler/BedrockAgentFunction/infra/cdk.json +deleted file mode 100644 +index eea31fee..00000000 +--- a/examples/Event Handler/BedrockAgentFunction/infra/cdk.json ++++ /dev/null +@@ -1,96 +0,0 @@ +-{ +- "app": "npx ts-node --prefer-ts-exts bin/infra.ts", +- "watch": { +- "include": [ +- "**" +- ], +- "exclude": [ +- "README.md", +- "cdk*.json", +- "**/*.d.ts", +- "**/*.js", +- "tsconfig.json", +- "package*.json", +- "yarn.lock", +- "node_modules", +- "test" +- ] +- }, +- "context": { +- "@aws-cdk/aws-lambda:recognizeLayerVersion": true, +- "@aws-cdk/core:checkSecretUsage": true, +- "@aws-cdk/core:target-partitions": [ +- "aws", +- "aws-cn" +- ], +- "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, +- "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, +- "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, +- "@aws-cdk/aws-iam:minimizePolicies": true, +- "@aws-cdk/core:validateSnapshotRemovalPolicy": true, +- "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, +- "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, +- "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, +- "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, +- "@aws-cdk/core:enablePartitionLiterals": true, +- "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, +- "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, +- "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, +- "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, +- "@aws-cdk/aws-route53-patters:useCertificate": true, +- "@aws-cdk/customresources:installLatestAwsSdkDefault": false, +- "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, +- "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, +- "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, +- "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, +- "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, +- "@aws-cdk/aws-redshift:columnId": true, +- "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, +- "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, +- "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, +- "@aws-cdk/aws-kms:aliasNameRef": true, +- "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, +- "@aws-cdk/core:includePrefixInUniqueNameGeneration": true, +- "@aws-cdk/aws-efs:denyAnonymousAccess": true, +- "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true, +- "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true, +- "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true, +- "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true, +- "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true, +- "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true, +- "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true, +- "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true, +- "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true, +- "@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true, +- "@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true, +- "@aws-cdk/aws-eks:nodegroupNameAttribute": true, +- "@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true, +- "@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true, +- "@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false, +- "@aws-cdk/aws-s3:keepNotificationInImportedBucket": false, +- "@aws-cdk/aws-ecs:enableImdsBlockingDeprecatedFeature": false, +- "@aws-cdk/aws-ecs:disableEcsImdsBlocking": true, +- "@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": true, +- "@aws-cdk/aws-dynamodb:resourcePolicyPerReplica": true, +- "@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": true, +- "@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": true, +- "@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": true, +- "@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": true, +- "@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": true, +- "@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": true, +- "@aws-cdk/aws-ec2:bastionHostUseAmazonLinux2023ByDefault": true, +- "@aws-cdk/aws-route53-targets:userPoolDomainNameMethodWithoutCustomResource": true, +- "@aws-cdk/aws-elasticloadbalancingV2:albDualstackWithoutPublicIpv4SecurityGroupRulesDefault": true, +- "@aws-cdk/aws-iam:oidcRejectUnauthorizedConnections": true, +- "@aws-cdk/core:enableAdditionalMetadataCollection": true, +- "@aws-cdk/aws-lambda:createNewPoliciesWithAddToRolePolicy": false, +- "@aws-cdk/aws-s3:setUniqueReplicationRoleName": true, +- "@aws-cdk/aws-events:requireEventBusPolicySid": true, +- "@aws-cdk/core:aspectPrioritiesMutating": true, +- "@aws-cdk/aws-dynamodb:retainTableReplica": true, +- "@aws-cdk/aws-stepfunctions:useDistributedMapResultWriterV2": true, +- "@aws-cdk/s3-notifications:addS3TrustKeyPolicyForSnsSubscriptions": true, +- "@aws-cdk/aws-ec2:requirePrivateSubnetsForEgressOnlyInternetGateway": true, +- "@aws-cdk/aws-s3:publicAccessBlockedByDefault": true +- } +-} +diff --git a/examples/Event Handler/BedrockAgentFunction/infra/jest.config.js b/examples/Event Handler/BedrockAgentFunction/infra/jest.config.js +deleted file mode 100644 +index 08263b89..00000000 +--- a/examples/Event Handler/BedrockAgentFunction/infra/jest.config.js ++++ /dev/null +@@ -1,8 +0,0 @@ +-module.exports = { +- testEnvironment: 'node', +- roots: ['/test'], +- testMatch: ['**/*.test.ts'], +- transform: { +- '^.+\\.tsx?$': 'ts-jest' +- } +-}; +diff --git a/examples/Event Handler/BedrockAgentFunction/infra/lib/bedrockagents-stack.ts b/examples/Event Handler/BedrockAgentFunction/infra/lib/bedrockagents-stack.ts +deleted file mode 100644 +index 001d9912..00000000 +--- a/examples/Event Handler/BedrockAgentFunction/infra/lib/bedrockagents-stack.ts ++++ /dev/null +@@ -1,121 +0,0 @@ +-import { +- Stack, +- type StackProps, +- CfnOutput, +- RemovalPolicy, +- Arn, +- Duration, +-} from 'aws-cdk-lib'; +-import type { Construct } from 'constructs'; +-import { Runtime, Function as LambdaFunction, Code, Architecture } from 'aws-cdk-lib/aws-lambda'; +-import { LogGroup, RetentionDays } from 'aws-cdk-lib/aws-logs'; +-import { CfnAgent } from 'aws-cdk-lib/aws-bedrock'; +-import { +- PolicyDocument, +- PolicyStatement, +- Role, +- ServicePrincipal, +-} from 'aws-cdk-lib/aws-iam'; +- +-export class BedrockAgentsStack extends Stack { +- constructor(scope: Construct, id: string, props?: StackProps) { +- super(scope, id, props); +- +- const fnName = 'BedrockAgentsFn'; +- const logGroup = new LogGroup(this, 'MyLogGroup', { +- logGroupName: `/aws/lambda/${fnName}`, +- removalPolicy: RemovalPolicy.DESTROY, +- retention: RetentionDays.ONE_DAY, +- }); +- +- const fn = new LambdaFunction(this, 'MyFunction', { +- functionName: fnName, +- logGroup, +- timeout: Duration.minutes(3), +- runtime: Runtime.DOTNET_8, +- handler: 'BedrockAgentFunction', +- code: Code.fromAsset('../release/BedrockAgentFunction.zip'), +- architecture: Architecture.X86_64, +- }); +- +- const agentRole = new Role(this, 'MyAgentRole', { +- assumedBy: new ServicePrincipal('bedrock.amazonaws.com'), +- description: 'Role for Bedrock airport agent', +- inlinePolicies: { +- bedrock: new PolicyDocument({ +- statements: [ +- new PolicyStatement({ +- actions: [ +- 'bedrock:*', +- ], +- resources: [ +- Arn.format( +- { +- service: 'bedrock', +- resource: 'foundation-model/*', +- region: 'us-*', +- account: '', +- }, +- Stack.of(this) +- ), +- Arn.format( +- { +- service: 'bedrock', +- resource: 'inference-profile/*', +- region: 'us-*', +- account: '*', +- }, +- Stack.of(this) +- ), +- ], +- }), +- ], +- }), +- }, +- }); +- +- const agent = new CfnAgent(this, 'MyCfnAgent', { +- agentName: 'airportAgent', +- actionGroups: [ +- { +- actionGroupName: 'airportActionGroup', +- actionGroupExecutor: { +- lambda: fn.functionArn, +- }, +- functionSchema: { +- functions: [ +- { +- name: 'getAirportCodeForCity', +- description: 'Get airport code and full airport name for a specific city', +- parameters: { +- city: { +- type: 'string', +- description: 'The name of the city to get the airport code for', +- required: true, +- }, +- }, +- }, +- ], +- }, +- }, +- ], +- agentResourceRoleArn: agentRole.roleArn, +- autoPrepare: true, +- description: 'A simple airport agent', +- foundationModel: `arn:aws:bedrock:us-west-2:${Stack.of(this).account}:inference-profile/us.amazon.nova-pro-v1:0`, +- instruction: +- 'You are an airport traffic control agent. You will be given a city name and you will return the airport code and airport full name for that city.', +- }); +- +- fn.addPermission('BedrockAgentInvokePermission', { +- principal: new ServicePrincipal('bedrock.amazonaws.com'), +- action: 'lambda:InvokeFunction', +- sourceAccount: this.account, +- sourceArn: `arn:aws:bedrock:${this.region}:${this.account}:agent/${agent.attrAgentId}`, +- }); +- +- new CfnOutput(this, 'FunctionArn', { +- value: fn.functionArn, +- }); +- } +-} +diff --git a/examples/Event Handler/BedrockAgentFunction/infra/package-lock.json b/examples/Event Handler/BedrockAgentFunction/infra/package-lock.json +deleted file mode 100644 +index 1cf13f4b..00000000 +--- a/examples/Event Handler/BedrockAgentFunction/infra/package-lock.json ++++ /dev/null +@@ -1,4453 +0,0 @@ +-{ +- "name": "infra", +- "version": "0.1.0", +- "lockfileVersion": 3, +- "requires": true, +- "packages": { +- "": { +- "name": "infra", +- "version": "0.1.0", +- "dependencies": { +- "aws-cdk-lib": "2.198.0", +- "constructs": "^10.0.0" +- }, +- "bin": { +- "infra": "bin/infra.js" +- }, +- "devDependencies": { +- "@types/jest": "^29.5.14", +- "@types/node": "22.7.9", +- "aws-cdk": "2.1017.1", +- "jest": "^29.7.0", +- "ts-jest": "^29.2.5", +- "ts-node": "^10.9.2", +- "typescript": "~5.6.3" +- } +- }, +- "node_modules/@ampproject/remapping": { +- "version": "2.3.0", +- "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", +- "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", +- "dev": true, +- "license": "Apache-2.0", +- "dependencies": { +- "@jridgewell/gen-mapping": "^0.3.5", +- "@jridgewell/trace-mapping": "^0.3.24" +- }, +- "engines": { +- "node": ">=6.0.0" +- } +- }, +- "node_modules/@aws-cdk/asset-awscli-v1": { +- "version": "2.2.237", +- "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.237.tgz", +- "integrity": "sha512-OlXylbXI52lboFVJBFLae+WB99qWmI121x/wXQHEMj2RaVNVbWE+OAHcDk2Um1BitUQCaTf9ki57B0Fuqx0Rvw==", +- "license": "Apache-2.0" +- }, +- "node_modules/@aws-cdk/asset-node-proxy-agent-v6": { +- "version": "2.1.0", +- "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.1.0.tgz", +- "integrity": "sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A==", +- "license": "Apache-2.0" +- }, +- "node_modules/@aws-cdk/cloud-assembly-schema": { +- "version": "41.2.0", +- "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-41.2.0.tgz", +- "integrity": "sha512-JaulVS6z9y5+u4jNmoWbHZRs9uGOnmn/ktXygNWKNu1k6lF3ad4so3s18eRu15XCbUIomxN9WPYT6Ehh7hzONw==", +- "bundleDependencies": [ +- "jsonschema", +- "semver" +- ], +- "license": "Apache-2.0", +- "dependencies": { +- "jsonschema": "~1.4.1", +- "semver": "^7.7.1" +- }, +- "engines": { +- "node": ">= 14.15.0" +- } +- }, +- "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": { +- "version": "1.4.1", +- "inBundle": true, +- "license": "MIT", +- "engines": { +- "node": "*" +- } +- }, +- "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": { +- "version": "7.7.1", +- "inBundle": true, +- "license": "ISC", +- "bin": { +- "semver": "bin/semver.js" +- }, +- "engines": { +- "node": ">=10" +- } +- }, +- "node_modules/@babel/code-frame": { +- "version": "7.27.1", +- "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", +- "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@babel/helper-validator-identifier": "^7.27.1", +- "js-tokens": "^4.0.0", +- "picocolors": "^1.1.1" +- }, +- "engines": { +- "node": ">=6.9.0" +- } +- }, +- "node_modules/@babel/compat-data": { +- "version": "7.27.3", +- "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.3.tgz", +- "integrity": "sha512-V42wFfx1ymFte+ecf6iXghnnP8kWTO+ZLXIyZq+1LAXHHvTZdVxicn4yiVYdYMGaCO3tmqub11AorKkv+iodqw==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=6.9.0" +- } +- }, +- "node_modules/@babel/core": { +- "version": "7.27.4", +- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz", +- "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@ampproject/remapping": "^2.2.0", +- "@babel/code-frame": "^7.27.1", +- "@babel/generator": "^7.27.3", +- "@babel/helper-compilation-targets": "^7.27.2", +- "@babel/helper-module-transforms": "^7.27.3", +- "@babel/helpers": "^7.27.4", +- "@babel/parser": "^7.27.4", +- "@babel/template": "^7.27.2", +- "@babel/traverse": "^7.27.4", +- "@babel/types": "^7.27.3", +- "convert-source-map": "^2.0.0", +- "debug": "^4.1.0", +- "gensync": "^1.0.0-beta.2", +- "json5": "^2.2.3", +- "semver": "^6.3.1" +- }, +- "engines": { +- "node": ">=6.9.0" +- }, +- "funding": { +- "type": "opencollective", +- "url": "https://opencollective.com/babel" +- } +- }, +- "node_modules/@babel/generator": { +- "version": "7.27.3", +- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.3.tgz", +- "integrity": "sha512-xnlJYj5zepml8NXtjkG0WquFUv8RskFqyFcVgTBp5k+NaA/8uw/K+OSVf8AMGw5e9HKP2ETd5xpK5MLZQD6b4Q==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@babel/parser": "^7.27.3", +- "@babel/types": "^7.27.3", +- "@jridgewell/gen-mapping": "^0.3.5", +- "@jridgewell/trace-mapping": "^0.3.25", +- "jsesc": "^3.0.2" +- }, +- "engines": { +- "node": ">=6.9.0" +- } +- }, +- "node_modules/@babel/helper-compilation-targets": { +- "version": "7.27.2", +- "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", +- "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@babel/compat-data": "^7.27.2", +- "@babel/helper-validator-option": "^7.27.1", +- "browserslist": "^4.24.0", +- "lru-cache": "^5.1.1", +- "semver": "^6.3.1" +- }, +- "engines": { +- "node": ">=6.9.0" +- } +- }, +- "node_modules/@babel/helper-module-imports": { +- "version": "7.27.1", +- "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", +- "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@babel/traverse": "^7.27.1", +- "@babel/types": "^7.27.1" +- }, +- "engines": { +- "node": ">=6.9.0" +- } +- }, +- "node_modules/@babel/helper-module-transforms": { +- "version": "7.27.3", +- "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", +- "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@babel/helper-module-imports": "^7.27.1", +- "@babel/helper-validator-identifier": "^7.27.1", +- "@babel/traverse": "^7.27.3" +- }, +- "engines": { +- "node": ">=6.9.0" +- }, +- "peerDependencies": { +- "@babel/core": "^7.0.0" +- } +- }, +- "node_modules/@babel/helper-plugin-utils": { +- "version": "7.27.1", +- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", +- "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=6.9.0" +- } +- }, +- "node_modules/@babel/helper-string-parser": { +- "version": "7.27.1", +- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", +- "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=6.9.0" +- } +- }, +- "node_modules/@babel/helper-validator-identifier": { +- "version": "7.27.1", +- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", +- "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=6.9.0" +- } +- }, +- "node_modules/@babel/helper-validator-option": { +- "version": "7.27.1", +- "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", +- "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=6.9.0" +- } +- }, +- "node_modules/@babel/helpers": { +- "version": "7.27.4", +- "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.4.tgz", +- "integrity": "sha512-Y+bO6U+I7ZKaM5G5rDUZiYfUvQPUibYmAFe7EnKdnKBbVXDZxvp+MWOH5gYciY0EPk4EScsuFMQBbEfpdRKSCQ==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@babel/template": "^7.27.2", +- "@babel/types": "^7.27.3" +- }, +- "engines": { +- "node": ">=6.9.0" +- } +- }, +- "node_modules/@babel/parser": { +- "version": "7.27.4", +- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.4.tgz", +- "integrity": "sha512-BRmLHGwpUqLFR2jzx9orBuX/ABDkj2jLKOXrHDTN2aOKL+jFDDKaRNo9nyYsIl9h/UE/7lMKdDjKQQyxKKDZ7g==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@babel/types": "^7.27.3" +- }, +- "bin": { +- "parser": "bin/babel-parser.js" +- }, +- "engines": { +- "node": ">=6.0.0" +- } +- }, +- "node_modules/@babel/plugin-syntax-async-generators": { +- "version": "7.8.4", +- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", +- "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@babel/helper-plugin-utils": "^7.8.0" +- }, +- "peerDependencies": { +- "@babel/core": "^7.0.0-0" +- } +- }, +- "node_modules/@babel/plugin-syntax-bigint": { +- "version": "7.8.3", +- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", +- "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@babel/helper-plugin-utils": "^7.8.0" +- }, +- "peerDependencies": { +- "@babel/core": "^7.0.0-0" +- } +- }, +- "node_modules/@babel/plugin-syntax-class-properties": { +- "version": "7.12.13", +- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", +- "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@babel/helper-plugin-utils": "^7.12.13" +- }, +- "peerDependencies": { +- "@babel/core": "^7.0.0-0" +- } +- }, +- "node_modules/@babel/plugin-syntax-class-static-block": { +- "version": "7.14.5", +- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", +- "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@babel/helper-plugin-utils": "^7.14.5" +- }, +- "engines": { +- "node": ">=6.9.0" +- }, +- "peerDependencies": { +- "@babel/core": "^7.0.0-0" +- } +- }, +- "node_modules/@babel/plugin-syntax-import-attributes": { +- "version": "7.27.1", +- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", +- "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@babel/helper-plugin-utils": "^7.27.1" +- }, +- "engines": { +- "node": ">=6.9.0" +- }, +- "peerDependencies": { +- "@babel/core": "^7.0.0-0" +- } +- }, +- "node_modules/@babel/plugin-syntax-import-meta": { +- "version": "7.10.4", +- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", +- "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@babel/helper-plugin-utils": "^7.10.4" +- }, +- "peerDependencies": { +- "@babel/core": "^7.0.0-0" +- } +- }, +- "node_modules/@babel/plugin-syntax-json-strings": { +- "version": "7.8.3", +- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", +- "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@babel/helper-plugin-utils": "^7.8.0" +- }, +- "peerDependencies": { +- "@babel/core": "^7.0.0-0" +- } +- }, +- "node_modules/@babel/plugin-syntax-jsx": { +- "version": "7.27.1", +- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", +- "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@babel/helper-plugin-utils": "^7.27.1" +- }, +- "engines": { +- "node": ">=6.9.0" +- }, +- "peerDependencies": { +- "@babel/core": "^7.0.0-0" +- } +- }, +- "node_modules/@babel/plugin-syntax-logical-assignment-operators": { +- "version": "7.10.4", +- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", +- "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@babel/helper-plugin-utils": "^7.10.4" +- }, +- "peerDependencies": { +- "@babel/core": "^7.0.0-0" +- } +- }, +- "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { +- "version": "7.8.3", +- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", +- "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@babel/helper-plugin-utils": "^7.8.0" +- }, +- "peerDependencies": { +- "@babel/core": "^7.0.0-0" +- } +- }, +- "node_modules/@babel/plugin-syntax-numeric-separator": { +- "version": "7.10.4", +- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", +- "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@babel/helper-plugin-utils": "^7.10.4" +- }, +- "peerDependencies": { +- "@babel/core": "^7.0.0-0" +- } +- }, +- "node_modules/@babel/plugin-syntax-object-rest-spread": { +- "version": "7.8.3", +- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", +- "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@babel/helper-plugin-utils": "^7.8.0" +- }, +- "peerDependencies": { +- "@babel/core": "^7.0.0-0" +- } +- }, +- "node_modules/@babel/plugin-syntax-optional-catch-binding": { +- "version": "7.8.3", +- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", +- "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@babel/helper-plugin-utils": "^7.8.0" +- }, +- "peerDependencies": { +- "@babel/core": "^7.0.0-0" +- } +- }, +- "node_modules/@babel/plugin-syntax-optional-chaining": { +- "version": "7.8.3", +- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", +- "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@babel/helper-plugin-utils": "^7.8.0" +- }, +- "peerDependencies": { +- "@babel/core": "^7.0.0-0" +- } +- }, +- "node_modules/@babel/plugin-syntax-private-property-in-object": { +- "version": "7.14.5", +- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", +- "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@babel/helper-plugin-utils": "^7.14.5" +- }, +- "engines": { +- "node": ">=6.9.0" +- }, +- "peerDependencies": { +- "@babel/core": "^7.0.0-0" +- } +- }, +- "node_modules/@babel/plugin-syntax-top-level-await": { +- "version": "7.14.5", +- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", +- "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@babel/helper-plugin-utils": "^7.14.5" +- }, +- "engines": { +- "node": ">=6.9.0" +- }, +- "peerDependencies": { +- "@babel/core": "^7.0.0-0" +- } +- }, +- "node_modules/@babel/plugin-syntax-typescript": { +- "version": "7.27.1", +- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", +- "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@babel/helper-plugin-utils": "^7.27.1" +- }, +- "engines": { +- "node": ">=6.9.0" +- }, +- "peerDependencies": { +- "@babel/core": "^7.0.0-0" +- } +- }, +- "node_modules/@babel/template": { +- "version": "7.27.2", +- "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", +- "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@babel/code-frame": "^7.27.1", +- "@babel/parser": "^7.27.2", +- "@babel/types": "^7.27.1" +- }, +- "engines": { +- "node": ">=6.9.0" +- } +- }, +- "node_modules/@babel/traverse": { +- "version": "7.27.4", +- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", +- "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@babel/code-frame": "^7.27.1", +- "@babel/generator": "^7.27.3", +- "@babel/parser": "^7.27.4", +- "@babel/template": "^7.27.2", +- "@babel/types": "^7.27.3", +- "debug": "^4.3.1", +- "globals": "^11.1.0" +- }, +- "engines": { +- "node": ">=6.9.0" +- } +- }, +- "node_modules/@babel/types": { +- "version": "7.27.3", +- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.3.tgz", +- "integrity": "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@babel/helper-string-parser": "^7.27.1", +- "@babel/helper-validator-identifier": "^7.27.1" +- }, +- "engines": { +- "node": ">=6.9.0" +- } +- }, +- "node_modules/@bcoe/v8-coverage": { +- "version": "0.2.3", +- "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", +- "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", +- "dev": true, +- "license": "MIT" +- }, +- "node_modules/@cspotcode/source-map-support": { +- "version": "0.8.1", +- "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", +- "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@jridgewell/trace-mapping": "0.3.9" +- }, +- "engines": { +- "node": ">=12" +- } +- }, +- "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { +- "version": "0.3.9", +- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", +- "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@jridgewell/resolve-uri": "^3.0.3", +- "@jridgewell/sourcemap-codec": "^1.4.10" +- } +- }, +- "node_modules/@istanbuljs/load-nyc-config": { +- "version": "1.1.0", +- "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", +- "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", +- "dev": true, +- "license": "ISC", +- "dependencies": { +- "camelcase": "^5.3.1", +- "find-up": "^4.1.0", +- "get-package-type": "^0.1.0", +- "js-yaml": "^3.13.1", +- "resolve-from": "^5.0.0" +- }, +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/@istanbuljs/schema": { +- "version": "0.1.3", +- "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", +- "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/@jest/console": { +- "version": "29.7.0", +- "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", +- "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@jest/types": "^29.6.3", +- "@types/node": "*", +- "chalk": "^4.0.0", +- "jest-message-util": "^29.7.0", +- "jest-util": "^29.7.0", +- "slash": "^3.0.0" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- } +- }, +- "node_modules/@jest/core": { +- "version": "29.7.0", +- "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", +- "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@jest/console": "^29.7.0", +- "@jest/reporters": "^29.7.0", +- "@jest/test-result": "^29.7.0", +- "@jest/transform": "^29.7.0", +- "@jest/types": "^29.6.3", +- "@types/node": "*", +- "ansi-escapes": "^4.2.1", +- "chalk": "^4.0.0", +- "ci-info": "^3.2.0", +- "exit": "^0.1.2", +- "graceful-fs": "^4.2.9", +- "jest-changed-files": "^29.7.0", +- "jest-config": "^29.7.0", +- "jest-haste-map": "^29.7.0", +- "jest-message-util": "^29.7.0", +- "jest-regex-util": "^29.6.3", +- "jest-resolve": "^29.7.0", +- "jest-resolve-dependencies": "^29.7.0", +- "jest-runner": "^29.7.0", +- "jest-runtime": "^29.7.0", +- "jest-snapshot": "^29.7.0", +- "jest-util": "^29.7.0", +- "jest-validate": "^29.7.0", +- "jest-watcher": "^29.7.0", +- "micromatch": "^4.0.4", +- "pretty-format": "^29.7.0", +- "slash": "^3.0.0", +- "strip-ansi": "^6.0.0" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- }, +- "peerDependencies": { +- "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" +- }, +- "peerDependenciesMeta": { +- "node-notifier": { +- "optional": true +- } +- } +- }, +- "node_modules/@jest/environment": { +- "version": "29.7.0", +- "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", +- "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@jest/fake-timers": "^29.7.0", +- "@jest/types": "^29.6.3", +- "@types/node": "*", +- "jest-mock": "^29.7.0" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- } +- }, +- "node_modules/@jest/expect": { +- "version": "29.7.0", +- "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", +- "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "expect": "^29.7.0", +- "jest-snapshot": "^29.7.0" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- } +- }, +- "node_modules/@jest/expect-utils": { +- "version": "29.7.0", +- "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", +- "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "jest-get-type": "^29.6.3" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- } +- }, +- "node_modules/@jest/fake-timers": { +- "version": "29.7.0", +- "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", +- "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@jest/types": "^29.6.3", +- "@sinonjs/fake-timers": "^10.0.2", +- "@types/node": "*", +- "jest-message-util": "^29.7.0", +- "jest-mock": "^29.7.0", +- "jest-util": "^29.7.0" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- } +- }, +- "node_modules/@jest/globals": { +- "version": "29.7.0", +- "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", +- "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@jest/environment": "^29.7.0", +- "@jest/expect": "^29.7.0", +- "@jest/types": "^29.6.3", +- "jest-mock": "^29.7.0" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- } +- }, +- "node_modules/@jest/reporters": { +- "version": "29.7.0", +- "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", +- "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@bcoe/v8-coverage": "^0.2.3", +- "@jest/console": "^29.7.0", +- "@jest/test-result": "^29.7.0", +- "@jest/transform": "^29.7.0", +- "@jest/types": "^29.6.3", +- "@jridgewell/trace-mapping": "^0.3.18", +- "@types/node": "*", +- "chalk": "^4.0.0", +- "collect-v8-coverage": "^1.0.0", +- "exit": "^0.1.2", +- "glob": "^7.1.3", +- "graceful-fs": "^4.2.9", +- "istanbul-lib-coverage": "^3.0.0", +- "istanbul-lib-instrument": "^6.0.0", +- "istanbul-lib-report": "^3.0.0", +- "istanbul-lib-source-maps": "^4.0.0", +- "istanbul-reports": "^3.1.3", +- "jest-message-util": "^29.7.0", +- "jest-util": "^29.7.0", +- "jest-worker": "^29.7.0", +- "slash": "^3.0.0", +- "string-length": "^4.0.1", +- "strip-ansi": "^6.0.0", +- "v8-to-istanbul": "^9.0.1" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- }, +- "peerDependencies": { +- "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" +- }, +- "peerDependenciesMeta": { +- "node-notifier": { +- "optional": true +- } +- } +- }, +- "node_modules/@jest/schemas": { +- "version": "29.6.3", +- "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", +- "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@sinclair/typebox": "^0.27.8" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- } +- }, +- "node_modules/@jest/source-map": { +- "version": "29.6.3", +- "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", +- "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@jridgewell/trace-mapping": "^0.3.18", +- "callsites": "^3.0.0", +- "graceful-fs": "^4.2.9" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- } +- }, +- "node_modules/@jest/test-result": { +- "version": "29.7.0", +- "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", +- "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@jest/console": "^29.7.0", +- "@jest/types": "^29.6.3", +- "@types/istanbul-lib-coverage": "^2.0.0", +- "collect-v8-coverage": "^1.0.0" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- } +- }, +- "node_modules/@jest/test-sequencer": { +- "version": "29.7.0", +- "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", +- "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@jest/test-result": "^29.7.0", +- "graceful-fs": "^4.2.9", +- "jest-haste-map": "^29.7.0", +- "slash": "^3.0.0" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- } +- }, +- "node_modules/@jest/transform": { +- "version": "29.7.0", +- "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", +- "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@babel/core": "^7.11.6", +- "@jest/types": "^29.6.3", +- "@jridgewell/trace-mapping": "^0.3.18", +- "babel-plugin-istanbul": "^6.1.1", +- "chalk": "^4.0.0", +- "convert-source-map": "^2.0.0", +- "fast-json-stable-stringify": "^2.1.0", +- "graceful-fs": "^4.2.9", +- "jest-haste-map": "^29.7.0", +- "jest-regex-util": "^29.6.3", +- "jest-util": "^29.7.0", +- "micromatch": "^4.0.4", +- "pirates": "^4.0.4", +- "slash": "^3.0.0", +- "write-file-atomic": "^4.0.2" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- } +- }, +- "node_modules/@jest/types": { +- "version": "29.6.3", +- "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", +- "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@jest/schemas": "^29.6.3", +- "@types/istanbul-lib-coverage": "^2.0.0", +- "@types/istanbul-reports": "^3.0.0", +- "@types/node": "*", +- "@types/yargs": "^17.0.8", +- "chalk": "^4.0.0" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- } +- }, +- "node_modules/@jridgewell/gen-mapping": { +- "version": "0.3.8", +- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", +- "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@jridgewell/set-array": "^1.2.1", +- "@jridgewell/sourcemap-codec": "^1.4.10", +- "@jridgewell/trace-mapping": "^0.3.24" +- }, +- "engines": { +- "node": ">=6.0.0" +- } +- }, +- "node_modules/@jridgewell/resolve-uri": { +- "version": "3.1.2", +- "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", +- "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=6.0.0" +- } +- }, +- "node_modules/@jridgewell/set-array": { +- "version": "1.2.1", +- "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", +- "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=6.0.0" +- } +- }, +- "node_modules/@jridgewell/sourcemap-codec": { +- "version": "1.5.0", +- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", +- "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", +- "dev": true, +- "license": "MIT" +- }, +- "node_modules/@jridgewell/trace-mapping": { +- "version": "0.3.25", +- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", +- "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@jridgewell/resolve-uri": "^3.1.0", +- "@jridgewell/sourcemap-codec": "^1.4.14" +- } +- }, +- "node_modules/@sinclair/typebox": { +- "version": "0.27.8", +- "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", +- "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", +- "dev": true, +- "license": "MIT" +- }, +- "node_modules/@sinonjs/commons": { +- "version": "3.0.1", +- "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", +- "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", +- "dev": true, +- "license": "BSD-3-Clause", +- "dependencies": { +- "type-detect": "4.0.8" +- } +- }, +- "node_modules/@sinonjs/fake-timers": { +- "version": "10.3.0", +- "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", +- "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", +- "dev": true, +- "license": "BSD-3-Clause", +- "dependencies": { +- "@sinonjs/commons": "^3.0.0" +- } +- }, +- "node_modules/@tsconfig/node10": { +- "version": "1.0.11", +- "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", +- "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", +- "dev": true, +- "license": "MIT" +- }, +- "node_modules/@tsconfig/node12": { +- "version": "1.0.11", +- "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", +- "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", +- "dev": true, +- "license": "MIT" +- }, +- "node_modules/@tsconfig/node14": { +- "version": "1.0.3", +- "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", +- "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", +- "dev": true, +- "license": "MIT" +- }, +- "node_modules/@tsconfig/node16": { +- "version": "1.0.4", +- "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", +- "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", +- "dev": true, +- "license": "MIT" +- }, +- "node_modules/@types/babel__core": { +- "version": "7.20.5", +- "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", +- "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@babel/parser": "^7.20.7", +- "@babel/types": "^7.20.7", +- "@types/babel__generator": "*", +- "@types/babel__template": "*", +- "@types/babel__traverse": "*" +- } +- }, +- "node_modules/@types/babel__generator": { +- "version": "7.27.0", +- "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", +- "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@babel/types": "^7.0.0" +- } +- }, +- "node_modules/@types/babel__template": { +- "version": "7.4.4", +- "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", +- "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@babel/parser": "^7.1.0", +- "@babel/types": "^7.0.0" +- } +- }, +- "node_modules/@types/babel__traverse": { +- "version": "7.20.7", +- "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", +- "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@babel/types": "^7.20.7" +- } +- }, +- "node_modules/@types/graceful-fs": { +- "version": "4.1.9", +- "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", +- "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@types/node": "*" +- } +- }, +- "node_modules/@types/istanbul-lib-coverage": { +- "version": "2.0.6", +- "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", +- "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", +- "dev": true, +- "license": "MIT" +- }, +- "node_modules/@types/istanbul-lib-report": { +- "version": "3.0.3", +- "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", +- "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@types/istanbul-lib-coverage": "*" +- } +- }, +- "node_modules/@types/istanbul-reports": { +- "version": "3.0.4", +- "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", +- "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@types/istanbul-lib-report": "*" +- } +- }, +- "node_modules/@types/jest": { +- "version": "29.5.14", +- "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", +- "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "expect": "^29.0.0", +- "pretty-format": "^29.0.0" +- } +- }, +- "node_modules/@types/node": { +- "version": "22.7.9", +- "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.9.tgz", +- "integrity": "sha512-jrTfRC7FM6nChvU7X2KqcrgquofrWLFDeYC1hKfwNWomVvrn7JIksqf344WN2X/y8xrgqBd2dJATZV4GbatBfg==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "undici-types": "~6.19.2" +- } +- }, +- "node_modules/@types/stack-utils": { +- "version": "2.0.3", +- "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", +- "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", +- "dev": true, +- "license": "MIT" +- }, +- "node_modules/@types/yargs": { +- "version": "17.0.33", +- "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", +- "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@types/yargs-parser": "*" +- } +- }, +- "node_modules/@types/yargs-parser": { +- "version": "21.0.3", +- "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", +- "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", +- "dev": true, +- "license": "MIT" +- }, +- "node_modules/acorn": { +- "version": "8.14.1", +- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", +- "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", +- "dev": true, +- "license": "MIT", +- "bin": { +- "acorn": "bin/acorn" +- }, +- "engines": { +- "node": ">=0.4.0" +- } +- }, +- "node_modules/acorn-walk": { +- "version": "8.3.4", +- "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", +- "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "acorn": "^8.11.0" +- }, +- "engines": { +- "node": ">=0.4.0" +- } +- }, +- "node_modules/ansi-escapes": { +- "version": "4.3.2", +- "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", +- "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "type-fest": "^0.21.3" +- }, +- "engines": { +- "node": ">=8" +- }, +- "funding": { +- "url": "https://github.com/sponsors/sindresorhus" +- } +- }, +- "node_modules/ansi-regex": { +- "version": "5.0.1", +- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", +- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/ansi-styles": { +- "version": "4.3.0", +- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", +- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "color-convert": "^2.0.1" +- }, +- "engines": { +- "node": ">=8" +- }, +- "funding": { +- "url": "https://github.com/chalk/ansi-styles?sponsor=1" +- } +- }, +- "node_modules/anymatch": { +- "version": "3.1.3", +- "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", +- "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", +- "dev": true, +- "license": "ISC", +- "dependencies": { +- "normalize-path": "^3.0.0", +- "picomatch": "^2.0.4" +- }, +- "engines": { +- "node": ">= 8" +- } +- }, +- "node_modules/arg": { +- "version": "4.1.3", +- "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", +- "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", +- "dev": true, +- "license": "MIT" +- }, +- "node_modules/argparse": { +- "version": "1.0.10", +- "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", +- "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "sprintf-js": "~1.0.2" +- } +- }, +- "node_modules/async": { +- "version": "3.2.6", +- "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", +- "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", +- "dev": true, +- "license": "MIT" +- }, +- "node_modules/aws-cdk": { +- "version": "2.1017.1", +- "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1017.1.tgz", +- "integrity": "sha512-KtDdkMhfVjDeexjpMrVoSlz2mTYI5BE/KotvJ7iFbZy1G0nkpW1ImZ54TdBefeeFmZ+8DAjU3I6nUFtymyOI1A==", +- "dev": true, +- "license": "Apache-2.0", +- "bin": { +- "cdk": "bin/cdk" +- }, +- "engines": { +- "node": ">= 14.15.0" +- }, +- "optionalDependencies": { +- "fsevents": "2.3.2" +- } +- }, +- "node_modules/aws-cdk-lib": { +- "version": "2.198.0", +- "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.198.0.tgz", +- "integrity": "sha512-CyZ+lnRsCsLskzQLPO0EiGl5EVcLluhfa67df3b8/gJfsm+91SHJa75OH+ymdGtUp5Vn/MWUPsujw0EhWMfsIQ==", +- "bundleDependencies": [ +- "@balena/dockerignore", +- "case", +- "fs-extra", +- "ignore", +- "jsonschema", +- "minimatch", +- "punycode", +- "semver", +- "table", +- "yaml", +- "mime-types" +- ], +- "license": "Apache-2.0", +- "dependencies": { +- "@aws-cdk/asset-awscli-v1": "2.2.237", +- "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.0", +- "@aws-cdk/cloud-assembly-schema": "^41.2.0", +- "@balena/dockerignore": "^1.0.2", +- "case": "1.6.3", +- "fs-extra": "^11.3.0", +- "ignore": "^5.3.2", +- "jsonschema": "^1.5.0", +- "mime-types": "^2.1.35", +- "minimatch": "^3.1.2", +- "punycode": "^2.3.1", +- "semver": "^7.7.2", +- "table": "^6.9.0", +- "yaml": "1.10.2" +- }, +- "engines": { +- "node": ">= 14.15.0" +- }, +- "peerDependencies": { +- "constructs": "^10.0.0" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/@balena/dockerignore": { +- "version": "1.0.2", +- "inBundle": true, +- "license": "Apache-2.0" +- }, +- "node_modules/aws-cdk-lib/node_modules/ajv": { +- "version": "8.17.1", +- "inBundle": true, +- "license": "MIT", +- "dependencies": { +- "fast-deep-equal": "^3.1.3", +- "fast-uri": "^3.0.1", +- "json-schema-traverse": "^1.0.0", +- "require-from-string": "^2.0.2" +- }, +- "funding": { +- "type": "github", +- "url": "https://github.com/sponsors/epoberezkin" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/ansi-regex": { +- "version": "5.0.1", +- "inBundle": true, +- "license": "MIT", +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/ansi-styles": { +- "version": "4.3.0", +- "inBundle": true, +- "license": "MIT", +- "dependencies": { +- "color-convert": "^2.0.1" +- }, +- "engines": { +- "node": ">=8" +- }, +- "funding": { +- "url": "https://github.com/chalk/ansi-styles?sponsor=1" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/astral-regex": { +- "version": "2.0.0", +- "inBundle": true, +- "license": "MIT", +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/balanced-match": { +- "version": "1.0.2", +- "inBundle": true, +- "license": "MIT" +- }, +- "node_modules/aws-cdk-lib/node_modules/brace-expansion": { +- "version": "1.1.11", +- "inBundle": true, +- "license": "MIT", +- "dependencies": { +- "balanced-match": "^1.0.0", +- "concat-map": "0.0.1" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/case": { +- "version": "1.6.3", +- "inBundle": true, +- "license": "(MIT OR GPL-3.0-or-later)", +- "engines": { +- "node": ">= 0.8.0" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/color-convert": { +- "version": "2.0.1", +- "inBundle": true, +- "license": "MIT", +- "dependencies": { +- "color-name": "~1.1.4" +- }, +- "engines": { +- "node": ">=7.0.0" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/color-name": { +- "version": "1.1.4", +- "inBundle": true, +- "license": "MIT" +- }, +- "node_modules/aws-cdk-lib/node_modules/concat-map": { +- "version": "0.0.1", +- "inBundle": true, +- "license": "MIT" +- }, +- "node_modules/aws-cdk-lib/node_modules/emoji-regex": { +- "version": "8.0.0", +- "inBundle": true, +- "license": "MIT" +- }, +- "node_modules/aws-cdk-lib/node_modules/fast-deep-equal": { +- "version": "3.1.3", +- "inBundle": true, +- "license": "MIT" +- }, +- "node_modules/aws-cdk-lib/node_modules/fast-uri": { +- "version": "3.0.6", +- "funding": [ +- { +- "type": "github", +- "url": "https://github.com/sponsors/fastify" +- }, +- { +- "type": "opencollective", +- "url": "https://opencollective.com/fastify" +- } +- ], +- "inBundle": true, +- "license": "BSD-3-Clause" +- }, +- "node_modules/aws-cdk-lib/node_modules/fs-extra": { +- "version": "11.3.0", +- "inBundle": true, +- "license": "MIT", +- "dependencies": { +- "graceful-fs": "^4.2.0", +- "jsonfile": "^6.0.1", +- "universalify": "^2.0.0" +- }, +- "engines": { +- "node": ">=14.14" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/graceful-fs": { +- "version": "4.2.11", +- "inBundle": true, +- "license": "ISC" +- }, +- "node_modules/aws-cdk-lib/node_modules/ignore": { +- "version": "5.3.2", +- "inBundle": true, +- "license": "MIT", +- "engines": { +- "node": ">= 4" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/is-fullwidth-code-point": { +- "version": "3.0.0", +- "inBundle": true, +- "license": "MIT", +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/json-schema-traverse": { +- "version": "1.0.0", +- "inBundle": true, +- "license": "MIT" +- }, +- "node_modules/aws-cdk-lib/node_modules/jsonfile": { +- "version": "6.1.0", +- "inBundle": true, +- "license": "MIT", +- "dependencies": { +- "universalify": "^2.0.0" +- }, +- "optionalDependencies": { +- "graceful-fs": "^4.1.6" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/jsonschema": { +- "version": "1.5.0", +- "inBundle": true, +- "license": "MIT", +- "engines": { +- "node": "*" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/lodash.truncate": { +- "version": "4.4.2", +- "inBundle": true, +- "license": "MIT" +- }, +- "node_modules/aws-cdk-lib/node_modules/mime-db": { +- "version": "1.52.0", +- "inBundle": true, +- "license": "MIT", +- "engines": { +- "node": ">= 0.6" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/mime-types": { +- "version": "2.1.35", +- "inBundle": true, +- "license": "MIT", +- "dependencies": { +- "mime-db": "1.52.0" +- }, +- "engines": { +- "node": ">= 0.6" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/minimatch": { +- "version": "3.1.2", +- "inBundle": true, +- "license": "ISC", +- "dependencies": { +- "brace-expansion": "^1.1.7" +- }, +- "engines": { +- "node": "*" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/punycode": { +- "version": "2.3.1", +- "inBundle": true, +- "license": "MIT", +- "engines": { +- "node": ">=6" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/require-from-string": { +- "version": "2.0.2", +- "inBundle": true, +- "license": "MIT", +- "engines": { +- "node": ">=0.10.0" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/semver": { +- "version": "7.7.2", +- "inBundle": true, +- "license": "ISC", +- "bin": { +- "semver": "bin/semver.js" +- }, +- "engines": { +- "node": ">=10" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/slice-ansi": { +- "version": "4.0.0", +- "inBundle": true, +- "license": "MIT", +- "dependencies": { +- "ansi-styles": "^4.0.0", +- "astral-regex": "^2.0.0", +- "is-fullwidth-code-point": "^3.0.0" +- }, +- "engines": { +- "node": ">=10" +- }, +- "funding": { +- "url": "https://github.com/chalk/slice-ansi?sponsor=1" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/string-width": { +- "version": "4.2.3", +- "inBundle": true, +- "license": "MIT", +- "dependencies": { +- "emoji-regex": "^8.0.0", +- "is-fullwidth-code-point": "^3.0.0", +- "strip-ansi": "^6.0.1" +- }, +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/strip-ansi": { +- "version": "6.0.1", +- "inBundle": true, +- "license": "MIT", +- "dependencies": { +- "ansi-regex": "^5.0.1" +- }, +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/table": { +- "version": "6.9.0", +- "inBundle": true, +- "license": "BSD-3-Clause", +- "dependencies": { +- "ajv": "^8.0.1", +- "lodash.truncate": "^4.4.2", +- "slice-ansi": "^4.0.0", +- "string-width": "^4.2.3", +- "strip-ansi": "^6.0.1" +- }, +- "engines": { +- "node": ">=10.0.0" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/universalify": { +- "version": "2.0.1", +- "inBundle": true, +- "license": "MIT", +- "engines": { +- "node": ">= 10.0.0" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/yaml": { +- "version": "1.10.2", +- "inBundle": true, +- "license": "ISC", +- "engines": { +- "node": ">= 6" +- } +- }, +- "node_modules/babel-jest": { +- "version": "29.7.0", +- "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", +- "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@jest/transform": "^29.7.0", +- "@types/babel__core": "^7.1.14", +- "babel-plugin-istanbul": "^6.1.1", +- "babel-preset-jest": "^29.6.3", +- "chalk": "^4.0.0", +- "graceful-fs": "^4.2.9", +- "slash": "^3.0.0" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- }, +- "peerDependencies": { +- "@babel/core": "^7.8.0" +- } +- }, +- "node_modules/babel-plugin-istanbul": { +- "version": "6.1.1", +- "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", +- "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", +- "dev": true, +- "license": "BSD-3-Clause", +- "dependencies": { +- "@babel/helper-plugin-utils": "^7.0.0", +- "@istanbuljs/load-nyc-config": "^1.0.0", +- "@istanbuljs/schema": "^0.1.2", +- "istanbul-lib-instrument": "^5.0.4", +- "test-exclude": "^6.0.0" +- }, +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { +- "version": "5.2.1", +- "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", +- "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", +- "dev": true, +- "license": "BSD-3-Clause", +- "dependencies": { +- "@babel/core": "^7.12.3", +- "@babel/parser": "^7.14.7", +- "@istanbuljs/schema": "^0.1.2", +- "istanbul-lib-coverage": "^3.2.0", +- "semver": "^6.3.0" +- }, +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/babel-plugin-jest-hoist": { +- "version": "29.6.3", +- "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", +- "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@babel/template": "^7.3.3", +- "@babel/types": "^7.3.3", +- "@types/babel__core": "^7.1.14", +- "@types/babel__traverse": "^7.0.6" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- } +- }, +- "node_modules/babel-preset-current-node-syntax": { +- "version": "1.1.0", +- "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", +- "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@babel/plugin-syntax-async-generators": "^7.8.4", +- "@babel/plugin-syntax-bigint": "^7.8.3", +- "@babel/plugin-syntax-class-properties": "^7.12.13", +- "@babel/plugin-syntax-class-static-block": "^7.14.5", +- "@babel/plugin-syntax-import-attributes": "^7.24.7", +- "@babel/plugin-syntax-import-meta": "^7.10.4", +- "@babel/plugin-syntax-json-strings": "^7.8.3", +- "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", +- "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", +- "@babel/plugin-syntax-numeric-separator": "^7.10.4", +- "@babel/plugin-syntax-object-rest-spread": "^7.8.3", +- "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", +- "@babel/plugin-syntax-optional-chaining": "^7.8.3", +- "@babel/plugin-syntax-private-property-in-object": "^7.14.5", +- "@babel/plugin-syntax-top-level-await": "^7.14.5" +- }, +- "peerDependencies": { +- "@babel/core": "^7.0.0" +- } +- }, +- "node_modules/babel-preset-jest": { +- "version": "29.6.3", +- "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", +- "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "babel-plugin-jest-hoist": "^29.6.3", +- "babel-preset-current-node-syntax": "^1.0.0" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- }, +- "peerDependencies": { +- "@babel/core": "^7.0.0" +- } +- }, +- "node_modules/balanced-match": { +- "version": "1.0.2", +- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", +- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", +- "dev": true, +- "license": "MIT" +- }, +- "node_modules/brace-expansion": { +- "version": "1.1.12", +- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", +- "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "balanced-match": "^1.0.0", +- "concat-map": "0.0.1" +- } +- }, +- "node_modules/braces": { +- "version": "3.0.3", +- "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", +- "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "fill-range": "^7.1.1" +- }, +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/browserslist": { +- "version": "4.25.0", +- "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", +- "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", +- "dev": true, +- "funding": [ +- { +- "type": "opencollective", +- "url": "https://opencollective.com/browserslist" +- }, +- { +- "type": "tidelift", +- "url": "https://tidelift.com/funding/github/npm/browserslist" +- }, +- { +- "type": "github", +- "url": "https://github.com/sponsors/ai" +- } +- ], +- "license": "MIT", +- "dependencies": { +- "caniuse-lite": "^1.0.30001718", +- "electron-to-chromium": "^1.5.160", +- "node-releases": "^2.0.19", +- "update-browserslist-db": "^1.1.3" +- }, +- "bin": { +- "browserslist": "cli.js" +- }, +- "engines": { +- "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" +- } +- }, +- "node_modules/bs-logger": { +- "version": "0.2.6", +- "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", +- "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "fast-json-stable-stringify": "2.x" +- }, +- "engines": { +- "node": ">= 6" +- } +- }, +- "node_modules/bser": { +- "version": "2.1.1", +- "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", +- "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", +- "dev": true, +- "license": "Apache-2.0", +- "dependencies": { +- "node-int64": "^0.4.0" +- } +- }, +- "node_modules/buffer-from": { +- "version": "1.1.2", +- "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", +- "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", +- "dev": true, +- "license": "MIT" +- }, +- "node_modules/callsites": { +- "version": "3.1.0", +- "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", +- "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=6" +- } +- }, +- "node_modules/camelcase": { +- "version": "5.3.1", +- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", +- "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=6" +- } +- }, +- "node_modules/caniuse-lite": { +- "version": "1.0.30001720", +- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001720.tgz", +- "integrity": "sha512-Ec/2yV2nNPwb4DnTANEV99ZWwm3ZWfdlfkQbWSDDt+PsXEVYwlhPH8tdMaPunYTKKmz7AnHi2oNEi1GcmKCD8g==", +- "dev": true, +- "funding": [ +- { +- "type": "opencollective", +- "url": "https://opencollective.com/browserslist" +- }, +- { +- "type": "tidelift", +- "url": "https://tidelift.com/funding/github/npm/caniuse-lite" +- }, +- { +- "type": "github", +- "url": "https://github.com/sponsors/ai" +- } +- ], +- "license": "CC-BY-4.0" +- }, +- "node_modules/chalk": { +- "version": "4.1.2", +- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", +- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "ansi-styles": "^4.1.0", +- "supports-color": "^7.1.0" +- }, +- "engines": { +- "node": ">=10" +- }, +- "funding": { +- "url": "https://github.com/chalk/chalk?sponsor=1" +- } +- }, +- "node_modules/char-regex": { +- "version": "1.0.2", +- "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", +- "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=10" +- } +- }, +- "node_modules/ci-info": { +- "version": "3.9.0", +- "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", +- "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", +- "dev": true, +- "funding": [ +- { +- "type": "github", +- "url": "https://github.com/sponsors/sibiraj-s" +- } +- ], +- "license": "MIT", +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/cjs-module-lexer": { +- "version": "1.4.3", +- "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", +- "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", +- "dev": true, +- "license": "MIT" +- }, +- "node_modules/cliui": { +- "version": "8.0.1", +- "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", +- "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", +- "dev": true, +- "license": "ISC", +- "dependencies": { +- "string-width": "^4.2.0", +- "strip-ansi": "^6.0.1", +- "wrap-ansi": "^7.0.0" +- }, +- "engines": { +- "node": ">=12" +- } +- }, +- "node_modules/co": { +- "version": "4.6.0", +- "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", +- "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "iojs": ">= 1.0.0", +- "node": ">= 0.12.0" +- } +- }, +- "node_modules/collect-v8-coverage": { +- "version": "1.0.2", +- "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", +- "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", +- "dev": true, +- "license": "MIT" +- }, +- "node_modules/color-convert": { +- "version": "2.0.1", +- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", +- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "color-name": "~1.1.4" +- }, +- "engines": { +- "node": ">=7.0.0" +- } +- }, +- "node_modules/color-name": { +- "version": "1.1.4", +- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", +- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", +- "dev": true, +- "license": "MIT" +- }, +- "node_modules/concat-map": { +- "version": "0.0.1", +- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", +- "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", +- "dev": true, +- "license": "MIT" +- }, +- "node_modules/constructs": { +- "version": "10.4.2", +- "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.4.2.tgz", +- "integrity": "sha512-wsNxBlAott2qg8Zv87q3eYZYgheb9lchtBfjHzzLHtXbttwSrHPs1NNQbBrmbb1YZvYg2+Vh0Dor76w4mFxJkA==", +- "license": "Apache-2.0" +- }, +- "node_modules/convert-source-map": { +- "version": "2.0.0", +- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", +- "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", +- "dev": true, +- "license": "MIT" +- }, +- "node_modules/create-jest": { +- "version": "29.7.0", +- "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", +- "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@jest/types": "^29.6.3", +- "chalk": "^4.0.0", +- "exit": "^0.1.2", +- "graceful-fs": "^4.2.9", +- "jest-config": "^29.7.0", +- "jest-util": "^29.7.0", +- "prompts": "^2.0.1" +- }, +- "bin": { +- "create-jest": "bin/create-jest.js" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- } +- }, +- "node_modules/create-require": { +- "version": "1.1.1", +- "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", +- "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", +- "dev": true, +- "license": "MIT" +- }, +- "node_modules/cross-spawn": { +- "version": "7.0.6", +- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", +- "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "path-key": "^3.1.0", +- "shebang-command": "^2.0.0", +- "which": "^2.0.1" +- }, +- "engines": { +- "node": ">= 8" +- } +- }, +- "node_modules/debug": { +- "version": "4.4.1", +- "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", +- "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "ms": "^2.1.3" +- }, +- "engines": { +- "node": ">=6.0" +- }, +- "peerDependenciesMeta": { +- "supports-color": { +- "optional": true +- } +- } +- }, +- "node_modules/dedent": { +- "version": "1.6.0", +- "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", +- "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", +- "dev": true, +- "license": "MIT", +- "peerDependencies": { +- "babel-plugin-macros": "^3.1.0" +- }, +- "peerDependenciesMeta": { +- "babel-plugin-macros": { +- "optional": true +- } +- } +- }, +- "node_modules/deepmerge": { +- "version": "4.3.1", +- "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", +- "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=0.10.0" +- } +- }, +- "node_modules/detect-newline": { +- "version": "3.1.0", +- "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", +- "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/diff": { +- "version": "4.0.2", +- "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", +- "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", +- "dev": true, +- "license": "BSD-3-Clause", +- "engines": { +- "node": ">=0.3.1" +- } +- }, +- "node_modules/diff-sequences": { +- "version": "29.6.3", +- "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", +- "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- } +- }, +- "node_modules/ejs": { +- "version": "3.1.10", +- "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", +- "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", +- "dev": true, +- "license": "Apache-2.0", +- "dependencies": { +- "jake": "^10.8.5" +- }, +- "bin": { +- "ejs": "bin/cli.js" +- }, +- "engines": { +- "node": ">=0.10.0" +- } +- }, +- "node_modules/electron-to-chromium": { +- "version": "1.5.161", +- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.161.tgz", +- "integrity": "sha512-hwtetwfKNZo/UlwHIVBlKZVdy7o8bIZxxKs0Mv/ROPiQQQmDgdm5a+KvKtBsxM8ZjFzTaCeLoodZ8jiBE3o9rA==", +- "dev": true, +- "license": "ISC" +- }, +- "node_modules/emittery": { +- "version": "0.13.1", +- "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", +- "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=12" +- }, +- "funding": { +- "url": "https://github.com/sindresorhus/emittery?sponsor=1" +- } +- }, +- "node_modules/emoji-regex": { +- "version": "8.0.0", +- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", +- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", +- "dev": true, +- "license": "MIT" +- }, +- "node_modules/error-ex": { +- "version": "1.3.2", +- "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", +- "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "is-arrayish": "^0.2.1" +- } +- }, +- "node_modules/escalade": { +- "version": "3.2.0", +- "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", +- "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=6" +- } +- }, +- "node_modules/escape-string-regexp": { +- "version": "2.0.0", +- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", +- "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/esprima": { +- "version": "4.0.1", +- "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", +- "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", +- "dev": true, +- "license": "BSD-2-Clause", +- "bin": { +- "esparse": "bin/esparse.js", +- "esvalidate": "bin/esvalidate.js" +- }, +- "engines": { +- "node": ">=4" +- } +- }, +- "node_modules/execa": { +- "version": "5.1.1", +- "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", +- "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "cross-spawn": "^7.0.3", +- "get-stream": "^6.0.0", +- "human-signals": "^2.1.0", +- "is-stream": "^2.0.0", +- "merge-stream": "^2.0.0", +- "npm-run-path": "^4.0.1", +- "onetime": "^5.1.2", +- "signal-exit": "^3.0.3", +- "strip-final-newline": "^2.0.0" +- }, +- "engines": { +- "node": ">=10" +- }, +- "funding": { +- "url": "https://github.com/sindresorhus/execa?sponsor=1" +- } +- }, +- "node_modules/exit": { +- "version": "0.1.2", +- "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", +- "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", +- "dev": true, +- "engines": { +- "node": ">= 0.8.0" +- } +- }, +- "node_modules/expect": { +- "version": "29.7.0", +- "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", +- "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@jest/expect-utils": "^29.7.0", +- "jest-get-type": "^29.6.3", +- "jest-matcher-utils": "^29.7.0", +- "jest-message-util": "^29.7.0", +- "jest-util": "^29.7.0" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- } +- }, +- "node_modules/fast-json-stable-stringify": { +- "version": "2.1.0", +- "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", +- "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", +- "dev": true, +- "license": "MIT" +- }, +- "node_modules/fb-watchman": { +- "version": "2.0.2", +- "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", +- "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", +- "dev": true, +- "license": "Apache-2.0", +- "dependencies": { +- "bser": "2.1.1" +- } +- }, +- "node_modules/filelist": { +- "version": "1.0.4", +- "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", +- "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", +- "dev": true, +- "license": "Apache-2.0", +- "dependencies": { +- "minimatch": "^5.0.1" +- } +- }, +- "node_modules/filelist/node_modules/brace-expansion": { +- "version": "2.0.2", +- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", +- "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "balanced-match": "^1.0.0" +- } +- }, +- "node_modules/filelist/node_modules/minimatch": { +- "version": "5.1.6", +- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", +- "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", +- "dev": true, +- "license": "ISC", +- "dependencies": { +- "brace-expansion": "^2.0.1" +- }, +- "engines": { +- "node": ">=10" +- } +- }, +- "node_modules/fill-range": { +- "version": "7.1.1", +- "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", +- "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "to-regex-range": "^5.0.1" +- }, +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/find-up": { +- "version": "4.1.0", +- "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", +- "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "locate-path": "^5.0.0", +- "path-exists": "^4.0.0" +- }, +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/fs.realpath": { +- "version": "1.0.0", +- "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", +- "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", +- "dev": true, +- "license": "ISC" +- }, +- "node_modules/fsevents": { +- "version": "2.3.2", +- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", +- "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", +- "dev": true, +- "hasInstallScript": true, +- "license": "MIT", +- "optional": true, +- "os": [ +- "darwin" +- ], +- "engines": { +- "node": "^8.16.0 || ^10.6.0 || >=11.0.0" +- } +- }, +- "node_modules/function-bind": { +- "version": "1.1.2", +- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", +- "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", +- "dev": true, +- "license": "MIT", +- "funding": { +- "url": "https://github.com/sponsors/ljharb" +- } +- }, +- "node_modules/gensync": { +- "version": "1.0.0-beta.2", +- "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", +- "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=6.9.0" +- } +- }, +- "node_modules/get-caller-file": { +- "version": "2.0.5", +- "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", +- "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", +- "dev": true, +- "license": "ISC", +- "engines": { +- "node": "6.* || 8.* || >= 10.*" +- } +- }, +- "node_modules/get-package-type": { +- "version": "0.1.0", +- "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", +- "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=8.0.0" +- } +- }, +- "node_modules/get-stream": { +- "version": "6.0.1", +- "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", +- "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=10" +- }, +- "funding": { +- "url": "https://github.com/sponsors/sindresorhus" +- } +- }, +- "node_modules/glob": { +- "version": "7.2.3", +- "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", +- "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", +- "deprecated": "Glob versions prior to v9 are no longer supported", +- "dev": true, +- "license": "ISC", +- "dependencies": { +- "fs.realpath": "^1.0.0", +- "inflight": "^1.0.4", +- "inherits": "2", +- "minimatch": "^3.1.1", +- "once": "^1.3.0", +- "path-is-absolute": "^1.0.0" +- }, +- "engines": { +- "node": "*" +- }, +- "funding": { +- "url": "https://github.com/sponsors/isaacs" +- } +- }, +- "node_modules/globals": { +- "version": "11.12.0", +- "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", +- "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=4" +- } +- }, +- "node_modules/graceful-fs": { +- "version": "4.2.11", +- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", +- "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", +- "dev": true, +- "license": "ISC" +- }, +- "node_modules/has-flag": { +- "version": "4.0.0", +- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", +- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/hasown": { +- "version": "2.0.2", +- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", +- "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "function-bind": "^1.1.2" +- }, +- "engines": { +- "node": ">= 0.4" +- } +- }, +- "node_modules/html-escaper": { +- "version": "2.0.2", +- "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", +- "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", +- "dev": true, +- "license": "MIT" +- }, +- "node_modules/human-signals": { +- "version": "2.1.0", +- "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", +- "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", +- "dev": true, +- "license": "Apache-2.0", +- "engines": { +- "node": ">=10.17.0" +- } +- }, +- "node_modules/import-local": { +- "version": "3.2.0", +- "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", +- "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "pkg-dir": "^4.2.0", +- "resolve-cwd": "^3.0.0" +- }, +- "bin": { +- "import-local-fixture": "fixtures/cli.js" +- }, +- "engines": { +- "node": ">=8" +- }, +- "funding": { +- "url": "https://github.com/sponsors/sindresorhus" +- } +- }, +- "node_modules/imurmurhash": { +- "version": "0.1.4", +- "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", +- "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=0.8.19" +- } +- }, +- "node_modules/inflight": { +- "version": "1.0.6", +- "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", +- "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", +- "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", +- "dev": true, +- "license": "ISC", +- "dependencies": { +- "once": "^1.3.0", +- "wrappy": "1" +- } +- }, +- "node_modules/inherits": { +- "version": "2.0.4", +- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", +- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", +- "dev": true, +- "license": "ISC" +- }, +- "node_modules/is-arrayish": { +- "version": "0.2.1", +- "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", +- "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", +- "dev": true, +- "license": "MIT" +- }, +- "node_modules/is-core-module": { +- "version": "2.16.1", +- "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", +- "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "hasown": "^2.0.2" +- }, +- "engines": { +- "node": ">= 0.4" +- }, +- "funding": { +- "url": "https://github.com/sponsors/ljharb" +- } +- }, +- "node_modules/is-fullwidth-code-point": { +- "version": "3.0.0", +- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", +- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/is-generator-fn": { +- "version": "2.1.0", +- "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", +- "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=6" +- } +- }, +- "node_modules/is-number": { +- "version": "7.0.0", +- "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", +- "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=0.12.0" +- } +- }, +- "node_modules/is-stream": { +- "version": "2.0.1", +- "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", +- "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=8" +- }, +- "funding": { +- "url": "https://github.com/sponsors/sindresorhus" +- } +- }, +- "node_modules/isexe": { +- "version": "2.0.0", +- "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", +- "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", +- "dev": true, +- "license": "ISC" +- }, +- "node_modules/istanbul-lib-coverage": { +- "version": "3.2.2", +- "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", +- "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", +- "dev": true, +- "license": "BSD-3-Clause", +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/istanbul-lib-instrument": { +- "version": "6.0.3", +- "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", +- "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", +- "dev": true, +- "license": "BSD-3-Clause", +- "dependencies": { +- "@babel/core": "^7.23.9", +- "@babel/parser": "^7.23.9", +- "@istanbuljs/schema": "^0.1.3", +- "istanbul-lib-coverage": "^3.2.0", +- "semver": "^7.5.4" +- }, +- "engines": { +- "node": ">=10" +- } +- }, +- "node_modules/istanbul-lib-instrument/node_modules/semver": { +- "version": "7.7.2", +- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", +- "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", +- "dev": true, +- "license": "ISC", +- "bin": { +- "semver": "bin/semver.js" +- }, +- "engines": { +- "node": ">=10" +- } +- }, +- "node_modules/istanbul-lib-report": { +- "version": "3.0.1", +- "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", +- "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", +- "dev": true, +- "license": "BSD-3-Clause", +- "dependencies": { +- "istanbul-lib-coverage": "^3.0.0", +- "make-dir": "^4.0.0", +- "supports-color": "^7.1.0" +- }, +- "engines": { +- "node": ">=10" +- } +- }, +- "node_modules/istanbul-lib-source-maps": { +- "version": "4.0.1", +- "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", +- "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", +- "dev": true, +- "license": "BSD-3-Clause", +- "dependencies": { +- "debug": "^4.1.1", +- "istanbul-lib-coverage": "^3.0.0", +- "source-map": "^0.6.1" +- }, +- "engines": { +- "node": ">=10" +- } +- }, +- "node_modules/istanbul-reports": { +- "version": "3.1.7", +- "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", +- "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", +- "dev": true, +- "license": "BSD-3-Clause", +- "dependencies": { +- "html-escaper": "^2.0.0", +- "istanbul-lib-report": "^3.0.0" +- }, +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/jake": { +- "version": "10.9.2", +- "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", +- "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", +- "dev": true, +- "license": "Apache-2.0", +- "dependencies": { +- "async": "^3.2.3", +- "chalk": "^4.0.2", +- "filelist": "^1.0.4", +- "minimatch": "^3.1.2" +- }, +- "bin": { +- "jake": "bin/cli.js" +- }, +- "engines": { +- "node": ">=10" +- } +- }, +- "node_modules/jest": { +- "version": "29.7.0", +- "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", +- "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@jest/core": "^29.7.0", +- "@jest/types": "^29.6.3", +- "import-local": "^3.0.2", +- "jest-cli": "^29.7.0" +- }, +- "bin": { +- "jest": "bin/jest.js" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- }, +- "peerDependencies": { +- "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" +- }, +- "peerDependenciesMeta": { +- "node-notifier": { +- "optional": true +- } +- } +- }, +- "node_modules/jest-changed-files": { +- "version": "29.7.0", +- "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", +- "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "execa": "^5.0.0", +- "jest-util": "^29.7.0", +- "p-limit": "^3.1.0" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- } +- }, +- "node_modules/jest-circus": { +- "version": "29.7.0", +- "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", +- "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@jest/environment": "^29.7.0", +- "@jest/expect": "^29.7.0", +- "@jest/test-result": "^29.7.0", +- "@jest/types": "^29.6.3", +- "@types/node": "*", +- "chalk": "^4.0.0", +- "co": "^4.6.0", +- "dedent": "^1.0.0", +- "is-generator-fn": "^2.0.0", +- "jest-each": "^29.7.0", +- "jest-matcher-utils": "^29.7.0", +- "jest-message-util": "^29.7.0", +- "jest-runtime": "^29.7.0", +- "jest-snapshot": "^29.7.0", +- "jest-util": "^29.7.0", +- "p-limit": "^3.1.0", +- "pretty-format": "^29.7.0", +- "pure-rand": "^6.0.0", +- "slash": "^3.0.0", +- "stack-utils": "^2.0.3" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- } +- }, +- "node_modules/jest-cli": { +- "version": "29.7.0", +- "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", +- "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@jest/core": "^29.7.0", +- "@jest/test-result": "^29.7.0", +- "@jest/types": "^29.6.3", +- "chalk": "^4.0.0", +- "create-jest": "^29.7.0", +- "exit": "^0.1.2", +- "import-local": "^3.0.2", +- "jest-config": "^29.7.0", +- "jest-util": "^29.7.0", +- "jest-validate": "^29.7.0", +- "yargs": "^17.3.1" +- }, +- "bin": { +- "jest": "bin/jest.js" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- }, +- "peerDependencies": { +- "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" +- }, +- "peerDependenciesMeta": { +- "node-notifier": { +- "optional": true +- } +- } +- }, +- "node_modules/jest-config": { +- "version": "29.7.0", +- "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", +- "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@babel/core": "^7.11.6", +- "@jest/test-sequencer": "^29.7.0", +- "@jest/types": "^29.6.3", +- "babel-jest": "^29.7.0", +- "chalk": "^4.0.0", +- "ci-info": "^3.2.0", +- "deepmerge": "^4.2.2", +- "glob": "^7.1.3", +- "graceful-fs": "^4.2.9", +- "jest-circus": "^29.7.0", +- "jest-environment-node": "^29.7.0", +- "jest-get-type": "^29.6.3", +- "jest-regex-util": "^29.6.3", +- "jest-resolve": "^29.7.0", +- "jest-runner": "^29.7.0", +- "jest-util": "^29.7.0", +- "jest-validate": "^29.7.0", +- "micromatch": "^4.0.4", +- "parse-json": "^5.2.0", +- "pretty-format": "^29.7.0", +- "slash": "^3.0.0", +- "strip-json-comments": "^3.1.1" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- }, +- "peerDependencies": { +- "@types/node": "*", +- "ts-node": ">=9.0.0" +- }, +- "peerDependenciesMeta": { +- "@types/node": { +- "optional": true +- }, +- "ts-node": { +- "optional": true +- } +- } +- }, +- "node_modules/jest-diff": { +- "version": "29.7.0", +- "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", +- "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "chalk": "^4.0.0", +- "diff-sequences": "^29.6.3", +- "jest-get-type": "^29.6.3", +- "pretty-format": "^29.7.0" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- } +- }, +- "node_modules/jest-docblock": { +- "version": "29.7.0", +- "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", +- "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "detect-newline": "^3.0.0" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- } +- }, +- "node_modules/jest-each": { +- "version": "29.7.0", +- "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", +- "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@jest/types": "^29.6.3", +- "chalk": "^4.0.0", +- "jest-get-type": "^29.6.3", +- "jest-util": "^29.7.0", +- "pretty-format": "^29.7.0" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- } +- }, +- "node_modules/jest-environment-node": { +- "version": "29.7.0", +- "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", +- "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@jest/environment": "^29.7.0", +- "@jest/fake-timers": "^29.7.0", +- "@jest/types": "^29.6.3", +- "@types/node": "*", +- "jest-mock": "^29.7.0", +- "jest-util": "^29.7.0" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- } +- }, +- "node_modules/jest-get-type": { +- "version": "29.6.3", +- "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", +- "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- } +- }, +- "node_modules/jest-haste-map": { +- "version": "29.7.0", +- "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", +- "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@jest/types": "^29.6.3", +- "@types/graceful-fs": "^4.1.3", +- "@types/node": "*", +- "anymatch": "^3.0.3", +- "fb-watchman": "^2.0.0", +- "graceful-fs": "^4.2.9", +- "jest-regex-util": "^29.6.3", +- "jest-util": "^29.7.0", +- "jest-worker": "^29.7.0", +- "micromatch": "^4.0.4", +- "walker": "^1.0.8" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- }, +- "optionalDependencies": { +- "fsevents": "^2.3.2" +- } +- }, +- "node_modules/jest-leak-detector": { +- "version": "29.7.0", +- "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", +- "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "jest-get-type": "^29.6.3", +- "pretty-format": "^29.7.0" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- } +- }, +- "node_modules/jest-matcher-utils": { +- "version": "29.7.0", +- "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", +- "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "chalk": "^4.0.0", +- "jest-diff": "^29.7.0", +- "jest-get-type": "^29.6.3", +- "pretty-format": "^29.7.0" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- } +- }, +- "node_modules/jest-message-util": { +- "version": "29.7.0", +- "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", +- "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@babel/code-frame": "^7.12.13", +- "@jest/types": "^29.6.3", +- "@types/stack-utils": "^2.0.0", +- "chalk": "^4.0.0", +- "graceful-fs": "^4.2.9", +- "micromatch": "^4.0.4", +- "pretty-format": "^29.7.0", +- "slash": "^3.0.0", +- "stack-utils": "^2.0.3" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- } +- }, +- "node_modules/jest-mock": { +- "version": "29.7.0", +- "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", +- "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@jest/types": "^29.6.3", +- "@types/node": "*", +- "jest-util": "^29.7.0" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- } +- }, +- "node_modules/jest-pnp-resolver": { +- "version": "1.2.3", +- "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", +- "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=6" +- }, +- "peerDependencies": { +- "jest-resolve": "*" +- }, +- "peerDependenciesMeta": { +- "jest-resolve": { +- "optional": true +- } +- } +- }, +- "node_modules/jest-regex-util": { +- "version": "29.6.3", +- "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", +- "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- } +- }, +- "node_modules/jest-resolve": { +- "version": "29.7.0", +- "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", +- "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "chalk": "^4.0.0", +- "graceful-fs": "^4.2.9", +- "jest-haste-map": "^29.7.0", +- "jest-pnp-resolver": "^1.2.2", +- "jest-util": "^29.7.0", +- "jest-validate": "^29.7.0", +- "resolve": "^1.20.0", +- "resolve.exports": "^2.0.0", +- "slash": "^3.0.0" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- } +- }, +- "node_modules/jest-resolve-dependencies": { +- "version": "29.7.0", +- "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", +- "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "jest-regex-util": "^29.6.3", +- "jest-snapshot": "^29.7.0" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- } +- }, +- "node_modules/jest-runner": { +- "version": "29.7.0", +- "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", +- "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@jest/console": "^29.7.0", +- "@jest/environment": "^29.7.0", +- "@jest/test-result": "^29.7.0", +- "@jest/transform": "^29.7.0", +- "@jest/types": "^29.6.3", +- "@types/node": "*", +- "chalk": "^4.0.0", +- "emittery": "^0.13.1", +- "graceful-fs": "^4.2.9", +- "jest-docblock": "^29.7.0", +- "jest-environment-node": "^29.7.0", +- "jest-haste-map": "^29.7.0", +- "jest-leak-detector": "^29.7.0", +- "jest-message-util": "^29.7.0", +- "jest-resolve": "^29.7.0", +- "jest-runtime": "^29.7.0", +- "jest-util": "^29.7.0", +- "jest-watcher": "^29.7.0", +- "jest-worker": "^29.7.0", +- "p-limit": "^3.1.0", +- "source-map-support": "0.5.13" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- } +- }, +- "node_modules/jest-runtime": { +- "version": "29.7.0", +- "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", +- "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@jest/environment": "^29.7.0", +- "@jest/fake-timers": "^29.7.0", +- "@jest/globals": "^29.7.0", +- "@jest/source-map": "^29.6.3", +- "@jest/test-result": "^29.7.0", +- "@jest/transform": "^29.7.0", +- "@jest/types": "^29.6.3", +- "@types/node": "*", +- "chalk": "^4.0.0", +- "cjs-module-lexer": "^1.0.0", +- "collect-v8-coverage": "^1.0.0", +- "glob": "^7.1.3", +- "graceful-fs": "^4.2.9", +- "jest-haste-map": "^29.7.0", +- "jest-message-util": "^29.7.0", +- "jest-mock": "^29.7.0", +- "jest-regex-util": "^29.6.3", +- "jest-resolve": "^29.7.0", +- "jest-snapshot": "^29.7.0", +- "jest-util": "^29.7.0", +- "slash": "^3.0.0", +- "strip-bom": "^4.0.0" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- } +- }, +- "node_modules/jest-snapshot": { +- "version": "29.7.0", +- "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", +- "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@babel/core": "^7.11.6", +- "@babel/generator": "^7.7.2", +- "@babel/plugin-syntax-jsx": "^7.7.2", +- "@babel/plugin-syntax-typescript": "^7.7.2", +- "@babel/types": "^7.3.3", +- "@jest/expect-utils": "^29.7.0", +- "@jest/transform": "^29.7.0", +- "@jest/types": "^29.6.3", +- "babel-preset-current-node-syntax": "^1.0.0", +- "chalk": "^4.0.0", +- "expect": "^29.7.0", +- "graceful-fs": "^4.2.9", +- "jest-diff": "^29.7.0", +- "jest-get-type": "^29.6.3", +- "jest-matcher-utils": "^29.7.0", +- "jest-message-util": "^29.7.0", +- "jest-util": "^29.7.0", +- "natural-compare": "^1.4.0", +- "pretty-format": "^29.7.0", +- "semver": "^7.5.3" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- } +- }, +- "node_modules/jest-snapshot/node_modules/semver": { +- "version": "7.7.2", +- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", +- "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", +- "dev": true, +- "license": "ISC", +- "bin": { +- "semver": "bin/semver.js" +- }, +- "engines": { +- "node": ">=10" +- } +- }, +- "node_modules/jest-util": { +- "version": "29.7.0", +- "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", +- "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@jest/types": "^29.6.3", +- "@types/node": "*", +- "chalk": "^4.0.0", +- "ci-info": "^3.2.0", +- "graceful-fs": "^4.2.9", +- "picomatch": "^2.2.3" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- } +- }, +- "node_modules/jest-validate": { +- "version": "29.7.0", +- "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", +- "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@jest/types": "^29.6.3", +- "camelcase": "^6.2.0", +- "chalk": "^4.0.0", +- "jest-get-type": "^29.6.3", +- "leven": "^3.1.0", +- "pretty-format": "^29.7.0" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- } +- }, +- "node_modules/jest-validate/node_modules/camelcase": { +- "version": "6.3.0", +- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", +- "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=10" +- }, +- "funding": { +- "url": "https://github.com/sponsors/sindresorhus" +- } +- }, +- "node_modules/jest-watcher": { +- "version": "29.7.0", +- "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", +- "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@jest/test-result": "^29.7.0", +- "@jest/types": "^29.6.3", +- "@types/node": "*", +- "ansi-escapes": "^4.2.1", +- "chalk": "^4.0.0", +- "emittery": "^0.13.1", +- "jest-util": "^29.7.0", +- "string-length": "^4.0.1" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- } +- }, +- "node_modules/jest-worker": { +- "version": "29.7.0", +- "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", +- "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@types/node": "*", +- "jest-util": "^29.7.0", +- "merge-stream": "^2.0.0", +- "supports-color": "^8.0.0" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- } +- }, +- "node_modules/jest-worker/node_modules/supports-color": { +- "version": "8.1.1", +- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", +- "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "has-flag": "^4.0.0" +- }, +- "engines": { +- "node": ">=10" +- }, +- "funding": { +- "url": "https://github.com/chalk/supports-color?sponsor=1" +- } +- }, +- "node_modules/js-tokens": { +- "version": "4.0.0", +- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", +- "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", +- "dev": true, +- "license": "MIT" +- }, +- "node_modules/js-yaml": { +- "version": "3.14.1", +- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", +- "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "argparse": "^1.0.7", +- "esprima": "^4.0.0" +- }, +- "bin": { +- "js-yaml": "bin/js-yaml.js" +- } +- }, +- "node_modules/jsesc": { +- "version": "3.1.0", +- "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", +- "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", +- "dev": true, +- "license": "MIT", +- "bin": { +- "jsesc": "bin/jsesc" +- }, +- "engines": { +- "node": ">=6" +- } +- }, +- "node_modules/json-parse-even-better-errors": { +- "version": "2.3.1", +- "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", +- "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", +- "dev": true, +- "license": "MIT" +- }, +- "node_modules/json5": { +- "version": "2.2.3", +- "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", +- "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", +- "dev": true, +- "license": "MIT", +- "bin": { +- "json5": "lib/cli.js" +- }, +- "engines": { +- "node": ">=6" +- } +- }, +- "node_modules/kleur": { +- "version": "3.0.3", +- "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", +- "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=6" +- } +- }, +- "node_modules/leven": { +- "version": "3.1.0", +- "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", +- "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=6" +- } +- }, +- "node_modules/lines-and-columns": { +- "version": "1.2.4", +- "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", +- "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", +- "dev": true, +- "license": "MIT" +- }, +- "node_modules/locate-path": { +- "version": "5.0.0", +- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", +- "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "p-locate": "^4.1.0" +- }, +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/lodash.memoize": { +- "version": "4.1.2", +- "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", +- "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", +- "dev": true, +- "license": "MIT" +- }, +- "node_modules/lru-cache": { +- "version": "5.1.1", +- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", +- "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", +- "dev": true, +- "license": "ISC", +- "dependencies": { +- "yallist": "^3.0.2" +- } +- }, +- "node_modules/make-dir": { +- "version": "4.0.0", +- "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", +- "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "semver": "^7.5.3" +- }, +- "engines": { +- "node": ">=10" +- }, +- "funding": { +- "url": "https://github.com/sponsors/sindresorhus" +- } +- }, +- "node_modules/make-dir/node_modules/semver": { +- "version": "7.7.2", +- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", +- "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", +- "dev": true, +- "license": "ISC", +- "bin": { +- "semver": "bin/semver.js" +- }, +- "engines": { +- "node": ">=10" +- } +- }, +- "node_modules/make-error": { +- "version": "1.3.6", +- "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", +- "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", +- "dev": true, +- "license": "ISC" +- }, +- "node_modules/makeerror": { +- "version": "1.0.12", +- "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", +- "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", +- "dev": true, +- "license": "BSD-3-Clause", +- "dependencies": { +- "tmpl": "1.0.5" +- } +- }, +- "node_modules/merge-stream": { +- "version": "2.0.0", +- "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", +- "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", +- "dev": true, +- "license": "MIT" +- }, +- "node_modules/micromatch": { +- "version": "4.0.8", +- "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", +- "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "braces": "^3.0.3", +- "picomatch": "^2.3.1" +- }, +- "engines": { +- "node": ">=8.6" +- } +- }, +- "node_modules/mimic-fn": { +- "version": "2.1.0", +- "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", +- "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=6" +- } +- }, +- "node_modules/minimatch": { +- "version": "3.1.2", +- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", +- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", +- "dev": true, +- "license": "ISC", +- "dependencies": { +- "brace-expansion": "^1.1.7" +- }, +- "engines": { +- "node": "*" +- } +- }, +- "node_modules/ms": { +- "version": "2.1.3", +- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", +- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", +- "dev": true, +- "license": "MIT" +- }, +- "node_modules/natural-compare": { +- "version": "1.4.0", +- "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", +- "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", +- "dev": true, +- "license": "MIT" +- }, +- "node_modules/node-int64": { +- "version": "0.4.0", +- "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", +- "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", +- "dev": true, +- "license": "MIT" +- }, +- "node_modules/node-releases": { +- "version": "2.0.19", +- "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", +- "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", +- "dev": true, +- "license": "MIT" +- }, +- "node_modules/normalize-path": { +- "version": "3.0.0", +- "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", +- "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=0.10.0" +- } +- }, +- "node_modules/npm-run-path": { +- "version": "4.0.1", +- "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", +- "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "path-key": "^3.0.0" +- }, +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/once": { +- "version": "1.4.0", +- "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", +- "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", +- "dev": true, +- "license": "ISC", +- "dependencies": { +- "wrappy": "1" +- } +- }, +- "node_modules/onetime": { +- "version": "5.1.2", +- "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", +- "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "mimic-fn": "^2.1.0" +- }, +- "engines": { +- "node": ">=6" +- }, +- "funding": { +- "url": "https://github.com/sponsors/sindresorhus" +- } +- }, +- "node_modules/p-limit": { +- "version": "3.1.0", +- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", +- "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "yocto-queue": "^0.1.0" +- }, +- "engines": { +- "node": ">=10" +- }, +- "funding": { +- "url": "https://github.com/sponsors/sindresorhus" +- } +- }, +- "node_modules/p-locate": { +- "version": "4.1.0", +- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", +- "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "p-limit": "^2.2.0" +- }, +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/p-locate/node_modules/p-limit": { +- "version": "2.3.0", +- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", +- "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "p-try": "^2.0.0" +- }, +- "engines": { +- "node": ">=6" +- }, +- "funding": { +- "url": "https://github.com/sponsors/sindresorhus" +- } +- }, +- "node_modules/p-try": { +- "version": "2.2.0", +- "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", +- "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=6" +- } +- }, +- "node_modules/parse-json": { +- "version": "5.2.0", +- "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", +- "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@babel/code-frame": "^7.0.0", +- "error-ex": "^1.3.1", +- "json-parse-even-better-errors": "^2.3.0", +- "lines-and-columns": "^1.1.6" +- }, +- "engines": { +- "node": ">=8" +- }, +- "funding": { +- "url": "https://github.com/sponsors/sindresorhus" +- } +- }, +- "node_modules/path-exists": { +- "version": "4.0.0", +- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", +- "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/path-is-absolute": { +- "version": "1.0.1", +- "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", +- "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=0.10.0" +- } +- }, +- "node_modules/path-key": { +- "version": "3.1.1", +- "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", +- "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/path-parse": { +- "version": "1.0.7", +- "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", +- "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", +- "dev": true, +- "license": "MIT" +- }, +- "node_modules/picocolors": { +- "version": "1.1.1", +- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", +- "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", +- "dev": true, +- "license": "ISC" +- }, +- "node_modules/picomatch": { +- "version": "2.3.1", +- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", +- "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=8.6" +- }, +- "funding": { +- "url": "https://github.com/sponsors/jonschlinkert" +- } +- }, +- "node_modules/pirates": { +- "version": "4.0.7", +- "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", +- "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">= 6" +- } +- }, +- "node_modules/pkg-dir": { +- "version": "4.2.0", +- "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", +- "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "find-up": "^4.0.0" +- }, +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/pretty-format": { +- "version": "29.7.0", +- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", +- "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@jest/schemas": "^29.6.3", +- "ansi-styles": "^5.0.0", +- "react-is": "^18.0.0" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || >=18.0.0" +- } +- }, +- "node_modules/pretty-format/node_modules/ansi-styles": { +- "version": "5.2.0", +- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", +- "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=10" +- }, +- "funding": { +- "url": "https://github.com/chalk/ansi-styles?sponsor=1" +- } +- }, +- "node_modules/prompts": { +- "version": "2.4.2", +- "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", +- "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "kleur": "^3.0.3", +- "sisteransi": "^1.0.5" +- }, +- "engines": { +- "node": ">= 6" +- } +- }, +- "node_modules/pure-rand": { +- "version": "6.1.0", +- "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", +- "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", +- "dev": true, +- "funding": [ +- { +- "type": "individual", +- "url": "https://github.com/sponsors/dubzzz" +- }, +- { +- "type": "opencollective", +- "url": "https://opencollective.com/fast-check" +- } +- ], +- "license": "MIT" +- }, +- "node_modules/react-is": { +- "version": "18.3.1", +- "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", +- "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", +- "dev": true, +- "license": "MIT" +- }, +- "node_modules/require-directory": { +- "version": "2.1.1", +- "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", +- "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=0.10.0" +- } +- }, +- "node_modules/resolve": { +- "version": "1.22.10", +- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", +- "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "is-core-module": "^2.16.0", +- "path-parse": "^1.0.7", +- "supports-preserve-symlinks-flag": "^1.0.0" +- }, +- "bin": { +- "resolve": "bin/resolve" +- }, +- "engines": { +- "node": ">= 0.4" +- }, +- "funding": { +- "url": "https://github.com/sponsors/ljharb" +- } +- }, +- "node_modules/resolve-cwd": { +- "version": "3.0.0", +- "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", +- "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "resolve-from": "^5.0.0" +- }, +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/resolve-from": { +- "version": "5.0.0", +- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", +- "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/resolve.exports": { +- "version": "2.0.3", +- "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", +- "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=10" +- } +- }, +- "node_modules/semver": { +- "version": "6.3.1", +- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", +- "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", +- "dev": true, +- "license": "ISC", +- "bin": { +- "semver": "bin/semver.js" +- } +- }, +- "node_modules/shebang-command": { +- "version": "2.0.0", +- "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", +- "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "shebang-regex": "^3.0.0" +- }, +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/shebang-regex": { +- "version": "3.0.0", +- "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", +- "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/signal-exit": { +- "version": "3.0.7", +- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", +- "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", +- "dev": true, +- "license": "ISC" +- }, +- "node_modules/sisteransi": { +- "version": "1.0.5", +- "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", +- "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", +- "dev": true, +- "license": "MIT" +- }, +- "node_modules/slash": { +- "version": "3.0.0", +- "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", +- "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/source-map": { +- "version": "0.6.1", +- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", +- "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", +- "dev": true, +- "license": "BSD-3-Clause", +- "engines": { +- "node": ">=0.10.0" +- } +- }, +- "node_modules/source-map-support": { +- "version": "0.5.13", +- "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", +- "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "buffer-from": "^1.0.0", +- "source-map": "^0.6.0" +- } +- }, +- "node_modules/sprintf-js": { +- "version": "1.0.3", +- "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", +- "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", +- "dev": true, +- "license": "BSD-3-Clause" +- }, +- "node_modules/stack-utils": { +- "version": "2.0.6", +- "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", +- "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "escape-string-regexp": "^2.0.0" +- }, +- "engines": { +- "node": ">=10" +- } +- }, +- "node_modules/string-length": { +- "version": "4.0.2", +- "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", +- "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "char-regex": "^1.0.2", +- "strip-ansi": "^6.0.0" +- }, +- "engines": { +- "node": ">=10" +- } +- }, +- "node_modules/string-width": { +- "version": "4.2.3", +- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", +- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "emoji-regex": "^8.0.0", +- "is-fullwidth-code-point": "^3.0.0", +- "strip-ansi": "^6.0.1" +- }, +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/strip-ansi": { +- "version": "6.0.1", +- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", +- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "ansi-regex": "^5.0.1" +- }, +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/strip-bom": { +- "version": "4.0.0", +- "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", +- "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/strip-final-newline": { +- "version": "2.0.0", +- "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", +- "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=6" +- } +- }, +- "node_modules/strip-json-comments": { +- "version": "3.1.1", +- "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", +- "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=8" +- }, +- "funding": { +- "url": "https://github.com/sponsors/sindresorhus" +- } +- }, +- "node_modules/supports-color": { +- "version": "7.2.0", +- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", +- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "has-flag": "^4.0.0" +- }, +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/supports-preserve-symlinks-flag": { +- "version": "1.0.0", +- "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", +- "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">= 0.4" +- }, +- "funding": { +- "url": "https://github.com/sponsors/ljharb" +- } +- }, +- "node_modules/test-exclude": { +- "version": "6.0.0", +- "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", +- "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", +- "dev": true, +- "license": "ISC", +- "dependencies": { +- "@istanbuljs/schema": "^0.1.2", +- "glob": "^7.1.4", +- "minimatch": "^3.0.4" +- }, +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/tmpl": { +- "version": "1.0.5", +- "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", +- "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", +- "dev": true, +- "license": "BSD-3-Clause" +- }, +- "node_modules/to-regex-range": { +- "version": "5.0.1", +- "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", +- "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "is-number": "^7.0.0" +- }, +- "engines": { +- "node": ">=8.0" +- } +- }, +- "node_modules/ts-jest": { +- "version": "29.3.4", +- "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.3.4.tgz", +- "integrity": "sha512-Iqbrm8IXOmV+ggWHOTEbjwyCf2xZlUMv5npExksXohL+tk8va4Fjhb+X2+Rt9NBmgO7bJ8WpnMLOwih/DnMlFA==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "bs-logger": "^0.2.6", +- "ejs": "^3.1.10", +- "fast-json-stable-stringify": "^2.1.0", +- "jest-util": "^29.0.0", +- "json5": "^2.2.3", +- "lodash.memoize": "^4.1.2", +- "make-error": "^1.3.6", +- "semver": "^7.7.2", +- "type-fest": "^4.41.0", +- "yargs-parser": "^21.1.1" +- }, +- "bin": { +- "ts-jest": "cli.js" +- }, +- "engines": { +- "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" +- }, +- "peerDependencies": { +- "@babel/core": ">=7.0.0-beta.0 <8", +- "@jest/transform": "^29.0.0", +- "@jest/types": "^29.0.0", +- "babel-jest": "^29.0.0", +- "jest": "^29.0.0", +- "typescript": ">=4.3 <6" +- }, +- "peerDependenciesMeta": { +- "@babel/core": { +- "optional": true +- }, +- "@jest/transform": { +- "optional": true +- }, +- "@jest/types": { +- "optional": true +- }, +- "babel-jest": { +- "optional": true +- }, +- "esbuild": { +- "optional": true +- } +- } +- }, +- "node_modules/ts-jest/node_modules/semver": { +- "version": "7.7.2", +- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", +- "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", +- "dev": true, +- "license": "ISC", +- "bin": { +- "semver": "bin/semver.js" +- }, +- "engines": { +- "node": ">=10" +- } +- }, +- "node_modules/ts-jest/node_modules/type-fest": { +- "version": "4.41.0", +- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", +- "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", +- "dev": true, +- "license": "(MIT OR CC0-1.0)", +- "engines": { +- "node": ">=16" +- }, +- "funding": { +- "url": "https://github.com/sponsors/sindresorhus" +- } +- }, +- "node_modules/ts-node": { +- "version": "10.9.2", +- "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", +- "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "@cspotcode/source-map-support": "^0.8.0", +- "@tsconfig/node10": "^1.0.7", +- "@tsconfig/node12": "^1.0.7", +- "@tsconfig/node14": "^1.0.0", +- "@tsconfig/node16": "^1.0.2", +- "acorn": "^8.4.1", +- "acorn-walk": "^8.1.1", +- "arg": "^4.1.0", +- "create-require": "^1.1.0", +- "diff": "^4.0.1", +- "make-error": "^1.1.1", +- "v8-compile-cache-lib": "^3.0.1", +- "yn": "3.1.1" +- }, +- "bin": { +- "ts-node": "dist/bin.js", +- "ts-node-cwd": "dist/bin-cwd.js", +- "ts-node-esm": "dist/bin-esm.js", +- "ts-node-script": "dist/bin-script.js", +- "ts-node-transpile-only": "dist/bin-transpile.js", +- "ts-script": "dist/bin-script-deprecated.js" +- }, +- "peerDependencies": { +- "@swc/core": ">=1.2.50", +- "@swc/wasm": ">=1.2.50", +- "@types/node": "*", +- "typescript": ">=2.7" +- }, +- "peerDependenciesMeta": { +- "@swc/core": { +- "optional": true +- }, +- "@swc/wasm": { +- "optional": true +- } +- } +- }, +- "node_modules/type-detect": { +- "version": "4.0.8", +- "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", +- "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=4" +- } +- }, +- "node_modules/type-fest": { +- "version": "0.21.3", +- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", +- "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", +- "dev": true, +- "license": "(MIT OR CC0-1.0)", +- "engines": { +- "node": ">=10" +- }, +- "funding": { +- "url": "https://github.com/sponsors/sindresorhus" +- } +- }, +- "node_modules/typescript": { +- "version": "5.6.3", +- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", +- "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", +- "dev": true, +- "license": "Apache-2.0", +- "bin": { +- "tsc": "bin/tsc", +- "tsserver": "bin/tsserver" +- }, +- "engines": { +- "node": ">=14.17" +- } +- }, +- "node_modules/undici-types": { +- "version": "6.19.8", +- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", +- "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", +- "dev": true, +- "license": "MIT" +- }, +- "node_modules/update-browserslist-db": { +- "version": "1.1.3", +- "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", +- "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", +- "dev": true, +- "funding": [ +- { +- "type": "opencollective", +- "url": "https://opencollective.com/browserslist" +- }, +- { +- "type": "tidelift", +- "url": "https://tidelift.com/funding/github/npm/browserslist" +- }, +- { +- "type": "github", +- "url": "https://github.com/sponsors/ai" +- } +- ], +- "license": "MIT", +- "dependencies": { +- "escalade": "^3.2.0", +- "picocolors": "^1.1.1" +- }, +- "bin": { +- "update-browserslist-db": "cli.js" +- }, +- "peerDependencies": { +- "browserslist": ">= 4.21.0" +- } +- }, +- "node_modules/v8-compile-cache-lib": { +- "version": "3.0.1", +- "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", +- "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", +- "dev": true, +- "license": "MIT" +- }, +- "node_modules/v8-to-istanbul": { +- "version": "9.3.0", +- "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", +- "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", +- "dev": true, +- "license": "ISC", +- "dependencies": { +- "@jridgewell/trace-mapping": "^0.3.12", +- "@types/istanbul-lib-coverage": "^2.0.1", +- "convert-source-map": "^2.0.0" +- }, +- "engines": { +- "node": ">=10.12.0" +- } +- }, +- "node_modules/walker": { +- "version": "1.0.8", +- "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", +- "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", +- "dev": true, +- "license": "Apache-2.0", +- "dependencies": { +- "makeerror": "1.0.12" +- } +- }, +- "node_modules/which": { +- "version": "2.0.2", +- "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", +- "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", +- "dev": true, +- "license": "ISC", +- "dependencies": { +- "isexe": "^2.0.0" +- }, +- "bin": { +- "node-which": "bin/node-which" +- }, +- "engines": { +- "node": ">= 8" +- } +- }, +- "node_modules/wrap-ansi": { +- "version": "7.0.0", +- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", +- "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "ansi-styles": "^4.0.0", +- "string-width": "^4.1.0", +- "strip-ansi": "^6.0.0" +- }, +- "engines": { +- "node": ">=10" +- }, +- "funding": { +- "url": "https://github.com/chalk/wrap-ansi?sponsor=1" +- } +- }, +- "node_modules/wrappy": { +- "version": "1.0.2", +- "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", +- "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", +- "dev": true, +- "license": "ISC" +- }, +- "node_modules/write-file-atomic": { +- "version": "4.0.2", +- "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", +- "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", +- "dev": true, +- "license": "ISC", +- "dependencies": { +- "imurmurhash": "^0.1.4", +- "signal-exit": "^3.0.7" +- }, +- "engines": { +- "node": "^12.13.0 || ^14.15.0 || >=16.0.0" +- } +- }, +- "node_modules/y18n": { +- "version": "5.0.8", +- "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", +- "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", +- "dev": true, +- "license": "ISC", +- "engines": { +- "node": ">=10" +- } +- }, +- "node_modules/yallist": { +- "version": "3.1.1", +- "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", +- "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", +- "dev": true, +- "license": "ISC" +- }, +- "node_modules/yargs": { +- "version": "17.7.2", +- "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", +- "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", +- "dev": true, +- "license": "MIT", +- "dependencies": { +- "cliui": "^8.0.1", +- "escalade": "^3.1.1", +- "get-caller-file": "^2.0.5", +- "require-directory": "^2.1.1", +- "string-width": "^4.2.3", +- "y18n": "^5.0.5", +- "yargs-parser": "^21.1.1" +- }, +- "engines": { +- "node": ">=12" +- } +- }, +- "node_modules/yargs-parser": { +- "version": "21.1.1", +- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", +- "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", +- "dev": true, +- "license": "ISC", +- "engines": { +- "node": ">=12" +- } +- }, +- "node_modules/yn": { +- "version": "3.1.1", +- "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", +- "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=6" +- } +- }, +- "node_modules/yocto-queue": { +- "version": "0.1.0", +- "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", +- "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", +- "dev": true, +- "license": "MIT", +- "engines": { +- "node": ">=10" +- }, +- "funding": { +- "url": "https://github.com/sponsors/sindresorhus" +- } +- } +- } +-} +diff --git a/examples/Event Handler/BedrockAgentFunction/infra/package.json b/examples/Event Handler/BedrockAgentFunction/infra/package.json +deleted file mode 100644 +index eb6545ca..00000000 +--- a/examples/Event Handler/BedrockAgentFunction/infra/package.json ++++ /dev/null +@@ -1,26 +0,0 @@ +-{ +- "name": "infra", +- "version": "0.1.0", +- "bin": { +- "infra": "bin/infra.js" +- }, +- "scripts": { +- "build": "tsc", +- "watch": "tsc -w", +- "test": "jest", +- "cdk": "cdk" +- }, +- "devDependencies": { +- "@types/jest": "^29.5.14", +- "@types/node": "22.7.9", +- "jest": "^29.7.0", +- "ts-jest": "^29.2.5", +- "aws-cdk": "2.1017.1", +- "ts-node": "^10.9.2", +- "typescript": "~5.6.3" +- }, +- "dependencies": { +- "aws-cdk-lib": "2.198.0", +- "constructs": "^10.0.0" +- } +-} +diff --git a/examples/Event Handler/BedrockAgentFunction/infra/tsconfig.json b/examples/Event Handler/BedrockAgentFunction/infra/tsconfig.json +deleted file mode 100644 +index 28bb557f..00000000 +--- a/examples/Event Handler/BedrockAgentFunction/infra/tsconfig.json ++++ /dev/null +@@ -1,31 +0,0 @@ +-{ +- "compilerOptions": { +- "target": "ES2022", +- "module": "NodeNext", +- "moduleResolution": "NodeNext", +- "lib": [ +- "es2022" +- ], +- "declaration": true, +- "strict": true, +- "noImplicitAny": true, +- "strictNullChecks": true, +- "noImplicitThis": true, +- "alwaysStrict": true, +- "noUnusedLocals": false, +- "noUnusedParameters": false, +- "noImplicitReturns": true, +- "noFallthroughCasesInSwitch": false, +- "inlineSourceMap": true, +- "inlineSources": true, +- "experimentalDecorators": true, +- "strictPropertyInitialization": false, +- "typeRoots": [ +- "./node_modules/@types" +- ] +- }, +- "exclude": [ +- "node_modules", +- "cdk.out" +- ] +-} +diff --git a/examples/Event Handler/BedrockAgentFunction/src/AirportService.cs b/examples/Event Handler/BedrockAgentFunction/src/AirportService.cs +deleted file mode 100644 +index aa26e7f9..00000000 +--- a/examples/Event Handler/BedrockAgentFunction/src/AirportService.cs ++++ /dev/null +@@ -1,222 +0,0 @@ +-namespace BedrockAgentFunction; +- +-public class AirportService +-{ +- private readonly Dictionary _airportsByCity = new(StringComparer.OrdinalIgnoreCase) +- { +- { +- "New York", +- new AirportInfo { City = "New York", Code = "JFK", Name = "John F. Kennedy International Airport" } +- }, +- { "London", new AirportInfo { City = "London", Code = "LHR", Name = "London Heathrow Airport" } }, +- { "Paris", new AirportInfo { City = "Paris", Code = "CDG", Name = "Charles de Gaulle Airport" } }, +- { "Tokyo", new AirportInfo { City = "Tokyo", Code = "HND", Name = "Tokyo Haneda Airport" } }, +- { "Sydney", new AirportInfo { City = "Sydney", Code = "SYD", Name = "Sydney Airport" } }, +- { +- "Los Angeles", +- new AirportInfo { City = "Los Angeles", Code = "LAX", Name = "Los Angeles International Airport" } +- }, +- { "Berlin", new AirportInfo { City = "Berlin", Code = "TXL", Name = "Berlin Tegel Airport" } }, +- { "Dubai", new AirportInfo { City = "Dubai", Code = "DXB", Name = "Dubai International Airport" } }, +- { +- "Toronto", +- new AirportInfo { City = "Toronto", Code = "YYZ", Name = "Toronto Pearson International Airport" } +- }, +- { "Singapore", new AirportInfo { City = "Singapore", Code = "SIN", Name = "Singapore Changi Airport" } }, +- { "Hong Kong", new AirportInfo { City = "Hong Kong", Code = "HKG", Name = "Hong Kong International Airport" } }, +- { "Madrid", new AirportInfo { City = "Madrid", Code = "MAD", Name = "Adolfo Suárez Madrid–Barajas Airport" } }, +- { "Rome", new AirportInfo { City = "Rome", Code = "FCO", Name = "Leonardo da Vinci International Airport" } }, +- { "Moscow", new AirportInfo { City = "Moscow", Code = "SVO", Name = "Sheremetyevo International Airport" } }, +- { +- "São Paulo", +- new AirportInfo +- { +- City = "São Paulo", Code = "GRU", +- Name = "São Paulo/Guarulhos–Governador André Franco Montoro International Airport" +- } +- }, +- { "Istanbul", new AirportInfo { City = "Istanbul", Code = "IST", Name = "Istanbul Airport" } }, +- { "Bangkok", new AirportInfo { City = "Bangkok", Code = "BKK", Name = "Suvarnabhumi Airport" } }, +- { +- "Mexico City", +- new AirportInfo { City = "Mexico City", Code = "MEX", Name = "Mexico City International Airport" } +- }, +- { "Cairo", new AirportInfo { City = "Cairo", Code = "CAI", Name = "Cairo International Airport" } }, +- { +- "Buenos Aires", +- new AirportInfo { City = "Buenos Aires", Code = "EZE", Name = "Ministro Pistarini International Airport" } +- }, +- { +- "Kuala Lumpur", +- new AirportInfo { City = "Kuala Lumpur", Code = "KUL", Name = "Kuala Lumpur International Airport" } +- }, +- { "Amsterdam", new AirportInfo { City = "Amsterdam", Code = "AMS", Name = "Amsterdam Airport Schiphol" } }, +- { "Barcelona", new AirportInfo { City = "Barcelona", Code = "BCN", Name = "Barcelona–El Prat Airport" } }, +- { "Lima", new AirportInfo { City = "Lima", Code = "LIM", Name = "Jorge Chávez International Airport" } }, +- { "Seoul", new AirportInfo { City = "Seoul", Code = "ICN", Name = "Incheon International Airport" } }, +- { +- "Rio de Janeiro", +- new AirportInfo +- { +- City = "Rio de Janeiro", Code = "GIG", +- Name = "Rio de Janeiro/Galeão–Antonio Carlos Jobim International Airport" +- } +- }, +- { "Dublin", new AirportInfo { City = "Dublin", Code = "DUB", Name = "Dublin Airport" } }, +- { "Brussels", new AirportInfo { City = "Brussels", Code = "BRU", Name = "Brussels Airport" } }, +- { "Lisbon", new AirportInfo { City = "Lisbon", Code = "LIS", Name = "Lisbon Portela Airport" } }, +- { "Athens", new AirportInfo { City = "Athens", Code = "ATH", Name = "Athens International Airport" } }, +- { "Oslo", new AirportInfo { City = "Oslo", Code = "OSL", Name = "Oslo Airport, Gardermoen" } }, +- { "Stockholm", new AirportInfo { City = "Stockholm", Code = "ARN", Name = "Stockholm Arlanda Airport" } }, +- { "Helsinki", new AirportInfo { City = "Helsinki", Code = "HEL", Name = "Helsinki-Vantaa Airport" } }, +- { "Prague", new AirportInfo { City = "Prague", Code = "PRG", Name = "Václav Havel Airport Prague" } }, +- { "Warsaw", new AirportInfo { City = "Warsaw", Code = "WAW", Name = "Warsaw Chopin Airport" } }, +- { "Copenhagen", new AirportInfo { City = "Copenhagen", Code = "CPH", Name = "Copenhagen Airport" } }, +- { +- "Budapest", +- new AirportInfo { City = "Budapest", Code = "BUD", Name = "Budapest Ferenc Liszt International Airport" } +- }, +- { "Osaka", new AirportInfo { City = "Osaka", Code = "KIX", Name = "Kansai International Airport" } }, +- { +- "San Francisco", +- new AirportInfo { City = "San Francisco", Code = "SFO", Name = "San Francisco International Airport" } +- }, +- { "Miami", new AirportInfo { City = "Miami", Code = "MIA", Name = "Miami International Airport" } }, +- { +- "Seattle", new AirportInfo { City = "Seattle", Code = "SEA", Name = "Seattle–Tacoma International Airport" } +- }, +- { "Vancouver", new AirportInfo { City = "Vancouver", Code = "YVR", Name = "Vancouver International Airport" } }, +- { "Melbourne", new AirportInfo { City = "Melbourne", Code = "MEL", Name = "Melbourne Airport" } }, +- { "Auckland", new AirportInfo { City = "Auckland", Code = "AKL", Name = "Auckland Airport" } }, +- { "Doha", new AirportInfo { City = "Doha", Code = "DOH", Name = "Hamad International Airport" } }, +- { +- "Kuwait City", new AirportInfo { City = "Kuwait City", Code = "KWI", Name = "Kuwait International Airport" } +- }, +- { +- "Bangalore", new AirportInfo { City = "Bangalore", Code = "BLR", Name = "Kempegowda International Airport" } +- }, +- { +- "Beijing", +- new AirportInfo { City = "Beijing", Code = "PEK", Name = "Beijing Capital International Airport" } +- }, +- { +- "Shanghai", +- new AirportInfo { City = "Shanghai", Code = "PVG", Name = "Shanghai Pudong International Airport" } +- }, +- { "Manila", new AirportInfo { City = "Manila", Code = "MNL", Name = "Ninoy Aquino International Airport" } }, +- { +- "Jakarta", new AirportInfo { City = "Jakarta", Code = "CGK", Name = "Soekarno–Hatta International Airport" } +- }, +- { +- "Santiago", +- new AirportInfo +- { City = "Santiago", Code = "SCL", Name = "Comodoro Arturo Merino Benítez International Airport" } +- }, +- { "Lagos", new AirportInfo { City = "Lagos", Code = "LOS", Name = "Murtala Muhammed International Airport" } }, +- { "Nairobi", new AirportInfo { City = "Nairobi", Code = "NBO", Name = "Jomo Kenyatta International Airport" } }, +- { "Chicago", new AirportInfo { City = "Chicago", Code = "ORD", Name = "O'Hare International Airport" } }, +- { +- "Atlanta", +- new AirportInfo +- { City = "Atlanta", Code = "ATL", Name = "Hartsfield–Jackson Atlanta International Airport" } +- }, +- { +- "Dallas", +- new AirportInfo { City = "Dallas", Code = "DFW", Name = "Dallas/Fort Worth International Airport" } +- }, +- { +- "Washington, D.C.", +- new AirportInfo +- { City = "Washington, D.C.", Code = "IAD", Name = "Washington Dulles International Airport" } +- }, +- { "Boston", new AirportInfo { City = "Boston", Code = "BOS", Name = "Logan International Airport" } }, +- { +- "Philadelphia", +- new AirportInfo { City = "Philadelphia", Code = "PHL", Name = "Philadelphia International Airport" } +- }, +- { "Orlando", new AirportInfo { City = "Orlando", Code = "MCO", Name = "Orlando International Airport" } }, +- { "Denver", new AirportInfo { City = "Denver", Code = "DEN", Name = "Denver International Airport" } }, +- { +- "Phoenix", +- new AirportInfo { City = "Phoenix", Code = "PHX", Name = "Phoenix Sky Harbor International Airport" } +- }, +- { "Las Vegas", new AirportInfo { City = "Las Vegas", Code = "LAS", Name = "McCarran International Airport" } }, +- { +- "Houston", new AirportInfo { City = "Houston", Code = "IAH", Name = "George Bush Intercontinental Airport" } +- }, +- { +- "Detroit", +- new AirportInfo { City = "Detroit", Code = "DTW", Name = "Detroit Metropolitan Wayne County Airport" } +- }, +- { +- "Charlotte", +- new AirportInfo { City = "Charlotte", Code = "CLT", Name = "Charlotte Douglas International Airport" } +- }, +- { +- "Baltimore", +- new AirportInfo +- { +- City = "Baltimore", Code = "BWI", Name = "Baltimore/Washington International Thurgood Marshall Airport" +- } +- }, +- { +- "Minneapolis", +- new AirportInfo +- { City = "Minneapolis", Code = "MSP", Name = "Minneapolis–Saint Paul International Airport" } +- }, +- { "San Diego", new AirportInfo { City = "San Diego", Code = "SAN", Name = "San Diego International Airport" } }, +- { "Portland", new AirportInfo { City = "Portland", Code = "PDX", Name = "Portland International Airport" } }, +- { +- "Salt Lake City", +- new AirportInfo { City = "Salt Lake City", Code = "SLC", Name = "Salt Lake City International Airport" } +- }, +- { +- "Cincinnati", +- new AirportInfo +- { City = "Cincinnati", Code = "CVG", Name = "Cincinnati/Northern Kentucky International Airport" } +- }, +- { +- "St. Louis", +- new AirportInfo { City = "St. Louis", Code = "STL", Name = "St. Louis Lambert International Airport" } +- }, +- { +- "Indianapolis", +- new AirportInfo { City = "Indianapolis", Code = "IND", Name = "Indianapolis International Airport" } +- }, +- { "Tampa", new AirportInfo { City = "Tampa", Code = "TPA", Name = "Tampa International Airport" } }, +- { "Milan", new AirportInfo { City = "Milan", Code = "MXP", Name = "Milan Malpensa Airport" } }, +- { "Frankfurt", new AirportInfo { City = "Frankfurt", Code = "FRA", Name = "Frankfurt am Main Airport" } }, +- { "Munich", new AirportInfo { City = "Munich", Code = "MUC", Name = "Munich Airport" } }, +- { +- "Mumbai", +- new AirportInfo +- { City = "Mumbai", Code = "BOM", Name = "Chhatrapati Shivaji Maharaj International Airport" } +- }, +- { "Cape Town", new AirportInfo { City = "Cape Town", Code = "CPT", Name = "Cape Town International Airport" } }, +- { "Zurich", new AirportInfo { City = "Zurich", Code = "ZRH", Name = "Zurich Airport" } }, +- { "Vienna", new AirportInfo { City = "Vienna", Code = "VIE", Name = "Vienna International Airport" } } +- // Add more airports as needed +- }; +- +- public AirportInfo GetAirportInfoForCity(string city) +- { +- if (_airportsByCity.TryGetValue(city, out var airportInfo)) +- { +- return airportInfo; +- } +- +- throw new KeyNotFoundException($"No airport information found for city: {city}"); +- } +-} +- +-public class AirportInfo +-{ +- public string City { get; set; } = string.Empty; +- public string Code { get; set; } = string.Empty; +- public string Name { get; set; } = string.Empty; +- +- public override string ToString() +- { +- return $"{Name} ({Code}) in {City}"; +- } +-} +\ No newline at end of file +diff --git a/examples/Event Handler/BedrockAgentFunction/src/BedrockAgentFunction.csproj b/examples/Event Handler/BedrockAgentFunction/src/BedrockAgentFunction.csproj +deleted file mode 100644 +index bcd2c51c..00000000 +--- a/examples/Event Handler/BedrockAgentFunction/src/BedrockAgentFunction.csproj ++++ /dev/null +@@ -1,22 +0,0 @@ +- +- +- Exe +- net8.0 +- enable +- enable +- true +- Lambda +- +- true +- +- true +- +- +- +- +- +- +- +- +- +- +\ No newline at end of file +diff --git a/examples/Event Handler/BedrockAgentFunction/src/Function.cs b/examples/Event Handler/BedrockAgentFunction/src/Function.cs +deleted file mode 100644 +index c4e847ef..00000000 +--- a/examples/Event Handler/BedrockAgentFunction/src/Function.cs ++++ /dev/null +@@ -1,45 +0,0 @@ +-using Amazon.Lambda.Core; +-using Amazon.Lambda.RuntimeSupport; +-using Amazon.Lambda.Serialization.SystemTextJson; +-using AWS.Lambda.Powertools.EventHandler.Resolvers; +-using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; +-using AWS.Lambda.Powertools.Logging; +-using BedrockAgentFunction; +-using Microsoft.Extensions.Logging; +- +- +-var logger = LoggerFactory.Create(builder => +-{ +- builder.AddPowertoolsLogger(config => { config.Service = "AirportService"; }); +-}).CreatePowertoolsLogger(); +- +-var resolver = new BedrockAgentFunctionResolver(); +- +- +-resolver.Tool("getAirportCodeForCity", "Get airport code and full name for a specific city", (string city, ILambdaContext context) => +-{ +- logger.LogInformation("Getting airport code for city: {City}", city); +- var airportService = new AirportService(); +- var airportInfo = airportService.GetAirportInfoForCity(city); +- +- logger.LogInformation("Airport for {City}: {AirportInfoCode} - {AirportInfoName}", city, airportInfo.Code, airportInfo.Name); +- +- // Note: Best approach is to override the ToString method in the AirportInfo class +- return airportInfo; +-}); +- +- +-// The function handler that will be called for each Lambda event +-var handler = async (BedrockFunctionRequest input, ILambdaContext context) => +-{ +- return await resolver.ResolveAsync(input, context); +-}; +- +-// Build the Lambda runtime client passing in the handler to call for each +-// event and the JSON serializer to use for translating Lambda JSON documents +-// to .NET types. +-await LambdaBootstrapBuilder.Create(handler, new DefaultLambdaJsonSerializer()) +- .Build() +- .RunAsync(); +- +- +diff --git a/examples/Event Handler/BedrockAgentFunction/src/aws-lambda-tools-defaults.json b/examples/Event Handler/BedrockAgentFunction/src/aws-lambda-tools-defaults.json +deleted file mode 100644 +index 1dc447ae..00000000 +--- a/examples/Event Handler/BedrockAgentFunction/src/aws-lambda-tools-defaults.json ++++ /dev/null +@@ -1,15 +0,0 @@ +-{ +- "Information": [ +- "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", +- "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", +- "dotnet lambda help", +- "All the command line options for the Lambda command can be specified in this file." +- ], +- "profile": "", +- "region": "", +- "configuration": "Release", +- "function-runtime": "dotnet8", +- "function-memory-size": 512, +- "function-timeout": 30, +- "function-handler": "BedrockAgentFunction" +-} +\ No newline at end of file +diff --git a/examples/Idempotency/src/HelloWorld/HelloWorld.csproj b/examples/Idempotency/src/HelloWorld/HelloWorld.csproj +index 39615764..86f103f9 100644 +--- a/examples/Idempotency/src/HelloWorld/HelloWorld.csproj ++++ b/examples/Idempotency/src/HelloWorld/HelloWorld.csproj +@@ -5,10 +5,10 @@ + enable + + +- ++ + +- +- +- ++ ++ ++ + + +diff --git a/examples/Idempotency/test/HelloWorld.Test/HelloWorld.Tests.csproj b/examples/Idempotency/test/HelloWorld.Test/HelloWorld.Tests.csproj +index e143aa86..b00a6873 100644 +--- a/examples/Idempotency/test/HelloWorld.Test/HelloWorld.Tests.csproj ++++ b/examples/Idempotency/test/HelloWorld.Test/HelloWorld.Tests.csproj +@@ -3,9 +3,9 @@ + net6.0;net8.0 + + +- ++ + +- ++ + + + +diff --git a/examples/Kafka/Avro/src/Avro.csproj b/examples/Kafka/Avro/src/Avro.csproj +deleted file mode 100644 +index 05314f2f..00000000 +--- a/examples/Kafka/Avro/src/Avro.csproj ++++ /dev/null +@@ -1,35 +0,0 @@ +- +- +- Exe +- net8.0 +- enable +- enable +- true +- Lambda +- +- true +- +- true +- +- Avro.Example +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- PreserveNewest +- +- +- +\ No newline at end of file +diff --git a/examples/Kafka/Avro/src/CustomerProfile.avsc b/examples/Kafka/Avro/src/CustomerProfile.avsc +deleted file mode 100644 +index bf8cc090..00000000 +--- a/examples/Kafka/Avro/src/CustomerProfile.avsc ++++ /dev/null +@@ -1,46 +0,0 @@ +-{ +- "type": "record", +- "name": "CustomerProfile", +- "namespace": "com.example", +- "fields": [ +- {"name": "user_id", "type": "string"}, +- {"name": "full_name", "type": "string"}, +- {"name": "email", "type": { +- "type": "record", +- "name": "EmailAddress", +- "fields": [ +- {"name": "address", "type": "string"}, +- {"name": "verified", "type": "boolean"}, +- {"name": "primary", "type": "boolean"} +- ] +- }}, +- {"name": "age", "type": "int"}, +- {"name": "address", "type": { +- "type": "record", +- "name": "Address", +- "fields": [ +- {"name": "street", "type": "string"}, +- {"name": "city", "type": "string"}, +- {"name": "state", "type": "string"}, +- {"name": "country", "type": "string"}, +- {"name": "zip_code", "type": "string"} +- ] +- }}, +- {"name": "phone_numbers", "type": { +- "type": "array", +- "items": { +- "type": "record", +- "name": "PhoneNumber", +- "fields": [ +- {"name": "number", "type": "string"}, +- {"name": "type", "type": {"type": "enum", "name": "PhoneType", "symbols": ["HOME", "WORK", "MOBILE"]}} +- ] +- } +- }}, +- {"name": "preferences", "type": { +- "type": "map", +- "values": "string" +- }}, +- {"name": "account_status", "type": {"type": "enum", "name": "AccountStatus", "symbols": ["ACTIVE", "INACTIVE", "SUSPENDED"]}} +- ] +-} +\ No newline at end of file +diff --git a/examples/Kafka/Avro/src/Function.cs b/examples/Kafka/Avro/src/Function.cs +deleted file mode 100644 +index 6ca9ebdb..00000000 +--- a/examples/Kafka/Avro/src/Function.cs ++++ /dev/null +@@ -1,21 +0,0 @@ +-using Amazon.Lambda.Core; +-using Amazon.Lambda.RuntimeSupport; +-using AWS.Lambda.Powertools.Kafka; +-using AWS.Lambda.Powertools.Kafka.Avro; +-using AWS.Lambda.Powertools.Logging; +-using com.example; +- +-string Handler(ConsumerRecords records, ILambdaContext context) +-{ +- foreach (var record in records) +- { +- Logger.LogInformation("Record Value: {@record}", record.Value); +- } +- +- return "Processed " + records.Count() + " records"; +-} +- +-await LambdaBootstrapBuilder.Create((Func, ILambdaContext, string>?)Handler, +- new PowertoolsKafkaAvroSerializer()) // Use PowertoolsKafkaAvroSerializer for Avro serialization +- .Build() +- .RunAsync(); +\ No newline at end of file +diff --git a/examples/Kafka/Avro/src/Generated/com/example/AccountStatus.cs b/examples/Kafka/Avro/src/Generated/com/example/AccountStatus.cs +deleted file mode 100644 +index c7809f51..00000000 +--- a/examples/Kafka/Avro/src/Generated/com/example/AccountStatus.cs ++++ /dev/null +@@ -1,23 +0,0 @@ +-// ------------------------------------------------------------------------------ +-// +-// Generated by avrogen, version 1.12.0+8c27801dc8d42ccc00997f25c0b8f45f8d4a233e +-// Changes to this file may cause incorrect behavior and will be lost if code +-// is regenerated +-// +-// ------------------------------------------------------------------------------ +-namespace com.example +-{ +- using System; +- using System.Collections.Generic; +- using System.Text; +- using global::Avro; +- using global::Avro.Specific; +- +- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("avrogen", "1.12.0+8c27801dc8d42ccc00997f25c0b8f45f8d4a233e")] +- public enum AccountStatus +- { +- ACTIVE, +- INACTIVE, +- SUSPENDED, +- } +-} +diff --git a/examples/Kafka/Avro/src/Generated/com/example/Address.cs b/examples/Kafka/Avro/src/Generated/com/example/Address.cs +deleted file mode 100644 +index e2053e0f..00000000 +--- a/examples/Kafka/Avro/src/Generated/com/example/Address.cs ++++ /dev/null +@@ -1,115 +0,0 @@ +-// ------------------------------------------------------------------------------ +-// +-// Generated by avrogen, version 1.12.0+8c27801dc8d42ccc00997f25c0b8f45f8d4a233e +-// Changes to this file may cause incorrect behavior and will be lost if code +-// is regenerated +-// +-// ------------------------------------------------------------------------------ +-namespace com.example +-{ +- using System; +- using System.Collections.Generic; +- using System.Text; +- using global::Avro; +- using global::Avro.Specific; +- +- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("avrogen", "1.12.0+8c27801dc8d42ccc00997f25c0b8f45f8d4a233e")] +- public partial class Address : global::Avro.Specific.ISpecificRecord +- { +- public static global::Avro.Schema _SCHEMA = global::Avro.Schema.Parse("{\"type\":\"record\",\"name\":\"Address\",\"namespace\":\"com.example\",\"fields\":[{\"name\":\"st" + +- "reet\",\"type\":\"string\"},{\"name\":\"city\",\"type\":\"string\"},{\"name\":\"state\",\"type\":\"s" + +- "tring\"},{\"name\":\"country\",\"type\":\"string\"},{\"name\":\"zip_code\",\"type\":\"string\"}]}" + +- ""); +- private string _street; +- private string _city; +- private string _state; +- private string _country; +- private string _zip_code; +- public virtual global::Avro.Schema Schema +- { +- get +- { +- return Address._SCHEMA; +- } +- } +- public string street +- { +- get +- { +- return this._street; +- } +- set +- { +- this._street = value; +- } +- } +- public string city +- { +- get +- { +- return this._city; +- } +- set +- { +- this._city = value; +- } +- } +- public string state +- { +- get +- { +- return this._state; +- } +- set +- { +- this._state = value; +- } +- } +- public string country +- { +- get +- { +- return this._country; +- } +- set +- { +- this._country = value; +- } +- } +- public string zip_code +- { +- get +- { +- return this._zip_code; +- } +- set +- { +- this._zip_code = value; +- } +- } +- public virtual object Get(int fieldPos) +- { +- switch (fieldPos) +- { +- case 0: return this.street; +- case 1: return this.city; +- case 2: return this.state; +- case 3: return this.country; +- case 4: return this.zip_code; +- default: throw new global::Avro.AvroRuntimeException("Bad index " + fieldPos + " in Get()"); +- }; +- } +- public virtual void Put(int fieldPos, object fieldValue) +- { +- switch (fieldPos) +- { +- case 0: this.street = (System.String)fieldValue; break; +- case 1: this.city = (System.String)fieldValue; break; +- case 2: this.state = (System.String)fieldValue; break; +- case 3: this.country = (System.String)fieldValue; break; +- case 4: this.zip_code = (System.String)fieldValue; break; +- default: throw new global::Avro.AvroRuntimeException("Bad index " + fieldPos + " in Put()"); +- }; +- } +- } +-} +diff --git a/examples/Kafka/Avro/src/Generated/com/example/CustomerProfile.cs b/examples/Kafka/Avro/src/Generated/com/example/CustomerProfile.cs +deleted file mode 100644 +index 15d62095..00000000 +--- a/examples/Kafka/Avro/src/Generated/com/example/CustomerProfile.cs ++++ /dev/null +@@ -1,154 +0,0 @@ +-// ------------------------------------------------------------------------------ +-// +-// Generated by avrogen, version 1.12.0+8c27801dc8d42ccc00997f25c0b8f45f8d4a233e +-// Changes to this file may cause incorrect behavior and will be lost if code +-// is regenerated +-// +-// ------------------------------------------------------------------------------ +-namespace com.example +-{ +- using System; +- using System.Collections.Generic; +- using System.Text; +- using global::Avro; +- using global::Avro.Specific; +- +- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("avrogen", "1.12.0+8c27801dc8d42ccc00997f25c0b8f45f8d4a233e")] +- public partial class CustomerProfile : global::Avro.Specific.ISpecificRecord +- { +- public static global::Avro.Schema _SCHEMA = global::Avro.Schema.Parse(@"{""type"":""record"",""name"":""CustomerProfile"",""namespace"":""com.example"",""fields"":[{""name"":""user_id"",""type"":""string""},{""name"":""full_name"",""type"":""string""},{""name"":""email"",""type"":{""type"":""record"",""name"":""EmailAddress"",""namespace"":""com.example"",""fields"":[{""name"":""address"",""type"":""string""},{""name"":""verified"",""type"":""boolean""},{""name"":""primary"",""type"":""boolean""}]}},{""name"":""age"",""type"":""int""},{""name"":""address"",""type"":{""type"":""record"",""name"":""Address"",""namespace"":""com.example"",""fields"":[{""name"":""street"",""type"":""string""},{""name"":""city"",""type"":""string""},{""name"":""state"",""type"":""string""},{""name"":""country"",""type"":""string""},{""name"":""zip_code"",""type"":""string""}]}},{""name"":""phone_numbers"",""type"":{""type"":""array"",""items"":{""type"":""record"",""name"":""PhoneNumber"",""namespace"":""com.example"",""fields"":[{""name"":""number"",""type"":""string""},{""name"":""type"",""type"":{""type"":""enum"",""name"":""PhoneType"",""namespace"":""com.example"",""symbols"":[""HOME"",""WORK"",""MOBILE""]}}]}}},{""name"":""preferences"",""type"":{""type"":""map"",""values"":""string""}},{""name"":""account_status"",""type"":{""type"":""enum"",""name"":""AccountStatus"",""namespace"":""com.example"",""symbols"":[""ACTIVE"",""INACTIVE"",""SUSPENDED""]}}]}"); +- private string _user_id; +- private string _full_name; +- private com.example.EmailAddress _email; +- private int _age; +- private com.example.Address _address; +- private IList _phone_numbers; +- private IDictionary _preferences; +- private com.example.AccountStatus _account_status; +- public virtual global::Avro.Schema Schema +- { +- get +- { +- return CustomerProfile._SCHEMA; +- } +- } +- public string user_id +- { +- get +- { +- return this._user_id; +- } +- set +- { +- this._user_id = value; +- } +- } +- public string full_name +- { +- get +- { +- return this._full_name; +- } +- set +- { +- this._full_name = value; +- } +- } +- public com.example.EmailAddress email +- { +- get +- { +- return this._email; +- } +- set +- { +- this._email = value; +- } +- } +- public int age +- { +- get +- { +- return this._age; +- } +- set +- { +- this._age = value; +- } +- } +- public com.example.Address address +- { +- get +- { +- return this._address; +- } +- set +- { +- this._address = value; +- } +- } +- public IList phone_numbers +- { +- get +- { +- return this._phone_numbers; +- } +- set +- { +- this._phone_numbers = value; +- } +- } +- public IDictionary preferences +- { +- get +- { +- return this._preferences; +- } +- set +- { +- this._preferences = value; +- } +- } +- public com.example.AccountStatus account_status +- { +- get +- { +- return this._account_status; +- } +- set +- { +- this._account_status = value; +- } +- } +- public virtual object Get(int fieldPos) +- { +- switch (fieldPos) +- { +- case 0: return this.user_id; +- case 1: return this.full_name; +- case 2: return this.email; +- case 3: return this.age; +- case 4: return this.address; +- case 5: return this.phone_numbers; +- case 6: return this.preferences; +- case 7: return this.account_status; +- default: throw new global::Avro.AvroRuntimeException("Bad index " + fieldPos + " in Get()"); +- }; +- } +- public virtual void Put(int fieldPos, object fieldValue) +- { +- switch (fieldPos) +- { +- case 0: this.user_id = (System.String)fieldValue; break; +- case 1: this.full_name = (System.String)fieldValue; break; +- case 2: this.email = (com.example.EmailAddress)fieldValue; break; +- case 3: this.age = (System.Int32)fieldValue; break; +- case 4: this.address = (com.example.Address)fieldValue; break; +- case 5: this.phone_numbers = (IList)fieldValue; break; +- case 6: this.preferences = (IDictionary)fieldValue; break; +- case 7: this.account_status = (com.example.AccountStatus)fieldValue; break; +- default: throw new global::Avro.AvroRuntimeException("Bad index " + fieldPos + " in Put()"); +- }; +- } +- } +-} +diff --git a/examples/Kafka/Avro/src/Generated/com/example/EmailAddress.cs b/examples/Kafka/Avro/src/Generated/com/example/EmailAddress.cs +deleted file mode 100644 +index 4a25a6e0..00000000 +--- a/examples/Kafka/Avro/src/Generated/com/example/EmailAddress.cs ++++ /dev/null +@@ -1,86 +0,0 @@ +-// ------------------------------------------------------------------------------ +-// +-// Generated by avrogen, version 1.12.0+8c27801dc8d42ccc00997f25c0b8f45f8d4a233e +-// Changes to this file may cause incorrect behavior and will be lost if code +-// is regenerated +-// +-// ------------------------------------------------------------------------------ +-namespace com.example +-{ +- using System; +- using System.Collections.Generic; +- using System.Text; +- using global::Avro; +- using global::Avro.Specific; +- +- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("avrogen", "1.12.0+8c27801dc8d42ccc00997f25c0b8f45f8d4a233e")] +- public partial class EmailAddress : global::Avro.Specific.ISpecificRecord +- { +- public static global::Avro.Schema _SCHEMA = global::Avro.Schema.Parse("{\"type\":\"record\",\"name\":\"EmailAddress\",\"namespace\":\"com.example\",\"fields\":[{\"name" + +- "\":\"address\",\"type\":\"string\"},{\"name\":\"verified\",\"type\":\"boolean\"},{\"name\":\"prima" + +- "ry\",\"type\":\"boolean\"}]}"); +- private string _address; +- private bool _verified; +- private bool _primary; +- public virtual global::Avro.Schema Schema +- { +- get +- { +- return EmailAddress._SCHEMA; +- } +- } +- public string address +- { +- get +- { +- return this._address; +- } +- set +- { +- this._address = value; +- } +- } +- public bool verified +- { +- get +- { +- return this._verified; +- } +- set +- { +- this._verified = value; +- } +- } +- public bool primary +- { +- get +- { +- return this._primary; +- } +- set +- { +- this._primary = value; +- } +- } +- public virtual object Get(int fieldPos) +- { +- switch (fieldPos) +- { +- case 0: return this.address; +- case 1: return this.verified; +- case 2: return this.primary; +- default: throw new global::Avro.AvroRuntimeException("Bad index " + fieldPos + " in Get()"); +- }; +- } +- public virtual void Put(int fieldPos, object fieldValue) +- { +- switch (fieldPos) +- { +- case 0: this.address = (System.String)fieldValue; break; +- case 1: this.verified = (System.Boolean)fieldValue; break; +- case 2: this.primary = (System.Boolean)fieldValue; break; +- default: throw new global::Avro.AvroRuntimeException("Bad index " + fieldPos + " in Put()"); +- }; +- } +- } +-} +diff --git a/examples/Kafka/Avro/src/Generated/com/example/PhoneNumber.cs b/examples/Kafka/Avro/src/Generated/com/example/PhoneNumber.cs +deleted file mode 100644 +index ea3d2b8e..00000000 +--- a/examples/Kafka/Avro/src/Generated/com/example/PhoneNumber.cs ++++ /dev/null +@@ -1,72 +0,0 @@ +-// ------------------------------------------------------------------------------ +-// +-// Generated by avrogen, version 1.12.0+8c27801dc8d42ccc00997f25c0b8f45f8d4a233e +-// Changes to this file may cause incorrect behavior and will be lost if code +-// is regenerated +-// +-// ------------------------------------------------------------------------------ +-namespace com.example +-{ +- using System; +- using System.Collections.Generic; +- using System.Text; +- using global::Avro; +- using global::Avro.Specific; +- +- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("avrogen", "1.12.0+8c27801dc8d42ccc00997f25c0b8f45f8d4a233e")] +- public partial class PhoneNumber : global::Avro.Specific.ISpecificRecord +- { +- public static global::Avro.Schema _SCHEMA = global::Avro.Schema.Parse("{\"type\":\"record\",\"name\":\"PhoneNumber\",\"namespace\":\"com.example\",\"fields\":[{\"name\"" + +- ":\"number\",\"type\":\"string\"},{\"name\":\"type\",\"type\":{\"type\":\"enum\",\"name\":\"PhoneTyp" + +- "e\",\"namespace\":\"com.example\",\"symbols\":[\"HOME\",\"WORK\",\"MOBILE\"]}}]}"); +- private string _number; +- private com.example.PhoneType _type; +- public virtual global::Avro.Schema Schema +- { +- get +- { +- return PhoneNumber._SCHEMA; +- } +- } +- public string number +- { +- get +- { +- return this._number; +- } +- set +- { +- this._number = value; +- } +- } +- public com.example.PhoneType type +- { +- get +- { +- return this._type; +- } +- set +- { +- this._type = value; +- } +- } +- public virtual object Get(int fieldPos) +- { +- switch (fieldPos) +- { +- case 0: return this.number; +- case 1: return this.type; +- default: throw new global::Avro.AvroRuntimeException("Bad index " + fieldPos + " in Get()"); +- }; +- } +- public virtual void Put(int fieldPos, object fieldValue) +- { +- switch (fieldPos) +- { +- case 0: this.number = (System.String)fieldValue; break; +- case 1: this.type = (com.example.PhoneType)fieldValue; break; +- default: throw new global::Avro.AvroRuntimeException("Bad index " + fieldPos + " in Put()"); +- }; +- } +- } +-} +diff --git a/examples/Kafka/Avro/src/Generated/com/example/PhoneType.cs b/examples/Kafka/Avro/src/Generated/com/example/PhoneType.cs +deleted file mode 100644 +index f592d869..00000000 +--- a/examples/Kafka/Avro/src/Generated/com/example/PhoneType.cs ++++ /dev/null +@@ -1,23 +0,0 @@ +-// ------------------------------------------------------------------------------ +-// +-// Generated by avrogen, version 1.12.0+8c27801dc8d42ccc00997f25c0b8f45f8d4a233e +-// Changes to this file may cause incorrect behavior and will be lost if code +-// is regenerated +-// +-// ------------------------------------------------------------------------------ +-namespace com.example +-{ +- using System; +- using System.Collections.Generic; +- using System.Text; +- using global::Avro; +- using global::Avro.Specific; +- +- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("avrogen", "1.12.0+8c27801dc8d42ccc00997f25c0b8f45f8d4a233e")] +- public enum PhoneType +- { +- HOME, +- WORK, +- MOBILE, +- } +-} +diff --git a/examples/Kafka/Avro/src/aws-lambda-tools-defaults.json b/examples/Kafka/Avro/src/aws-lambda-tools-defaults.json +deleted file mode 100644 +index cd93437e..00000000 +--- a/examples/Kafka/Avro/src/aws-lambda-tools-defaults.json ++++ /dev/null +@@ -1,15 +0,0 @@ +-{ +- "Information": [ +- "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", +- "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", +- "dotnet lambda help", +- "All the command line options for the Lambda command can be specified in this file." +- ], +- "profile": "", +- "region": "", +- "configuration": "Release", +- "function-runtime": "dotnet8", +- "function-memory-size": 512, +- "function-timeout": 30, +- "function-handler": "Avro.Example" +-} +\ No newline at end of file +diff --git a/examples/Kafka/Avro/src/kafka-avro-event.json b/examples/Kafka/Avro/src/kafka-avro-event.json +deleted file mode 100644 +index 6f5e045e..00000000 +--- a/examples/Kafka/Avro/src/kafka-avro-event.json ++++ /dev/null +@@ -1,23 +0,0 @@ +-{ +- "eventSource": "aws:kafka", +- "eventSourceArn": "arn:aws:kafka:us-east-1:0123456789019:cluster/CustomerCluster/abcd1234-abcd-cafe-abab-9876543210ab-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": { +- "customer-topic-0": [ +- { +- "topic": "customer-topic", +- "partition": 0, +- "offset": 15, +- "timestamp": 1545084650987, +- "timestampType": "CREATE_TIME", +- "key": "dXNlcl85NzU0", +- "value": "EnVzZXJfOTc1NBxVc2VyIHVzZXJfOTc1NCh1c2VyXzk3NTRAaWNsb3VkLmNvbQABahg5MzQwIE1haW4gU3QQU2FuIEpvc2UEQ0EGVVNBCjM5NTk2AhgyNDQtNDA3LTg4NzECAAYQdGltZXpvbmUOZW5hYmxlZBBsYW5ndWFnZRBkaXNhYmxlZBpub3RpZmljYXRpb25zCGRhcmsABA==", +- "headers": [ +- { +- "headerKey": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] +- } +- ] +- } +- ] +- } +-} +\ No newline at end of file +diff --git a/examples/Kafka/Avro/src/template.yaml b/examples/Kafka/Avro/src/template.yaml +deleted file mode 100644 +index a08325be..00000000 +--- a/examples/Kafka/Avro/src/template.yaml ++++ /dev/null +@@ -1,27 +0,0 @@ +-AWSTemplateFormatVersion: '2010-09-09' +-Transform: AWS::Serverless-2016-10-31 +-Description: > +- kafka +- +- Sample SAM Template for kafka +- +-# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst +-Globals: +- Function: +- Timeout: 15 +- MemorySize: 512 +- Runtime: dotnet8 +- +-Resources: +- AvroDeserializationFunction: +- Type: AWS::Serverless::Function +- Properties: +- Handler: Avro.Example +- Architectures: +- - x86_64 +- Tracing: Active +- Environment: # Powertools env vars: https://awslabs.github.io/aws-lambda-powertools-python/#environment-variables +- Variables: +- POWERTOOLS_SERVICE_NAME: PowertoolsHelloWorld +- POWERTOOLS_LOG_LEVEL: Info +- POWERTOOLS_LOGGER_CASE: PascalCase # Allowed values are: CamelCase, PascalCase and SnakeCase (Default) +\ No newline at end of file +diff --git a/examples/Kafka/Json/src/Function.cs b/examples/Kafka/Json/src/Function.cs +deleted file mode 100644 +index d7d96bfc..00000000 +--- a/examples/Kafka/Json/src/Function.cs ++++ /dev/null +@@ -1,21 +0,0 @@ +-using Amazon.Lambda.Core; +-using Amazon.Lambda.RuntimeSupport; +-using AWS.Lambda.Powertools.Kafka; +-using AWS.Lambda.Powertools.Kafka.Json; +-using AWS.Lambda.Powertools.Logging; +-using Json.Models; +- +-string Handler(ConsumerRecords records, ILambdaContext context) +-{ +- foreach (var record in records) +- { +- Logger.LogInformation("Record Value: {@record}", record.Value); +- } +- +- return "Processed " + records.Count() + " records"; +-} +- +-await LambdaBootstrapBuilder.Create((Func, ILambdaContext, string>?)Handler, +- new PowertoolsKafkaJsonSerializer()) // Use PowertoolsKafkaJsonSerializer for Json serialization +- .Build() +- .RunAsync(); +\ No newline at end of file +diff --git a/examples/Kafka/Json/src/Json.csproj b/examples/Kafka/Json/src/Json.csproj +deleted file mode 100644 +index aba6cde8..00000000 +--- a/examples/Kafka/Json/src/Json.csproj ++++ /dev/null +@@ -1,30 +0,0 @@ +- +- +- Exe +- net8.0 +- enable +- enable +- true +- Lambda +- +- true +- +- true +- +- +- +- +- +- +- +- +- +- +- +- +- +- PreserveNewest +- +- +- +- +\ No newline at end of file +diff --git a/examples/Kafka/Json/src/Models/Address.cs b/examples/Kafka/Json/src/Models/Address.cs +deleted file mode 100644 +index a011b3ce..00000000 +--- a/examples/Kafka/Json/src/Models/Address.cs ++++ /dev/null +@@ -1,16 +0,0 @@ +-using System.Text.Json.Serialization; +- +-namespace Json.Models; +- +-public partial class Address +-{ +- [JsonPropertyName("street")] public string Street { get; set; } +- +- [JsonPropertyName("city")] public string City { get; set; } +- +- [JsonPropertyName("state")] public string State { get; set; } +- +- [JsonPropertyName("country")] public string Country { get; set; } +- +- [JsonPropertyName("zip_code")] public string ZipCode { get; set; } +-} +\ No newline at end of file +diff --git a/examples/Kafka/Json/src/Models/CustomerProfile.cs b/examples/Kafka/Json/src/Models/CustomerProfile.cs +deleted file mode 100644 +index 1e7ab62b..00000000 +--- a/examples/Kafka/Json/src/Models/CustomerProfile.cs ++++ /dev/null +@@ -1,22 +0,0 @@ +-using System.Text.Json.Serialization; +- +-namespace Json.Models; +- +-public partial class CustomerProfile +-{ +- [JsonPropertyName("user_id")] public string UserId { get; set; } +- +- [JsonPropertyName("full_name")] public string FullName { get; set; } +- +- [JsonPropertyName("email")] public Email Email { get; set; } +- +- [JsonPropertyName("age")] public long Age { get; set; } +- +- [JsonPropertyName("address")] public Address Address { get; set; } +- +- [JsonPropertyName("phone_numbers")] public List PhoneNumbers { get; set; } +- +- [JsonPropertyName("preferences")] public Preferences Preferences { get; set; } +- +- [JsonPropertyName("account_status")] public string AccountStatus { get; set; } +-} +\ No newline at end of file +diff --git a/examples/Kafka/Json/src/Models/Email.cs b/examples/Kafka/Json/src/Models/Email.cs +deleted file mode 100644 +index 045118ba..00000000 +--- a/examples/Kafka/Json/src/Models/Email.cs ++++ /dev/null +@@ -1,12 +0,0 @@ +-using System.Text.Json.Serialization; +- +-namespace Json.Models; +- +-public partial class Email +-{ +- [JsonPropertyName("address")] public string Address { get; set; } +- +- [JsonPropertyName("verified")] public bool Verified { get; set; } +- +- [JsonPropertyName("primary")] public bool Primary { get; set; } +-} +\ No newline at end of file +diff --git a/examples/Kafka/Json/src/Models/PhoneNumber.cs b/examples/Kafka/Json/src/Models/PhoneNumber.cs +deleted file mode 100644 +index 7681265d..00000000 +--- a/examples/Kafka/Json/src/Models/PhoneNumber.cs ++++ /dev/null +@@ -1,10 +0,0 @@ +-using System.Text.Json.Serialization; +- +-namespace Json.Models; +- +-public partial class PhoneNumber +-{ +- [JsonPropertyName("number")] public string Number { get; set; } +- +- [JsonPropertyName("type")] public string Type { get; set; } +-} +\ No newline at end of file +diff --git a/examples/Kafka/Json/src/Models/Preferences.cs b/examples/Kafka/Json/src/Models/Preferences.cs +deleted file mode 100644 +index 5dd84aa9..00000000 +--- a/examples/Kafka/Json/src/Models/Preferences.cs ++++ /dev/null +@@ -1,12 +0,0 @@ +-using System.Text.Json.Serialization; +- +-namespace Json.Models; +- +-public partial class Preferences +-{ +- [JsonPropertyName("language")] public string Language { get; set; } +- +- [JsonPropertyName("notifications")] public string Notifications { get; set; } +- +- [JsonPropertyName("timezone")] public string Timezone { get; set; } +-} +\ No newline at end of file +diff --git a/examples/Kafka/Json/src/aws-lambda-tools-defaults.json b/examples/Kafka/Json/src/aws-lambda-tools-defaults.json +deleted file mode 100644 +index fb324090..00000000 +--- a/examples/Kafka/Json/src/aws-lambda-tools-defaults.json ++++ /dev/null +@@ -1,15 +0,0 @@ +-{ +- "Information": [ +- "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", +- "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", +- "dotnet lambda help", +- "All the command line options for the Lambda command can be specified in this file." +- ], +- "profile": "", +- "region": "", +- "configuration": "Release", +- "function-runtime": "dotnet8", +- "function-memory-size": 512, +- "function-timeout": 30, +- "function-handler": "Json" +-} +\ No newline at end of file +diff --git a/examples/Kafka/Json/src/kafka-json-event.json b/examples/Kafka/Json/src/kafka-json-event.json +deleted file mode 100644 +index 66dc2ab5..00000000 +--- a/examples/Kafka/Json/src/kafka-json-event.json ++++ /dev/null +@@ -1,23 +0,0 @@ +-{ +- "eventSource": "aws:kafka", +- "eventSourceArn": "arn:aws:kafka:us-east-1:0123456789019:cluster/CustomerCluster/abcd1234-abcd-cafe-abab-9876543210ab-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": { +- "customer-topic-0": [ +- { +- "topic": "customer-topic", +- "partition": 0, +- "offset": 15, +- "timestamp": 1545084650987, +- "timestampType": "CREATE_TIME", +- "key": "dXNlcl85NzU0", +- "value": "eyJwaG9uZV9udW1iZXJzIjpbeyJudW1iZXIiOiIyNDQtNDA3LTg4NzEiLCJ0eXBlIjoiV09SSyJ9XSwicHJlZmVyZW5jZXMiOnsidGltZXpvbmUiOiJlbmFibGVkIiwibGFuZ3VhZ2UiOiJkaXNhYmxlZCIsIm5vdGlmaWNhdGlvbnMiOiJkYXJrIn0sImZ1bGxfbmFtZSI6IlVzZXIgdXNlcl85NzU0IiwiYWRkcmVzcyI6eyJjb3VudHJ5IjoiVVNBIiwiY2l0eSI6IlNhbiBKb3NlIiwic3RyZWV0IjoiOTM0MCBNYWluIFN0Iiwic3RhdGUiOiJDQSIsInppcF9jb2RlIjoiMzk1OTYifSwidXNlcl9pZCI6InVzZXJfOTc1NCIsImFjY291bnRfc3RhdHVzIjoiU1VTUEVOREVEIiwiYWdlIjo1MywiZW1haWwiOnsiYWRkcmVzcyI6InVzZXJfOTc1NEBpY2xvdWQuY29tIiwidmVyaWZpZWQiOmZhbHNlLCJwcmltYXJ5Ijp0cnVlfX0=", +- "headers": [ +- { +- "headerKey": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] +- } +- ] +- } +- ] +- } +-} +\ No newline at end of file +diff --git a/examples/Kafka/Json/src/template.yaml b/examples/Kafka/Json/src/template.yaml +deleted file mode 100644 +index dd4bfb9f..00000000 +--- a/examples/Kafka/Json/src/template.yaml ++++ /dev/null +@@ -1,27 +0,0 @@ +-AWSTemplateFormatVersion: '2010-09-09' +-Transform: AWS::Serverless-2016-10-31 +-Description: > +- kafka +- +- Sample SAM Template for kafka +- +-# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst +-Globals: +- Function: +- Timeout: 15 +- MemorySize: 512 +- Runtime: dotnet8 +- +-Resources: +- JsonDeserializationFunction: +- Type: AWS::Serverless::Function +- Properties: +- Handler: Json +- Architectures: +- - x86_64 +- Tracing: Active +- Environment: # Powertools env vars: https://awslabs.github.io/aws-lambda-powertools-python/#environment-variables +- Variables: +- POWERTOOLS_SERVICE_NAME: PowertoolsHelloWorld +- POWERTOOLS_LOG_LEVEL: Info +- POWERTOOLS_LOGGER_CASE: PascalCase # Allowed values are: CamelCase, PascalCase and SnakeCase (Default) +\ No newline at end of file +diff --git a/examples/Kafka/JsonClassLibrary/src/CustomerProfile.proto b/examples/Kafka/JsonClassLibrary/src/CustomerProfile.proto +deleted file mode 100644 +index 9c69b1c4..00000000 +--- a/examples/Kafka/JsonClassLibrary/src/CustomerProfile.proto ++++ /dev/null +@@ -1,49 +0,0 @@ +-syntax = "proto3"; +- +-package com.example; +- +-enum PhoneType { +- HOME = 0; +- WORK = 1; +- MOBILE = 2; +-} +- +-enum AccountStatus { +- ACTIVE = 0; +- INACTIVE = 1; +- SUSPENDED = 2; +-} +- +-// EmailAddress message +-message EmailAddress { +- string address = 1; +- bool verified = 2; +- bool primary = 3; +-} +- +-// Address message +-message Address { +- string street = 1; +- string city = 2; +- string state = 3; +- string country = 4; +- string zip_code = 5; +-} +- +-// PhoneNumber message +-message PhoneNumber { +- string number = 1; +- PhoneType type = 2; +-} +- +-// CustomerProfile message +-message CustomerProfile { +- string user_id = 1; +- string full_name = 2; +- EmailAddress email = 3; +- int32 age = 4; +- Address address = 5; +- repeated PhoneNumber phone_numbers = 6; +- map preferences = 7; +- AccountStatus account_status = 8; +-} +\ No newline at end of file +diff --git a/examples/Kafka/JsonClassLibrary/src/Function.cs b/examples/Kafka/JsonClassLibrary/src/Function.cs +deleted file mode 100644 +index 98795029..00000000 +--- a/examples/Kafka/JsonClassLibrary/src/Function.cs ++++ /dev/null +@@ -1,32 +0,0 @@ +-using Amazon.Lambda.Core; +-using AWS.Lambda.Powertools.Kafka; +-using AWS.Lambda.Powertools.Kafka.Protobuf; +-using AWS.Lambda.Powertools.Logging; +-using Com.Example; +- +-// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class. +-[assembly: LambdaSerializer(typeof(PowertoolsKafkaProtobufSerializer))] +- +-namespace ProtoBufClassLibrary; +- +-public class Function +-{ +- public string FunctionHandler(ConsumerRecords records, ILambdaContext context) +- { +- foreach (var record in records) +- { +- Logger.LogInformation("Processing messagem from topic: {topic}", record.Topic); +- Logger.LogInformation("Partition: {partition}, Offset: {offset}", record.Partition, record.Offset); +- Logger.LogInformation("Produced at: {timestamp}", record.Timestamp); +- +- foreach (var header in record.Headers.DecodedValues()) +- { +- Logger.LogInformation($"{header.Key}: {header.Value}"); +- } +- +- Logger.LogInformation("Processing order for: {fullName}", record.Value.FullName); +- } +- +- return "Processed " + records.Count() + " records"; +- } +-} +\ No newline at end of file +diff --git a/examples/Kafka/JsonClassLibrary/src/ProtoBufClassLibrary.csproj b/examples/Kafka/JsonClassLibrary/src/ProtoBufClassLibrary.csproj +deleted file mode 100644 +index a28e1a2f..00000000 +--- a/examples/Kafka/JsonClassLibrary/src/ProtoBufClassLibrary.csproj ++++ /dev/null +@@ -1,42 +0,0 @@ +- +- +- net8.0 +- enable +- enable +- true +- Lambda +- +- true +- +- true +- +- +- +- +- +- +- all +- runtime; build; native; contentfiles; analyzers; buildtransitive +- +- +- +- +- +- +- +- PreserveNewest +- +- +- +- +- Client +- Public +- True +- True +- obj/Debug/net8.0/ +- MSBuild:Compile +- PreserveNewest +- +- +- +- +\ No newline at end of file +diff --git a/examples/Kafka/JsonClassLibrary/src/aws-lambda-tools-defaults.json b/examples/Kafka/JsonClassLibrary/src/aws-lambda-tools-defaults.json +deleted file mode 100644 +index d4ec43f1..00000000 +--- a/examples/Kafka/JsonClassLibrary/src/aws-lambda-tools-defaults.json ++++ /dev/null +@@ -1,16 +0,0 @@ +-{ +- "Information": [ +- "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", +- "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", +- "dotnet lambda help", +- "All the command line options for the Lambda command can be specified in this file." +- ], +- "profile": "", +- "region": "", +- "configuration": "Release", +- "function-architecture": "x86_64", +- "function-runtime": "dotnet8", +- "function-memory-size": 512, +- "function-timeout": 30, +- "function-handler": "ProtoBufClassLibrary::ProtoBufClassLibrary.Function::FunctionHandler" +-} +\ No newline at end of file +diff --git a/examples/Kafka/JsonClassLibrary/src/kafka-protobuf-event.json b/examples/Kafka/JsonClassLibrary/src/kafka-protobuf-event.json +deleted file mode 100644 +index 6731ceb4..00000000 +--- a/examples/Kafka/JsonClassLibrary/src/kafka-protobuf-event.json ++++ /dev/null +@@ -1,23 +0,0 @@ +-{ +- "eventSource": "aws:kafka", +- "eventSourceArn": "arn:aws:kafka:us-east-1:0123456789019:cluster/CustomerCluster/abcd1234-abcd-cafe-abab-9876543210ab-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": { +- "customer-topic-0": [ +- { +- "topic": "customer-topic", +- "partition": 0, +- "offset": 15, +- "timestamp": 1545084650987, +- "timestampType": "CREATE_TIME", +- "key": "dXNlcl85NzU0", +- "value": "Cgl1c2VyXzk3NTQSDlVzZXIgdXNlcl85NzU0GhgKFHVzZXJfOTc1NEBpY2xvdWQuY29tGAEgNSooCgw5MzQwIE1haW4gU3QSCFNhbiBKb3NlGgJDQSIDVVNBKgUzOTU5NjIQCgwyNDQtNDA3LTg4NzEQAToUCghsYW5ndWFnZRIIZGlzYWJsZWQ6FQoNbm90aWZpY2F0aW9ucxIEZGFyazoTCgh0aW1lem9uZRIHZW5hYmxlZEAC", +- "headers": [ +- { +- "headerKey": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] +- } +- ] +- } +- ] +- } +-} +\ No newline at end of file +diff --git a/examples/Kafka/JsonClassLibrary/src/template.yaml b/examples/Kafka/JsonClassLibrary/src/template.yaml +deleted file mode 100644 +index 0df5feaa..00000000 +--- a/examples/Kafka/JsonClassLibrary/src/template.yaml ++++ /dev/null +@@ -1,27 +0,0 @@ +-AWSTemplateFormatVersion: '2010-09-09' +-Transform: AWS::Serverless-2016-10-31 +-Description: > +- kafka +- +- Sample SAM Template for kafka +- +-# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst +-Globals: +- Function: +- Timeout: 15 +- MemorySize: 512 +- Runtime: dotnet8 +- +-Resources: +- ProtobufClassLibraryDeserializationFunction: +- Type: AWS::Serverless::Function +- Properties: +- Handler: ProtoBufClassLibrary::ProtoBufClassLibrary.Function::FunctionHandler +- Architectures: +- - x86_64 +- Tracing: Active +- Environment: # Powertools env vars: https://awslabs.github.io/aws-lambda-powertools-python/#environment-variables +- Variables: +- POWERTOOLS_SERVICE_NAME: PowertoolsHelloWorld +- POWERTOOLS_LOG_LEVEL: Info +- POWERTOOLS_LOGGER_CASE: PascalCase # Allowed values are: CamelCase, PascalCase and SnakeCase (Default) +\ No newline at end of file +diff --git a/examples/Kafka/Protobuf/src/CustomerProfile.proto b/examples/Kafka/Protobuf/src/CustomerProfile.proto +deleted file mode 100644 +index 9c69b1c4..00000000 +--- a/examples/Kafka/Protobuf/src/CustomerProfile.proto ++++ /dev/null +@@ -1,49 +0,0 @@ +-syntax = "proto3"; +- +-package com.example; +- +-enum PhoneType { +- HOME = 0; +- WORK = 1; +- MOBILE = 2; +-} +- +-enum AccountStatus { +- ACTIVE = 0; +- INACTIVE = 1; +- SUSPENDED = 2; +-} +- +-// EmailAddress message +-message EmailAddress { +- string address = 1; +- bool verified = 2; +- bool primary = 3; +-} +- +-// Address message +-message Address { +- string street = 1; +- string city = 2; +- string state = 3; +- string country = 4; +- string zip_code = 5; +-} +- +-// PhoneNumber message +-message PhoneNumber { +- string number = 1; +- PhoneType type = 2; +-} +- +-// CustomerProfile message +-message CustomerProfile { +- string user_id = 1; +- string full_name = 2; +- EmailAddress email = 3; +- int32 age = 4; +- Address address = 5; +- repeated PhoneNumber phone_numbers = 6; +- map preferences = 7; +- AccountStatus account_status = 8; +-} +\ No newline at end of file +diff --git a/examples/Kafka/Protobuf/src/Function.cs b/examples/Kafka/Protobuf/src/Function.cs +deleted file mode 100644 +index 44632869..00000000 +--- a/examples/Kafka/Protobuf/src/Function.cs ++++ /dev/null +@@ -1,22 +0,0 @@ +-using Amazon.Lambda.Core; +-using Amazon.Lambda.RuntimeSupport; +-using AWS.Lambda.Powertools.Kafka; +-using AWS.Lambda.Powertools.Kafka.Protobuf; +-using AWS.Lambda.Powertools.Logging; +-using Com.Example; +- +-string Handler(ConsumerRecords records, ILambdaContext context) +-{ +- foreach (var record in records) +- { +- Logger.LogInformation("Record Value: {@record}", record.Value); +- } +- +- return "Processed " + records.Count() + " records"; +-} +- +-await LambdaBootstrapBuilder.Create((Func, ILambdaContext, string>?)Handler, +- new PowertoolsKafkaProtobufSerializer()) // Use PowertoolsKafkaProtobufSerializer for Protobuf serialization +- .Build() +- .RunAsync(); +- +diff --git a/examples/Kafka/Protobuf/src/Protobuf.csproj b/examples/Kafka/Protobuf/src/Protobuf.csproj +deleted file mode 100644 +index 858ccfb4..00000000 +--- a/examples/Kafka/Protobuf/src/Protobuf.csproj ++++ /dev/null +@@ -1,44 +0,0 @@ +- +- +- Exe +- net8.0 +- enable +- enable +- true +- Lambda +- +- true +- +- true +- +- +- +- +- +- +- +- all +- runtime; build; native; contentfiles; analyzers; buildtransitive +- +- +- +- +- PreserveNewest +- +- +- +- +- Client +- Public +- True +- +- True +- obj\Debug/net8.0/ +- MSBuild:Compile +- PreserveNewest +- +- +- +- +- +- +\ No newline at end of file +diff --git a/examples/Kafka/Protobuf/src/aws-lambda-tools-defaults.json b/examples/Kafka/Protobuf/src/aws-lambda-tools-defaults.json +deleted file mode 100644 +index 1a1c5de1..00000000 +--- a/examples/Kafka/Protobuf/src/aws-lambda-tools-defaults.json ++++ /dev/null +@@ -1,15 +0,0 @@ +-{ +- "Information": [ +- "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", +- "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", +- "dotnet lambda help", +- "All the command line options for the Lambda command can be specified in this file." +- ], +- "profile": "", +- "region": "", +- "configuration": "Release", +- "function-runtime": "dotnet8", +- "function-memory-size": 512, +- "function-timeout": 30, +- "function-handler": "Protobuf" +-} +\ No newline at end of file +diff --git a/examples/Kafka/Protobuf/src/kafka-protobuf-event.json b/examples/Kafka/Protobuf/src/kafka-protobuf-event.json +deleted file mode 100644 +index 6731ceb4..00000000 +--- a/examples/Kafka/Protobuf/src/kafka-protobuf-event.json ++++ /dev/null +@@ -1,23 +0,0 @@ +-{ +- "eventSource": "aws:kafka", +- "eventSourceArn": "arn:aws:kafka:us-east-1:0123456789019:cluster/CustomerCluster/abcd1234-abcd-cafe-abab-9876543210ab-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": { +- "customer-topic-0": [ +- { +- "topic": "customer-topic", +- "partition": 0, +- "offset": 15, +- "timestamp": 1545084650987, +- "timestampType": "CREATE_TIME", +- "key": "dXNlcl85NzU0", +- "value": "Cgl1c2VyXzk3NTQSDlVzZXIgdXNlcl85NzU0GhgKFHVzZXJfOTc1NEBpY2xvdWQuY29tGAEgNSooCgw5MzQwIE1haW4gU3QSCFNhbiBKb3NlGgJDQSIDVVNBKgUzOTU5NjIQCgwyNDQtNDA3LTg4NzEQAToUCghsYW5ndWFnZRIIZGlzYWJsZWQ6FQoNbm90aWZpY2F0aW9ucxIEZGFyazoTCgh0aW1lem9uZRIHZW5hYmxlZEAC", +- "headers": [ +- { +- "headerKey": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] +- } +- ] +- } +- ] +- } +-} +\ No newline at end of file +diff --git a/examples/Kafka/Protobuf/src/template.yaml b/examples/Kafka/Protobuf/src/template.yaml +deleted file mode 100644 +index b8f7df6a..00000000 +--- a/examples/Kafka/Protobuf/src/template.yaml ++++ /dev/null +@@ -1,27 +0,0 @@ +-AWSTemplateFormatVersion: '2010-09-09' +-Transform: AWS::Serverless-2016-10-31 +-Description: > +- kafka +- +- Sample SAM Template for kafka +- +-# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst +-Globals: +- Function: +- Timeout: 15 +- MemorySize: 512 +- Runtime: dotnet8 +- +-Resources: +- ProtobufDeserializationFunction: +- Type: AWS::Serverless::Function +- Properties: +- Handler: Protobuf +- Architectures: +- - x86_64 +- Tracing: Active +- Environment: # Powertools env vars: https://awslabs.github.io/aws-lambda-powertools-python/#environment-variables +- Variables: +- POWERTOOLS_SERVICE_NAME: PowertoolsHelloWorld +- POWERTOOLS_LOG_LEVEL: Info +- POWERTOOLS_LOGGER_CASE: PascalCase # Allowed values are: CamelCase, PascalCase and SnakeCase (Default) +\ No newline at end of file +diff --git a/examples/Logging/src/HelloWorld/Dockerfile b/examples/Logging/src/HelloWorld/Dockerfile +index 0b025f36..eb6d0e0b 100644 +--- a/examples/Logging/src/HelloWorld/Dockerfile ++++ b/examples/Logging/src/HelloWorld/Dockerfile +@@ -1,4 +1,4 @@ +-FROM mcr.microsoft.com/dotnet/sdk@sha256:c8fdd06e430de9f4ddd066b475ea350d771f341b77dd5ff4c2fafa748e3f2ef2 AS build-image ++FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-image + + ARG FUNCTION_DIR="/build" + ARG SAM_BUILD_MODE="run" +@@ -15,7 +15,7 @@ RUN mkdir -p build_artifacts + RUN if [ "$SAM_BUILD_MODE" = "debug" ]; then dotnet lambda package --configuration Debug; else dotnet lambda package --configuration Release; fi + RUN if [ "$SAM_BUILD_MODE" = "debug" ]; then cp -r /build/bin/Debug/net6.0/publish/* /build/build_artifacts; else cp -r /build/bin/Release/net6.0/publish/* /build/build_artifacts; fi + +-FROM public.ecr.aws/lambda/dotnet@sha256:ec61a7f638e2a0c86d75204117cc7710bcdc70222ffc777e3fc1458287b09834 ++FROM public.ecr.aws/lambda/dotnet:6 + + COPY --from=build-image /build/build_artifacts/ /var/task/ + # Command can be overwritten by providing a different command in the template directly. +diff --git a/examples/Logging/src/HelloWorld/HelloWorld.csproj b/examples/Logging/src/HelloWorld/HelloWorld.csproj +index 36e8ed0d..2fa0c42d 100644 +--- a/examples/Logging/src/HelloWorld/HelloWorld.csproj ++++ b/examples/Logging/src/HelloWorld/HelloWorld.csproj +@@ -5,10 +5,10 @@ + enable + + +- ++ + +- +- ++ ++ + + + +diff --git a/examples/Logging/test/HelloWorld.Test/HelloWorld.Tests.csproj b/examples/Logging/test/HelloWorld.Test/HelloWorld.Tests.csproj +index 14917e4c..446d7f28 100644 +--- a/examples/Logging/test/HelloWorld.Test/HelloWorld.Tests.csproj ++++ b/examples/Logging/test/HelloWorld.Test/HelloWorld.Tests.csproj +@@ -3,9 +3,9 @@ + net6.0;net8.0 + + +- ++ + +- ++ + + + +diff --git a/examples/Metrics/src/HelloWorld/Dockerfile b/examples/Metrics/src/HelloWorld/Dockerfile +index 932a2740..8cf3b466 100644 +--- a/examples/Metrics/src/HelloWorld/Dockerfile ++++ b/examples/Metrics/src/HelloWorld/Dockerfile +@@ -1,4 +1,4 @@ +-FROM mcr.microsoft.com/dotnet/sdk@sha256:c8fdd06e430de9f4ddd066b475ea350d771f341b77dd5ff4c2fafa748e3f2ef2 AS build-image ++FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-image + + ARG FUNCTION_DIR="/build" + ARG SAM_BUILD_MODE="run" +@@ -16,7 +16,7 @@ RUN mkdir -p build_artifacts + RUN if [ "$SAM_BUILD_MODE" = "debug" ]; then dotnet lambda package --configuration Debug; else dotnet lambda package --configuration Release; fi + RUN if [ "$SAM_BUILD_MODE" = "debug" ]; then cp -r /build/bin/Debug/net6.0/publish/* /build/build_artifacts; else cp -r /build/bin/Release/net6.0/publish/* /build/build_artifacts; fi + +-FROM public.ecr.aws/lambda/dotnet@sha256:ec61a7f638e2a0c86d75204117cc7710bcdc70222ffc777e3fc1458287b09834 ++FROM public.ecr.aws/lambda/dotnet:6 + + COPY --from=build-image /build/build_artifacts/ /var/task/ + # Command can be overwritten by providing a different command in the template directly. +diff --git a/examples/Metrics/src/HelloWorld/Function.cs b/examples/Metrics/src/HelloWorld/Function.cs +index 9c797f6e..f99cf395 100644 +--- a/examples/Metrics/src/HelloWorld/Function.cs ++++ b/examples/Metrics/src/HelloWorld/Function.cs +@@ -82,11 +82,11 @@ public class Function + + // Add Metric to capture the amount of time + Metrics.PushSingleMetric( +- name: "CallingIP", ++ metricName: "CallingIP", + value: 1, + unit: MetricUnit.Count, + service: "lambda-powertools-metrics-example", +- dimensions: new Dictionary ++ defaultDimensions: new Dictionary + { + { "Metric Type", "Single" } + }); +@@ -104,11 +104,11 @@ public class Function + try + { + Metrics.PushSingleMetric( +- name: "RecordsSaved", ++ metricName: "RecordsSaved", + value: 1, + unit: MetricUnit.Count, + service: "lambda-powertools-metrics-example", +- dimensions: new Dictionary ++ defaultDimensions: new Dictionary + { + { "Metric Type", "Single" } + }); +diff --git a/examples/Metrics/src/HelloWorld/HelloWorld.csproj b/examples/Metrics/src/HelloWorld/HelloWorld.csproj +index dc82111b..14d90df2 100644 +--- a/examples/Metrics/src/HelloWorld/HelloWorld.csproj ++++ b/examples/Metrics/src/HelloWorld/HelloWorld.csproj +@@ -5,11 +5,11 @@ + enable + + +- ++ + +- +- +- ++ ++ ++ + + + +diff --git a/examples/Metrics/test/HelloWorld.Test/HelloWorld.Tests.csproj b/examples/Metrics/test/HelloWorld.Test/HelloWorld.Tests.csproj +index 14917e4c..446d7f28 100644 +--- a/examples/Metrics/test/HelloWorld.Test/HelloWorld.Tests.csproj ++++ b/examples/Metrics/test/HelloWorld.Test/HelloWorld.Tests.csproj +@@ -3,9 +3,9 @@ + net6.0;net8.0 + + +- ++ + +- ++ + + + +diff --git a/examples/Parameters/cfn/HelloWorld.Cfn/HelloWorld.Cfn.csproj b/examples/Parameters/cfn/HelloWorld.Cfn/HelloWorld.Cfn.csproj +index cf97597d..713914f2 100644 +--- a/examples/Parameters/cfn/HelloWorld.Cfn/HelloWorld.Cfn.csproj ++++ b/examples/Parameters/cfn/HelloWorld.Cfn/HelloWorld.Cfn.csproj +@@ -6,8 +6,8 @@ + HelloWorld.Cfn + + +- +- ++ ++ + + + +diff --git a/examples/Parameters/src/HelloWorld/HelloWorld.csproj b/examples/Parameters/src/HelloWorld/HelloWorld.csproj +index 99b13a66..6b29f425 100644 +--- a/examples/Parameters/src/HelloWorld/HelloWorld.csproj ++++ b/examples/Parameters/src/HelloWorld/HelloWorld.csproj +@@ -5,9 +5,9 @@ + enable + + +- ++ + +- ++ + + + +diff --git a/examples/Parameters/test/HelloWorld.Test/HelloWorld.Tests.csproj b/examples/Parameters/test/HelloWorld.Test/HelloWorld.Tests.csproj +index 589c8306..9b17d57f 100644 +--- a/examples/Parameters/test/HelloWorld.Test/HelloWorld.Tests.csproj ++++ b/examples/Parameters/test/HelloWorld.Test/HelloWorld.Tests.csproj +@@ -3,9 +3,9 @@ + net6.0;net8.0 + + +- ++ + +- ++ + + + +diff --git a/examples/ServerlessApi/src/LambdaPowertoolsAPI/LambdaPowertoolsAPI.csproj b/examples/ServerlessApi/src/LambdaPowertoolsAPI/LambdaPowertoolsAPI.csproj +index fd91c9de..f529937d 100644 +--- a/examples/ServerlessApi/src/LambdaPowertoolsAPI/LambdaPowertoolsAPI.csproj ++++ b/examples/ServerlessApi/src/LambdaPowertoolsAPI/LambdaPowertoolsAPI.csproj +@@ -13,8 +13,8 @@ + + + +- +- +- ++ ++ ++ + + +diff --git a/examples/ServerlessApi/test/LambdaPowertoolsAPI.Tests/LambdaPowertoolsAPI.Tests.csproj b/examples/ServerlessApi/test/LambdaPowertoolsAPI.Tests/LambdaPowertoolsAPI.Tests.csproj +index edfda0a5..d9cdaef4 100644 +--- a/examples/ServerlessApi/test/LambdaPowertoolsAPI.Tests/LambdaPowertoolsAPI.Tests.csproj ++++ b/examples/ServerlessApi/test/LambdaPowertoolsAPI.Tests/LambdaPowertoolsAPI.Tests.csproj +@@ -16,7 +16,7 @@ + + + +- ++ + + + +diff --git a/examples/Tracing/src/HelloWorld/Dockerfile b/examples/Tracing/src/HelloWorld/Dockerfile +index 932a2740..8cf3b466 100644 +--- a/examples/Tracing/src/HelloWorld/Dockerfile ++++ b/examples/Tracing/src/HelloWorld/Dockerfile +@@ -1,4 +1,4 @@ +-FROM mcr.microsoft.com/dotnet/sdk@sha256:c8fdd06e430de9f4ddd066b475ea350d771f341b77dd5ff4c2fafa748e3f2ef2 AS build-image ++FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-image + + ARG FUNCTION_DIR="/build" + ARG SAM_BUILD_MODE="run" +@@ -16,7 +16,7 @@ RUN mkdir -p build_artifacts + RUN if [ "$SAM_BUILD_MODE" = "debug" ]; then dotnet lambda package --configuration Debug; else dotnet lambda package --configuration Release; fi + RUN if [ "$SAM_BUILD_MODE" = "debug" ]; then cp -r /build/bin/Debug/net6.0/publish/* /build/build_artifacts; else cp -r /build/bin/Release/net6.0/publish/* /build/build_artifacts; fi + +-FROM public.ecr.aws/lambda/dotnet@sha256:ec61a7f638e2a0c86d75204117cc7710bcdc70222ffc777e3fc1458287b09834 ++FROM public.ecr.aws/lambda/dotnet:6 + + COPY --from=build-image /build/build_artifacts/ /var/task/ + # Command can be overwritten by providing a different command in the template directly. +diff --git a/examples/Tracing/src/HelloWorld/HelloWorld.csproj b/examples/Tracing/src/HelloWorld/HelloWorld.csproj +index f6c4873c..0f61b8f4 100644 +--- a/examples/Tracing/src/HelloWorld/HelloWorld.csproj ++++ b/examples/Tracing/src/HelloWorld/HelloWorld.csproj +@@ -5,11 +5,11 @@ + enable + + +- ++ + +- +- +- ++ ++ ++ + + + +diff --git a/examples/Tracing/test/HelloWorld.Test/HelloWorld.Tests.csproj b/examples/Tracing/test/HelloWorld.Test/HelloWorld.Tests.csproj +index 14917e4c..446d7f28 100644 +--- a/examples/Tracing/test/HelloWorld.Test/HelloWorld.Tests.csproj ++++ b/examples/Tracing/test/HelloWorld.Test/HelloWorld.Tests.csproj +@@ -3,9 +3,9 @@ + net6.0;net8.0 + + +- ++ + +- ++ + + + +diff --git a/examples/examples.sln b/examples/examples.sln +index 6b9fa877..10ec4850 100644 +--- a/examples/examples.sln ++++ b/examples/examples.sln +@@ -109,16 +109,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AOT_Logging", "AOT\AOT_Logg + EndProject + Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AOT_Logging.Tests", "AOT\AOT_Logging\test\AOT_Logging.Tests\AOT_Logging.Tests.csproj", "{FC010A0E-64A9-4440-97FE-DEDA8CEE0BE5}" + EndProject +-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Kafka", "Kafka", "{71027B81-CA39-498C-9A50-ADDAFA2AC2F5}" +-EndProject +-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Json", "Kafka\Json\src\Json.csproj", "{58EC305E-353A-4996-A541-3CF7FC0EDD80}" +-EndProject +-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Protobuf", "Kafka\Protobuf\src\Protobuf.csproj", "{853F6FE9-1762-4BA3-BAF4-2FCD605B81CF}" +-EndProject +-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avro", "Kafka\Avro\src\Avro.csproj", "{B03F22B2-315C-429B-9CC0-C15BE94CBF77}" +-EndProject +-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtoBufClassLibrary", "Kafka\JsonClassLibrary\src\ProtoBufClassLibrary.csproj", "{B6B3136D-B739-4917-AD3D-30F19FE12D3F}" +-EndProject + Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU +@@ -212,22 +202,6 @@ Global + {FC010A0E-64A9-4440-97FE-DEDA8CEE0BE5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FC010A0E-64A9-4440-97FE-DEDA8CEE0BE5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FC010A0E-64A9-4440-97FE-DEDA8CEE0BE5}.Release|Any CPU.Build.0 = Release|Any CPU +- {58EC305E-353A-4996-A541-3CF7FC0EDD80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU +- {58EC305E-353A-4996-A541-3CF7FC0EDD80}.Debug|Any CPU.Build.0 = Debug|Any CPU +- {58EC305E-353A-4996-A541-3CF7FC0EDD80}.Release|Any CPU.ActiveCfg = Release|Any CPU +- {58EC305E-353A-4996-A541-3CF7FC0EDD80}.Release|Any CPU.Build.0 = Release|Any CPU +- {853F6FE9-1762-4BA3-BAF4-2FCD605B81CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU +- {853F6FE9-1762-4BA3-BAF4-2FCD605B81CF}.Debug|Any CPU.Build.0 = Debug|Any CPU +- {853F6FE9-1762-4BA3-BAF4-2FCD605B81CF}.Release|Any CPU.ActiveCfg = Release|Any CPU +- {853F6FE9-1762-4BA3-BAF4-2FCD605B81CF}.Release|Any CPU.Build.0 = Release|Any CPU +- {B03F22B2-315C-429B-9CC0-C15BE94CBF77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU +- {B03F22B2-315C-429B-9CC0-C15BE94CBF77}.Debug|Any CPU.Build.0 = Debug|Any CPU +- {B03F22B2-315C-429B-9CC0-C15BE94CBF77}.Release|Any CPU.ActiveCfg = Release|Any CPU +- {B03F22B2-315C-429B-9CC0-C15BE94CBF77}.Release|Any CPU.Build.0 = Release|Any CPU +- {B6B3136D-B739-4917-AD3D-30F19FE12D3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU +- {B6B3136D-B739-4917-AD3D-30F19FE12D3F}.Debug|Any CPU.Build.0 = Debug|Any CPU +- {B6B3136D-B739-4917-AD3D-30F19FE12D3F}.Release|Any CPU.ActiveCfg = Release|Any CPU +- {B6B3136D-B739-4917-AD3D-30F19FE12D3F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {0CC66DBC-C1DF-4AF6-8EEB-FFED6C578BF4} = {526F1EF7-5A9C-4BFF-ABAE-75992ACD8F78} +@@ -275,9 +249,5 @@ Global + {343CF6B9-C006-43F8-924C-BF5BF5B6D051} = {FE1CAA26-87E9-4B71-800E-81D2997A7B53} + {FC02CF45-DE15-4413-958A-D86808B99146} = {FEE72EAB-494F-403B-A75A-825E713C3D43} + {FC010A0E-64A9-4440-97FE-DEDA8CEE0BE5} = {F3480212-EE7F-46FE-9ED5-24ACAB5B681D} +- {58EC305E-353A-4996-A541-3CF7FC0EDD80} = {71027B81-CA39-498C-9A50-ADDAFA2AC2F5} +- {853F6FE9-1762-4BA3-BAF4-2FCD605B81CF} = {71027B81-CA39-498C-9A50-ADDAFA2AC2F5} +- {B03F22B2-315C-429B-9CC0-C15BE94CBF77} = {71027B81-CA39-498C-9A50-ADDAFA2AC2F5} +- {B6B3136D-B739-4917-AD3D-30F19FE12D3F} = {71027B81-CA39-498C-9A50-ADDAFA2AC2F5} + EndGlobalSection + EndGlobal +diff --git a/libraries/AWS.Lambda.Powertools.sln b/libraries/AWS.Lambda.Powertools.sln +index 325c683e..72aea967 100644 +--- a/libraries/AWS.Lambda.Powertools.sln ++++ b/libraries/AWS.Lambda.Powertools.sln +@@ -97,32 +97,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AOT-FunctionHandlerTest", " + EndProject + Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AOT-FunctionMethodAttributeTest", "tests\e2e\functions\idempotency\AOT-Function\src\AOT-FunctionMethodAttributeTest\AOT-FunctionMethodAttributeTest.csproj", "{CC8CFF43-DC72-464C-A42D-55E023DE8500}" + EndProject +-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.Lambda.Powertools.Metrics.AspNetCore", "src\AWS.Lambda.Powertools.Metrics.AspNetCore\AWS.Lambda.Powertools.Metrics.AspNetCore.csproj", "{A2AD98B1-2BED-4864-B573-77BE7B52FED2}" +-EndProject +-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.Lambda.Powertools.Metrics.AspNetCore.Tests", "tests\AWS.Lambda.Powertools.Metrics.AspNetCore.Tests\AWS.Lambda.Powertools.Metrics.AspNetCore.Tests.csproj", "{F8F80477-1EAD-4C5C-A329-CBC0A60C7CAB}" +-EndProject +-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Metrics", "Metrics", "{A566F2D7-F8FE-466A-8306-85F266B7E656}" +-EndProject +-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AOT-Function-ILogger", "tests\e2e\functions\core\logging\AOT-Function-ILogger\src\AOT-Function-ILogger\AOT-Function-ILogger.csproj", "{7FC6DD65-0352-4139-8D08-B25C0A0403E3}" +-EndProject +-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.Lambda.Powertools.EventHandler.Tests", "tests\AWS.Lambda.Powertools.EventHandler.Tests\AWS.Lambda.Powertools.EventHandler.Tests.csproj", "{61374D8E-F77C-4A31-AE07-35DAF1847369}" +-EndProject +-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.Lambda.Powertools.EventHandler", "src\AWS.Lambda.Powertools.EventHandler\AWS.Lambda.Powertools.EventHandler.csproj", "{F4B8D5AF-D3CA-4910-A14D-E5BAEF0FD1DE}" +-EndProject +-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction", "src\AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction\AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.csproj", "{281F7EB5-ACE5-458F-BC88-46A8899DF3BA}" +-EndProject +-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore", "src\AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore\AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore.csproj", "{8A22F22E-D10A-4897-A89A-DC76C267F6BB}" +-EndProject +-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.Lambda.Powertools.Kafka", "src\AWS.Lambda.Powertools.Kafka\AWS.Lambda.Powertools.Kafka.csproj", "{5B0DDE6F-ED16-452F-90D3-F0B6086D51B3}" +-EndProject +-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.Lambda.Powertools.Kafka.Tests", "tests\AWS.Lambda.Powertools.Kafka.Tests\AWS.Lambda.Powertools.Kafka.Tests.csproj", "{FDBDB9F8-B3E2-4ACA-9FC6-E12FF3D95645}" +-EndProject +-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.Lambda.Powertools.Kafka.Avro", "src\AWS.Lambda.Powertools.Kafka.Avro\AWS.Lambda.Powertools.Kafka.Avro.csproj", "{25F0929B-2E04-4ED6-A0ED-5379A0A755B0}" +-EndProject +-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.Lambda.Powertools.Kafka.Json", "src\AWS.Lambda.Powertools.Kafka.Json\AWS.Lambda.Powertools.Kafka.Json.csproj", "{9E2B8160-3E76-4B33-86AB-DE35A5FCDB1E}" +-EndProject +-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.Lambda.Powertools.Kafka.Protobuf", "src\AWS.Lambda.Powertools.Kafka.Protobuf\AWS.Lambda.Powertools.Kafka.Protobuf.csproj", "{B640DB80-C982-407B-A2EC-CD29AC77DDB8}" +-EndProject + Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU +@@ -544,150 +518,6 @@ Global + {CC8CFF43-DC72-464C-A42D-55E023DE8500}.Release|x64.Build.0 = Release|Any CPU + {CC8CFF43-DC72-464C-A42D-55E023DE8500}.Release|x86.ActiveCfg = Release|Any CPU + {CC8CFF43-DC72-464C-A42D-55E023DE8500}.Release|x86.Build.0 = Release|Any CPU +- {A2AD98B1-2BED-4864-B573-77BE7B52FED2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU +- {A2AD98B1-2BED-4864-B573-77BE7B52FED2}.Debug|Any CPU.Build.0 = Debug|Any CPU +- {A2AD98B1-2BED-4864-B573-77BE7B52FED2}.Debug|x64.ActiveCfg = Debug|Any CPU +- {A2AD98B1-2BED-4864-B573-77BE7B52FED2}.Debug|x64.Build.0 = Debug|Any CPU +- {A2AD98B1-2BED-4864-B573-77BE7B52FED2}.Debug|x86.ActiveCfg = Debug|Any CPU +- {A2AD98B1-2BED-4864-B573-77BE7B52FED2}.Debug|x86.Build.0 = Debug|Any CPU +- {A2AD98B1-2BED-4864-B573-77BE7B52FED2}.Release|Any CPU.ActiveCfg = Release|Any CPU +- {A2AD98B1-2BED-4864-B573-77BE7B52FED2}.Release|Any CPU.Build.0 = Release|Any CPU +- {A2AD98B1-2BED-4864-B573-77BE7B52FED2}.Release|x64.ActiveCfg = Release|Any CPU +- {A2AD98B1-2BED-4864-B573-77BE7B52FED2}.Release|x64.Build.0 = Release|Any CPU +- {A2AD98B1-2BED-4864-B573-77BE7B52FED2}.Release|x86.ActiveCfg = Release|Any CPU +- {A2AD98B1-2BED-4864-B573-77BE7B52FED2}.Release|x86.Build.0 = Release|Any CPU +- {F8F80477-1EAD-4C5C-A329-CBC0A60C7CAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU +- {F8F80477-1EAD-4C5C-A329-CBC0A60C7CAB}.Debug|Any CPU.Build.0 = Debug|Any CPU +- {F8F80477-1EAD-4C5C-A329-CBC0A60C7CAB}.Debug|x64.ActiveCfg = Debug|Any CPU +- {F8F80477-1EAD-4C5C-A329-CBC0A60C7CAB}.Debug|x64.Build.0 = Debug|Any CPU +- {F8F80477-1EAD-4C5C-A329-CBC0A60C7CAB}.Debug|x86.ActiveCfg = Debug|Any CPU +- {F8F80477-1EAD-4C5C-A329-CBC0A60C7CAB}.Debug|x86.Build.0 = Debug|Any CPU +- {F8F80477-1EAD-4C5C-A329-CBC0A60C7CAB}.Release|Any CPU.ActiveCfg = Release|Any CPU +- {F8F80477-1EAD-4C5C-A329-CBC0A60C7CAB}.Release|Any CPU.Build.0 = Release|Any CPU +- {F8F80477-1EAD-4C5C-A329-CBC0A60C7CAB}.Release|x64.ActiveCfg = Release|Any CPU +- {F8F80477-1EAD-4C5C-A329-CBC0A60C7CAB}.Release|x64.Build.0 = Release|Any CPU +- {F8F80477-1EAD-4C5C-A329-CBC0A60C7CAB}.Release|x86.ActiveCfg = Release|Any CPU +- {F8F80477-1EAD-4C5C-A329-CBC0A60C7CAB}.Release|x86.Build.0 = Release|Any CPU +- {7FC6DD65-0352-4139-8D08-B25C0A0403E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU +- {7FC6DD65-0352-4139-8D08-B25C0A0403E3}.Debug|Any CPU.Build.0 = Debug|Any CPU +- {7FC6DD65-0352-4139-8D08-B25C0A0403E3}.Debug|x64.ActiveCfg = Debug|Any CPU +- {7FC6DD65-0352-4139-8D08-B25C0A0403E3}.Debug|x64.Build.0 = Debug|Any CPU +- {7FC6DD65-0352-4139-8D08-B25C0A0403E3}.Debug|x86.ActiveCfg = Debug|Any CPU +- {7FC6DD65-0352-4139-8D08-B25C0A0403E3}.Debug|x86.Build.0 = Debug|Any CPU +- {7FC6DD65-0352-4139-8D08-B25C0A0403E3}.Release|Any CPU.ActiveCfg = Release|Any CPU +- {7FC6DD65-0352-4139-8D08-B25C0A0403E3}.Release|Any CPU.Build.0 = Release|Any CPU +- {7FC6DD65-0352-4139-8D08-B25C0A0403E3}.Release|x64.ActiveCfg = Release|Any CPU +- {7FC6DD65-0352-4139-8D08-B25C0A0403E3}.Release|x64.Build.0 = Release|Any CPU +- {7FC6DD65-0352-4139-8D08-B25C0A0403E3}.Release|x86.ActiveCfg = Release|Any CPU +- {7FC6DD65-0352-4139-8D08-B25C0A0403E3}.Release|x86.Build.0 = Release|Any CPU +- {61374D8E-F77C-4A31-AE07-35DAF1847369}.Debug|Any CPU.ActiveCfg = Debug|Any CPU +- {61374D8E-F77C-4A31-AE07-35DAF1847369}.Debug|Any CPU.Build.0 = Debug|Any CPU +- {61374D8E-F77C-4A31-AE07-35DAF1847369}.Debug|x64.ActiveCfg = Debug|Any CPU +- {61374D8E-F77C-4A31-AE07-35DAF1847369}.Debug|x64.Build.0 = Debug|Any CPU +- {61374D8E-F77C-4A31-AE07-35DAF1847369}.Debug|x86.ActiveCfg = Debug|Any CPU +- {61374D8E-F77C-4A31-AE07-35DAF1847369}.Debug|x86.Build.0 = Debug|Any CPU +- {61374D8E-F77C-4A31-AE07-35DAF1847369}.Release|Any CPU.ActiveCfg = Release|Any CPU +- {61374D8E-F77C-4A31-AE07-35DAF1847369}.Release|Any CPU.Build.0 = Release|Any CPU +- {61374D8E-F77C-4A31-AE07-35DAF1847369}.Release|x64.ActiveCfg = Release|Any CPU +- {61374D8E-F77C-4A31-AE07-35DAF1847369}.Release|x64.Build.0 = Release|Any CPU +- {61374D8E-F77C-4A31-AE07-35DAF1847369}.Release|x86.ActiveCfg = Release|Any CPU +- {61374D8E-F77C-4A31-AE07-35DAF1847369}.Release|x86.Build.0 = Release|Any CPU +- {F4B8D5AF-D3CA-4910-A14D-E5BAEF0FD1DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU +- {F4B8D5AF-D3CA-4910-A14D-E5BAEF0FD1DE}.Debug|Any CPU.Build.0 = Debug|Any CPU +- {F4B8D5AF-D3CA-4910-A14D-E5BAEF0FD1DE}.Debug|x64.ActiveCfg = Debug|Any CPU +- {F4B8D5AF-D3CA-4910-A14D-E5BAEF0FD1DE}.Debug|x64.Build.0 = Debug|Any CPU +- {F4B8D5AF-D3CA-4910-A14D-E5BAEF0FD1DE}.Debug|x86.ActiveCfg = Debug|Any CPU +- {F4B8D5AF-D3CA-4910-A14D-E5BAEF0FD1DE}.Debug|x86.Build.0 = Debug|Any CPU +- {F4B8D5AF-D3CA-4910-A14D-E5BAEF0FD1DE}.Release|Any CPU.ActiveCfg = Release|Any CPU +- {F4B8D5AF-D3CA-4910-A14D-E5BAEF0FD1DE}.Release|Any CPU.Build.0 = Release|Any CPU +- {F4B8D5AF-D3CA-4910-A14D-E5BAEF0FD1DE}.Release|x64.ActiveCfg = Release|Any CPU +- {F4B8D5AF-D3CA-4910-A14D-E5BAEF0FD1DE}.Release|x64.Build.0 = Release|Any CPU +- {F4B8D5AF-D3CA-4910-A14D-E5BAEF0FD1DE}.Release|x86.ActiveCfg = Release|Any CPU +- {F4B8D5AF-D3CA-4910-A14D-E5BAEF0FD1DE}.Release|x86.Build.0 = Release|Any CPU +- {281F7EB5-ACE5-458F-BC88-46A8899DF3BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU +- {281F7EB5-ACE5-458F-BC88-46A8899DF3BA}.Debug|Any CPU.Build.0 = Debug|Any CPU +- {281F7EB5-ACE5-458F-BC88-46A8899DF3BA}.Debug|x64.ActiveCfg = Debug|Any CPU +- {281F7EB5-ACE5-458F-BC88-46A8899DF3BA}.Debug|x64.Build.0 = Debug|Any CPU +- {281F7EB5-ACE5-458F-BC88-46A8899DF3BA}.Debug|x86.ActiveCfg = Debug|Any CPU +- {281F7EB5-ACE5-458F-BC88-46A8899DF3BA}.Debug|x86.Build.0 = Debug|Any CPU +- {281F7EB5-ACE5-458F-BC88-46A8899DF3BA}.Release|Any CPU.ActiveCfg = Release|Any CPU +- {281F7EB5-ACE5-458F-BC88-46A8899DF3BA}.Release|Any CPU.Build.0 = Release|Any CPU +- {281F7EB5-ACE5-458F-BC88-46A8899DF3BA}.Release|x64.ActiveCfg = Release|Any CPU +- {281F7EB5-ACE5-458F-BC88-46A8899DF3BA}.Release|x64.Build.0 = Release|Any CPU +- {281F7EB5-ACE5-458F-BC88-46A8899DF3BA}.Release|x86.ActiveCfg = Release|Any CPU +- {281F7EB5-ACE5-458F-BC88-46A8899DF3BA}.Release|x86.Build.0 = Release|Any CPU +- {8A22F22E-D10A-4897-A89A-DC76C267F6BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU +- {8A22F22E-D10A-4897-A89A-DC76C267F6BB}.Debug|Any CPU.Build.0 = Debug|Any CPU +- {8A22F22E-D10A-4897-A89A-DC76C267F6BB}.Debug|x64.ActiveCfg = Debug|Any CPU +- {8A22F22E-D10A-4897-A89A-DC76C267F6BB}.Debug|x64.Build.0 = Debug|Any CPU +- {8A22F22E-D10A-4897-A89A-DC76C267F6BB}.Debug|x86.ActiveCfg = Debug|Any CPU +- {8A22F22E-D10A-4897-A89A-DC76C267F6BB}.Debug|x86.Build.0 = Debug|Any CPU +- {8A22F22E-D10A-4897-A89A-DC76C267F6BB}.Release|Any CPU.ActiveCfg = Release|Any CPU +- {8A22F22E-D10A-4897-A89A-DC76C267F6BB}.Release|Any CPU.Build.0 = Release|Any CPU +- {8A22F22E-D10A-4897-A89A-DC76C267F6BB}.Release|x64.ActiveCfg = Release|Any CPU +- {8A22F22E-D10A-4897-A89A-DC76C267F6BB}.Release|x64.Build.0 = Release|Any CPU +- {8A22F22E-D10A-4897-A89A-DC76C267F6BB}.Release|x86.ActiveCfg = Release|Any CPU +- {8A22F22E-D10A-4897-A89A-DC76C267F6BB}.Release|x86.Build.0 = Release|Any CPU +- {5B0DDE6F-ED16-452F-90D3-F0B6086D51B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU +- {5B0DDE6F-ED16-452F-90D3-F0B6086D51B3}.Debug|Any CPU.Build.0 = Debug|Any CPU +- {5B0DDE6F-ED16-452F-90D3-F0B6086D51B3}.Debug|x64.ActiveCfg = Debug|Any CPU +- {5B0DDE6F-ED16-452F-90D3-F0B6086D51B3}.Debug|x64.Build.0 = Debug|Any CPU +- {5B0DDE6F-ED16-452F-90D3-F0B6086D51B3}.Debug|x86.ActiveCfg = Debug|Any CPU +- {5B0DDE6F-ED16-452F-90D3-F0B6086D51B3}.Debug|x86.Build.0 = Debug|Any CPU +- {5B0DDE6F-ED16-452F-90D3-F0B6086D51B3}.Release|Any CPU.ActiveCfg = Release|Any CPU +- {5B0DDE6F-ED16-452F-90D3-F0B6086D51B3}.Release|Any CPU.Build.0 = Release|Any CPU +- {5B0DDE6F-ED16-452F-90D3-F0B6086D51B3}.Release|x64.ActiveCfg = Release|Any CPU +- {5B0DDE6F-ED16-452F-90D3-F0B6086D51B3}.Release|x64.Build.0 = Release|Any CPU +- {5B0DDE6F-ED16-452F-90D3-F0B6086D51B3}.Release|x86.ActiveCfg = Release|Any CPU +- {5B0DDE6F-ED16-452F-90D3-F0B6086D51B3}.Release|x86.Build.0 = Release|Any CPU +- {FDBDB9F8-B3E2-4ACA-9FC6-E12FF3D95645}.Debug|Any CPU.ActiveCfg = Debug|Any CPU +- {FDBDB9F8-B3E2-4ACA-9FC6-E12FF3D95645}.Debug|Any CPU.Build.0 = Debug|Any CPU +- {FDBDB9F8-B3E2-4ACA-9FC6-E12FF3D95645}.Debug|x64.ActiveCfg = Debug|Any CPU +- {FDBDB9F8-B3E2-4ACA-9FC6-E12FF3D95645}.Debug|x64.Build.0 = Debug|Any CPU +- {FDBDB9F8-B3E2-4ACA-9FC6-E12FF3D95645}.Debug|x86.ActiveCfg = Debug|Any CPU +- {FDBDB9F8-B3E2-4ACA-9FC6-E12FF3D95645}.Debug|x86.Build.0 = Debug|Any CPU +- {FDBDB9F8-B3E2-4ACA-9FC6-E12FF3D95645}.Release|Any CPU.ActiveCfg = Release|Any CPU +- {FDBDB9F8-B3E2-4ACA-9FC6-E12FF3D95645}.Release|Any CPU.Build.0 = Release|Any CPU +- {FDBDB9F8-B3E2-4ACA-9FC6-E12FF3D95645}.Release|x64.ActiveCfg = Release|Any CPU +- {FDBDB9F8-B3E2-4ACA-9FC6-E12FF3D95645}.Release|x64.Build.0 = Release|Any CPU +- {FDBDB9F8-B3E2-4ACA-9FC6-E12FF3D95645}.Release|x86.ActiveCfg = Release|Any CPU +- {FDBDB9F8-B3E2-4ACA-9FC6-E12FF3D95645}.Release|x86.Build.0 = Release|Any CPU +- {25F0929B-2E04-4ED6-A0ED-5379A0A755B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU +- {25F0929B-2E04-4ED6-A0ED-5379A0A755B0}.Debug|Any CPU.Build.0 = Debug|Any CPU +- {25F0929B-2E04-4ED6-A0ED-5379A0A755B0}.Debug|x64.ActiveCfg = Debug|Any CPU +- {25F0929B-2E04-4ED6-A0ED-5379A0A755B0}.Debug|x64.Build.0 = Debug|Any CPU +- {25F0929B-2E04-4ED6-A0ED-5379A0A755B0}.Debug|x86.ActiveCfg = Debug|Any CPU +- {25F0929B-2E04-4ED6-A0ED-5379A0A755B0}.Debug|x86.Build.0 = Debug|Any CPU +- {25F0929B-2E04-4ED6-A0ED-5379A0A755B0}.Release|Any CPU.ActiveCfg = Release|Any CPU +- {25F0929B-2E04-4ED6-A0ED-5379A0A755B0}.Release|Any CPU.Build.0 = Release|Any CPU +- {25F0929B-2E04-4ED6-A0ED-5379A0A755B0}.Release|x64.ActiveCfg = Release|Any CPU +- {25F0929B-2E04-4ED6-A0ED-5379A0A755B0}.Release|x64.Build.0 = Release|Any CPU +- {25F0929B-2E04-4ED6-A0ED-5379A0A755B0}.Release|x86.ActiveCfg = Release|Any CPU +- {25F0929B-2E04-4ED6-A0ED-5379A0A755B0}.Release|x86.Build.0 = Release|Any CPU +- {9E2B8160-3E76-4B33-86AB-DE35A5FCDB1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU +- {9E2B8160-3E76-4B33-86AB-DE35A5FCDB1E}.Debug|Any CPU.Build.0 = Debug|Any CPU +- {9E2B8160-3E76-4B33-86AB-DE35A5FCDB1E}.Debug|x64.ActiveCfg = Debug|Any CPU +- {9E2B8160-3E76-4B33-86AB-DE35A5FCDB1E}.Debug|x64.Build.0 = Debug|Any CPU +- {9E2B8160-3E76-4B33-86AB-DE35A5FCDB1E}.Debug|x86.ActiveCfg = Debug|Any CPU +- {9E2B8160-3E76-4B33-86AB-DE35A5FCDB1E}.Debug|x86.Build.0 = Debug|Any CPU +- {9E2B8160-3E76-4B33-86AB-DE35A5FCDB1E}.Release|Any CPU.ActiveCfg = Release|Any CPU +- {9E2B8160-3E76-4B33-86AB-DE35A5FCDB1E}.Release|Any CPU.Build.0 = Release|Any CPU +- {9E2B8160-3E76-4B33-86AB-DE35A5FCDB1E}.Release|x64.ActiveCfg = Release|Any CPU +- {9E2B8160-3E76-4B33-86AB-DE35A5FCDB1E}.Release|x64.Build.0 = Release|Any CPU +- {9E2B8160-3E76-4B33-86AB-DE35A5FCDB1E}.Release|x86.ActiveCfg = Release|Any CPU +- {9E2B8160-3E76-4B33-86AB-DE35A5FCDB1E}.Release|x86.Build.0 = Release|Any CPU +- {B640DB80-C982-407B-A2EC-CD29AC77DDB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU +- {B640DB80-C982-407B-A2EC-CD29AC77DDB8}.Debug|Any CPU.Build.0 = Debug|Any CPU +- {B640DB80-C982-407B-A2EC-CD29AC77DDB8}.Debug|x64.ActiveCfg = Debug|Any CPU +- {B640DB80-C982-407B-A2EC-CD29AC77DDB8}.Debug|x64.Build.0 = Debug|Any CPU +- {B640DB80-C982-407B-A2EC-CD29AC77DDB8}.Debug|x86.ActiveCfg = Debug|Any CPU +- {B640DB80-C982-407B-A2EC-CD29AC77DDB8}.Debug|x86.Build.0 = Debug|Any CPU +- {B640DB80-C982-407B-A2EC-CD29AC77DDB8}.Release|Any CPU.ActiveCfg = Release|Any CPU +- {B640DB80-C982-407B-A2EC-CD29AC77DDB8}.Release|Any CPU.Build.0 = Release|Any CPU +- {B640DB80-C982-407B-A2EC-CD29AC77DDB8}.Release|x64.ActiveCfg = Release|Any CPU +- {B640DB80-C982-407B-A2EC-CD29AC77DDB8}.Release|x64.Build.0 = Release|Any CPU +- {B640DB80-C982-407B-A2EC-CD29AC77DDB8}.Release|x86.ActiveCfg = Release|Any CPU +- {B640DB80-C982-407B-A2EC-CD29AC77DDB8}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + + GlobalSection(NestedProjects) = preSolution +@@ -696,6 +526,7 @@ Global + {3BA6251D-DE4E-4547-AAA9-25F4BA04C636} = {73C9B1E5-3893-47E8-B373-17E5F5D7E6F5} + {1A3AC28C-3AEE-40FE-B229-9E38BB609547} = {73C9B1E5-3893-47E8-B373-17E5F5D7E6F5} + {B68A0D0A-4785-48CB-864F-29E3A8ACA526} = {1CFF5568-8486-475F-81F6-06105C437528} ++ {A422C742-2CF9-409D-BDAE-15825AB62113} = {1CFF5568-8486-475F-81F6-06105C437528} + {4EC48E6A-45B5-4E25-ABBD-C23FE2BD6E1E} = {1CFF5568-8486-475F-81F6-06105C437528} + {A040AED5-BBB8-4BFA-B2A5-BBD82817B8A5} = {1CFF5568-8486-475F-81F6-06105C437528} + {1ECB31E8-2EF0-41E2-8C71-CB9876D207F0} = {73C9B1E5-3893-47E8-B373-17E5F5D7E6F5} +@@ -732,19 +563,5 @@ Global + {ACA789EA-BD38-490B-A7F8-6A3A86985025} = {FB2C7DA3-6FCE-429D-86F9-5775D0231EC6} + {E71C48D2-AD56-4177-BBD7-6BB859A40C92} = {FB2C7DA3-6FCE-429D-86F9-5775D0231EC6} + {CC8CFF43-DC72-464C-A42D-55E023DE8500} = {FB2C7DA3-6FCE-429D-86F9-5775D0231EC6} +- {A2AD98B1-2BED-4864-B573-77BE7B52FED2} = {73C9B1E5-3893-47E8-B373-17E5F5D7E6F5} +- {A566F2D7-F8FE-466A-8306-85F266B7E656} = {1CFF5568-8486-475F-81F6-06105C437528} +- {F8F80477-1EAD-4C5C-A329-CBC0A60C7CAB} = {A566F2D7-F8FE-466A-8306-85F266B7E656} +- {A422C742-2CF9-409D-BDAE-15825AB62113} = {A566F2D7-F8FE-466A-8306-85F266B7E656} +- {7FC6DD65-0352-4139-8D08-B25C0A0403E3} = {4EAB66F9-C9CB-4E8A-BEE6-A14CD7FDE02F} +- {61374D8E-F77C-4A31-AE07-35DAF1847369} = {1CFF5568-8486-475F-81F6-06105C437528} +- {F4B8D5AF-D3CA-4910-A14D-E5BAEF0FD1DE} = {73C9B1E5-3893-47E8-B373-17E5F5D7E6F5} +- {281F7EB5-ACE5-458F-BC88-46A8899DF3BA} = {73C9B1E5-3893-47E8-B373-17E5F5D7E6F5} +- {8A22F22E-D10A-4897-A89A-DC76C267F6BB} = {73C9B1E5-3893-47E8-B373-17E5F5D7E6F5} +- {5B0DDE6F-ED16-452F-90D3-F0B6086D51B3} = {73C9B1E5-3893-47E8-B373-17E5F5D7E6F5} +- {FDBDB9F8-B3E2-4ACA-9FC6-E12FF3D95645} = {1CFF5568-8486-475F-81F6-06105C437528} +- {25F0929B-2E04-4ED6-A0ED-5379A0A755B0} = {73C9B1E5-3893-47E8-B373-17E5F5D7E6F5} +- {9E2B8160-3E76-4B33-86AB-DE35A5FCDB1E} = {73C9B1E5-3893-47E8-B373-17E5F5D7E6F5} +- {B640DB80-C982-407B-A2EC-CD29AC77DDB8} = {73C9B1E5-3893-47E8-B373-17E5F5D7E6F5} + EndGlobalSection + EndGlobal +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/AWS.Lambda.Powertools.BatchProcessing.csproj b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/AWS.Lambda.Powertools.BatchProcessing.csproj +index e4bb98ea..54af1670 100644 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/AWS.Lambda.Powertools.BatchProcessing.csproj ++++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/AWS.Lambda.Powertools.BatchProcessing.csproj +@@ -10,7 +10,6 @@ + + + +- + + + +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/BatchItemFailuresResponse.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/BatchItemFailuresResponse.cs +index 16248718..d7ef6bfa 100644 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/BatchItemFailuresResponse.cs ++++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/BatchItemFailuresResponse.cs +@@ -1,4 +1,17 @@ +- ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ + + using System.Collections.Generic; + using System.Runtime.Serialization; +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/BatchProcessor.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/BatchProcessor.cs +index 6afeebfa..ba3c5f3f 100644 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/BatchProcessor.cs ++++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/BatchProcessor.cs +@@ -1,4 +1,19 @@ +-using System; ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ ++using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Linq; +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/BatchProcessorAttribute.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/BatchProcessorAttribute.cs +index f28f7f3b..d693d4ec 100644 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/BatchProcessorAttribute.cs ++++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/BatchProcessorAttribute.cs +@@ -1,10 +1,21 @@ +- ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ + + using System; + using System.Collections.Generic; +-using System.Diagnostics.CodeAnalysis; + using System.Linq; +-using System.Text.Json.Serialization; + using System.Threading; + using System.Threading.Tasks; + using Amazon.Lambda.DynamoDBEvents; +@@ -130,62 +141,23 @@ public class BatchProcessorAttribute : UniversalWrapperAttribute + /// + /// Type of batch processor. + /// +- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + public Type BatchProcessor { get; set; } + + /// + /// Type of batch processor provider. + /// +- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + public Type BatchProcessorProvider { get; set; } + + /// + /// Type of record handler. + /// +- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + public Type RecordHandler { get; set; } + + /// + /// Type of record handler provider. + /// +- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] + public Type RecordHandlerProvider { get; set; } + +- /// +- /// Type of typed record handler for strongly-typed processing. +- /// +- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] +- public Type TypedRecordHandler { get; set; } +- +- /// +- /// Type of typed record handler provider for strongly-typed processing. +- /// +- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] +- public Type TypedRecordHandlerProvider { get; set; } +- +- /// +- /// Type of typed record handler with context for strongly-typed processing. +- /// +- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] +- public Type TypedRecordHandlerWithContext { get; set; } +- +- /// +- /// Type of typed record handler with context provider for strongly-typed processing. +- /// +- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] +- public Type TypedRecordHandlerWithContextProvider { get; set; } +- +- /// +- /// JsonSerializerContext type for AOT-compatible deserialization. +- /// +- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] +- public Type JsonSerializerContext { get; set; } +- +- /// +- /// Policy for handling deserialization errors. +- /// +- public DeserializationErrorPolicy DeserializationErrorPolicy { get; set; } = DeserializationErrorPolicy.FailRecord; +- + /// + /// Error handling policy. + /// +@@ -238,12 +210,6 @@ public class BatchProcessorAttribute : UniversalWrapperAttribute + {BatchEventType.KinesisDataStream, typeof(IRecordHandlerProvider)}, + {BatchEventType.Sqs, typeof(IRecordHandlerProvider)} + }; +- private static readonly Dictionary TypedBatchProcessorTypes = new() +- { +- {BatchEventType.DynamoDbStream, typeof(ITypedBatchProcessor)}, +- {BatchEventType.KinesisDataStream, typeof(ITypedBatchProcessor)}, +- {BatchEventType.Sqs, typeof(ITypedBatchProcessor)} +- }; + + /// + protected internal override T WrapSync(Func target, object[] args, AspectEventArgs eventArgs) +@@ -264,7 +230,7 @@ public class BatchProcessorAttribute : UniversalWrapperAttribute + return await target(args); + } + +- internal IBatchProcessingAspectHandler CreateAspectHandler(IReadOnlyList args) ++ private IBatchProcessingAspectHandler CreateAspectHandler(IReadOnlyList args) + { + // Try get event type + if (args == null || args.Count == 0 || !EventTypes.TryGetValue(args[0].GetType(), out var eventType)) +@@ -296,15 +262,6 @@ public class BatchProcessorAttribute : UniversalWrapperAttribute + throw new ArgumentException($"The provided record handler provider must implement: '{RecordHandlerProviderTypes[eventType]}'.", nameof(RecordHandlerProvider)); + } + +- // Validate typed handler configurations +- ValidateTypedHandlerConfiguration(); +- +- // Check if typed handlers are configured (not yet fully supported in attributes) +- if (IsTypedHandlerConfigured()) +- { +- throw new NotSupportedException("Typed record handlers are not yet fully supported with BatchProcessorAttribute. Please use direct typed batch processor calls for typed processing."); +- } +- + // Create aspect handler + return eventType switch + { +@@ -394,40 +351,4 @@ public class BatchProcessorAttribute : UniversalWrapperAttribute + ThrowOnFullBatchFailure = ThrowOnFullBatchFailure + }); + } +- +- private void ValidateTypedHandlerConfiguration() +- { +- // Ensure only one type of handler is configured +- var handlerCount = 0; +- if (RecordHandler != null) handlerCount++; +- if (RecordHandlerProvider != null) handlerCount++; +- if (TypedRecordHandler != null) handlerCount++; +- if (TypedRecordHandlerProvider != null) handlerCount++; +- if (TypedRecordHandlerWithContext != null) handlerCount++; +- if (TypedRecordHandlerWithContextProvider != null) handlerCount++; +- +- if (handlerCount == 0) +- { +- throw new InvalidOperationException("A record handler, record handler provider, typed record handler, or typed record handler provider is required."); +- } +- +- if (handlerCount > 1) +- { +- throw new InvalidOperationException("Only one type of handler (traditional or typed) can be configured at a time."); +- } +- +- // Validate JsonSerializerContext type if provided +- if (JsonSerializerContext != null && !JsonSerializerContext.IsAssignableTo(typeof(JsonSerializerContext))) +- { +- throw new InvalidOperationException($"The provided JsonSerializerContext must inherit from: '{typeof(JsonSerializerContext)}'."); +- } +- } +- +- private bool IsTypedHandlerConfigured() +- { +- return TypedRecordHandler != null || +- TypedRecordHandlerProvider != null || +- TypedRecordHandlerWithContext != null || +- TypedRecordHandlerWithContextProvider != null; +- } + } +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/BatchProcessorErrorHandlingPolicy.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/BatchProcessorErrorHandlingPolicy.cs +index 87401c13..13ab9b12 100644 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/BatchProcessorErrorHandlingPolicy.cs ++++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/BatchProcessorErrorHandlingPolicy.cs +@@ -1,4 +1,17 @@ +- ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ + + namespace AWS.Lambda.Powertools.BatchProcessing; + +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DeserializationErrorPolicy.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DeserializationErrorPolicy.cs +deleted file mode 100644 +index bad1f34d..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DeserializationErrorPolicy.cs ++++ /dev/null +@@ -1,27 +0,0 @@ +- +- +-namespace AWS.Lambda.Powertools.BatchProcessing; +- +-/// +-/// Defines how deserialization errors should be handled during batch processing. +-/// +-public enum DeserializationErrorPolicy +-{ +- /// +- /// Mark the record as failed when deserialization fails (default behavior). +- /// The record will be included in the batch failure response. +- /// +- FailRecord, +- +- /// +- /// Skip records that fail deserialization and continue processing other records. +- /// Failed records will not be included in the batch failure response. +- /// +- IgnoreRecord, +- +- /// +- /// Use a custom error handler to process deserialization failures. +- /// The custom handler determines how to handle the failed record. +- /// +- CustomHandler +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DeserializationOptions.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DeserializationOptions.cs +deleted file mode 100644 +index 780eadaa..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DeserializationOptions.cs ++++ /dev/null +@@ -1,64 +0,0 @@ +- +- +-using System; +-using System.Text.Json; +-using System.Text.Json.Serialization; +- +-namespace AWS.Lambda.Powertools.BatchProcessing; +- +-/// +-/// Configuration options for deserialization operations. +-/// +-public class DeserializationOptions +-{ +- /// +- /// Gets or sets the JsonSerializerContext to use for AOT-compatible deserialization. +- /// When provided, this takes precedence over JsonSerializerOptions. +- /// +- public JsonSerializerContext JsonSerializerContext { get; set; } +- +- /// +- /// Gets or sets the JsonSerializerOptions to use for deserialization. +- /// This is ignored if JsonSerializerContext is provided. +- /// +- public JsonSerializerOptions JsonSerializerOptions { get; set; } +- +- /// +- /// Gets or sets a value indicating whether deserialization errors should be ignored. +- /// When true, failed deserialization attempts will not throw exceptions but will return default values. +- /// Default is false. +- /// +- [Obsolete("Use ErrorPolicy property instead. This property will be removed in a future version.")] +- public bool IgnoreDeserializationErrors { get; set; } = false; +- +- /// +- /// Gets or sets the policy for handling deserialization errors. +- /// Default is FailRecord. +- /// +- public DeserializationErrorPolicy ErrorPolicy { get; set; } = DeserializationErrorPolicy.FailRecord; +- +- /// +- /// Creates a new instance of DeserializationOptions with default settings. +- /// +- public DeserializationOptions() +- { +- } +- +- /// +- /// Creates a new instance of DeserializationOptions with the specified JsonSerializerContext. +- /// +- /// The JsonSerializerContext to use for AOT-compatible deserialization. +- public DeserializationOptions(JsonSerializerContext jsonSerializerContext) +- { +- JsonSerializerContext = jsonSerializerContext; +- } +- +- /// +- /// Creates a new instance of DeserializationOptions with the specified JsonSerializerOptions. +- /// +- /// The JsonSerializerOptions to use for deserialization. +- public DeserializationOptions(JsonSerializerOptions jsonSerializerOptions) +- { +- JsonSerializerOptions = jsonSerializerOptions; +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DynamoDb/DynamoDbRecordDataExtractor.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DynamoDb/DynamoDbRecordDataExtractor.cs +deleted file mode 100644 +index b39b967a..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DynamoDb/DynamoDbRecordDataExtractor.cs ++++ /dev/null +@@ -1,43 +0,0 @@ +- +- +-using System.Text.Json; +-using Amazon.Lambda.DynamoDBEvents; +- +-namespace AWS.Lambda.Powertools.BatchProcessing.DynamoDb; +- +-/// +-/// Extracts data from DynamoDB stream records for deserialization. +-/// +-public class DynamoDbRecordDataExtractor : IRecordDataExtractor +-{ +- /// +- /// The singleton instance of the DynamoDB record data extractor. +- /// +- public static readonly DynamoDbRecordDataExtractor Instance = new(); +- +- /// +- /// Extracts the data from a DynamoDB stream record by serializing the entire DynamoDB record. +- /// For INSERT and MODIFY events, this includes the NewImage. For REMOVE events, this includes the OldImage. +- /// +- /// The DynamoDB stream record. +- /// The serialized DynamoDB record data. +- public string ExtractData(DynamoDBEvent.DynamodbStreamRecord record) +- { +- if (record?.Dynamodb == null) +- return string.Empty; +- +- // Create a simplified representation of the DynamoDB record for deserialization +- var recordData = new +- { +- record.EventName, +- record.Dynamodb.Keys, +- record.Dynamodb.NewImage, +- record.Dynamodb.OldImage, +- record.Dynamodb.SequenceNumber, +- record.Dynamodb.SizeBytes, +- record.Dynamodb.StreamViewType +- }; +- +- return JsonSerializer.Serialize(recordData); +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DynamoDb/DynamoDbStreamBatchProcessor.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DynamoDb/DynamoDbStreamBatchProcessor.cs +index f17d0e71..c19b4a44 100644 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DynamoDb/DynamoDbStreamBatchProcessor.cs ++++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DynamoDb/DynamoDbStreamBatchProcessor.cs +@@ -1,4 +1,17 @@ +- ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ + + using System.Collections.Generic; + using Amazon.Lambda.DynamoDBEvents; +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DynamoDb/IDynamoDbStreamBatchProcessor.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DynamoDb/IDynamoDbStreamBatchProcessor.cs +index 7a6aa721..2641338a 100644 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DynamoDb/IDynamoDbStreamBatchProcessor.cs ++++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DynamoDb/IDynamoDbStreamBatchProcessor.cs +@@ -1,4 +1,17 @@ +- ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ + + using Amazon.Lambda.DynamoDBEvents; + +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DynamoDb/IDynamoDbStreamRecordHandler.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DynamoDb/IDynamoDbStreamRecordHandler.cs +index 1d910daa..ed24545d 100644 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DynamoDb/IDynamoDbStreamRecordHandler.cs ++++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DynamoDb/IDynamoDbStreamRecordHandler.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using Amazon.Lambda.DynamoDBEvents; + + namespace AWS.Lambda.Powertools.BatchProcessing.DynamoDb; +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DynamoDb/TypedDynamoDbStreamBatchProcessor.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DynamoDb/TypedDynamoDbStreamBatchProcessor.cs +deleted file mode 100644 +index a5f25975..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DynamoDb/TypedDynamoDbStreamBatchProcessor.cs ++++ /dev/null +@@ -1,216 +0,0 @@ +- +- +-using System; +-using System.Threading; +-using System.Threading.Tasks; +-using Amazon.Lambda.Core; +-using Amazon.Lambda.DynamoDBEvents; +-using AWS.Lambda.Powertools.BatchProcessing.Exceptions; +-using AWS.Lambda.Powertools.BatchProcessing.Internal; +-using AWS.Lambda.Powertools.Common; +- +-namespace AWS.Lambda.Powertools.BatchProcessing.DynamoDb; +- +-/// +-/// Typed batch processor for DynamoDB stream events that supports automatic deserialization of record data. +-/// +-public class TypedDynamoDbStreamBatchProcessor : DynamoDbStreamBatchProcessor, ITypedBatchProcessor +-{ +- private readonly IDeserializationService _deserializationService; +- private readonly IRecordDataExtractor _recordDataExtractor; +- +- +- +- /// +- /// Initializes a new instance of the TypedDynamoDbStreamBatchProcessor class. +- /// +- /// The Powertools configurations. +- /// The deserialization service. If null, uses JsonDeserializationService.Instance. +- /// The record data extractor. If null, uses DynamoDbRecordDataExtractor.Instance. +- public TypedDynamoDbStreamBatchProcessor( +- IPowertoolsConfigurations powertoolsConfigurations, +- IDeserializationService deserializationService = null, +- IRecordDataExtractor recordDataExtractor = null) +- : base(powertoolsConfigurations) +- { +- _deserializationService = deserializationService ?? JsonDeserializationService.Instance; +- _recordDataExtractor = recordDataExtractor ?? DynamoDbRecordDataExtractor.Instance; +- } +- +- /// +- /// Default constructor for when consumers create a custom typed batch processor. +- /// +- protected TypedDynamoDbStreamBatchProcessor() : this(PowertoolsConfigurations.Instance) +- { +- } +- +- /// +- public async Task> ProcessAsync( +- DynamoDBEvent @event, +- ITypedRecordHandler recordHandler) +- { +- return await ProcessAsync(@event, recordHandler, null, CancellationToken.None); +- } +- +- /// +- public async Task> ProcessAsync( +- DynamoDBEvent @event, +- ITypedRecordHandler recordHandler, +- DeserializationOptions deserializationOptions) +- { +- return await ProcessAsync(@event, recordHandler, deserializationOptions, CancellationToken.None); +- } +- +- /// +- public async Task> ProcessAsync( +- DynamoDBEvent @event, +- ITypedRecordHandler recordHandler, +- CancellationToken cancellationToken) +- { +- return await ProcessAsync(@event, recordHandler, null, cancellationToken); +- } +- +- /// +- public async Task> ProcessAsync( +- DynamoDBEvent @event, +- ITypedRecordHandler recordHandler, +- DeserializationOptions deserializationOptions, +- CancellationToken cancellationToken) +- { +- var processingOptions = new ProcessingOptions +- { +- CancellationToken = cancellationToken +- }; +- return await ProcessAsync(@event, recordHandler, deserializationOptions, processingOptions); +- } +- +- /// +- public async Task> ProcessAsync( +- DynamoDBEvent @event, +- ITypedRecordHandler recordHandler, +- DeserializationOptions deserializationOptions, +- ProcessingOptions processingOptions) +- { +- // Validate AOT compatibility before processing +- AotCompatibilityHelper.ValidateAotCompatibility(deserializationOptions); +- +- var wrappedHandler = new TypedRecordHandlerWrapper(recordHandler, _deserializationService, _recordDataExtractor, deserializationOptions); +- return await ProcessAsync(@event, wrappedHandler, processingOptions); +- } +- +- /// +- public async Task> ProcessAsync( +- DynamoDBEvent @event, +- ITypedRecordHandlerWithContext recordHandler, +- ILambdaContext context) +- { +- return await ProcessAsync(@event, recordHandler, context, null, CancellationToken.None); +- } +- +- /// +- public async Task> ProcessAsync( +- DynamoDBEvent @event, +- ITypedRecordHandlerWithContext recordHandler, +- ILambdaContext context, +- DeserializationOptions deserializationOptions) +- { +- return await ProcessAsync(@event, recordHandler, context, deserializationOptions, CancellationToken.None); +- } +- +- /// +- public async Task> ProcessAsync( +- DynamoDBEvent @event, +- ITypedRecordHandlerWithContext recordHandler, +- ILambdaContext context, +- CancellationToken cancellationToken) +- { +- return await ProcessAsync(@event, recordHandler, context, null, cancellationToken); +- } +- +- /// +- public async Task> ProcessAsync( +- DynamoDBEvent @event, +- ITypedRecordHandlerWithContext recordHandler, +- ILambdaContext context, +- DeserializationOptions deserializationOptions, +- CancellationToken cancellationToken) +- { +- var processingOptions = new ProcessingOptions +- { +- CancellationToken = cancellationToken +- }; +- return await ProcessAsync(@event, recordHandler, context, deserializationOptions, processingOptions); +- } +- +- /// +- public async Task> ProcessAsync( +- DynamoDBEvent @event, +- ITypedRecordHandlerWithContext recordHandler, +- ILambdaContext context, +- DeserializationOptions deserializationOptions, +- ProcessingOptions processingOptions) +- { +- // Validate AOT compatibility before processing +- AotCompatibilityHelper.ValidateAotCompatibility(deserializationOptions); +- +- var wrappedHandler = new TypedRecordHandlerWithContextWrapper(recordHandler, context, _deserializationService, _recordDataExtractor, deserializationOptions); +- return await ProcessAsync(@event, wrappedHandler, processingOptions); +- } +- +- /// +- /// Wrapper class that adapts ITypedRecordHandler to IRecordHandler. +- /// +- private sealed class TypedRecordHandlerWrapper : TypedRecordHandlerWrapperBase +- { +- private readonly ITypedRecordHandler _typedHandler; +- +- public TypedRecordHandlerWrapper( +- ITypedRecordHandler typedHandler, +- IDeserializationService deserializationService, +- IRecordDataExtractor recordDataExtractor, +- DeserializationOptions deserializationOptions) +- : base(deserializationService, recordDataExtractor, deserializationOptions) +- { +- _typedHandler = typedHandler ?? throw new ArgumentNullException(nameof(typedHandler)); +- } +- +- protected override async Task HandleTypedRecordAsync(T deserializedData, CancellationToken cancellationToken) +- { +- return await _typedHandler.HandleAsync(deserializedData, cancellationToken); +- } +- +- protected override string GetDeserializationErrorMessage(DynamoDBEvent.DynamodbStreamRecord record, DeserializationException ex) +- { +- return $"Failed to deserialize DynamoDB stream record '{record.Dynamodb.SequenceNumber}' to type '{typeof(T).Name}'. See inner exception for details."; +- } +- } +- +- /// +- /// Wrapper class that adapts ITypedRecordHandlerWithContext to IRecordHandler. +- /// +- private sealed class TypedRecordHandlerWithContextWrapper : TypedRecordHandlerWithContextWrapperBase +- { +- private readonly ITypedRecordHandlerWithContext _typedHandler; +- +- public TypedRecordHandlerWithContextWrapper( +- ITypedRecordHandlerWithContext typedHandler, +- ILambdaContext context, +- IDeserializationService deserializationService, +- IRecordDataExtractor recordDataExtractor, +- DeserializationOptions deserializationOptions) +- : base(context, deserializationService, recordDataExtractor, deserializationOptions) +- { +- _typedHandler = typedHandler ?? throw new ArgumentNullException(nameof(typedHandler)); +- } +- +- protected override async Task HandleTypedRecordWithContextAsync(T deserializedData, ILambdaContext context, CancellationToken cancellationToken) +- { +- return await _typedHandler.HandleAsync(deserializedData, context, cancellationToken); +- } +- +- protected override string GetDeserializationErrorMessage(DynamoDBEvent.DynamodbStreamRecord record, DeserializationException ex) +- { +- return $"Failed to deserialize DynamoDB stream record '{record.Dynamodb.SequenceNumber}' to type '{typeof(T).Name}'. See inner exception for details."; +- } +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/AotCompatibilityException.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/AotCompatibilityException.cs +deleted file mode 100644 +index b75711c3..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/AotCompatibilityException.cs ++++ /dev/null +@@ -1,39 +0,0 @@ +- +- +-using System; +- +-namespace AWS.Lambda.Powertools.BatchProcessing.Exceptions; +- +-/// +-/// Exception thrown when AOT (Ahead-of-Time) compilation compatibility requirements are not met. +-/// +-public class AotCompatibilityException : Exception +-{ +- /// +- /// Gets the type that caused the AOT compatibility issue. +- /// +- public Type TargetType { get; } +- +- /// +- /// Initializes a new instance of the AotCompatibilityException class. +- /// +- /// The type that caused the AOT compatibility issue. +- /// The error message. +- public AotCompatibilityException(Type targetType, string message) +- : base(message) +- { +- TargetType = targetType; +- } +- +- /// +- /// Initializes a new instance of the AotCompatibilityException class. +- /// +- /// The type that caused the AOT compatibility issue. +- /// The error message. +- /// The inner exception. +- public AotCompatibilityException(Type targetType, string message, Exception innerException) +- : base(message, innerException) +- { +- TargetType = targetType; +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/AotTypeValidationException.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/AotTypeValidationException.cs +deleted file mode 100644 +index 472c158b..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/AotTypeValidationException.cs ++++ /dev/null +@@ -1,39 +0,0 @@ +- +- +-using System; +- +-namespace AWS.Lambda.Powertools.BatchProcessing.Exceptions; +- +-/// +-/// Exception thrown when type validation fails in AOT scenarios. +-/// +-public class AotTypeValidationException : Exception +-{ +- /// +- /// Gets the type that failed validation. +- /// +- public Type TargetType { get; } +- +- /// +- /// Initializes a new instance of the AotTypeValidationException class. +- /// +- /// The type that failed validation. +- /// The error message. +- public AotTypeValidationException(Type targetType, string message) +- : base(message) +- { +- TargetType = targetType; +- } +- +- /// +- /// Initializes a new instance of the AotTypeValidationException class. +- /// +- /// The type that failed validation. +- /// The error message. +- /// The inner exception. +- public AotTypeValidationException(Type targetType, string message, Exception innerException) +- : base(message, innerException) +- { +- TargetType = targetType; +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/BatchProcessingException.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/BatchProcessingException.cs +index a4f8669c..73160630 100644 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/BatchProcessingException.cs ++++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/BatchProcessingException.cs +@@ -1,4 +1,17 @@ +- ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ + + using System; + using System.Collections.Generic; +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/CircuitBreakerException.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/CircuitBreakerException.cs +index 63e9c91f..a7aee255 100644 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/CircuitBreakerException.cs ++++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/CircuitBreakerException.cs +@@ -1,4 +1,17 @@ +- ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ + + using System; + +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/DeserializationException.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/DeserializationException.cs +deleted file mode 100644 +index 0b36e101..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/DeserializationException.cs ++++ /dev/null +@@ -1,71 +0,0 @@ +- +- +-using System; +- +-namespace AWS.Lambda.Powertools.BatchProcessing.Exceptions; +- +-/// +-/// Exception thrown when deserialization of record data fails. +-/// +-public class DeserializationException : Exception +-{ +- /// +- /// Gets the raw record data that failed to deserialize. +- /// +- public string RecordData { get; } +- +- /// +- /// Gets the target type that the record data was being deserialized to. +- /// +- public Type TargetType { get; } +- +- /// +- /// Gets the record identifier, if available. +- /// +- public string RecordId { get; } +- +- /// +- /// Initializes a new instance of the DeserializationException class. +- /// +- /// The raw record data that failed to deserialize. +- /// The target type that the record data was being deserialized to. +- /// The record identifier, if available. +- /// The exception that caused the deserialization failure. +- public DeserializationException(string recordData, Type targetType, string recordId, Exception innerException) +- : base($"Failed to deserialize record '{recordId}' to type '{targetType?.Name ?? "Unknown"}'. See inner exception for details.", innerException) +- { +- RecordData = recordData; +- TargetType = targetType; +- RecordId = recordId; +- } +- +- /// +- /// Initializes a new instance of the DeserializationException class. +- /// +- /// The raw record data that failed to deserialize. +- /// The target type that the record data was being deserialized to. +- /// The exception that caused the deserialization failure. +- public DeserializationException(string recordData, Type targetType, Exception innerException) +- : this(recordData, targetType, "Unknown", innerException) +- { +- } +- +- /// +- /// Initializes a new instance of the DeserializationException class. +- /// +- /// The error message that explains the reason for the exception. +- /// The exception that caused the deserialization failure. +- public DeserializationException(string message, Exception innerException) +- : base(message, innerException) +- { +- } +- +- /// +- /// Initializes a new instance of the DeserializationException class. +- /// +- /// The error message that explains the reason for the exception. +- public DeserializationException(string message) +- : base(message) +- { +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/RecordProcessingException.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/RecordProcessingException.cs +index 102aa9f6..4b2a87c4 100644 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/RecordProcessingException.cs ++++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/RecordProcessingException.cs +@@ -1,4 +1,17 @@ +- ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ + + using System; + +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/UnprocessedRecordException.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/UnprocessedRecordException.cs +index 88e633b5..543d71f9 100644 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/UnprocessedRecordException.cs ++++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/UnprocessedRecordException.cs +@@ -1,4 +1,17 @@ +- ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ + + using System; + +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IBatchProcessor.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IBatchProcessor.cs +index 61dae738..dbe52df1 100644 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IBatchProcessor.cs ++++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IBatchProcessor.cs +@@ -1,4 +1,17 @@ +- ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ + + using System.Threading; + using System.Threading.Tasks; +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IBatchProcessorProvider.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IBatchProcessorProvider.cs +index f3bee954..ab191f99 100644 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IBatchProcessorProvider.cs ++++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IBatchProcessorProvider.cs +@@ -1,4 +1,17 @@ +- ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ + + namespace AWS.Lambda.Powertools.BatchProcessing; + +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IDeserializationErrorHandler.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IDeserializationErrorHandler.cs +deleted file mode 100644 +index cb26e423..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IDeserializationErrorHandler.cs ++++ /dev/null +@@ -1,23 +0,0 @@ +- +- +-using System; +-using System.Threading; +-using System.Threading.Tasks; +- +-namespace AWS.Lambda.Powertools.BatchProcessing; +- +-/// +-/// Interface for handling deserialization errors during batch processing. +-/// +-/// The type of the record being processed. +-public interface IDeserializationErrorHandler +-{ +- /// +- /// Handles a deserialization error for a specific record. +- /// +- /// The record that failed to deserialize. +- /// The exception that occurred during deserialization. +- /// The cancellation token. +- /// A task that represents the asynchronous operation. The task result contains the record handler result. +- Task HandleDeserializationError(TRecord record, Exception exception, CancellationToken cancellationToken); +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IDeserializationService.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IDeserializationService.cs +deleted file mode 100644 +index 1b49c7bf..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IDeserializationService.cs ++++ /dev/null +@@ -1,42 +0,0 @@ +- +- +-using System; +- +-namespace AWS.Lambda.Powertools.BatchProcessing; +- +-/// +-/// Service for deserializing record data into strongly-typed objects. +-/// +-public interface IDeserializationService +-{ +- /// +- /// Deserializes the provided data string into the specified type. +- /// +- /// The target type to deserialize to. +- /// The data string to deserialize. +- /// Optional deserialization options. +- /// The deserialized object of type T. +- /// Thrown when deserialization fails. +- T Deserialize(string data, DeserializationOptions options = null); +- +- /// +- /// Attempts to deserialize the provided data string into the specified type. +- /// +- /// The target type to deserialize to. +- /// The data string to deserialize. +- /// When this method returns, contains the deserialized object if successful, or the default value if unsuccessful. +- /// Optional deserialization options. +- /// true if deserialization was successful; otherwise, false. +- bool TryDeserialize(string data, out T result, DeserializationOptions options = null); +- +- /// +- /// Attempts to deserialize the provided data string into the specified type, capturing any exception that occurs. +- /// +- /// The target type to deserialize to. +- /// The data string to deserialize. +- /// When this method returns, contains the deserialized object if successful, or the default value if unsuccessful. +- /// When this method returns, contains the exception that occurred during deserialization if unsuccessful, or null if successful. +- /// Optional deserialization options. +- /// true if deserialization was successful; otherwise, false. +- bool TryDeserialize(string data, out T result, out Exception exception, DeserializationOptions options = null); +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IRecordDataExtractor.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IRecordDataExtractor.cs +deleted file mode 100644 +index 6eb49f97..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IRecordDataExtractor.cs ++++ /dev/null +@@ -1,17 +0,0 @@ +- +- +-namespace AWS.Lambda.Powertools.BatchProcessing; +- +-/// +-/// Interface for extracting data from event records for deserialization. +-/// +-/// The type of the event record. +-public interface IRecordDataExtractor +-{ +- /// +- /// Extracts the data string from the event record that should be deserialized. +- /// +- /// The event record to extract data from. +- /// The data string to be deserialized. +- string ExtractData(TRecord record); +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IRecordHandler.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IRecordHandler.cs +index 4cb068db..975c0b36 100644 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IRecordHandler.cs ++++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IRecordHandler.cs +@@ -1,4 +1,17 @@ +- ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ + + using System.Threading; + using System.Threading.Tasks; +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IRecordHandlerProvider.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IRecordHandlerProvider.cs +index b73e0454..79394228 100644 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IRecordHandlerProvider.cs ++++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IRecordHandlerProvider.cs +@@ -1,4 +1,17 @@ +- ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ + + namespace AWS.Lambda.Powertools.BatchProcessing; + +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ITypedBatchProcessor.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ITypedBatchProcessor.cs +deleted file mode 100644 +index 83cdc18f..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ITypedBatchProcessor.cs ++++ /dev/null +@@ -1,95 +0,0 @@ +- +- +-using System.Threading; +-using System.Threading.Tasks; +-using Amazon.Lambda.Core; +- +-namespace AWS.Lambda.Powertools.BatchProcessing; +- +-/// +-/// The interface for strongly-typed batch processing. +-/// +-/// Type of batch event. +-/// Type of batch record. +-public interface ITypedBatchProcessor +-{ +- /// +- /// The of the latest batch processing run. This includes a object with the identifiers of the batch items that failed processing. +- /// +- ProcessingResult ProcessingResult { get; } +- +- /// +- /// +- /// +- Task> ProcessAsync(TEvent @event, ITypedRecordHandler recordHandler); +- +- /// +- /// +- /// +- /// Options for controlling deserialization behavior. +- Task> ProcessAsync(TEvent @event, ITypedRecordHandler recordHandler, DeserializationOptions deserializationOptions); +- +- /// +- /// +- /// +- /// The cancellation token to monitor. +- Task> ProcessAsync(TEvent @event, ITypedRecordHandler recordHandler, CancellationToken cancellationToken); +- +- /// +- /// +- /// +- /// Options for controlling deserialization behavior. +- /// The cancellation token to monitor. +- Task> ProcessAsync(TEvent @event, ITypedRecordHandler recordHandler, DeserializationOptions deserializationOptions, CancellationToken cancellationToken); +- +- /// +- /// Processes a batch event with strongly-typed record handling. +- /// +- /// The type to deserialize record data to. +- /// The event to process. +- /// The typed record handler containing the per-record processing logic. +- /// Options for controlling deserialization behavior. +- /// Processing options to control settings such as cancellation, error handling policy and parallelism. +- /// A of the latest batch processing run. This includes a object with the identifiers of the batch items that failed processing. +- Task> ProcessAsync(TEvent @event, ITypedRecordHandler recordHandler, DeserializationOptions deserializationOptions, ProcessingOptions processingOptions); +- +- /// +- /// +- /// +- /// +- Task> ProcessAsync(TEvent @event, ITypedRecordHandlerWithContext recordHandler, ILambdaContext context); +- +- /// +- /// +- /// +- /// +- /// Options for controlling deserialization behavior. +- Task> ProcessAsync(TEvent @event, ITypedRecordHandlerWithContext recordHandler, ILambdaContext context, DeserializationOptions deserializationOptions); +- +- /// +- /// +- /// +- /// +- /// The cancellation token to monitor. +- Task> ProcessAsync(TEvent @event, ITypedRecordHandlerWithContext recordHandler, ILambdaContext context, CancellationToken cancellationToken); +- +- /// +- /// +- /// +- /// +- /// Options for controlling deserialization behavior. +- /// The cancellation token to monitor. +- Task> ProcessAsync(TEvent @event, ITypedRecordHandlerWithContext recordHandler, ILambdaContext context, DeserializationOptions deserializationOptions, CancellationToken cancellationToken); +- +- /// +- /// Processes a batch event with strongly-typed record handling and Lambda context. +- /// +- /// The type to deserialize record data to. +- /// The event to process. +- /// The typed record handler with context containing the per-record processing logic. +- /// The Lambda context for the current invocation. +- /// Options for controlling deserialization behavior. +- /// Processing options to control settings such as cancellation, error handling policy and parallelism. +- /// A of the latest batch processing run. This includes a object with the identifiers of the batch items that failed processing. +- Task> ProcessAsync(TEvent @event, ITypedRecordHandlerWithContext recordHandler, ILambdaContext context, DeserializationOptions deserializationOptions, ProcessingOptions processingOptions); +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ITypedRecordHandler.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ITypedRecordHandler.cs +deleted file mode 100644 +index 4c00df2d..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ITypedRecordHandler.cs ++++ /dev/null +@@ -1,21 +0,0 @@ +- +- +-using System.Threading; +-using System.Threading.Tasks; +- +-namespace AWS.Lambda.Powertools.BatchProcessing; +- +-/// +-/// The interface for strongly-typed record handling. +-/// +-/// Type of the deserialized data from the batch record. +-public interface ITypedRecordHandler +-{ +- /// +- /// Handles processing of a given batch record with strongly-typed data. +- /// +- /// The deserialized data from the record to process. +- /// The cancellation token to monitor. +- /// An awaitable with a . +- Task HandleAsync(T data, CancellationToken cancellationToken); +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ITypedRecordHandlerProvider.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ITypedRecordHandlerProvider.cs +deleted file mode 100644 +index 757a8472..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ITypedRecordHandlerProvider.cs ++++ /dev/null +@@ -1,16 +0,0 @@ +- +- +-namespace AWS.Lambda.Powertools.BatchProcessing; +- +-/// +-/// The interface for creating strongly-typed record handlers. +-/// +-/// Type of the deserialized data from the batch record. +-public interface ITypedRecordHandlerProvider +-{ +- /// +- /// Creates a typed record handler. +- /// +- /// The created typed record handler. +- ITypedRecordHandler Create(); +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ITypedRecordHandlerWithContext.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ITypedRecordHandlerWithContext.cs +deleted file mode 100644 +index 31d38e76..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ITypedRecordHandlerWithContext.cs ++++ /dev/null +@@ -1,23 +0,0 @@ +- +- +-using System.Threading; +-using System.Threading.Tasks; +-using Amazon.Lambda.Core; +- +-namespace AWS.Lambda.Powertools.BatchProcessing; +- +-/// +-/// The interface for strongly-typed record handling with Lambda context. +-/// +-/// Type of the deserialized data from the batch record. +-public interface ITypedRecordHandlerWithContext +-{ +- /// +- /// Handles processing of a given batch record with strongly-typed data and Lambda context. +- /// +- /// The deserialized data from the record to process. +- /// The Lambda context for the current invocation. +- /// The cancellation token to monitor. +- /// An awaitable with a . +- Task HandleAsync(T data, ILambdaContext context, CancellationToken cancellationToken); +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ITypedRecordHandlerWithContextProvider.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ITypedRecordHandlerWithContextProvider.cs +deleted file mode 100644 +index 07eb0e5a..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ITypedRecordHandlerWithContextProvider.cs ++++ /dev/null +@@ -1,16 +0,0 @@ +- +- +-namespace AWS.Lambda.Powertools.BatchProcessing; +- +-/// +-/// The interface for creating strongly-typed record handlers with Lambda context. +-/// +-/// Type of the deserialized data from the batch record. +-public interface ITypedRecordHandlerWithContextProvider +-{ +- /// +- /// Creates a typed record handler with context. +- /// +- /// The created typed record handler with context. +- ITypedRecordHandlerWithContext Create(); +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/AotCompatibilityHelper.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/AotCompatibilityHelper.cs +deleted file mode 100644 +index a4020432..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/AotCompatibilityHelper.cs ++++ /dev/null +@@ -1,134 +0,0 @@ +- +- +-using System; +-using System.Runtime.CompilerServices; +-using System.Text.Json; +-using System.Text.Json.Serialization; +-using AWS.Lambda.Powertools.BatchProcessing.Exceptions; +- +-namespace AWS.Lambda.Powertools.BatchProcessing.Internal; +- +-/// +-/// Helper class for AOT (Ahead-of-Time) compilation compatibility features. +-/// +-internal static class AotCompatibilityHelper +-{ +- /// +- /// Determines if the current runtime environment is AOT compiled. +- /// +- /// True if running in AOT mode, false otherwise. +- public static bool IsAotMode() +- { +- // In AOT mode, RuntimeFeature.IsDynamicCodeSupported returns false +- return !RuntimeFeature.IsDynamicCodeSupported; +- } +- +- /// +- /// Validates that the JsonSerializerContext contains type information for the specified type. +- /// +- /// The type to validate. +- /// The JsonSerializerContext to validate. +- /// Whether to throw an exception if type information is missing. +- /// True if type information is available, false otherwise. +- /// Thrown when type information is missing and throwOnMissing is true. +- public static bool ValidateTypeInContext(JsonSerializerContext context, bool throwOnMissing = true) +- { +- if (context == null) +- { +- if (throwOnMissing) +- { +- throw new AotTypeValidationException(typeof(T), "JsonSerializerContext is null. AOT compilation requires a JsonSerializerContext with type information."); +- } +- return false; +- } +- +- try +- { +- var typeInfo = context.GetTypeInfo(typeof(T)); +- if (typeInfo == null) +- { +- if (throwOnMissing) +- { +- throw new AotTypeValidationException(typeof(T), +- $"Type '{typeof(T).FullName}' is not registered in the provided JsonSerializerContext. " + +- $"Add [JsonSerializable(typeof({typeof(T).Name}))] to your JsonSerializerContext class."); +- } +- return false; +- } +- return true; +- } +- catch (NotSupportedException ex) +- { +- if (throwOnMissing) +- { +- throw new AotTypeValidationException(typeof(T), +- $"Type '{typeof(T).FullName}' is not supported by the provided JsonSerializerContext. " + +- $"Ensure the type is properly registered with [JsonSerializable(typeof({typeof(T).Name}))].", ex); +- } +- return false; +- } +- } +- +- /// +- /// Provides a fallback deserialization strategy when JsonSerializerContext is not available in AOT mode. +- /// +- /// The type to deserialize. +- /// The JSON data to deserialize. +- /// The deserialization options. +- /// The deserialized object. +- /// Thrown when AOT mode requires JsonSerializerContext but none is provided. +- +- public static T FallbackDeserialize(string data, DeserializationOptions options) +- { +- if (IsAotMode() && options?.JsonSerializerContext == null) +- { +- throw new AotCompatibilityException(typeof(T), +- "AOT compilation detected but no JsonSerializerContext provided. " + +- "For AOT compatibility, provide a JsonSerializerContext with type information using DeserializationOptions."); +- } +- +- // Use reflection-based deserialization as fallback +- return JsonSerializer.Deserialize(data, options?.JsonSerializerOptions); +- } +- +- /// +- /// Gets a user-friendly error message for AOT compatibility issues. +- /// +- /// The type that caused the issue. +- /// Whether a JsonSerializerContext was provided. +- /// A descriptive error message with guidance. +- public static string GetAotCompatibilityErrorMessage(Type targetType, bool contextProvided) +- { +- if (!contextProvided) +- { +- return $"AOT compilation requires a JsonSerializerContext for type '{targetType.FullName}'. " + +- $"Create a JsonSerializerContext with [JsonSerializable(typeof({targetType.Name}))] and provide it via DeserializationOptions."; +- } +- +- return $"The provided JsonSerializerContext does not contain type information for '{targetType.FullName}'. " + +- $"Add [JsonSerializable(typeof({targetType.Name}))] to your JsonSerializerContext class."; +- } +- +- /// +- /// Validates AOT compatibility for the given type and options. +- /// +- /// The type to validate. +- /// The deserialization options. +- /// Thrown when AOT requirements are not met. +- /// Thrown when type is not registered in JsonSerializerContext. +- public static void ValidateAotCompatibility(DeserializationOptions options) +- { +- // If we're in AOT mode and no context is provided, that's an error +- if (IsAotMode() && options?.JsonSerializerContext == null) +- { +- throw new AotCompatibilityException(typeof(T), GetAotCompatibilityErrorMessage(typeof(T), false)); +- } +- +- // If a JsonSerializerContext is provided, always validate the type is registered +- // This provides early validation regardless of runtime mode +- if (options?.JsonSerializerContext != null) +- { +- ValidateTypeInContext(options.JsonSerializerContext); +- } +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/BatchEventType.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/BatchEventType.cs +index 3f196d05..df11ade7 100644 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/BatchEventType.cs ++++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/BatchEventType.cs +@@ -1,4 +1,17 @@ +- ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ + + namespace AWS.Lambda.Powertools.BatchProcessing.Internal; + +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/BatchProcessingAspectHandler.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/BatchProcessingAspectHandler.cs +index e7c71ad8..c0037357 100644 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/BatchProcessingAspectHandler.cs ++++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/BatchProcessingAspectHandler.cs +@@ -1,4 +1,17 @@ +- ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ + + using System; + using System.Linq; +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/IBatchProcessingAspectHandler.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/IBatchProcessingAspectHandler.cs +index 2dcfed5e..c43ec06b 100644 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/IBatchProcessingAspectHandler.cs ++++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/IBatchProcessingAspectHandler.cs +@@ -1,4 +1,17 @@ +- ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ + + using System.Threading.Tasks; + +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/TypedRecordHandlerWithContextWrapperBase.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/TypedRecordHandlerWithContextWrapperBase.cs +deleted file mode 100644 +index aac7faae..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/TypedRecordHandlerWithContextWrapperBase.cs ++++ /dev/null +@@ -1,86 +0,0 @@ +-using System; +-using System.Threading; +-using System.Threading.Tasks; +-using Amazon.Lambda.Core; +-using AWS.Lambda.Powertools.BatchProcessing.Exceptions; +- +-namespace AWS.Lambda.Powertools.BatchProcessing.Internal; +- +-/// +-/// Base wrapper class that adapts ITypedRecordHandlerWithContext to IRecordHandler with common deserialization logic. +-/// +-/// The type of the record being processed. +-/// The type to deserialize the record data to. +-internal abstract class TypedRecordHandlerWithContextWrapperBase : IRecordHandler +-{ +- protected readonly ILambdaContext Context; +- protected readonly IDeserializationService DeserializationService; +- protected readonly IRecordDataExtractor RecordDataExtractor; +- protected readonly DeserializationOptions DeserializationOptions; +- +- protected TypedRecordHandlerWithContextWrapperBase( +- ILambdaContext context, +- IDeserializationService deserializationService, +- IRecordDataExtractor recordDataExtractor, +- DeserializationOptions deserializationOptions) +- { +- Context = context; // Context can be null +- DeserializationService = deserializationService ?? throw new ArgumentNullException(nameof(deserializationService)); +- RecordDataExtractor = recordDataExtractor ?? throw new ArgumentNullException(nameof(recordDataExtractor)); +- DeserializationOptions = deserializationOptions; +- } +- +- public async Task HandleAsync(TRecord record, CancellationToken cancellationToken) +- { +- try +- { +- var recordData = RecordDataExtractor.ExtractData(record); +- +- // Use TryDeserialize to check if deserialization was successful +- if (DeserializationOptions?.ErrorPolicy == DeserializationErrorPolicy.IgnoreRecord || +- DeserializationOptions?.IgnoreDeserializationErrors == true) +- { +- if (!DeserializationService.TryDeserialize(recordData, out var deserializedData, out _, DeserializationOptions)) +- { +- // Deserialization failed and we're ignoring errors, don't call the handler +- return RecordHandlerResult.None; +- } +- return await HandleTypedRecordWithContextAsync(deserializedData, Context, cancellationToken); +- } +- else +- { +- // Use regular deserialize which will throw on errors +- var deserializedData = DeserializationService.Deserialize(recordData, DeserializationOptions); +- return await HandleTypedRecordWithContextAsync(deserializedData, Context, cancellationToken); +- } +- } +- catch (DeserializationException ex) +- { +- // Handle deserialization errors based on policy +- if (DeserializationOptions?.ErrorPolicy == DeserializationErrorPolicy.IgnoreRecord) +- { +- return RecordHandlerResult.None; +- } +- +- // For FailRecord policy or default, re-throw the exception +- throw new RecordProcessingException(GetDeserializationErrorMessage(record, ex), ex); +- } +- } +- +- /// +- /// Handles the typed record with context after successful deserialization. +- /// +- /// The deserialized data. +- /// The Lambda context. +- /// The cancellation token. +- /// The result of handling the record. +- protected abstract Task HandleTypedRecordWithContextAsync(T deserializedData, ILambdaContext context, CancellationToken cancellationToken); +- +- /// +- /// Gets the error message for deserialization failures. +- /// +- /// The record that failed to deserialize. +- /// The deserialization exception. +- /// The error message. +- protected abstract string GetDeserializationErrorMessage(TRecord record, DeserializationException ex); +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/TypedRecordHandlerWrapperBase.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/TypedRecordHandlerWrapperBase.cs +deleted file mode 100644 +index f9e51e70..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/TypedRecordHandlerWrapperBase.cs ++++ /dev/null +@@ -1,81 +0,0 @@ +-using System; +-using System.Threading; +-using System.Threading.Tasks; +-using AWS.Lambda.Powertools.BatchProcessing.Exceptions; +- +-namespace AWS.Lambda.Powertools.BatchProcessing.Internal; +- +-/// +-/// Base wrapper class that adapts ITypedRecordHandler to IRecordHandler with common deserialization logic. +-/// +-/// The type of the record being processed. +-/// The type to deserialize the record data to. +-internal abstract class TypedRecordHandlerWrapperBase : IRecordHandler +-{ +- protected readonly IDeserializationService DeserializationService; +- protected readonly IRecordDataExtractor RecordDataExtractor; +- protected readonly DeserializationOptions DeserializationOptions; +- +- protected TypedRecordHandlerWrapperBase( +- IDeserializationService deserializationService, +- IRecordDataExtractor recordDataExtractor, +- DeserializationOptions deserializationOptions) +- { +- DeserializationService = deserializationService ?? throw new ArgumentNullException(nameof(deserializationService)); +- RecordDataExtractor = recordDataExtractor ?? throw new ArgumentNullException(nameof(recordDataExtractor)); +- DeserializationOptions = deserializationOptions; +- } +- +- public async Task HandleAsync(TRecord record, CancellationToken cancellationToken) +- { +- try +- { +- var recordData = RecordDataExtractor.ExtractData(record); +- +- // Use TryDeserialize to check if deserialization was successful +- if (DeserializationOptions?.ErrorPolicy == DeserializationErrorPolicy.IgnoreRecord || +- DeserializationOptions?.IgnoreDeserializationErrors == true) +- { +- if (!DeserializationService.TryDeserialize(recordData, out var deserializedData, out _, DeserializationOptions)) +- { +- // Deserialization failed and we're ignoring errors, don't call the handler +- return RecordHandlerResult.None; +- } +- return await HandleTypedRecordAsync(deserializedData, cancellationToken); +- } +- else +- { +- // Use regular deserialize which will throw on errors +- var deserializedData = DeserializationService.Deserialize(recordData, DeserializationOptions); +- return await HandleTypedRecordAsync(deserializedData, cancellationToken); +- } +- } +- catch (DeserializationException ex) +- { +- // Handle deserialization errors based on policy +- if (DeserializationOptions?.ErrorPolicy == DeserializationErrorPolicy.IgnoreRecord) +- { +- return RecordHandlerResult.None; +- } +- +- // For FailRecord policy or default, re-throw the exception +- throw new RecordProcessingException(GetDeserializationErrorMessage(record, ex), ex); +- } +- } +- +- /// +- /// Handles the typed record after successful deserialization. +- /// +- /// The deserialized data. +- /// The cancellation token. +- /// The result of handling the record. +- protected abstract Task HandleTypedRecordAsync(T deserializedData, CancellationToken cancellationToken); +- +- /// +- /// Gets the error message for deserialization failures. +- /// +- /// The record that failed to deserialize. +- /// The deserialization exception. +- /// The error message. +- protected abstract string GetDeserializationErrorMessage(TRecord record, DeserializationException ex); +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/InternalsVisibleTo.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/InternalsVisibleTo.cs +index 1fbd2680..0a7bb3e2 100644 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/InternalsVisibleTo.cs ++++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/InternalsVisibleTo.cs +@@ -1,4 +1,17 @@ +- ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ + + using System.Runtime.CompilerServices; + +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/JsonDeserializationService.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/JsonDeserializationService.cs +deleted file mode 100644 +index 99ef35a3..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/JsonDeserializationService.cs ++++ /dev/null +@@ -1,120 +0,0 @@ +- +- +-using System; +-using System.Text.Json; +-using AWS.Lambda.Powertools.BatchProcessing.Exceptions; +-using AWS.Lambda.Powertools.BatchProcessing.Internal; +- +-namespace AWS.Lambda.Powertools.BatchProcessing; +- +-/// +-/// JSON-based implementation of IDeserializationService using System.Text.Json. +-/// +-public class JsonDeserializationService : IDeserializationService +-{ +- /// +- /// Gets the singleton instance of JsonDeserializationService. +- /// +- public static JsonDeserializationService Instance { get; } = new(); +- +- /// +- /// Initializes a new instance of the JsonDeserializationService class. +- /// +- public JsonDeserializationService() +- { +- } +- +- /// +- /// Performs deserialization with AOT compatibility validation and fallback behavior. +- /// +- /// The type to deserialize to. +- /// The JSON data to deserialize. +- /// The deserialization options. +- /// The deserialized object. +- +- private static T DeserializeWithFallback(string data, DeserializationOptions options) +- { +- // Check if we're in AOT mode and provide appropriate guidance +- if (AotCompatibilityHelper.IsAotMode()) +- { +- throw new AotCompatibilityException(typeof(T), +- AotCompatibilityHelper.GetAotCompatibilityErrorMessage(typeof(T), false)); +- } +- +- // Use reflection-based deserialization as fallback for non-AOT scenarios +- return JsonSerializer.Deserialize(data, options?.JsonSerializerOptions); +- } +- +- /// +- public T Deserialize(string data, DeserializationOptions options = null) +- { +- if (string.IsNullOrWhiteSpace(data)) +- { +- throw new DeserializationException("Data cannot be null or empty.", new ArgumentException("Data cannot be null or empty.", nameof(data))); +- } +- +- try +- { +- if (options?.JsonSerializerContext != null) +- { +- // Validate AOT compatibility when JsonSerializerContext is provided +- AotCompatibilityHelper.ValidateTypeInContext(options.JsonSerializerContext); +- return (T)JsonSerializer.Deserialize(data, typeof(T), options.JsonSerializerContext); +- } +- +- // Use fallback deserialization with AOT validation +- return DeserializeWithFallback(data, options); +- } +- catch (Exception ex) when (ex is JsonException || ex is NotSupportedException || ex is ArgumentException) +- { +- if (options?.IgnoreDeserializationErrors == true || options?.ErrorPolicy == DeserializationErrorPolicy.IgnoreRecord) +- { +- return default(T); +- } +- +- throw new DeserializationException(data, typeof(T), ex); +- } +- } +- +- /// +- public bool TryDeserialize(string data, out T result, DeserializationOptions options = null) +- { +- return TryDeserialize(data, out result, out _, options); +- } +- +- /// +- public bool TryDeserialize(string data, out T result, out Exception exception, DeserializationOptions options = null) +- { +- result = default(T); +- exception = null; +- +- if (string.IsNullOrWhiteSpace(data)) +- { +- exception = new ArgumentException("Data cannot be null or empty.", nameof(data)); +- return false; +- } +- +- try +- { +- if (options?.JsonSerializerContext != null) +- { +- // Validate AOT compatibility when JsonSerializerContext is provided +- AotCompatibilityHelper.ValidateTypeInContext(options.JsonSerializerContext); +- result = (T)JsonSerializer.Deserialize(data, typeof(T), options.JsonSerializerContext); +- } +- else +- { +- // Use fallback deserialization with AOT validation +- result = DeserializeWithFallback(data, options); +- } +- +- return true; +- } +- catch (Exception ex) when (ex is JsonException || ex is NotSupportedException || ex is ArgumentException || +- ex is AotCompatibilityException || ex is AotTypeValidationException) +- { +- exception = ex; +- return false; +- } +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Kinesis/IKinesisEventBatchProcessor.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Kinesis/IKinesisEventBatchProcessor.cs +index d911e6e3..097a1fca 100644 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Kinesis/IKinesisEventBatchProcessor.cs ++++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Kinesis/IKinesisEventBatchProcessor.cs +@@ -1,4 +1,19 @@ +-using Amazon.Lambda.KinesisEvents; ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ ++using Amazon.Lambda.KinesisEvents; + + namespace AWS.Lambda.Powertools.BatchProcessing.Kinesis; + +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Kinesis/IKinesisEventRecordHandler.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Kinesis/IKinesisEventRecordHandler.cs +index 37def333..5a21afc4 100644 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Kinesis/IKinesisEventRecordHandler.cs ++++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Kinesis/IKinesisEventRecordHandler.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using Amazon.Lambda.KinesisEvents; + + namespace AWS.Lambda.Powertools.BatchProcessing.Kinesis; +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Kinesis/KinesisEventBatchProcessor.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Kinesis/KinesisEventBatchProcessor.cs +index 6c332308..1ea01041 100644 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Kinesis/KinesisEventBatchProcessor.cs ++++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Kinesis/KinesisEventBatchProcessor.cs +@@ -1,4 +1,19 @@ +-using System.Collections.Generic; ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ ++using System.Collections.Generic; + using Amazon.Lambda.KinesisEvents; + using AWS.Lambda.Powertools.Common; + +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Kinesis/KinesisRecordDataExtractor.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Kinesis/KinesisRecordDataExtractor.cs +deleted file mode 100644 +index 1ca62a3e..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Kinesis/KinesisRecordDataExtractor.cs ++++ /dev/null +@@ -1,45 +0,0 @@ +- +- +-using System; +-using System.IO; +-using System.Text; +-using Amazon.Lambda.KinesisEvents; +- +-namespace AWS.Lambda.Powertools.BatchProcessing.Kinesis; +- +-/// +-/// Extracts data from Kinesis event records for deserialization. +-/// +-public class KinesisRecordDataExtractor : IRecordDataExtractor +-{ +- /// +- /// The singleton instance of the Kinesis record data extractor. +- /// +- public static readonly KinesisRecordDataExtractor Instance = new(); +- +- /// +- /// Extracts the data from a Kinesis event record by reading from the MemoryStream. +- /// +- /// The Kinesis event record. +- /// The decoded data string. +- public string ExtractData(KinesisEvent.KinesisEventRecord record) +- { +- if (record?.Kinesis?.Data == null) +- return string.Empty; +- +- try +- { +- // Reset the stream position to the beginning +- record.Kinesis.Data.Position = 0; +- +- // Read the data from the MemoryStream +- using var reader = new StreamReader(record.Kinesis.Data, Encoding.UTF8, leaveOpen: true); +- return reader.ReadToEnd(); +- } +- catch (Exception) +- { +- // If reading fails, return empty string +- return string.Empty; +- } +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Kinesis/TypedKinesisEventBatchProcessor.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Kinesis/TypedKinesisEventBatchProcessor.cs +deleted file mode 100644 +index 0311fa7c..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Kinesis/TypedKinesisEventBatchProcessor.cs ++++ /dev/null +@@ -1,216 +0,0 @@ +- +- +-using System; +-using System.Threading; +-using System.Threading.Tasks; +-using Amazon.Lambda.Core; +-using Amazon.Lambda.KinesisEvents; +-using AWS.Lambda.Powertools.BatchProcessing.Exceptions; +-using AWS.Lambda.Powertools.BatchProcessing.Internal; +-using AWS.Lambda.Powertools.Common; +- +-namespace AWS.Lambda.Powertools.BatchProcessing.Kinesis; +- +-/// +-/// Typed batch processor for Kinesis events that supports automatic deserialization of record data. +-/// +-public class TypedKinesisEventBatchProcessor : KinesisEventBatchProcessor, ITypedBatchProcessor +-{ +- private readonly IDeserializationService _deserializationService; +- private readonly IRecordDataExtractor _recordDataExtractor; +- +- +- +- /// +- /// Initializes a new instance of the TypedKinesisEventBatchProcessor class. +- /// +- /// The Powertools configurations. +- /// The deserialization service. If null, uses JsonDeserializationService.Instance. +- /// The record data extractor. If null, uses KinesisRecordDataExtractor.Instance. +- public TypedKinesisEventBatchProcessor( +- IPowertoolsConfigurations powertoolsConfigurations, +- IDeserializationService deserializationService = null, +- IRecordDataExtractor recordDataExtractor = null) +- : base(powertoolsConfigurations) +- { +- _deserializationService = deserializationService ?? JsonDeserializationService.Instance; +- _recordDataExtractor = recordDataExtractor ?? KinesisRecordDataExtractor.Instance; +- } +- +- /// +- /// Default constructor for when consumers create a custom typed batch processor. +- /// +- protected TypedKinesisEventBatchProcessor() : this(PowertoolsConfigurations.Instance) +- { +- } +- +- /// +- public async Task> ProcessAsync( +- KinesisEvent @event, +- ITypedRecordHandler recordHandler) +- { +- return await ProcessAsync(@event, recordHandler, null, CancellationToken.None); +- } +- +- /// +- public async Task> ProcessAsync( +- KinesisEvent @event, +- ITypedRecordHandler recordHandler, +- DeserializationOptions deserializationOptions) +- { +- return await ProcessAsync(@event, recordHandler, deserializationOptions, CancellationToken.None); +- } +- +- /// +- public async Task> ProcessAsync( +- KinesisEvent @event, +- ITypedRecordHandler recordHandler, +- CancellationToken cancellationToken) +- { +- return await ProcessAsync(@event, recordHandler, null, cancellationToken); +- } +- +- /// +- public async Task> ProcessAsync( +- KinesisEvent @event, +- ITypedRecordHandler recordHandler, +- DeserializationOptions deserializationOptions, +- CancellationToken cancellationToken) +- { +- var processingOptions = new ProcessingOptions +- { +- CancellationToken = cancellationToken +- }; +- return await ProcessAsync(@event, recordHandler, deserializationOptions, processingOptions); +- } +- +- /// +- public async Task> ProcessAsync( +- KinesisEvent @event, +- ITypedRecordHandler recordHandler, +- DeserializationOptions deserializationOptions, +- ProcessingOptions processingOptions) +- { +- // Validate AOT compatibility before processing +- AotCompatibilityHelper.ValidateAotCompatibility(deserializationOptions); +- +- var wrappedHandler = new TypedRecordHandlerWrapper(recordHandler, _deserializationService, _recordDataExtractor, deserializationOptions); +- return await ProcessAsync(@event, wrappedHandler, processingOptions); +- } +- +- /// +- public async Task> ProcessAsync( +- KinesisEvent @event, +- ITypedRecordHandlerWithContext recordHandler, +- ILambdaContext context) +- { +- return await ProcessAsync(@event, recordHandler, context, null, CancellationToken.None); +- } +- +- /// +- public async Task> ProcessAsync( +- KinesisEvent @event, +- ITypedRecordHandlerWithContext recordHandler, +- ILambdaContext context, +- DeserializationOptions deserializationOptions) +- { +- return await ProcessAsync(@event, recordHandler, context, deserializationOptions, CancellationToken.None); +- } +- +- /// +- public async Task> ProcessAsync( +- KinesisEvent @event, +- ITypedRecordHandlerWithContext recordHandler, +- ILambdaContext context, +- CancellationToken cancellationToken) +- { +- return await ProcessAsync(@event, recordHandler, context, null, cancellationToken); +- } +- +- /// +- public async Task> ProcessAsync( +- KinesisEvent @event, +- ITypedRecordHandlerWithContext recordHandler, +- ILambdaContext context, +- DeserializationOptions deserializationOptions, +- CancellationToken cancellationToken) +- { +- var processingOptions = new ProcessingOptions +- { +- CancellationToken = cancellationToken +- }; +- return await ProcessAsync(@event, recordHandler, context, deserializationOptions, processingOptions); +- } +- +- /// +- public async Task> ProcessAsync( +- KinesisEvent @event, +- ITypedRecordHandlerWithContext recordHandler, +- ILambdaContext context, +- DeserializationOptions deserializationOptions, +- ProcessingOptions processingOptions) +- { +- // Validate AOT compatibility before processing +- AotCompatibilityHelper.ValidateAotCompatibility(deserializationOptions); +- +- var wrappedHandler = new TypedRecordHandlerWithContextWrapper(recordHandler, context, _deserializationService, _recordDataExtractor, deserializationOptions); +- return await ProcessAsync(@event, wrappedHandler, processingOptions); +- } +- +- /// +- /// Wrapper class that adapts ITypedRecordHandler to IRecordHandler. +- /// +- private sealed class TypedRecordHandlerWrapper : TypedRecordHandlerWrapperBase +- { +- private readonly ITypedRecordHandler _typedHandler; +- +- public TypedRecordHandlerWrapper( +- ITypedRecordHandler typedHandler, +- IDeserializationService deserializationService, +- IRecordDataExtractor recordDataExtractor, +- DeserializationOptions deserializationOptions) +- : base(deserializationService, recordDataExtractor, deserializationOptions) +- { +- _typedHandler = typedHandler ?? throw new ArgumentNullException(nameof(typedHandler)); +- } +- +- protected override async Task HandleTypedRecordAsync(T deserializedData, CancellationToken cancellationToken) +- { +- return await _typedHandler.HandleAsync(deserializedData, cancellationToken); +- } +- +- protected override string GetDeserializationErrorMessage(KinesisEvent.KinesisEventRecord record, DeserializationException ex) +- { +- return $"Failed to deserialize Kinesis record '{record.Kinesis.SequenceNumber}' to type '{typeof(T).Name}'. See inner exception for details."; +- } +- } +- +- /// +- /// Wrapper class that adapts ITypedRecordHandlerWithContext to IRecordHandler. +- /// +- private sealed class TypedRecordHandlerWithContextWrapper : TypedRecordHandlerWithContextWrapperBase +- { +- private readonly ITypedRecordHandlerWithContext _typedHandler; +- +- public TypedRecordHandlerWithContextWrapper( +- ITypedRecordHandlerWithContext typedHandler, +- ILambdaContext context, +- IDeserializationService deserializationService, +- IRecordDataExtractor recordDataExtractor, +- DeserializationOptions deserializationOptions) +- : base(context, deserializationService, recordDataExtractor, deserializationOptions) +- { +- _typedHandler = typedHandler ?? throw new ArgumentNullException(nameof(typedHandler)); +- } +- +- protected override async Task HandleTypedRecordWithContextAsync(T deserializedData, ILambdaContext context, CancellationToken cancellationToken) +- { +- return await _typedHandler.HandleAsync(deserializedData, context, cancellationToken); +- } +- +- protected override string GetDeserializationErrorMessage(KinesisEvent.KinesisEventRecord record, DeserializationException ex) +- { +- return $"Failed to deserialize Kinesis record '{record.Kinesis.SequenceNumber}' to type '{typeof(T).Name}'. See inner exception for details."; +- } +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ProcessingOptions.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ProcessingOptions.cs +index 41049138..8fc7701f 100644 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ProcessingOptions.cs ++++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ProcessingOptions.cs +@@ -1,4 +1,17 @@ +- ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ + + using System; + using System.Threading; +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ProcessingResult.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ProcessingResult.cs +index 8deda4d7..088111df 100644 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ProcessingResult.cs ++++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ProcessingResult.cs +@@ -1,4 +1,17 @@ +- ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ + + using System.Collections.Generic; + +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/RecordFailure.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/RecordFailure.cs +index a6bf2051..60cf1bcd 100644 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/RecordFailure.cs ++++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/RecordFailure.cs +@@ -1,4 +1,17 @@ +- ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ + + using System; + +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/RecordHandler.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/RecordHandler.cs +index 7768916f..e3df2512 100644 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/RecordHandler.cs ++++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/RecordHandler.cs +@@ -1,4 +1,17 @@ +- ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ + + using System; + using System.Threading; +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/RecordHandlerResult.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/RecordHandlerResult.cs +index 84212e24..01419d89 100644 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/RecordHandlerResult.cs ++++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/RecordHandlerResult.cs +@@ -1,4 +1,17 @@ +- ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ + + namespace AWS.Lambda.Powertools.BatchProcessing; + +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/RecordSuccess.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/RecordSuccess.cs +index b22635c0..d244ae72 100644 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/RecordSuccess.cs ++++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/RecordSuccess.cs +@@ -1,4 +1,17 @@ +- ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ + + namespace AWS.Lambda.Powertools.BatchProcessing; + +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Sqs/ISqsBatchProcessor.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Sqs/ISqsBatchProcessor.cs +index 8fb6021b..72e933af 100644 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Sqs/ISqsBatchProcessor.cs ++++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Sqs/ISqsBatchProcessor.cs +@@ -1,4 +1,19 @@ +-using Amazon.Lambda.SQSEvents; ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ ++using Amazon.Lambda.SQSEvents; + + namespace AWS.Lambda.Powertools.BatchProcessing.Sqs; + +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Sqs/ISqsRecordHandler.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Sqs/ISqsRecordHandler.cs +index 232c7ff8..67213a15 100644 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Sqs/ISqsRecordHandler.cs ++++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Sqs/ISqsRecordHandler.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using Amazon.Lambda.SQSEvents; + + namespace AWS.Lambda.Powertools.BatchProcessing.Sqs; +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Sqs/SqsBatchProcessor.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Sqs/SqsBatchProcessor.cs +index f7741e52..bf191f9c 100644 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Sqs/SqsBatchProcessor.cs ++++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Sqs/SqsBatchProcessor.cs +@@ -1,4 +1,19 @@ +-using System; ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ ++using System; + using System.Collections.Generic; + using System.Linq; + using Amazon.Lambda.SQSEvents; +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Sqs/SqsRecordDataExtractor.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Sqs/SqsRecordDataExtractor.cs +deleted file mode 100644 +index 9fc80b44..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Sqs/SqsRecordDataExtractor.cs ++++ /dev/null +@@ -1,26 +0,0 @@ +- +- +-using Amazon.Lambda.SQSEvents; +- +-namespace AWS.Lambda.Powertools.BatchProcessing.Sqs; +- +-/// +-/// Extracts data from SQS message records for deserialization. +-/// +-public class SqsRecordDataExtractor : IRecordDataExtractor +-{ +- /// +- /// The singleton instance of the SQS record data extractor. +- /// +- public static readonly SqsRecordDataExtractor Instance = new(); +- +- /// +- /// Extracts the message body from an SQS message record. +- /// +- /// The SQS message record. +- /// The message body string. +- public string ExtractData(SQSEvent.SQSMessage record) +- { +- return record?.Body ?? string.Empty; +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Sqs/TypedSqsBatchProcessor.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Sqs/TypedSqsBatchProcessor.cs +deleted file mode 100644 +index a723f493..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Sqs/TypedSqsBatchProcessor.cs ++++ /dev/null +@@ -1,220 +0,0 @@ +- +- +-using System; +-using System.Threading; +-using System.Threading.Tasks; +-using Amazon.Lambda.Core; +-using Amazon.Lambda.SQSEvents; +-using AWS.Lambda.Powertools.BatchProcessing.Exceptions; +-using AWS.Lambda.Powertools.BatchProcessing.Internal; +-using AWS.Lambda.Powertools.Common; +- +-namespace AWS.Lambda.Powertools.BatchProcessing.Sqs; +- +-/// +-/// Typed batch processor for SQS events that supports automatic deserialization of message bodies. +-/// +-public class TypedSqsBatchProcessor : SqsBatchProcessor, ITypedBatchProcessor +-{ +- private readonly IDeserializationService _deserializationService; +- private readonly IRecordDataExtractor _recordDataExtractor; +- +- +- +- /// +- /// Initializes a new instance of the TypedSqsBatchProcessor class. +- /// +- /// The Powertools configurations. +- /// The deserialization service. If null, uses JsonDeserializationService.Instance. +- /// The record data extractor. If null, uses SqsRecordDataExtractor.Instance. +- public TypedSqsBatchProcessor( +- IPowertoolsConfigurations powertoolsConfigurations, +- IDeserializationService deserializationService = null, +- IRecordDataExtractor recordDataExtractor = null) +- : base(powertoolsConfigurations) +- { +- _deserializationService = deserializationService ?? JsonDeserializationService.Instance; +- _recordDataExtractor = recordDataExtractor ?? SqsRecordDataExtractor.Instance; +- } +- +- /// +- /// Default constructor for when consumers create a custom typed batch processor. +- /// +- protected TypedSqsBatchProcessor() : this(PowertoolsConfigurations.Instance) +- { +- } +- +- /// +- public async Task> ProcessAsync( +- SQSEvent @event, +- ITypedRecordHandler recordHandler) +- { +- return await ProcessAsync(@event, recordHandler, null, CancellationToken.None); +- } +- +- /// +- public async Task> ProcessAsync( +- SQSEvent @event, +- ITypedRecordHandler recordHandler, +- DeserializationOptions deserializationOptions) +- { +- return await ProcessAsync(@event, recordHandler, deserializationOptions, CancellationToken.None); +- } +- +- /// +- public async Task> ProcessAsync( +- SQSEvent @event, +- ITypedRecordHandler recordHandler, +- CancellationToken cancellationToken) +- { +- return await ProcessAsync(@event, recordHandler, null, cancellationToken); +- } +- +- /// +- public async Task> ProcessAsync( +- SQSEvent @event, +- ITypedRecordHandler recordHandler, +- DeserializationOptions deserializationOptions, +- CancellationToken cancellationToken) +- { +- var processingOptions = new ProcessingOptions +- { +- CancellationToken = cancellationToken +- }; +- return await ProcessAsync(@event, recordHandler, deserializationOptions, processingOptions); +- } +- +- /// +- public async Task> ProcessAsync( +- SQSEvent @event, +- ITypedRecordHandler recordHandler, +- DeserializationOptions deserializationOptions, +- ProcessingOptions processingOptions) +- { +- // Validate AOT compatibility before processing +- AotCompatibilityHelper.ValidateAotCompatibility(deserializationOptions); +- +- var wrappedHandler = new TypedRecordHandlerWrapper(recordHandler, _deserializationService, _recordDataExtractor, deserializationOptions); +- return await ProcessAsync(@event, wrappedHandler, processingOptions); +- } +- +- /// +- public async Task> ProcessAsync( +- SQSEvent @event, +- ITypedRecordHandlerWithContext recordHandler, +- ILambdaContext context) +- { +- return await ProcessAsync(@event, recordHandler, context, null, CancellationToken.None); +- } +- +- /// +- public async Task> ProcessAsync( +- SQSEvent @event, +- ITypedRecordHandlerWithContext recordHandler, +- ILambdaContext context, +- DeserializationOptions deserializationOptions) +- { +- return await ProcessAsync(@event, recordHandler, context, deserializationOptions, CancellationToken.None); +- } +- +- /// +- public async Task> ProcessAsync( +- SQSEvent @event, +- ITypedRecordHandlerWithContext recordHandler, +- ILambdaContext context, +- CancellationToken cancellationToken) +- { +- return await ProcessAsync(@event, recordHandler, context, null, cancellationToken); +- } +- +- /// +- public async Task> ProcessAsync( +- SQSEvent @event, +- ITypedRecordHandlerWithContext recordHandler, +- ILambdaContext context, +- DeserializationOptions deserializationOptions, +- CancellationToken cancellationToken) +- { +- var processingOptions = new ProcessingOptions +- { +- CancellationToken = cancellationToken +- }; +- return await ProcessAsync(@event, recordHandler, context, deserializationOptions, processingOptions); +- } +- +- /// +- public async Task> ProcessAsync( +- SQSEvent @event, +- ITypedRecordHandlerWithContext recordHandler, +- ILambdaContext context, +- DeserializationOptions deserializationOptions, +- ProcessingOptions processingOptions) +- { +- // Validate AOT compatibility before processing +- AotCompatibilityHelper.ValidateAotCompatibility(deserializationOptions); +- +- var wrappedHandler = new TypedRecordHandlerWithContextWrapper(recordHandler, context, _deserializationService, _recordDataExtractor, deserializationOptions); +- return await ProcessAsync(@event, wrappedHandler, processingOptions); +- } +- +- +- +- /// +- /// Wrapper class that adapts ITypedRecordHandler to IRecordHandler. +- /// +- private sealed class TypedRecordHandlerWrapper : TypedRecordHandlerWrapperBase +- { +- private readonly ITypedRecordHandler _typedHandler; +- +- public TypedRecordHandlerWrapper( +- ITypedRecordHandler typedHandler, +- IDeserializationService deserializationService, +- IRecordDataExtractor recordDataExtractor, +- DeserializationOptions deserializationOptions) +- : base(deserializationService, recordDataExtractor, deserializationOptions) +- { +- _typedHandler = typedHandler ?? throw new ArgumentNullException(nameof(typedHandler)); +- } +- +- protected override async Task HandleTypedRecordAsync(T deserializedData, CancellationToken cancellationToken) +- { +- return await _typedHandler.HandleAsync(deserializedData, cancellationToken); +- } +- +- protected override string GetDeserializationErrorMessage(SQSEvent.SQSMessage record, DeserializationException ex) +- { +- return $"Failed to deserialize SQS message '{record.MessageId}' to type '{typeof(T).Name}'. See inner exception for details."; +- } +- } +- +- +- +- /// +- /// Wrapper class that adapts ITypedRecordHandlerWithContext to IRecordHandler. +- /// +- private sealed class TypedRecordHandlerWithContextWrapper : TypedRecordHandlerWithContextWrapperBase +- { +- private readonly ITypedRecordHandlerWithContext _typedHandler; +- +- public TypedRecordHandlerWithContextWrapper( +- ITypedRecordHandlerWithContext typedHandler, +- ILambdaContext context, +- IDeserializationService deserializationService, +- IRecordDataExtractor recordDataExtractor, +- DeserializationOptions deserializationOptions) +- : base(context, deserializationService, recordDataExtractor, deserializationOptions) +- { +- _typedHandler = typedHandler ?? throw new ArgumentNullException(nameof(typedHandler)); +- } +- +- protected override async Task HandleTypedRecordWithContextAsync(T deserializedData, ILambdaContext context, CancellationToken cancellationToken) +- { +- return await _typedHandler.HandleAsync(deserializedData, context, cancellationToken); +- } +- +- protected override string GetDeserializationErrorMessage(SQSEvent.SQSMessage record, DeserializationException ex) +- { +- return $"Failed to deserialize SQS message '{record.MessageId}' to type '{typeof(T).Name}'. See inner exception for details."; +- } +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Aspects/UniversalWrapperAspect.cs b/libraries/src/AWS.Lambda.Powertools.Common/Aspects/UniversalWrapperAspect.cs +index 0656a8bd..c4b01468 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Common/Aspects/UniversalWrapperAspect.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Common/Aspects/UniversalWrapperAspect.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; +diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/ConsoleWrapper.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/ConsoleWrapper.cs +deleted file mode 100644 +index 4a124d94..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Common/Core/ConsoleWrapper.cs ++++ /dev/null +@@ -1,160 +0,0 @@ +-using System; +-using System.IO; +- +-namespace AWS.Lambda.Powertools.Common; +- +-/// +-public class ConsoleWrapper : IConsoleWrapper +-{ +- private static bool _override; +- private static TextWriter _testOutputStream; +- private static bool _inTestMode = false; +- +- /// +- public void WriteLine(string message) +- { +- if (_inTestMode && _testOutputStream != null) +- { +- _testOutputStream.WriteLine(message); +- } +- else +- { +- EnsureConsoleOutput(); +- Console.WriteLine(message); +- } +- } +- +- /// +- public void Debug(string message) +- { +- if (_inTestMode && _testOutputStream != null) +- { +- _testOutputStream.WriteLine(message); +- } +- else +- { +- EnsureConsoleOutput(); +- System.Diagnostics.Debug.WriteLine(message); +- } +- } +- +- /// +- public void Error(string message) +- { +- if (_inTestMode && _testOutputStream != null) +- { +- _testOutputStream.WriteLine(message); +- } +- else +- { +- if (!_override) +- { +- var errorOutput = new StreamWriter(Console.OpenStandardError()); +- errorOutput.AutoFlush = true; +- Console.SetError(errorOutput); +- } +- Console.Error.WriteLine(message); +- } +- } +- +- /// +- /// Set the ConsoleWrapper to use a different TextWriter +- /// This is useful for unit tests where you want to capture the output +- /// +- public static void SetOut(TextWriter consoleOut) +- { +- _testOutputStream = consoleOut; +- _inTestMode = true; +- _override = true; +- Console.SetOut(consoleOut); +- } +- +- private static void EnsureConsoleOutput() +- { +- // Check if we need to override console output for Lambda environment +- if (ShouldOverrideConsole()) +- { +- OverrideLambdaLogger(); +- } +- } +- +- private static bool ShouldOverrideConsole() +- { +- // Don't override if we're in test mode +- if (_inTestMode) return false; +- +- // Always override in Lambda environment to prevent Lambda's log wrapping +- var isLambda = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("AWS_LAMBDA_FUNCTION_NAME")); +- +- return isLambda && (!_override || HasLambdaReInterceptedConsole()); +- } +- +- internal static bool HasLambdaReInterceptedConsole() +- { +- return HasLambdaReInterceptedConsole(() => Console.Out); +- } +- +- internal static bool HasLambdaReInterceptedConsole(Func consoleOutAccessor) +- { +- // Lambda might re-intercept console between init and handler execution +- try +- { +- var currentOut = consoleOutAccessor(); +- // Check if current output stream looks like it might be Lambda's wrapper +- var typeName = currentOut.GetType().FullName ?? ""; +- return typeName.Contains("Lambda") || typeName == "System.IO.TextWriter+SyncTextWriter"; +- } +- catch +- { +- return true; // Assume re-interception if we can't determine +- } +- } +- +- internal static void OverrideLambdaLogger() +- { +- OverrideLambdaLogger(() => Console.OpenStandardOutput()); +- } +- +- internal static void OverrideLambdaLogger(Func standardOutputOpener) +- { +- try +- { +- // Force override of LambdaLogger +- var standardOutput = new StreamWriter(standardOutputOpener()) +- { +- AutoFlush = true +- }; +- Console.SetOut(standardOutput); +- _override = true; +- } +- catch (Exception) +- { +- // Log the failure but don't throw - degraded functionality is better than crash +- _override = false; +- } +- } +- +- internal static void WriteLine(string logLevel, string message) +- { +- Console.WriteLine($"{DateTime.UtcNow:yyyy-MM-ddTHH:mm:ss.fffZ}\t{logLevel}\t{message}"); +- } +- +- /// +- /// Reset the ConsoleWrapper to its original state +- /// +- public static void ResetForTest() +- { +- _override = false; +- _inTestMode = false; +- _testOutputStream = null; +- } +- +- /// +- /// Clear the output reset flag +- /// +- public static void ClearOutputResetFlag() +- { +- // This method is kept for backward compatibility but no longer needed +- // since we removed the _outputResetPerformed flag +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/Constants.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/Constants.cs +index 456a5092..912196da 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Common/Core/Constants.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Common/Core/Constants.cs +@@ -20,14 +20,6 @@ namespace AWS.Lambda.Powertools.Common; + /// + internal static class Constants + { +- /// +- /// Constant for AWS_LAMBDA_INITIALIZATION_TYPE environment variable +- /// This is used to determine if the Lambda function is running in provisioned concurrency mode +- /// or not. If the value is "provisioned-concurrency", it indicates that the function is running in provisioned +- /// concurrency mode. Otherwise, it is running in standard mode. +- /// +- internal const string AWSInitializationTypeEnv = "AWS_LAMBDA_INITIALIZATION_TYPE"; +- + /// + /// Constant for POWERTOOLS_SERVICE_NAME environment variable + /// +@@ -138,9 +130,4 @@ internal static class Constants + /// Constant for POWERTOOLS_BATCH_THROW_ON_FULL_BATCH_FAILURE environment variable + /// + internal const string BatchThrowOnFullBatchFailureEnv = "POWERTOOLS_BATCH_THROW_ON_FULL_BATCH_FAILURE"; +- +- /// +- /// Constant for POWERTOOLS_METRICS_DISABLED environment variable +- /// +- internal const string PowertoolsMetricsDisabledEnv = "POWERTOOLS_METRICS_DISABLED"; + } +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/IConsoleWrapper.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/IConsoleWrapper.cs +deleted file mode 100644 +index 9c4f1db1..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Common/Core/IConsoleWrapper.cs ++++ /dev/null +@@ -1,25 +0,0 @@ +-namespace AWS.Lambda.Powertools.Common; +- +-/// +-/// Wrapper for console operations to facilitate testing by abstracting system console interactions. +-/// +-public interface IConsoleWrapper +-{ +- /// +- /// Writes the specified message followed by a line terminator to the standard output stream. +- /// +- /// The message to write. +- void WriteLine(string message); +- +- /// +- /// Writes a debug message to the trace listeners in the Debug.Listeners collection. +- /// +- /// The debug message to write. +- void Debug(string message); +- +- /// +- /// Writes the specified error message followed by a line terminator to the standard error stream. +- /// +- /// The error message to write. +- void Error(string message); +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/IPowertoolsConfigurations.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/IPowertoolsConfigurations.cs +index 755d33ef..ff2c5664 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Common/Core/IPowertoolsConfigurations.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Common/Core/IPowertoolsConfigurations.cs +@@ -155,27 +155,11 @@ public interface IPowertoolsConfigurations + /// Gets the maximum degree of parallelism to apply during batch processing. + /// + /// Defaults to 1 (no parallelism). Specify -1 to automatically use the value of ProcessorCount. +- int BatchProcessingMaxDegreeOfParallelism { get; } +- ++ int BatchProcessingMaxDegreeOfParallelism { get; } ++ + /// + /// Gets a value indicating whether Batch processing will throw an exception on full batch failure. + /// + /// Defaults to true + bool BatchThrowOnFullBatchFailureEnabled { get; } +- +- /// +- /// Gets a value indicating whether Metrics are disabled. +- /// +- bool MetricsDisabled { get; } +- +- /// +- /// Indicates if the current execution is a cold start. +- /// +- bool IsColdStart { get; } +- +- /// +- /// AWS Lambda initialization type. +- /// This is set to "on-demand" for on-demand Lambda functions and "provisioned-concurrency" for provisioned concurrency. +- /// +- string AwsInitializationType { get; } + } +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/IPowertoolsEnvironment.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/IPowertoolsEnvironment.cs +index 6f57aabb..059cfb7e 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Common/Core/IPowertoolsEnvironment.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Common/Core/IPowertoolsEnvironment.cs +@@ -34,10 +34,4 @@ public interface IPowertoolsEnvironment + /// + /// Assembly Version in the Major.Minor.Build format + string GetAssemblyVersion(T type); +- +- /// +- /// Sets the execution Environment Variable (AWS_EXECUTION_ENV) +- /// +- /// +- void SetExecutionEnvironment(T type); + } +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/ISystemWrapper.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/ISystemWrapper.cs +new file mode 100644 +index 00000000..8a035984 +--- /dev/null ++++ b/libraries/src/AWS.Lambda.Powertools.Common/Core/ISystemWrapper.cs +@@ -0,0 +1,73 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ ++using System.IO; ++ ++namespace AWS.Lambda.Powertools.Common; ++ ++/// ++/// Interface ISystemWrapper ++/// ++public interface ISystemWrapper ++{ ++ /// ++ /// Gets the environment variable. ++ /// ++ /// The variable. ++ /// System.String. ++ string GetEnvironmentVariable(string variable); ++ ++ /// ++ /// Logs the specified value. ++ /// ++ /// The value. ++ void Log(string value); ++ ++ /// ++ /// Logs the line. ++ /// ++ /// The value. ++ void LogLine(string value); ++ ++ /// ++ /// Gets random number ++ /// ++ /// System.Double. ++ double GetRandom(); ++ ++ /// ++ /// Sets the environment variable. ++ /// ++ /// The variable. ++ /// ++ void SetEnvironmentVariable(string variable, string value); ++ ++ /// ++ /// Sets the execution Environment Variable (AWS_EXECUTION_ENV) ++ /// ++ /// ++ void SetExecutionEnvironment(T type); ++ ++ /// ++ /// Sets console output ++ /// Useful for testing and checking the console output ++ /// ++ /// var consoleOut = new StringWriter(); ++ /// SystemWrapper.Instance.SetOut(consoleOut); ++ /// ++ /// ++ /// The TextWriter instance where to write to ++ void SetOut(TextWriter writeTo); ++} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/LambdaLifecycleTracker.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/LambdaLifecycleTracker.cs +deleted file mode 100644 +index c802bd21..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Common/Core/LambdaLifecycleTracker.cs ++++ /dev/null +@@ -1,66 +0,0 @@ +-using System; +-using System.Threading; +- +-namespace AWS.Lambda.Powertools.Common.Core; +- +-/// +-/// Tracks Lambda lifecycle state including cold starts +-/// +-internal static class LambdaLifecycleTracker +-{ +- // Static flag that's true only for the first Lambda container initialization +- private static bool _isFirstContainer = true; +- +- // Store the cold start state for the current invocation +- private static readonly AsyncLocal CurrentInvocationColdStart = new AsyncLocal(); +- +- private static string _lambdaInitType; +- private static string LambdaInitType => _lambdaInitType ?? Environment.GetEnvironmentVariable(Constants.AWSInitializationTypeEnv); +- +- /// +- /// Returns true if the current Lambda invocation is a cold start +- /// +- public static bool IsColdStart +- { +- get +- { +- if(LambdaInitType == "provisioned-concurrency") +- { +- // If the Lambda is provisioned concurrency, it is not a cold start +- return false; +- } +- +- // Initialize the cold start state for this invocation if not already set +- if (!CurrentInvocationColdStart.Value.HasValue) +- { +- // Capture the container's cold start state for this entire invocation +- CurrentInvocationColdStart.Value = _isFirstContainer; +- +- // After detecting the first invocation, mark future ones as warm +- if (_isFirstContainer) +- { +- _isFirstContainer = false; +- } +- } +- +- // Return the cold start state for this invocation (cannot change during the invocation) +- return CurrentInvocationColdStart.Value ?? false; +- } +- } +- +- +- +- /// +- /// Resets the cold start state for testing +- /// +- /// Whether to reset the container state (defaults to true) +- internal static void Reset(bool resetContainer = true) +- { +- if (resetContainer) +- { +- _isFirstContainer = true; +- } +- CurrentInvocationColdStart.Value = null; +- _lambdaInitType = null; +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsConfigurations.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsConfigurations.cs +index e6b6f644..cf316389 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsConfigurations.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsConfigurations.cs +@@ -1,5 +1,19 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System.Globalization; +-using AWS.Lambda.Powertools.Common.Core; + + namespace AWS.Lambda.Powertools.Common; + +@@ -10,12 +24,10 @@ namespace AWS.Lambda.Powertools.Common; + /// + public class PowertoolsConfigurations : IPowertoolsConfigurations + { +- private readonly IPowertoolsEnvironment _powertoolsEnvironment; +- + /// + /// The maximum dimensions + /// +- public const int MaxDimensions = 29; ++ public const int MaxDimensions = 9; + + /// + /// The maximum metrics +@@ -27,13 +39,18 @@ public class PowertoolsConfigurations : IPowertoolsConfigurations + /// + private static IPowertoolsConfigurations _instance; + ++ /// ++ /// The system wrapper ++ /// ++ private readonly ISystemWrapper _systemWrapper; ++ + /// + /// Initializes a new instance of the class. + /// +- /// +- internal PowertoolsConfigurations(IPowertoolsEnvironment powertoolsEnvironment) ++ /// The system wrapper. ++ internal PowertoolsConfigurations(ISystemWrapper systemWrapper) + { +- _powertoolsEnvironment = powertoolsEnvironment; ++ _systemWrapper = systemWrapper; + } + + /// +@@ -41,7 +58,7 @@ public class PowertoolsConfigurations : IPowertoolsConfigurations + /// + /// The instance. + public static IPowertoolsConfigurations Instance => +- _instance ??= new PowertoolsConfigurations(PowertoolsEnvironment.Instance); ++ _instance ??= new PowertoolsConfigurations(SystemWrapper.Instance); + + /// + /// Gets the environment variable. +@@ -50,7 +67,7 @@ public class PowertoolsConfigurations : IPowertoolsConfigurations + /// System.String. + public string GetEnvironmentVariable(string variable) + { +- return _powertoolsEnvironment.GetEnvironmentVariable(variable); ++ return _systemWrapper.GetEnvironmentVariable(variable); + } + + /// +@@ -61,7 +78,7 @@ public class PowertoolsConfigurations : IPowertoolsConfigurations + /// System.String. + public string GetEnvironmentVariableOrDefault(string variable, string defaultValue) + { +- var result = _powertoolsEnvironment.GetEnvironmentVariable(variable); ++ var result = _systemWrapper.GetEnvironmentVariable(variable); + return string.IsNullOrWhiteSpace(result) ? defaultValue : result; + } + +@@ -73,7 +90,7 @@ public class PowertoolsConfigurations : IPowertoolsConfigurations + /// System.Int32. + public int GetEnvironmentVariableOrDefault(string variable, int defaultValue) + { +- var result = _powertoolsEnvironment.GetEnvironmentVariable(variable); ++ var result = _systemWrapper.GetEnvironmentVariable(variable); + return int.TryParse(result, out var parsedValue) ? parsedValue : defaultValue; + } + +@@ -85,7 +102,7 @@ public class PowertoolsConfigurations : IPowertoolsConfigurations + /// true if XXXX, false otherwise. + public bool GetEnvironmentVariableOrDefault(string variable, bool defaultValue) + { +- return bool.TryParse(_powertoolsEnvironment.GetEnvironmentVariable(variable), out var result) ++ return bool.TryParse(_systemWrapper.GetEnvironmentVariable(variable), out var result) + ? result + : defaultValue; + } +@@ -143,8 +160,7 @@ public class PowertoolsConfigurations : IPowertoolsConfigurations + /// + /// The logger sample rate. + public double LoggerSampleRate => +- double.TryParse(_powertoolsEnvironment.GetEnvironmentVariable(Constants.LoggerSampleRateNameEnv), +- NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var result) ++ double.TryParse(_systemWrapper.GetEnvironmentVariable(Constants.LoggerSampleRateNameEnv), NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var result) + ? result + : 0; + +@@ -174,7 +190,7 @@ public class PowertoolsConfigurations : IPowertoolsConfigurations + /// + /// true if this instance is Lambda; otherwise, false. + public bool IsLambdaEnvironment => GetEnvironmentVariable(Constants.LambdaTaskRoot) is not null; +- ++ + /// + /// Gets a value indicating whether [tracing is disabled]. + /// +@@ -185,7 +201,7 @@ public class PowertoolsConfigurations : IPowertoolsConfigurations + /// + public void SetExecutionEnvironment(T type) + { +- _powertoolsEnvironment.SetExecutionEnvironment(type); ++ _systemWrapper.SetExecutionEnvironment(type); + } + + /// +@@ -193,28 +209,14 @@ public class PowertoolsConfigurations : IPowertoolsConfigurations + GetEnvironmentVariableOrDefault(Constants.IdempotencyDisabledEnv, false); + + /// +- public string BatchProcessingErrorHandlingPolicy => +- GetEnvironmentVariableOrDefault(Constants.BatchErrorHandlingPolicyEnv, "DeriveFromEvent"); ++ public string BatchProcessingErrorHandlingPolicy => GetEnvironmentVariableOrDefault(Constants.BatchErrorHandlingPolicyEnv, "DeriveFromEvent"); + + /// +- public bool BatchParallelProcessingEnabled => +- GetEnvironmentVariableOrDefault(Constants.BatchParallelProcessingEnabled, false); ++ public bool BatchParallelProcessingEnabled => GetEnvironmentVariableOrDefault(Constants.BatchParallelProcessingEnabled, false); + + /// +- public int BatchProcessingMaxDegreeOfParallelism => +- GetEnvironmentVariableOrDefault(Constants.BatchMaxDegreeOfParallelismEnv, 1); ++ public int BatchProcessingMaxDegreeOfParallelism => GetEnvironmentVariableOrDefault(Constants.BatchMaxDegreeOfParallelismEnv, 1); + + /// +- public bool BatchThrowOnFullBatchFailureEnabled => +- GetEnvironmentVariableOrDefault(Constants.BatchThrowOnFullBatchFailureEnv, true); +- +- /// +- public bool MetricsDisabled => GetEnvironmentVariableOrDefault(Constants.PowertoolsMetricsDisabledEnv, false); +- +- /// +- public bool IsColdStart => LambdaLifecycleTracker.IsColdStart; +- +- /// +- public string AwsInitializationType => +- GetEnvironmentVariable(Constants.AWSInitializationTypeEnv); ++ public bool BatchThrowOnFullBatchFailureEnabled => GetEnvironmentVariableOrDefault(Constants.BatchThrowOnFullBatchFailureEnv, true); + } +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsEnvironment.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsEnvironment.cs +index afc796b6..3ad5317c 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsEnvironment.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsEnvironment.cs +@@ -1,6 +1,4 @@ + using System; +-using System.Collections.Concurrent; +-using System.Text; + + namespace AWS.Lambda.Powertools.Common; + +@@ -12,16 +10,6 @@ public class PowertoolsEnvironment : IPowertoolsEnvironment + /// + private static IPowertoolsEnvironment _instance; + +- /// +- /// Cached runtime environment string +- /// +- private static readonly string CachedRuntimeEnvironment = $"PTENV/AWS_LAMBDA_DOTNET{Environment.Version.Major}"; +- +- /// +- /// Cache for parsed assembly names to avoid repeated string operations +- /// +- private static readonly ConcurrentDictionary ParsedAssemblyNameCache = new(); +- + /// + /// Gets the instance. + /// +@@ -43,100 +31,13 @@ public class PowertoolsEnvironment : IPowertoolsEnvironment + /// + public string GetAssemblyName(T type) + { +- if (type is Type typeObject) +- { +- return typeObject.Assembly.GetName().Name; +- } +- + return type.GetType().Assembly.GetName().Name; + } + + /// + public string GetAssemblyVersion(T type) + { +- Version version; +- +- if (type is Type typeObject) +- { +- version = typeObject.Assembly.GetName().Version; +- } +- else +- { +- version = type.GetType().Assembly.GetName().Version; +- } +- ++ var version = type.GetType().Assembly.GetName().Version; + return version != null ? $"{version.Major}.{version.Minor}.{version.Build}" : string.Empty; + } +- +- /// +- public void SetExecutionEnvironment(T type) +- { +- const string envName = Constants.AwsExecutionEnvironmentVariableName; +- var currentEnvValue = GetEnvironmentVariable(envName); +- var assemblyName = ParseAssemblyName(GetAssemblyName(type)); +- +- // Check for duplication early +- if (!string.IsNullOrEmpty(currentEnvValue) && currentEnvValue.Contains(assemblyName)) +- { +- return; +- } +- +- var assemblyVersion = GetAssemblyVersion(type); +- var newEntry = $"{assemblyName}/{assemblyVersion}"; +- +- string finalValue; +- +- if (string.IsNullOrEmpty(currentEnvValue)) +- { +- // First entry: "PT/Assembly/1.0.0 PTENV/AWS_LAMBDA_DOTNET8" +- finalValue = $"{newEntry} {CachedRuntimeEnvironment}"; +- } +- else +- { +- // Check if PTENV already exists in one pass +- var containsPtenv = currentEnvValue.Contains("PTENV/"); +- +- if (containsPtenv) +- { +- // Just append the new entry: "existing PT/Assembly/1.0.0" +- finalValue = $"{currentEnvValue} {newEntry}"; +- } +- else +- { +- // Append new entry + PTENV: "existing PT/Assembly/1.0.0 PTENV/AWS_LAMBDA_DOTNET8" +- finalValue = $"{currentEnvValue} {newEntry} {CachedRuntimeEnvironment}"; +- } +- } +- +- SetEnvironmentVariable(envName, finalValue); +- } +- +- /// +- /// Parsing the name to conform with the required naming convention for the UserAgent header (PTFeature/Name/Version) +- /// Fallback to Assembly Name on exception +- /// +- /// +- /// +- internal static string ParseAssemblyName(string assemblyName) +- { +- // Use cache to avoid repeated string operations +- try +- { +- return ParsedAssemblyNameCache.GetOrAdd(assemblyName, name => +- { +- var lastDotIndex = name.LastIndexOf('.'); +- if (lastDotIndex >= 0 && lastDotIndex < name.Length - 1) +- { +- var parsedName = name.Substring(lastDotIndex + 1); +- return $"{Constants.FeatureContextIdentifier}/{parsedName}"; +- } +- +- return $"{Constants.FeatureContextIdentifier}/{name}"; +- }); +- } +- catch +- { +- return string.Empty; +- } +- } +-} ++} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/SystemWrapper.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/SystemWrapper.cs +new file mode 100644 +index 00000000..8f42bda4 +--- /dev/null ++++ b/libraries/src/AWS.Lambda.Powertools.Common/Core/SystemWrapper.cs +@@ -0,0 +1,155 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ ++using System; ++using System.IO; ++using System.Text; ++ ++namespace AWS.Lambda.Powertools.Common; ++ ++/// ++/// Class SystemWrapper. ++/// Implements the ++/// ++/// ++public class SystemWrapper : ISystemWrapper ++{ ++ private static IPowertoolsEnvironment _powertoolsEnvironment; ++ ++ /// ++ /// The instance ++ /// ++ private static ISystemWrapper _instance; ++ ++ /// ++ /// Prevents a default instance of the class from being created. ++ /// ++ public SystemWrapper(IPowertoolsEnvironment powertoolsEnvironment) ++ { ++ _powertoolsEnvironment = powertoolsEnvironment; ++ _instance ??= this; ++ ++ // Clear AWS SDK Console injected parameters StdOut and StdErr ++ var standardOutput = new StreamWriter(Console.OpenStandardOutput()); ++ standardOutput.AutoFlush = true; ++ Console.SetOut(standardOutput); ++ var errordOutput = new StreamWriter(Console.OpenStandardError()); ++ errordOutput.AutoFlush = true; ++ Console.SetError(errordOutput); ++ } ++ ++ /// ++ /// Gets the instance. ++ /// ++ /// The instance. ++ public static ISystemWrapper Instance => _instance ??= new SystemWrapper(PowertoolsEnvironment.Instance); ++ ++ /// ++ /// Gets the environment variable. ++ /// ++ /// The variable. ++ /// System.String. ++ public string GetEnvironmentVariable(string variable) ++ { ++ return _powertoolsEnvironment.GetEnvironmentVariable(variable); ++ } ++ ++ /// ++ /// Logs the specified value. ++ /// ++ /// The value. ++ public void Log(string value) ++ { ++ Console.Write(value); ++ } ++ ++ /// ++ /// Logs the line. ++ /// ++ /// The value. ++ public void LogLine(string value) ++ { ++ Console.WriteLine(value); ++ } ++ ++ /// ++ /// Gets random number ++ /// ++ /// System.Double. ++ public double GetRandom() ++ { ++ return new Random().NextDouble(); ++ } ++ ++ /// ++ public void SetEnvironmentVariable(string variable, string value) ++ { ++ _powertoolsEnvironment.SetEnvironmentVariable(variable, value); ++ } ++ ++ /// ++ public void SetExecutionEnvironment(T type) ++ { ++ const string envName = Constants.AwsExecutionEnvironmentVariableName; ++ var envValue = new StringBuilder(); ++ var currentEnvValue = GetEnvironmentVariable(envName); ++ var assemblyName = ParseAssemblyName(_powertoolsEnvironment.GetAssemblyName(type)); ++ ++ // If there is an existing execution environment variable add the annotations package as a suffix. ++ if (!string.IsNullOrEmpty(currentEnvValue)) ++ { ++ // Avoid duplication - should not happen since the calling Instances are Singletons - defensive purposes ++ if (currentEnvValue.Contains(assemblyName)) ++ { ++ return; ++ } ++ ++ envValue.Append($"{currentEnvValue} "); ++ } ++ ++ var assemblyVersion = _powertoolsEnvironment.GetAssemblyVersion(type); ++ ++ envValue.Append($"{assemblyName}/{assemblyVersion}"); ++ ++ SetEnvironmentVariable(envName, envValue.ToString()); ++ } ++ ++ /// ++ public void SetOut(TextWriter writeTo) ++ { ++ Console.SetOut(writeTo); ++ } ++ ++ /// ++ /// Parsing the name to conform with the required naming convention for the UserAgent header (PTFeature/Name/Version) ++ /// Fallback to Assembly Name on exception ++ /// ++ /// ++ /// ++ private string ParseAssemblyName(string assemblyName) ++ { ++ try ++ { ++ var parsedName = assemblyName.Substring(assemblyName.LastIndexOf(".", StringComparison.Ordinal) + 1); ++ return $"{Constants.FeatureContextIdentifier}/{parsedName}"; ++ } ++ catch ++ { ++ //NOOP ++ } ++ ++ return $"{Constants.FeatureContextIdentifier}/{assemblyName}"; ++ } ++} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Common/InternalsVisibleTo.cs b/libraries/src/AWS.Lambda.Powertools.Common/InternalsVisibleTo.cs +index 641de17c..575d005f 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Common/InternalsVisibleTo.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Common/InternalsVisibleTo.cs +@@ -17,7 +17,6 @@ using System.Runtime.CompilerServices; + + [assembly: InternalsVisibleTo("AWS.Lambda.Powertools.Logging")] + [assembly: InternalsVisibleTo("AWS.Lambda.Powertools.Metrics")] +-[assembly: InternalsVisibleTo("AWS.Lambda.Powertools.Tracing")] + [assembly: InternalsVisibleTo("AWS.Lambda.Powertools.Idempotency")] + [assembly: InternalsVisibleTo("AWS.Lambda.Powertools.Common.Tests")] + [assembly: InternalsVisibleTo("AWS.Lambda.Powertools.Tracing.Tests")] +diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Tests/TestLoggerOutput.cs b/libraries/src/AWS.Lambda.Powertools.Common/Tests/TestLoggerOutput.cs +deleted file mode 100644 +index b5dded35..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Common/Tests/TestLoggerOutput.cs ++++ /dev/null +@@ -1,49 +0,0 @@ +-using System.Text; +- +-namespace AWS.Lambda.Powertools.Common.Tests; +- +-/// +-/// Test logger output +-/// +-public class TestLoggerOutput : IConsoleWrapper +-{ +- /// +- /// Buffer for all the log messages written to the logger. +- /// +- private readonly StringBuilder _outputBuffer = new(); +- +- /// +- /// Cleasr the output buffer. +- /// +- public void Clear() +- { +- _outputBuffer.Clear(); +- } +- +- /// +- /// Output the contents of the buffer. +- /// +- /// +- public override string ToString() +- { +- return _outputBuffer.ToString(); +- } +- +- /// +- public void WriteLine(string message) +- { +- _outputBuffer.AppendLine(message); +- } +- +- /// +- public void Debug(string message) +- { +- _outputBuffer.AppendLine(message); +- } +- +- /// +- public void Error(string message) +- { +- _outputBuffer.AppendLine(message); +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore.csproj b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore.csproj +deleted file mode 100644 +index 5e5c6666..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore.csproj ++++ /dev/null +@@ -1,26 +0,0 @@ +- +- +- +- +- AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore +- Powertools for AWS Lambda (.NET) - Event Handler Bedrock Agent Function Resolver AspNetCore package. +- AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore +- AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore +- net8.0 +- false +- enable +- enable +- +- +- +- +- +- +- +- +- +- +- +- +- +- +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore/BedrockFunctionRegistration.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore/BedrockFunctionRegistration.cs +deleted file mode 100644 +index 7bc17dbd..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore/BedrockFunctionRegistration.cs ++++ /dev/null +@@ -1,41 +0,0 @@ +-namespace AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore; +- +-/// +-/// Helper class for function registration with fluent API pattern. +-/// +-internal class BedrockFunctionRegistration +-{ +- private readonly BedrockAgentFunctionResolver _resolver; +- +- /// +- /// Initializes a new instance of the class. +- /// +- /// The Bedrock agent function resolver. +- public BedrockFunctionRegistration(BedrockAgentFunctionResolver resolver) +- { +- _resolver = resolver; +- } +- +- /// +- /// Adds a function to the Bedrock resolver. +- /// +- /// The name of the function. +- /// The delegate handler. +- /// Optional description of the function. +- /// The function registration instance for method chaining. +- /// +- /// +- /// app.MapBedrockFunction("GetWeather", (string city, int month) => +- /// $"Weather forecast for {city} in month {month}: Warm and sunny"); +- /// +- /// app.MapBedrockFunction("Calculate", (int x, int y) => +- /// $"Result: {x + y}"); +- /// ); +- /// +- /// +- public BedrockFunctionRegistration Add(string name, Delegate handler, string description = "") +- { +- _resolver.Tool(name, description, handler); +- return this; +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore/BedrockMinimalApiExtensions.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore/BedrockMinimalApiExtensions.cs +deleted file mode 100644 +index ca9fd9ec..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore/BedrockMinimalApiExtensions.cs ++++ /dev/null +@@ -1,158 +0,0 @@ +-using System.Diagnostics.CodeAnalysis; +-using System.Text.Json; +-using System.Text.Json.Serialization; +-using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; +-using Microsoft.AspNetCore.Builder; +-using Microsoft.AspNetCore.Http; +-using Microsoft.Extensions.DependencyInjection; +- +-namespace AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore; +- +-// Source generation for JSON serialization +-[JsonSerializable(typeof(BedrockFunctionRequest))] +-internal partial class BedrockJsonContext : JsonSerializerContext +-{ +-} +- +-/// +-/// Extension methods for registering Bedrock Agent Functions in ASP.NET Core Minimal API. +-/// +-public static class BedrockMinimalApiExtensions +-{ +- // Static flag to track if handler is mapped (thread-safe with volatile) +- private static volatile bool _bedrockRequestHandlerMapped; +- +- // JSON options with case insensitivity +- private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions +- { +- PropertyNameCaseInsensitive = true +- }; +- +- /// +- /// Maps an individual Bedrock Agent function that will be called directly from the root endpoint. +- /// The function name is extracted from the incoming request payload. +- /// +- /// The web application to configure. +- /// The name of the function to register. +- /// The delegate handler that implements the function. +- /// Optional description of the function. +- /// The web application instance. +- /// +- /// +- /// // Register individual functions +- /// app.MapBedrockFunction("GetWeather", (string city, int month) => +- /// $"Weather forecast for {city} in month {month}: Warm and sunny"); +- /// +- /// app.MapBedrockFunction("Calculate", (int x, int y) => +- /// $"Result: {x + y}"); +- /// +- /// +- public static WebApplication MapBedrockFunction( +- this WebApplication app, +- string functionName, +- Delegate handler, +- string description = "") +- { +- // Get or create the resolver from services +- var resolver = app.Services.GetService() +- ?? new BedrockAgentFunctionResolver(); +- +- // Register the function with the resolver +- resolver.Tool(functionName, description, handler); +- +- // Ensure we have a global handler for Bedrock requests +- EnsureBedrockRequestHandler(app, resolver); +- +- return app; +- } +- +- [UnconditionalSuppressMessage("AOT", "IL3050:RequiresDynamicCode", +- Justification = "The handler implementation is controlled and AOT-compatible")] +- [UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode", +- Justification = "The handler implementation is controlled and trim-compatible")] +- private static void EnsureBedrockRequestHandler(WebApplication app, BedrockAgentFunctionResolver resolver) +- { +- // Check if we've already mapped the handler (we only need to do this once) +- if (_bedrockRequestHandlerMapped) +- return; +- +- // Map the root endpoint to handle all Bedrock Agent Function requests +- app.MapPost("/", [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Handler is AOT-friendly")] +- [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Handler is trim-friendly")] +- async (HttpContext context) => +- { +- try +- { +- // Read the request body +- string requestBody; +- using (var reader = new StreamReader(context.Request.Body)) +- { +- requestBody = await reader.ReadToEndAsync(); +- } +- +- // Use source-generated serialization for the request +- var bedrockRequest = JsonSerializer.Deserialize(requestBody, +- BedrockJsonContext.Default.BedrockFunctionRequest); +- +- if (bedrockRequest == null) +- return Results.BadRequest("Invalid request format"); +- +- // Process the request through the resolver +- var result = await resolver.ResolveAsync(bedrockRequest); +- +- // For the response, use the standard serializer with suppressed warnings +- // This is more compatible with different response types +- context.Response.ContentType = "application/json"; +- await context.Response.WriteAsJsonAsync(result, JsonOptions); +- return Results.Empty; +- } +- catch (Exception ex) +- { +- return Results.Problem($"Error processing Bedrock Agent request: {ex.Message}"); +- } +- }); +- +- // Mark that we've set up the handler +- _bedrockRequestHandlerMapped = true; +- } +- +- /// +- /// Registers all methods from a class marked with BedrockFunctionTypeAttribute. +- /// +- /// The type containing tool methods marked with BedrockFunctionToolAttribute +- /// The web application to configure. +- /// The web application instance. +- /// +- /// +- /// // Define your tool class +- /// [BedrockFunctionType] +- /// public class WeatherTools +- /// { +- /// [BedrockFunctionTool(Name = "GetWeather", Description = "Gets weather forecast")] +- /// public static string GetWeather(string location, int days) +- /// { +- /// return $"Weather forecast for {location} for the next {days} days"; +- /// } +- /// } +- /// +- /// // Register all tools from the class +- /// app.MapBedrockToolClass<WeatherTools>(); +- /// +- /// +- public static WebApplication MapBedrockToolType<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T>( +- this WebApplication app) +- where T : class +- { +- // Get or create the resolver from services +- var resolver = app.Services.GetService() +- ?? new BedrockAgentFunctionResolver(); +- +- // Register the tool class +- resolver.RegisterTool(); +- +- // Ensure we have a global handler for Bedrock requests +- EnsureBedrockRequestHandler(app, resolver); +- +- return app; +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.csproj b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.csproj +deleted file mode 100644 +index b0a7db73..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.csproj ++++ /dev/null +@@ -1,21 +0,0 @@ +- +- +- +- +- AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction +- Powertools for AWS Lambda (.NET) - Event Handler Bedrock Agent Function Resolver package. +- net8.0 +- false +- enable +- enable +- true +- true +- +- +- +- +- +- +- +- +- +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/BedrockAgentFunctionResolver.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/BedrockAgentFunctionResolver.cs +deleted file mode 100644 +index 4107a1b9..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/BedrockAgentFunctionResolver.cs ++++ /dev/null +@@ -1,364 +0,0 @@ +-using System.Text.Json.Serialization.Metadata; +-using Amazon.Lambda.Core; +-using AWS.Lambda.Powertools.Common; +-using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; +-using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Helpers; +- +-// ReSharper disable once CheckNamespace +-namespace AWS.Lambda.Powertools.EventHandler.Resolvers +-{ +- /// +- /// A resolver for Bedrock Agent functions that allows registering handlers for tool functions. +- /// +- /// +- /// Basic usage: +- /// +- /// var resolver = new BedrockAgentFunctionResolver(); +- /// resolver.Tool("GetWeather", (string city) => $"Weather in {city} is sunny"); +- /// +- /// // Lambda handler +- /// public BedrockFunctionResponse FunctionHandler(BedrockFunctionRequest input, ILambdaContext context) +- /// { +- /// return resolver.Resolve(input, context); +- /// } +- /// +- /// +- public class BedrockAgentFunctionResolver +- { +- private readonly +- Dictionary> +- _handlers = new(); +- +- private readonly ParameterTypeValidator _parameterValidator = new(); +- private readonly ResultConverter _resultConverter = new(); +- private readonly ParameterMapper _parameterMapper; +- +- /// +- /// Initializes a new instance of the class. +- /// Optionally accepts a type resolver for JSON serialization. +- /// +- public BedrockAgentFunctionResolver(IJsonTypeInfoResolver? typeResolver = null) +- { +- _parameterMapper = new ParameterMapper(typeResolver); +- PowertoolsEnvironment.Instance.SetExecutionEnvironment(this); +- } +- +- /// +- /// Checks if another tool can be registered, and logs a warning if the maximum limit is reached +- /// or if a tool with the same name is already registered +- /// +- /// The name of the tool being registered +- /// True if the tool can be registered, false if the maximum limit is reached +- private bool CanRegisterTool(string name) +- { +- if (_handlers.ContainsKey(name)) +- { +- Console.WriteLine($"WARNING: Tool {name} already registered. Overwriting with new definition."); +- } +- +- return true; +- } +- +- /// +- /// Registers a handler that directly accepts BedrockFunctionRequest and returns BedrockFunctionResponse +- /// +- /// The name of the tool function +- /// The handler function that accepts input and context and returns output +- /// Optional description of the tool function +- /// The resolver instance for method chaining +- public BedrockAgentFunctionResolver Tool( +- string name, +- Func handler, +- string description = "") +- { +- ArgumentNullException.ThrowIfNull(handler); +- +- if (!CanRegisterTool(name)) +- return this; +- +- _handlers[name] = handler; +- return this; +- } +- +- /// +- /// Registers a handler that directly accepts BedrockFunctionRequest and returns BedrockFunctionResponse +- /// +- /// The name of the tool function +- /// The handler function that accepts input and returns output +- /// Optional description of the tool function +- /// The resolver instance for method chaining +- public BedrockAgentFunctionResolver Tool( +- string name, +- Func handler, +- string description = "") +- { +- ArgumentNullException.ThrowIfNull(handler); +- +- if (!CanRegisterTool(name)) +- return this; +- +- _handlers[name] = (input, _) => handler(input); +- return this; +- } +- +- /// +- /// Registers a parameter-less handler that returns BedrockFunctionResponse +- /// +- /// The name of the tool function +- /// The handler function that returns output +- /// Optional description of the tool function +- /// The resolver instance for method chaining +- public BedrockAgentFunctionResolver Tool( +- string name, +- Func handler, +- string description = "") +- { +- ArgumentNullException.ThrowIfNull(handler); +- +- if (!CanRegisterTool(name)) +- return this; +- +- _handlers[name] = (_, _) => handler(); +- return this; +- } +- +- /// +- /// Registers a parameter-less handler with automatic string conversion +- /// +- /// The name of the tool function +- /// The handler function that returns a string +- /// Optional description of the tool function +- /// The resolver instance for method chaining +- public BedrockAgentFunctionResolver Tool( +- string name, +- Func handler, +- string description = "") +- { +- ArgumentNullException.ThrowIfNull(handler); +- +- if (!CanRegisterTool(name)) +- return this; +- +- _handlers[name] = (input, _) => BedrockFunctionResponse.WithText( +- handler(), +- input.ActionGroup, +- name, +- input.SessionAttributes, +- input.PromptSessionAttributes, +- new Dictionary()); +- return this; +- } +- +- /// +- /// Registers a parameter-less handler with automatic object conversion +- /// +- /// The name of the tool function +- /// The handler function that returns an object +- /// Optional description of the tool function +- /// The resolver instance for method chaining +- public BedrockAgentFunctionResolver Tool( +- string name, +- Func handler, +- string description = "") +- { +- ArgumentNullException.ThrowIfNull(handler); +- +- if (!CanRegisterTool(name)) +- return this; +- +- _handlers[name] = (input, _) => +- { +- var result = handler(); +- return _resultConverter.ConvertToOutput(result, input); +- }; +- return this; +- } +- +- /// +- /// Registers a handler for a tool function with automatically converted return type (no description). +- /// +- /// The name of the tool function +- /// The delegate handler function +- /// The resolver instance for method chaining +- public BedrockAgentFunctionResolver Tool( +- string name, +- Delegate handler) +- { +- return Tool(name, "", handler); +- } +- +- /// +- /// Registers a handler for a tool function with description and automatically converted return type. +- /// +- /// The name of the tool function +- /// Description of the tool function +- /// The delegate handler function +- /// The resolver instance for method chaining +- public BedrockAgentFunctionResolver Tool( +- string name, +- string description, +- Delegate handler) +- { +- return Tool(name, description, handler); +- } +- +- /// +- /// Registers a handler for a tool function with typed return value (no description). +- /// +- /// The return type of the handler +- /// The name of the tool function +- /// The delegate handler function +- /// The resolver instance for method chaining +- public BedrockAgentFunctionResolver Tool( +- string name, +- Delegate handler) +- { +- return Tool(name, "", handler); +- } +- +- /// +- /// Registers a handler for a tool function with description and typed return value. +- /// +- /// The return type of the handler +- /// The name of the tool function +- /// Description of the tool function +- /// The delegate handler function +- /// The resolver instance for method chaining +- public BedrockAgentFunctionResolver Tool( +- string name, +- string description, +- Delegate handler) +- { +- ArgumentNullException.ThrowIfNull(handler); +- +- if (!CanRegisterTool(name)) +- return this; +- +- _handlers[name] = RegisterToolHandler(handler, name); +- return this; +- } +- +- private Func RegisterToolHandler( +- Delegate handler, string functionName) +- { +- return (input, context) => +- { +- try +- { +- // Map parameters from Bedrock input and DI +- var serviceProvider = (this as DiBedrockAgentFunctionResolver)?.ServiceProvider; +- var args = _parameterMapper.MapParameters(handler.Method, input, context, serviceProvider); +- +- // Execute the handler and process result +- return ExecuteHandlerAndProcessResult(handler, args, input, context, functionName); +- } +- catch (Exception ex) +- { +- context?.Logger.LogError(ex.ToString()); +- var innerException = ex.InnerException ?? ex; +- return BedrockFunctionResponse.WithText( +- $"Error when invoking tool: {innerException.Message}", +- input.ActionGroup, +- functionName, +- input.SessionAttributes, +- input.PromptSessionAttributes, +- new Dictionary()); +- } +- }; +- } +- +- private BedrockFunctionResponse ExecuteHandlerAndProcessResult( +- Delegate handler, +- object?[] args, +- BedrockFunctionRequest input, +- ILambdaContext? context, +- string functionName) +- { +- try +- { +- // Execute the handler +- var result = handler.DynamicInvoke(args); +- +- // Process various result types +- return _resultConverter.ProcessResult(result, input, functionName, context); +- } +- catch (Exception ex) +- { +- context?.Logger.LogError(ex.ToString()); +- var innerException = ex.InnerException ?? ex; +- return BedrockFunctionResponse.WithText( +- $"Error when invoking tool: {innerException.Message}", +- input.ActionGroup, +- functionName, +- input.SessionAttributes, +- input.PromptSessionAttributes, +- new Dictionary()); +- } +- } +- +- /// +- /// Resolves and processes a Bedrock Agent function invocation. +- /// +- /// The Bedrock Agent input containing the function name and parameters +- /// Optional Lambda context +- /// The output from the function execution +- public BedrockFunctionResponse Resolve(BedrockFunctionRequest input, ILambdaContext? context = null) +- { +- return ResolveAsync(input, context).GetAwaiter().GetResult(); +- } +- +- /// +- /// Asynchronously resolves and processes a Bedrock Agent function invocation. +- /// +- /// The Bedrock Agent input containing the function name and parameters +- /// Optional Lambda context +- /// A task that completes with the output from the function execution +- public async Task ResolveAsync(BedrockFunctionRequest input, +- ILambdaContext? context = null) +- { +- return await Task.FromResult(HandleEvent(input, context)); +- } +- +- private BedrockFunctionResponse HandleEvent(BedrockFunctionRequest input, ILambdaContext? context) +- { +- if (string.IsNullOrEmpty(input.Function)) +- { +- return BedrockFunctionResponse.WithText( +- "No tool specified in the request", +- input.ActionGroup, +- "", +- input.SessionAttributes, +- input.PromptSessionAttributes, +- new Dictionary()); +- } +- +- if (_handlers.TryGetValue(input.Function, out var handler)) +- { +- try +- { +- return handler(input, context); +- } +- catch (Exception ex) +- { +- context?.Logger.LogError(ex.ToString()); +- return BedrockFunctionResponse.WithText( +- $"Error when invoking tool: {ex.Message}", +- input.ActionGroup, +- input.Function, +- input.SessionAttributes, +- input.PromptSessionAttributes, +- new Dictionary()); +- } +- } +- +- context?.Logger.LogWarning($"Tool {input.Function} has not been registered."); +- return BedrockFunctionResponse.WithText( +- $"Error: Tool {input.Function} has not been registered in handler", +- input.ActionGroup, +- input.Function, +- input.SessionAttributes, +- input.PromptSessionAttributes, +- new Dictionary()); +- } +- } +-} +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/BedrockAgentFunctionResolverExtensions.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/BedrockAgentFunctionResolverExtensions.cs +deleted file mode 100644 +index a00b9545..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/BedrockAgentFunctionResolverExtensions.cs ++++ /dev/null +@@ -1,106 +0,0 @@ +-using System.Diagnostics.CodeAnalysis; +-using System.Linq.Expressions; +-using System.Reflection; +-using System.Text.Json.Serialization.Metadata; +-using Microsoft.Extensions.DependencyInjection; +- +-// ReSharper disable once CheckNamespace +-namespace AWS.Lambda.Powertools.EventHandler.Resolvers +-{ +- /// +- /// Extension methods for Bedrock Agent Function Resolver. +- /// +- public static class BedrockResolverExtensions +- { +- /// +- /// Registers a Bedrock Agent Function Resolver with dependency injection support. +- /// +- /// The service collection to add the resolver to. +- /// +- /// The updated service collection. +- /// +- /// +- /// public void ConfigureServices(IServiceCollection services) +- /// { +- /// services.AddBedrockResolver(); +- /// +- /// // Now you can inject BedrockAgentFunctionResolver into your services +- /// } +- /// +- /// +- public static IServiceCollection AddBedrockResolver( +- this IServiceCollection services, +- IJsonTypeInfoResolver? typeResolver = null) +- { +- services.AddSingleton(sp => +- new DiBedrockAgentFunctionResolver(sp, typeResolver)); +- return services; +- } +- +- /// +- /// Registers tools from a type marked with BedrockFunctionTypeAttribute. +- /// +- /// The type containing tool methods marked with BedrockFunctionToolAttribute +- /// The resolver to register tools with +- /// The resolver for method chaining +- /// +- /// +- /// // Define your tool class +- /// [BedrockFunctionType] +- /// public class WeatherTools +- /// { +- /// [BedrockFunctionTool(Name = "GetWeather", Description = "Gets weather forecast")] +- /// public static string GetWeather(string location, int days) +- /// { +- /// return $"Weather forecast for {location} for the next {days} days"; +- /// } +- /// } +- /// +- /// // Register the tools +- /// var resolver = new BedrockAgentFunctionResolver(); +- /// resolver.RegisterTool<WeatherTools>(); +- /// +- /// +- public static BedrockAgentFunctionResolver RegisterTool< +- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T>( +- this BedrockAgentFunctionResolver resolver) +- where T : class +- { +- var type = typeof(T); +- +- // Check if class has the BedrockFunctionType attribute +- if (!type.IsDefined(typeof(BedrockFunctionTypeAttribute), false)) +- return resolver; +- +- // Look at all static methods with the tool attribute +- foreach (var method in type.GetMethods(BindingFlags.Static | BindingFlags.Public)) +- { +- var attr = method.GetCustomAttribute(); +- if (attr == null) continue; +- +- string toolName = attr.Name ?? method.Name; +- string description = attr.Description ?? +- string.Empty; +- +- // Create delegate from the static method +- var del = Delegate.CreateDelegate( +- GetDelegateType(method), +- method); +- +- // Call the Tool method directly instead of using reflection +- resolver.Tool(toolName, description, del); +- } +- +- return resolver; +- } +- +- private static Type GetDelegateType(MethodInfo method) +- { +- var parameters = method.GetParameters(); +- var parameterTypes = parameters.Select(p => p.ParameterType).ToList(); +- parameterTypes.Add(method.ReturnType); +- +- return Expression.GetDelegateType(parameterTypes.ToArray()); +- } +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/BedrockFunctionResolverContext.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/BedrockFunctionResolverContext.cs +deleted file mode 100644 +index 0c36c1d1..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/BedrockFunctionResolverContext.cs ++++ /dev/null +@@ -1,14 +0,0 @@ +-using System.Text.Json.Serialization; +- +-// ReSharper disable once CheckNamespace +-namespace AWS.Lambda.Powertools.EventHandler.Resolvers; +- +-[JsonSerializable(typeof(string[]))] +-[JsonSerializable(typeof(int[]))] +-[JsonSerializable(typeof(long[]))] +-[JsonSerializable(typeof(double[]))] +-[JsonSerializable(typeof(bool[]))] +-[JsonSerializable(typeof(decimal[]))] +-internal partial class BedrockFunctionResolverContext : JsonSerializerContext +-{ +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/BedrockFunctionToolAttribute.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/BedrockFunctionToolAttribute.cs +deleted file mode 100644 +index daf90f21..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/BedrockFunctionToolAttribute.cs ++++ /dev/null +@@ -1,45 +0,0 @@ +-// ReSharper disable once CheckNamespace +-namespace AWS.Lambda.Powertools.EventHandler.Resolvers; +- +-/// +-/// Marks a method as a Bedrock Agent function tool. +-/// +-/// +-/// +-/// [BedrockFunctionTool(Name = "GetWeather", Description = "Gets the weather for a location")] +-/// public static string GetWeather(string location, int days) +-/// { +-/// return $"Weather forecast for {location} for the next {days} days"; +-/// } +-/// +-/// +-[AttributeUsage(AttributeTargets.Method)] +-public class BedrockFunctionToolAttribute : Attribute +-{ +- /// +- /// The name of the tool. If not specified, the method name will be used. +- /// +- public string? Name { get; set; } +- +- /// +- /// The description of the tool. Used to provide context about the tool's functionality. +- /// +- public string? Description { get; set; } +-} +- +-/// +-/// Marks a class as containing Bedrock Agent function tools. +-/// +-/// +-/// +-/// [BedrockFunctionType] +-/// public class WeatherTools +-/// { +-/// // Methods that can be registered as tools +-/// } +-/// +-/// +-[AttributeUsage(AttributeTargets.Class)] +-public class BedrockFunctionTypeAttribute : Attribute +-{ +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/DiBedrockAgentFunctionResolver.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/DiBedrockAgentFunctionResolver.cs +deleted file mode 100644 +index 82064d43..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/DiBedrockAgentFunctionResolver.cs ++++ /dev/null +@@ -1,25 +0,0 @@ +-using System.Text.Json.Serialization.Metadata; +- +-namespace AWS.Lambda.Powertools.EventHandler.Resolvers; +- +-/// +-/// Extended Bedrock Agent Function Resolver with dependency injection support. +-/// +-internal class DiBedrockAgentFunctionResolver : BedrockAgentFunctionResolver +-{ +- /// +- /// Gets the service provider used for dependency injection. +- /// +- public IServiceProvider ServiceProvider { get; } +- +- /// +- /// Initializes a new instance of the class. +- /// +- /// The service provider for dependency injection. +- /// +- public DiBedrockAgentFunctionResolver(IServiceProvider serviceProvider, IJsonTypeInfoResolver? typeResolver = null) +- : base(typeResolver) +- { +- ServiceProvider = serviceProvider; +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Helpers/ParameterAccessor.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Helpers/ParameterAccessor.cs +deleted file mode 100644 +index abcd2223..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Helpers/ParameterAccessor.cs ++++ /dev/null +@@ -1,139 +0,0 @@ +-using System.Globalization; +- +-namespace AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Helpers; +- +-/// +-/// Provides strongly-typed access to the parameters of an agent function call. +-/// +-internal class ParameterAccessor +-{ +- private readonly List _parameters; +- +- internal ParameterAccessor(List? parameters) +- { +- _parameters = parameters ?? new List(); +- } +- +- /// +- /// Gets a parameter value by name with type conversion. +- /// +- public T Get(string name) +- { +- var parameter = _parameters.FirstOrDefault(p => string.Equals(p.Name, name, StringComparison.OrdinalIgnoreCase)); +- if (parameter == null || parameter.Value == null) +- { +- return default!; +- } +- +- return ConvertParameter(parameter); +- } +- +- /// +- /// Gets a parameter value by index with type conversion. +- /// +- public T GetAt(int index) +- { +- if (index < 0 || index >= _parameters.Count) +- { +- return default!; +- } +- +- var parameter = _parameters[index]; +- if (parameter.Value == null) +- { +- return default!; +- } +- +- return ConvertParameter(parameter); +- } +- +- /// +- /// Gets a parameter value by name with fallback to a default value. +- /// +- public T GetOrDefault(string name, T defaultValue) +- { +- var parameter = _parameters.FirstOrDefault(p => string.Equals(p.Name, name, StringComparison.OrdinalIgnoreCase)); +- if (parameter == null || parameter.Value == null) +- { +- return defaultValue; +- } +- +- try +- { +- var result = ConvertParameter(parameter); +- // If conversion returns default value but we have a non-null parameter, +- // that means conversion failed, so return the provided default value +- if (EqualityComparer.Default.Equals(result, default) && parameter.Value != null) +- { +- return defaultValue; +- } +- return result; +- } +- catch +- { +- return defaultValue; +- } +- } +- +- private static T ConvertParameter(Parameter? parameter) +- { +- if (parameter == null || parameter.Value == null) +- { +- return default!; +- } +- +- // Handle different types explicitly for AOT compatibility +- if (typeof(T) == typeof(string)) +- { +- return (T)(object)parameter.Value; +- } +- +- if (typeof(T) == typeof(int) || typeof(T) == typeof(int?)) +- { +- if (int.TryParse(parameter.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out int result)) +- { +- return (T)(object)result; +- } +- return default!; +- } +- +- if (typeof(T) == typeof(double) || typeof(T) == typeof(double?)) +- { +- if (double.TryParse(parameter.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out double result)) +- { +- return (T)(object)result; +- } +- return default!; +- } +- +- if (typeof(T) == typeof(bool) || typeof(T) == typeof(bool?)) +- { +- if (bool.TryParse(parameter.Value, out bool result)) +- { +- return (T)(object)result; +- } +- return default!; +- } +- +- if (typeof(T) == typeof(long) || typeof(T) == typeof(long?)) +- { +- if (long.TryParse(parameter.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out long result)) +- { +- return (T)(object)result; +- } +- return default!; +- } +- +- if (typeof(T) == typeof(decimal) || typeof(T) == typeof(decimal?)) +- { +- if (decimal.TryParse(parameter.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out decimal result)) +- { +- return (T)(object)result; +- } +- return default!; +- } +- +- // Return default for array and complex types +- return default!; +- } +-} +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Helpers/ParameterMapper.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Helpers/ParameterMapper.cs +deleted file mode 100644 +index d5eb9da0..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Helpers/ParameterMapper.cs ++++ /dev/null +@@ -1,194 +0,0 @@ +-using System.Reflection; +-using System.Text.Json; +-using System.Text.Json.Serialization.Metadata; +-using Amazon.Lambda.Core; +-using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; +- +-namespace AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Helpers +-{ +- /// +- /// Maps parameters for Bedrock Agent function handlers +- /// +- internal class ParameterMapper +- { +- private readonly ParameterTypeValidator _validator = new(); +- private readonly IJsonTypeInfoResolver? _typeResolver; +- +- public ParameterMapper(IJsonTypeInfoResolver? typeResolver = null) +- { +- _typeResolver = typeResolver; +- } +- +- /// +- /// Maps parameters for a handler method from a Bedrock function request +- /// +- /// The handler method +- /// The Bedrock function request +- /// The Lambda context +- /// Optional service provider for dependency injection +- /// Array of arguments to pass to the handler +- public object?[] MapParameters( +- MethodInfo methodInfo, +- BedrockFunctionRequest input, +- ILambdaContext? context, +- IServiceProvider? serviceProvider) +- { +- var parameters = methodInfo.GetParameters(); +- var args = new object?[parameters.Length]; +- var accessor = new ParameterAccessor(input.Parameters); +- +- for (var i = 0; i < parameters.Length; i++) +- { +- var parameter = parameters[i]; +- var paramType = parameter.ParameterType; +- +- if (paramType == typeof(ILambdaContext)) +- { +- args[i] = context; +- continue; // Skip further processing for this parameter +- } +- else if (paramType == typeof(BedrockFunctionRequest)) +- { +- args[i] = input; +- continue; // Skip further processing for this parameter +- } +- +- // Try to deserialize custom complex type from InputText +- if (!string.IsNullOrEmpty(input.InputText) && +- !paramType.IsPrimitive && +- paramType != typeof(string) && +- !paramType.IsEnum) +- { +- try +- { +- var options = new JsonSerializerOptions +- { +- PropertyNameCaseInsensitive = true +- }; +- +- if (_typeResolver != null) +- { +- options.TypeInfoResolver = _typeResolver; +- +- // Get the JsonTypeInfo for the parameter type +- var jsonTypeInfo = _typeResolver.GetTypeInfo(paramType, options); +- if (jsonTypeInfo != null) +- { +- // Use the AOT-friendly overload with JsonTypeInfo +- args[i] = JsonSerializer.Deserialize(input.InputText, jsonTypeInfo); +- +- if (args[i] != null) +- { +- continue; +- } +- } +- } +- else +- { +- // Fallback to non-AOT deserialization with warning +-#pragma warning disable IL2026, IL3050 +- args[i] = JsonSerializer.Deserialize(input.InputText, paramType, options); +-#pragma warning restore IL2026, IL3050 +- +- if (args[i] != null) +- { +- continue; +- } +- } +- } +- catch +- { +- // Deserialization failed, continue to regular parameter mapping +- } +- } +- +- if (_validator.IsBedrockParameter(paramType)) +- { +- args[i] = MapBedrockParameter(paramType, parameter.Name ?? $"arg{i}", accessor); +- } +- else if (serviceProvider != null) +- { +- // Resolve from DI +- args[i] = serviceProvider.GetService(paramType); +- } +- } +- +- return args; +- } +- +- private object? MapBedrockParameter(Type paramType, string paramName, ParameterAccessor accessor) +- { +- // Array parameter handling +- if (paramType.IsArray) +- { +- return MapArrayParameter(paramType, paramName, accessor); +- } +- +- // Scalar parameter handling +- return MapScalarParameter(paramType, paramName, accessor); +- } +- +- private object? MapArrayParameter(Type paramType, string paramName, ParameterAccessor accessor) +- { +- var jsonArrayStr = accessor.Get(paramName); +- +- if (string.IsNullOrEmpty(jsonArrayStr)) +- { +- return null; +- } +- +- try +- { +- // AOT-compatible deserialization using source generation +- if (paramType == typeof(string[])) +- return JsonSerializer.Deserialize(jsonArrayStr, BedrockFunctionResolverContext.Default.StringArray); +- if (paramType == typeof(int[])) +- return JsonSerializer.Deserialize(jsonArrayStr, BedrockFunctionResolverContext.Default.Int32Array); +- if (paramType == typeof(long[])) +- return JsonSerializer.Deserialize(jsonArrayStr, BedrockFunctionResolverContext.Default.Int64Array); +- if (paramType == typeof(double[])) +- return JsonSerializer.Deserialize(jsonArrayStr, BedrockFunctionResolverContext.Default.DoubleArray); +- if (paramType == typeof(bool[])) +- return JsonSerializer.Deserialize(jsonArrayStr, +- BedrockFunctionResolverContext.Default.BooleanArray); +- if (paramType == typeof(decimal[])) +- return JsonSerializer.Deserialize(jsonArrayStr, +- BedrockFunctionResolverContext.Default.DecimalArray); +- } +- catch (JsonException) +- { +- // Return null on error +- } +- +- return null; +- } +- +- private object? MapScalarParameter(Type paramType, string paramName, ParameterAccessor accessor) +- { +- if (paramType == typeof(string)) +- return accessor.Get(paramName); +- if (paramType == typeof(int)) +- return accessor.Get(paramName); +- if (paramType == typeof(long)) +- return accessor.Get(paramName); +- if (paramType == typeof(double)) +- return accessor.Get(paramName); +- if (paramType == typeof(bool)) +- return accessor.Get(paramName); +- if (paramType == typeof(decimal)) +- return accessor.Get(paramName); +- if (paramType == typeof(DateTime)) +- return accessor.Get(paramName); +- if (paramType == typeof(Guid)) +- return accessor.Get(paramName); +- if (paramType.IsEnum) +- { +- // For enums, get as string and parse +- var strValue = accessor.Get(paramName); +- return !string.IsNullOrEmpty(strValue) ? Enum.Parse(paramType, strValue) : null; +- } +- +- return null; +- } +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Helpers/ParameterTypeValidator.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Helpers/ParameterTypeValidator.cs +deleted file mode 100644 +index 064aa7d6..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Helpers/ParameterTypeValidator.cs ++++ /dev/null +@@ -1,35 +0,0 @@ +-namespace AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Helpers +-{ +- /// +- /// Validates parameter types for Bedrock Agent functions +- /// +- internal class ParameterTypeValidator +- { +- private static readonly HashSet BedrockParameterTypes = new() +- { +- typeof(string), +- typeof(int), +- typeof(long), +- typeof(double), +- typeof(bool), +- typeof(decimal), +- typeof(DateTime), +- typeof(Guid), +- typeof(string[]), +- typeof(int[]), +- typeof(long[]), +- typeof(double[]), +- typeof(bool[]), +- typeof(decimal[]) +- }; +- +- /// +- /// Checks if a type is a valid Bedrock parameter type +- /// +- /// The type to check +- /// True if the type is valid for Bedrock parameters +- public bool IsBedrockParameter(Type type) => +- BedrockParameterTypes.Contains(type) || type.IsEnum || +- (type.IsArray && BedrockParameterTypes.Contains(type.GetElementType()!)); +- } +-} +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Helpers/ResultConverter.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Helpers/ResultConverter.cs +deleted file mode 100644 +index 4a68f97e..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Helpers/ResultConverter.cs ++++ /dev/null +@@ -1,222 +0,0 @@ +-using System.Globalization; +-using Amazon.Lambda.Core; +-using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; +- +-namespace AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Helpers +-{ +- /// +- /// Converts handler results to BedrockFunctionResponse +- /// +- internal class ResultConverter +- { +- /// +- /// Processes results from handler functions and converts to BedrockFunctionResponse +- /// +- public BedrockFunctionResponse ProcessResult( +- object? result, +- BedrockFunctionRequest input, +- string functionName, +- ILambdaContext? context) +- { +- // Direct return for BedrockFunctionResponse +- if (result is BedrockFunctionResponse output) +- return EnsureResponseMetadata(output, input, functionName); +- +- // Handle async results with specific type checks (AOT-compatible) +- if (result is Task outputTask) +- return EnsureResponseMetadata(outputTask.Result, input, functionName); +- +- // Handle various Task types +- if (result is Task task) +- { +- return HandleTaskResult(task, input); +- } +- +- // Handle regular (non-task) results +- return ConvertToOutput(result, input); +- } +- +- private BedrockFunctionResponse HandleTaskResult(Task task, BedrockFunctionRequest input) +- { +- // For Task +- if (task is Task stringTask) +- return ConvertToOutput((TResult)(object)stringTask.Result, input); +- +- // For Task +- if (task is Task intTask) +- return ConvertToOutput((TResult)(object)intTask.Result, input); +- +- // For Task +- if (task is Task boolTask) +- return ConvertToOutput((TResult)(object)boolTask.Result, input); +- +- // For Task +- if (task is Task doubleTask) +- return ConvertToOutput((TResult)(object)doubleTask.Result, input); +- +- // For Task +- if (task is Task longTask) +- return ConvertToOutput((TResult)(object)longTask.Result, input); +- +- // For Task +- if (task is Task decimalTask) +- return ConvertToOutput((TResult)(object)decimalTask.Result, input); +- +- // For Task +- if (task is Task dateTimeTask) +- return ConvertToOutput((TResult)(object)dateTimeTask.Result, input); +- +- // For Task +- if (task is Task guidTask) +- return ConvertToOutput((TResult)(object)guidTask.Result, input); +- +- // For Task +- if (task is Task objectTask) +- return ConvertToOutput((TResult)objectTask.Result, input); +- +- // For regular Task with no result +- task.GetAwaiter().GetResult(); +- return BedrockFunctionResponse.WithText( +- string.Empty, +- input.ActionGroup, +- input.Function, +- input.SessionAttributes, +- input.PromptSessionAttributes, +- new Dictionary()); +- } +- +- /// +- /// Converts a result to a BedrockFunctionResponse +- /// +- public BedrockFunctionResponse ConvertToOutput(T result, BedrockFunctionRequest input) +- { +- var function = input.Function; +- +- if (EqualityComparer.Default.Equals(result, default(T))) +- { +- return CreateEmptyResponse(input); +- } +- +- // If result is already a BedrockFunctionResponse, ensure metadata is set +- if (result is BedrockFunctionResponse output) +- { +- return EnsureResponseMetadata(output, input, function); +- } +- +- // Handle primitive types +- return ConvertPrimitiveToOutput(result, input); +- } +- +- private BedrockFunctionResponse ConvertPrimitiveToOutput(T result, BedrockFunctionRequest input) +- { +- var actionGroup = input.ActionGroup; +- var function = input.Function; +- +- // For primitive types and strings, convert to string +- if (result is string str) +- { +- return BedrockFunctionResponse.WithText( +- str, +- actionGroup, +- function, +- input.SessionAttributes, +- input.PromptSessionAttributes, +- new Dictionary()); +- } +- +- if (result is int intVal) +- { +- return BedrockFunctionResponse.WithText( +- intVal.ToString(CultureInfo.InvariantCulture), +- actionGroup, +- function, +- input.SessionAttributes, +- input.PromptSessionAttributes, +- new Dictionary()); +- } +- +- if (result is double doubleVal) +- { +- return BedrockFunctionResponse.WithText( +- doubleVal.ToString(CultureInfo.InvariantCulture), +- actionGroup, +- function, +- input.SessionAttributes, +- input.PromptSessionAttributes, +- new Dictionary()); +- } +- +- if (result is bool boolVal) +- { +- return BedrockFunctionResponse.WithText( +- boolVal.ToString(), +- actionGroup, +- function, +- input.SessionAttributes, +- input.PromptSessionAttributes, +- new Dictionary()); +- } +- +- if (result is long longVal) +- { +- return BedrockFunctionResponse.WithText( +- longVal.ToString(CultureInfo.InvariantCulture), +- actionGroup, +- function, +- input.SessionAttributes, +- input.PromptSessionAttributes, +- new Dictionary()); +- } +- +- if (result is decimal decimalVal) +- { +- return BedrockFunctionResponse.WithText( +- decimalVal.ToString(CultureInfo.InvariantCulture), +- actionGroup, +- function, +- input.SessionAttributes, +- input.PromptSessionAttributes, +- new Dictionary()); +- } +- +- // For any other type, use ToString() +- return BedrockFunctionResponse.WithText( +- result?.ToString() ?? string.Empty, +- actionGroup, +- function, +- input.SessionAttributes, +- input.PromptSessionAttributes, +- new Dictionary()); +- } +- +- private BedrockFunctionResponse CreateEmptyResponse(BedrockFunctionRequest input) +- { +- return BedrockFunctionResponse.WithText( +- string.Empty, +- input.ActionGroup, +- input.Function, +- input.SessionAttributes, +- input.PromptSessionAttributes, +- new Dictionary()); +- } +- +- private BedrockFunctionResponse EnsureResponseMetadata( +- BedrockFunctionResponse response, +- BedrockFunctionRequest input, +- string functionName) +- { +- // If the action group or function are not set in the output, use the provided values +- if (string.IsNullOrEmpty(response.Response.ActionGroup)) +- { +- response.Response.ActionGroup = input.ActionGroup; +- } +- +- if (string.IsNullOrEmpty(response.Response.Function)) +- { +- response.Response.Function = functionName; +- } +- +- return response; +- } +- } +-} +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/InternalsVisibleTo.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/InternalsVisibleTo.cs +deleted file mode 100644 +index a4ee0e7a..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/InternalsVisibleTo.cs ++++ /dev/null +@@ -1,3 +0,0 @@ +-using System.Runtime.CompilerServices; +- +-[assembly: InternalsVisibleTo("AWS.Lambda.Powertools.EventHandler.Tests")] +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/Agent.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/Agent.cs +deleted file mode 100644 +index eb0a3eb4..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/Agent.cs ++++ /dev/null +@@ -1,33 +0,0 @@ +-using System.Text.Json.Serialization; +- +-namespace AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; +- +-/// +-/// Contains information about the name, ID, alias, and version of the agent that the action group belongs to. +-/// +-public class Agent +-{ +- /// +- /// Gets or sets the name of the agent. +- /// +- [JsonPropertyName("name")] +- public string Name { get; set; } = string.Empty; +- +- /// +- /// Gets or sets the version of the agent. +- /// +- [JsonPropertyName("version")] +- public string Version { get; set; } = string.Empty; +- +- /// +- /// Gets or sets the ID of the agent. +- /// +- [JsonPropertyName("id")] +- public string Id { get; set; } = string.Empty; +- +- /// +- /// Gets or sets the alias of the agent. +- /// +- [JsonPropertyName("alias")] +- public string Alias { get; set; } = string.Empty; +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/BedrockFunctionRequest.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/BedrockFunctionRequest.cs +deleted file mode 100644 +index 7f7e8ae7..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/BedrockFunctionRequest.cs ++++ /dev/null +@@ -1,64 +0,0 @@ +-using System.Text.Json.Serialization; +- +-namespace AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; +- +-/// +-/// Represents the input for a Bedrock Agent function. +-/// +-public class BedrockFunctionRequest +-{ +- /// +- /// The version of the message that identifies the format of the event data going into the Lambda function and the expected format of the response from a Lambda function. Amazon Bedrock only supports version 1.0. +- /// +- [JsonPropertyName("messageVersion")] +- public string MessageVersion { get; set; } = "1.0"; +- +- /// +- /// The name of the function as defined in the function details for the action group. +- /// +- [JsonPropertyName("function")] +- public string Function { get; set; } = string.Empty; +- +- /// +- /// Contains a list of objects. Each object contains the name, type, and value of a parameter in the API operation, as defined in the OpenAPI schema, or in the function. +- /// +- [JsonPropertyName("parameters")] +- public List Parameters { get; set; } = new List(); +- +- /// +- /// The unique identifier of the agent session. +- /// +- [JsonPropertyName("sessionId")] +- public string SessionId { get; set; } = string.Empty; +- +- /// +- /// Contains information about the name, ID, alias, and version of the agent that the action group belongs to. +- /// +- [JsonPropertyName("agent")] +- public Agent? Agent { get; set; } +- +- /// +- /// The name of the action group. +- /// +- [JsonPropertyName("actionGroup")] +- public string ActionGroup { get; set; } = string.Empty; +- +- /// +- /// Contains session attributes and their values. These attributes are stored over a session and provide context for the agent. +- /// For more information, see Session and prompt session attributes. +- /// +- [JsonPropertyName("sessionAttributes")] +- public Dictionary SessionAttributes { get; set; } = new Dictionary(); +- +- /// +- /// Contains prompt session attributes and their values. These attributes are stored over a turn and provide context for the agent. +- /// +- [JsonPropertyName("promptSessionAttributes")] +- public Dictionary PromptSessionAttributes { get; set; } = new Dictionary(); +- +- /// +- /// The user input for the conversation turn. +- /// +- [JsonPropertyName("inputText")] +- public string InputText { get; set; } = string.Empty; +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/BedrockFunctionResponse.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/BedrockFunctionResponse.cs +deleted file mode 100644 +index e606839c..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/BedrockFunctionResponse.cs ++++ /dev/null +@@ -1,71 +0,0 @@ +-using System.Text.Json.Serialization; +- +-namespace AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; +- +-/// +-/// The version of the message that identifies the format of the event data going into the Lambda function and the expected format of the response from a Lambda function. Amazon Bedrock only supports version 1.0. +-/// +-public class BedrockFunctionResponse +-{ +- /// +- /// Gets or sets the message version. +- /// +- [JsonPropertyName("messageVersion")] +- public string MessageVersion { get; } = "1.0"; +- +- /// +- /// Gets or sets the response. +- /// +- [JsonPropertyName("response")] +- public Response Response { get; set; } = new Response(); +- +- /// +- /// Contains session attributes and their values. For more information, Session and prompt session attributes. +- /// +- [JsonPropertyName("sessionAttributes")] +- public Dictionary SessionAttributes { get; set; } = new Dictionary(); +- +- /// +- /// Contains prompt attributes and their values. For more information, Session and prompt session attributes. +- /// +- [JsonPropertyName("promptSessionAttributes")] +- public Dictionary PromptSessionAttributes { get; set; } = new Dictionary(); +- +- /// +- /// Contains a list of query configurations for knowledge bases attached to the agent. For more information, Knowledge base retrieval configurations. +- /// +- [JsonPropertyName("knowledgeBasesConfiguration")] +- public Dictionary KnowledgeBasesConfiguration { get; set; } = new Dictionary(); +- +- +- /// +- /// Creates a new instance of BedrockFunctionResponse with the specified text. +- /// +- public static BedrockFunctionResponse WithText( +- string? text, +- string actionGroup = "", +- string function = "", +- Dictionary? sessionAttributes = null, +- Dictionary? promptSessionAttributes = null, +- Dictionary? knowledgeBasesConfiguration = null) +- { +- return new BedrockFunctionResponse +- { +- Response = new Response +- { +- ActionGroup = actionGroup, +- Function = function, +- FunctionResponse = new FunctionResponse +- { +- ResponseBody = new ResponseBody +- { +- Text = new TextBody { Body = text ?? string.Empty } +- } +- } +- }, +- SessionAttributes = sessionAttributes ?? new Dictionary(), +- PromptSessionAttributes = promptSessionAttributes ?? new Dictionary(), +- KnowledgeBasesConfiguration = knowledgeBasesConfiguration ?? new Dictionary() +- }; +- } +-} +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/FunctionResponse.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/FunctionResponse.cs +deleted file mode 100644 +index 5fd2a5d3..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/FunctionResponse.cs ++++ /dev/null +@@ -1,37 +0,0 @@ +-using System.Text.Json.Serialization; +-// ReSharper disable InconsistentNaming +-#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +- +-namespace AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; +- +-/// +-/// Represents the function response part of a Response. +-/// +-public class FunctionResponse +-{ +- /// +- /// Contains an object that defines the response from execution of the function. The key is the content type (currently only TEXT is supported) and the value is an object containing the body of the response. +- /// +- [JsonPropertyName("responseBody")] +- public ResponseBody ResponseBody { get; set; } = new ResponseBody(); +- +- /// +- /// (Optional) – Set to one of the following states to define the agent's behavior after processing the action: +- /// +- /// FAILURE – The agent throws a DependencyFailedException for the current session. Applies when the function execution fails because of a dependency failure. +- /// REPROMPT – The agent passes a response string to the model to reprompt it. Applies when the function execution fails because of invalid input. +- /// +- [JsonPropertyName("responseState")] +- [JsonConverter(typeof(JsonStringEnumConverter))] +- [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] +- public ResponseState? ResponseState { get; set; } +-} +- +-/// +-/// Represents the response state of a function response. +-/// +-public enum ResponseState +-{ +- FAILURE, +- REPROMPT +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/Parameter.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/Parameter.cs +deleted file mode 100644 +index 5e5a65ee..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/Parameter.cs ++++ /dev/null +@@ -1,29 +0,0 @@ +-using System.Text.Json.Serialization; +- +-// ReSharper disable once CheckNamespace +-namespace AWS.Lambda.Powertools.EventHandler.Resolvers +-{ +- /// +- /// Represents a parameter for a Bedrock Agent function. +- /// +- public class Parameter +- { +- /// +- /// Gets or sets the name of the parameter. +- /// +- [JsonPropertyName("name")] +- public string Name { get; set; } = string.Empty; +- +- /// +- /// Gets or sets the type of the parameter. +- /// +- [JsonPropertyName("type")] +- public string Type { get; set; } = string.Empty; +- +- /// +- /// Gets or sets the value of the parameter. +- /// +- [JsonPropertyName("value")] +- public string Value { get; set; } = string.Empty; +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/Response.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/Response.cs +deleted file mode 100644 +index 5d2e76a7..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/Response.cs ++++ /dev/null +@@ -1,27 +0,0 @@ +-using System.Text.Json.Serialization; +- +-namespace AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; +- +-/// +-/// Represents the response part of an BedrockFunctionResponse. +-/// +-public class Response +-{ +- /// +- /// Gets or sets the action group. +- /// +- [JsonPropertyName("actionGroup")] +- public string ActionGroup { get; internal set; } = string.Empty; +- +- /// +- /// Gets or sets the function. +- /// +- [JsonPropertyName("function")] +- public string Function { get; internal set; } = string.Empty; +- +- /// +- /// Gets or sets the function response. +- /// +- [JsonPropertyName("functionResponse")] +- public FunctionResponse FunctionResponse { get; set; } = new FunctionResponse(); +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/ResponseBody.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/ResponseBody.cs +deleted file mode 100644 +index 20bc59c2..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/ResponseBody.cs ++++ /dev/null +@@ -1,15 +0,0 @@ +-using System.Text.Json.Serialization; +- +-namespace AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; +- +-/// +-/// Represents the response body part of a FunctionResponse. +-/// +-public class ResponseBody +-{ +- /// +- /// Gets or sets the text body. +- /// +- [JsonPropertyName("TEXT")] +- public TextBody Text { get; set; } = new TextBody(); +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/TextBody.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/TextBody.cs +deleted file mode 100644 +index 8e9a41c7..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/TextBody.cs ++++ /dev/null +@@ -1,15 +0,0 @@ +-using System.Text.Json.Serialization; +- +-namespace AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; +- +-/// +-/// Represents the text body part of a ResponseBody. +-/// +-public class TextBody +-{ +- /// +- /// Gets or sets the body text. +- /// +- [JsonPropertyName("body")] +- public string Body { get; set; } = string.Empty; +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler/AWS.Lambda.Powertools.EventHandler.csproj b/libraries/src/AWS.Lambda.Powertools.EventHandler/AWS.Lambda.Powertools.EventHandler.csproj +deleted file mode 100644 +index 04f632fe..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler/AWS.Lambda.Powertools.EventHandler.csproj ++++ /dev/null +@@ -1,21 +0,0 @@ +- +- +- +- +- AWS.Lambda.Powertools.EventHandler +- Powertools for AWS Lambda (.NET) - Event Handler package. +- AWS.Lambda.Powertools.EventHandler +- AWS.Lambda.Powertools.EventHandler +- net8.0 +- false +- enable +- enable +- true +- +- +- +- +- +- +- +- +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncCognitoIdentity.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncCognitoIdentity.cs +deleted file mode 100644 +index e59f7949..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncCognitoIdentity.cs ++++ /dev/null +@@ -1,42 +0,0 @@ +-namespace AWS.Lambda.Powertools.EventHandler.AppSyncEvents; +- +-/// +-/// Represents Amazon Cognito User Pools authorization identity for AppSync +-/// +-public class AppSyncCognitoIdentity +-{ +- /// +- /// The source IP address of the caller received by AWS AppSync +- /// +- public List? SourceIp { get; set; } +- +- /// +- /// The username of the authenticated user +- /// +- public string? Username { get; set; } +- +- /// +- /// The UUID of the authenticated user +- /// +- public string? Sub { get; set; } +- +- /// +- /// The claims that the user has +- /// +- public Dictionary? Claims { get; set; } +- +- /// +- /// The default authorization strategy for this caller (ALLOW or DENY) +- /// +- public string? DefaultAuthStrategy { get; set; } +- +- /// +- /// List of OIDC groups +- /// +- public List? Groups { get; set; } +- +- /// +- /// The token issuer +- /// +- public string? Issuer { get; set; } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncEvent.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncEvent.cs +deleted file mode 100644 +index 9ed03423..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncEvent.cs ++++ /dev/null +@@ -1,29 +0,0 @@ +-using System.Text.Json.Serialization; +- +-namespace AWS.Lambda.Powertools.EventHandler.AppSyncEvents; +- +-/// +-/// Represents an event from AWS AppSync. +-/// +-public class AppSyncEvent +-{ +- /// +- /// Payload data when operation succeeds +- /// +- [JsonPropertyName("payload")] +- public Dictionary? Payload { get; set; } +- +- /// +- /// Error message when operation fails +- /// +- [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] +- [JsonPropertyName("error")] +- public string? Error { get; set; } +- +- /// +- /// Unique identifier for the event +- /// This Id is provided by AppSync and needs to be preserved. +- /// +- [JsonPropertyName("id")] +- public string? Id { get; set; } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncEventsOperation.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncEventsOperation.cs +deleted file mode 100644 +index ffb970dd..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncEventsOperation.cs ++++ /dev/null +@@ -1,20 +0,0 @@ +-using System.Text.Json.Serialization; +- +-namespace AWS.Lambda.Powertools.EventHandler.AppSyncEvents; +- +-/// +-/// Represents the operation type for AppSync events. +-/// +-[JsonConverter(typeof(JsonStringEnumConverter))] +-public enum AppSyncEventsOperation +-{ +- /// +- /// Represents a subscription operation. +- /// +- Subscribe, +- +- /// +- /// Represents a publish operation. +- /// +- Publish +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncEventsRequest.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncEventsRequest.cs +deleted file mode 100644 +index 46097d44..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncEventsRequest.cs ++++ /dev/null +@@ -1,74 +0,0 @@ +-using System.Text.Json.Serialization; +- +-namespace AWS.Lambda.Powertools.EventHandler.AppSyncEvents; +- +-/// +-/// Represents the event payload received from AWS AppSync. +-/// +-public class AppSyncEventsRequest +-{ +- /// +- /// An object that contains information about the caller. +- /// Returns null for API_KEY authorization. +- /// Returns AppSyncIamIdentity for AWS_IAM authorization. +- /// Returns AppSyncCognitoIdentity for AMAZON_COGNITO_USER_POOLS authorization. +- /// For AWS_LAMBDA authorization, returns the object returned by your Lambda authorizer function. +- /// +- /// +- /// The Identity object type depends on the authorization mode: +- /// - For API_KEY: null +- /// - For AWS_IAM: +- /// - For AMAZON_COGNITO_USER_POOLS: +- /// - For AWS_LAMBDA: +- /// - For OPENID_CONNECT: +- /// +- public object? Identity { get; set; } +- +- /// +- /// Gets or sets information about the data source that originated the event. +- /// +- [JsonPropertyName("source")] +- public object? Source { get; set; } +- +- /// +- /// Gets or sets information about the HTTP request that triggered the event. +- /// +- [JsonPropertyName("request")] +- public RequestContext? Request { get; set; } +- +- /// +- /// Gets or sets information about the previous state of the data before the operation was executed. +- /// +- [JsonPropertyName("prev")] +- public object? Prev { get; set; } +- +- /// +- /// Gets or sets information about the GraphQL operation being executed. +- /// +- [JsonPropertyName("info")] +- public Information? Info { get; set; } +- +- /// +- /// Gets or sets additional information that can be passed between Lambda functions during an AppSync pipeline. +- /// +- [JsonPropertyName("stash")] +- public Dictionary? Stash { get; set; } +- +- /// +- /// The error message when the operation fails. +- /// +- [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] +- [JsonPropertyName("error")] +- public string? Error { get; set; } +- +- /// +- /// The list of error message when the operation fails. +- /// +- public object[]? OutErrors { get; set; } +- +- /// +- /// The list of events sent. +- /// +- [JsonPropertyName("events")] +- public AppSyncEvent[]? Events { get; set; } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncEventsResolver.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncEventsResolver.cs +deleted file mode 100644 +index 09356b64..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncEventsResolver.cs ++++ /dev/null +@@ -1,552 +0,0 @@ +-using Amazon.Lambda.Core; +-using AWS.Lambda.Powertools.Common; +-using AWS.Lambda.Powertools.EventHandler.Internal; +- +-namespace AWS.Lambda.Powertools.EventHandler.AppSyncEvents; +- +-/// +-/// Resolver for AWS AppSync Events APIs. +-/// Handles onPublish and onSubscribe events from AppSync Events APIs, +-/// routing them to appropriate handlers based on path. +-/// +-public class AppSyncEventsResolver +-{ +- private readonly RouteHandlerRegistry _publishRoutes; +- private readonly RouteHandlerRegistry _subscribeRoutes; +- +- /// +- /// Initializes a new instance of the class. +- /// +- public AppSyncEventsResolver() +- { +- _publishRoutes = new RouteHandlerRegistry(); +- _subscribeRoutes = new RouteHandlerRegistry(); +- PowertoolsEnvironment.Instance.SetExecutionEnvironment(this); +- } +- +- #region OnPublish Methods +- +- +- /// +- /// Registers a sync handler for publish events on a specific channel path. +- /// +- /// The channel path to handle +- /// Sync handler without context +- public AppSyncEventsResolver OnPublish(string path, Func, object> handler) +- { +- RegisterPublishHandler(path, handler, false); +- return this; +- } +- +- /// +- /// Registers a sync handler with Lambda context for publish events on a specific channel path. +- /// +- /// The channel path to handle +- /// Sync handler with context +- public AppSyncEventsResolver OnPublish(string path, Func, ILambdaContext, object> handler) +- { +- RegisterPublishHandler(path, handler, false); +- return this; +- } +- +- #endregion +- +- #region OnPublishAsync Methods +- +- /// +- /// Explicitly registers an async handler for publish events on a specific channel path. +- /// Use this method when you want to clearly indicate that your handler is asynchronous. +- /// +- /// The channel path to handle +- /// Async handler without context +- public AppSyncEventsResolver OnPublishAsync(string path, Func, Task> handler) +- { +- RegisterPublishHandler(path, handler, false); +- return this; +- } +- +- /// +- /// Explicitly registers an async handler with Lambda context for publish events on a specific channel path. +- /// Use this method when you want to clearly indicate that your handler is asynchronous. +- /// +- /// The channel path to handle +- /// Async handler with context +- public AppSyncEventsResolver OnPublishAsync(string path, Func, ILambdaContext, Task> handler) +- { +- RegisterPublishHandler(path, handler, false); +- return this; +- } +- +- #endregion +- +- #region OnPublishAggregate Methods +- +- /// +- /// Registers a sync aggregate handler for publish events on a specific channel path. +- /// +- /// The channel path to handle +- /// Sync aggregate handler without context +- public AppSyncEventsResolver OnPublishAggregate(string path, Func handler) +- { +- RegisterAggregateHandler(path, handler); +- return this; +- } +- +- /// +- /// Registers a sync aggregate handler with Lambda context for publish events on a specific channel path. +- /// +- /// The channel path to handle +- /// Sync aggregate handler with context +- public AppSyncEventsResolver OnPublishAggregate(string path, Func handler) +- { +- RegisterAggregateHandler(path, handler); +- return this; +- } +- +- #endregion +- +- #region OnPublishAggregateAsync Methods +- +- /// +- /// Explicitly registers an async aggregate handler for publish events on a specific channel path. +- /// Use this method when you want to clearly indicate that your handler is asynchronous. +- /// +- /// The channel path to handle +- /// Async aggregate handler without context +- public AppSyncEventsResolver OnPublishAggregateAsync(string path, Func> handler) +- { +- RegisterAggregateHandler(path, handler); +- return this; +- } +- +- /// +- /// Explicitly registers an async aggregate handler with Lambda context for publish events on a specific channel path. +- /// Use this method when you want to clearly indicate that your handler is asynchronous. +- /// +- /// The channel path to handle +- /// Async aggregate handler with context +- public AppSyncEventsResolver OnPublishAggregateAsync(string path, Func> handler) +- { +- RegisterAggregateHandler(path, handler); +- return this; +- } +- +- #endregion +- +- #region OnSubscribe Methods +- +- /// +- /// Registers a sync handler for subscription events on a specific channel path. +- /// +- /// The channel path to handle +- /// Sync subscription handler without context +- public AppSyncEventsResolver OnSubscribe(string path, Func handler) +- { +- RegisterSubscribeHandler(path, handler); +- return this; +- } +- +- /// +- /// Registers a sync handler with Lambda context for subscription events on a specific channel path. +- /// +- /// The channel path to handle +- /// Sync subscription handler with context +- public AppSyncEventsResolver OnSubscribe(string path, Func handler) +- { +- RegisterSubscribeHandler(path, handler); +- return this; +- } +- +- #endregion +- +- #region OnSubscribeAsync Methods +- +- /// +- /// Explicitly registers an async handler for subscription events on a specific channel path. +- /// Use this method when you want to clearly indicate that your handler is asynchronous. +- /// +- /// The channel path to handle +- /// Async subscription handler without context +- public AppSyncEventsResolver OnSubscribeAsync(string path, Func> handler) +- { +- RegisterSubscribeHandler(path, handler); +- return this; +- } +- +- /// +- /// Explicitly registers an async handler with Lambda context for subscription events on a specific channel path. +- /// Use this method when you want to clearly indicate that your handler is asynchronous. +- /// +- /// The channel path to handle +- /// Async subscription handler with context +- public AppSyncEventsResolver OnSubscribeAsync(string path, Func> handler) +- { +- RegisterSubscribeHandler(path, handler); +- return this; +- } +- +- #endregion +- +- #region Handler Registration Methods +- +- private void RegisterPublishHandler(string path, Func, Task> handler, bool aggregate) +- { +- _publishRoutes.Register(new RouteHandlerOptions +- { +- Path = path, +- Handler = async (evt, _) => +- { +- var payload = evt.Events?.FirstOrDefault()?.Payload; +- return await handler(payload ?? new Dictionary()); +- }, +- Aggregate = aggregate +- }); +- } +- +- private void RegisterPublishHandler(string path, Func, ILambdaContext, Task> handler, bool aggregate) +- { +- _publishRoutes.Register(new RouteHandlerOptions +- { +- Path = path, +- Handler = async (evt, ctx) => +- { +- var payload = evt.Events?.FirstOrDefault()?.Payload; +- return await handler(payload ?? new Dictionary(), ctx); +- }, +- Aggregate = aggregate +- }); +- } +- +- private void RegisterPublishHandler(string path, Func, object> handler, bool aggregate) +- { +- _publishRoutes.Register(new RouteHandlerOptions +- { +- Path = path, +- Handler = (evt, _) => +- { +- var payload = evt.Events?.FirstOrDefault()?.Payload; +- return Task.FromResult(handler(payload ?? new Dictionary())); +- }, +- Aggregate = aggregate +- }); +- } +- +- private void RegisterPublishHandler(string path, Func, ILambdaContext, object> handler, bool aggregate) +- { +- _publishRoutes.Register(new RouteHandlerOptions +- { +- Path = path, +- Handler = (evt, ctx) => +- { +- var payload = evt.Events?.FirstOrDefault()?.Payload; +- return Task.FromResult(handler(payload ?? new Dictionary(), ctx)); +- }, +- Aggregate = aggregate +- }); +- } +- +- private void RegisterAggregateHandler(string path, Func> handler) +- { +- _publishRoutes.Register(new RouteHandlerOptions +- { +- Path = path, +- Handler = async (evt, _) => await handler(evt), +- Aggregate = true +- }); +- } +- +- private void RegisterAggregateHandler(string path, Func> handler) +- { +- _publishRoutes.Register(new RouteHandlerOptions +- { +- Path = path, +- Handler = async (evt, ctx) => await handler(evt, ctx), +- Aggregate = true +- }); +- } +- +- private void RegisterAggregateHandler(string path, Func handler) +- { +- _publishRoutes.Register(new RouteHandlerOptions +- { +- Path = path, +- Handler = (evt, _) => Task.FromResult((object)handler(evt)), +- Aggregate = true +- }); +- } +- +- private void RegisterAggregateHandler(string path, Func handler) +- { +- _publishRoutes.Register(new RouteHandlerOptions +- { +- Path = path, +- Handler = (evt, ctx) => Task.FromResult((object)handler(evt, ctx)), +- Aggregate = true +- }); +- } +- +- private void RegisterSubscribeHandler(string path, Func> handler) +- { +- _subscribeRoutes.Register(new RouteHandlerOptions +- { +- Path = path, +- Handler = async (evt, _) => await handler(evt) +- }); +- } +- +- private void RegisterSubscribeHandler(string path, Func> handler) +- { +- _subscribeRoutes.Register(new RouteHandlerOptions +- { +- Path = path, +- Handler = async (evt, ctx) => await handler(evt, ctx) +- }); +- } +- +- private void RegisterSubscribeHandler(string path, Func handler) +- { +- _subscribeRoutes.Register(new RouteHandlerOptions +- { +- Path = path, +- Handler = (evt, _) => Task.FromResult(handler(evt)) +- }); +- } +- +- private void RegisterSubscribeHandler(string path, Func handler) +- { +- _subscribeRoutes.Register(new RouteHandlerOptions +- { +- Path = path, +- Handler = (evt, ctx) => Task.FromResult(handler(evt, ctx)) +- }); +- } +- +- #endregion +- +- /// +- /// Resolves and processes an AppSync event through the registered handlers. +- /// +- /// The AppSync event to process +- /// Lambda execution context +- /// Response containing processed events or error information +- public AppSyncEventsResponse Resolve(AppSyncEventsRequest appsyncEvent, ILambdaContext context) +- { +- return ResolveAsync(appsyncEvent, context).GetAwaiter().GetResult(); +- } +- +- /// +- /// Resolves and processes an AppSync event through the registered handlers. +- /// +- /// The AppSync event to process +- /// Lambda execution context +- /// Response containing processed events or error information +- public async Task ResolveAsync(AppSyncEventsRequest appsyncEvent, ILambdaContext context) +- { +- if (IsPublishEvent(appsyncEvent)) +- { +- return await HandlePublishEvent(appsyncEvent, context); +- } +- +- if (IsSubscribeEvent(appsyncEvent)) +- { +- return (await HandleSubscribeEvent(appsyncEvent, context))!; +- } +- +- throw new InvalidOperationException("Unknown event type"); +- } +- +- private async Task HandlePublishEvent(AppSyncEventsRequest appsyncEvent, +- ILambdaContext context) +- { +- var channelPath = appsyncEvent.Info?.Channel?.Path; +- var handlerOptions = _publishRoutes.ResolveFirst(channelPath); +- +- context.Logger.LogInformation($"Resolving publish event for path: {channelPath}"); +- +- if (handlerOptions == null) +- { +- // Return unchanged events if no handler found +- var events = appsyncEvent.Events? +- .Select(e => new AppSyncEvent +- { +- Id = e.Id, +- Payload = e.Payload +- }) +- .ToList(); +- return new AppSyncEventsResponse { Events = events }; +- } +- +- var results = new List(); +- +- if (handlerOptions.Aggregate) +- { +- try +- { +- // Process entire event in one call +- var handlerResult = await handlerOptions.Handler(appsyncEvent, context); +- if (handlerResult is AppSyncEventsResponse { Events: not null } result) +- { +- return result; +- } +- +- // Handle unexpected return type +- return new AppSyncEventsResponse +- { +- Error = "Handler returned an invalid response type" +- }; +- } +- catch (UnauthorizedException) +- { +- throw; +- } +- catch (Exception ex) +- { +- return new AppSyncEventsResponse +- { +- Error = $"{ex.GetType().Name} - {ex.Message}" +- }; +- } +- } +- else +- { +- // Process each event individually +- if (appsyncEvent.Events == null) return new AppSyncEventsResponse { Events = results }; +- foreach (var eventItem in appsyncEvent.Events) +- { +- try +- { +- var result = await handlerOptions.Handler( +- new AppSyncEventsRequest +- { +- Info = appsyncEvent.Info, +- Events = [eventItem] +- }, context); +- +- var payload = ConvertToPayload(result, out var error); +- if (error != null) +- { +- results.Add(new AppSyncEvent +- { +- Id = eventItem.Id, +- Error = error +- }); +- } +- else +- { +- results.Add(new AppSyncEvent +- { +- Id = eventItem.Id, +- Payload = payload +- }); +- } +- } +- catch (UnauthorizedException) +- { +- throw; +- } +- catch (Exception ex) +- { +- results.Add(FormatErrorResponse(ex, eventItem.Id!)); +- } +- } +- } +- +- return new AppSyncEventsResponse { Events = results }; +- } +- +- /// +- /// Handles subscription events. +- /// Returns null on success, error response on failure. +- /// +- private async Task HandleSubscribeEvent(AppSyncEventsRequest appsyncEvent, +- ILambdaContext context) +- { +- var channelPath = appsyncEvent.Info?.Channel?.Path; +- var channelBase = $"/{appsyncEvent.Info?.Channel?.Segments?[0]}"; +- +- // Find matching subscribe handler +- var subscribeHandler = _subscribeRoutes.ResolveFirst(channelPath); +- if (subscribeHandler == null) +- { +- return null; +- } +- +- // Check if there's ANY publish handler for the base channel namespace +- bool hasAnyPublishHandler = _publishRoutes.GetAllHandlers() +- .Any(h => h.Path.StartsWith(channelBase)); +- +- if (!hasAnyPublishHandler) +- { +- return null; +- } +- +- try +- { +- var result = await subscribeHandler.Handler(appsyncEvent, context); +- return !result ? new AppSyncEventsResponse { Error = "Subscription failed" } : null; +- } +- catch (UnauthorizedException) +- { +- throw; +- } +- catch (Exception ex) +- { +- context.Logger.LogLine($"Error in subscribe handler: {ex.Message}"); +- return new AppSyncEventsResponse { Error = ex.Message }; +- } +- } +- +- private Dictionary? ConvertToPayload(object result, out string? error) +- { +- error = null; +- +- // Check if this is an error result from ProcessSingleEvent +- if (result is Dictionary dict && dict.ContainsKey("error")) +- { +- error = dict["error"].ToString(); +- return null; // No payload when there's an error +- } +- +- // Regular payload handling +- if (result is Dictionary payload) +- { +- return payload; +- } +- +- return new Dictionary { ["data"] = result }; +- } +- +- private AppSyncEvent FormatErrorResponse(Exception ex, string id) +- { +- return new AppSyncEvent +- { +- Id = id, +- Error = $"{ex.GetType().Name} - {ex.Message}" +- }; +- } +- +- private bool IsPublishEvent(AppSyncEventsRequest appsyncEvent) +- { +- return appsyncEvent.Info?.Operation == AppSyncEventsOperation.Publish; +- } +- +- private bool IsSubscribeEvent(AppSyncEventsRequest appsyncEvent) +- { +- return appsyncEvent.Info?.Operation == AppSyncEventsOperation.Subscribe; +- } +-} +- +-/// +-/// Exception thrown when subscription validation fails. +-/// This exception causes the Lambda invocation to fail, returning an error to AppSync. +-/// +-public class UnauthorizedException : Exception +-{ +- /// +- /// Initializes a new instance of the class. +- /// +- /// The error message +- public UnauthorizedException(string message) : base(message) +- { +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncEventsResponse.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncEventsResponse.cs +deleted file mode 100644 +index 7069cba5..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncEventsResponse.cs ++++ /dev/null +@@ -1,23 +0,0 @@ +-using System.Text.Json.Serialization; +- +-namespace AWS.Lambda.Powertools.EventHandler.AppSyncEvents; +- +-/// +-/// Represents the response for AppSync events. +-/// +-public class AppSyncEventsResponse +-{ +- /// +- /// Collection of event results +- /// +- [JsonPropertyName("events")] +- [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] +- public List? Events { get; set; } +- +- /// +- /// When operation fails, this will contain the error message +- /// +- [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] +- [JsonPropertyName("error")] +- public string? Error { get; set; } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncIamIdentity.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncIamIdentity.cs +deleted file mode 100644 +index f2cf0a17..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncIamIdentity.cs ++++ /dev/null +@@ -1,47 +0,0 @@ +-namespace AWS.Lambda.Powertools.EventHandler.AppSyncEvents; +- +-/// +-/// Represents AWS IAM authorization identity for AppSync +-/// +-public class AppSyncIamIdentity +-{ +- /// +- /// The source IP address of the caller received by AWS AppSync +- /// +- public List? SourceIp { get; set; } +- +- /// +- /// The username of the authenticated user (IAM user principal) +- /// +- public string? Username { get; set; } +- +- /// +- /// The AWS account ID of the caller +- /// +- public string? AccountId { get; set; } +- +- /// +- /// The Amazon Cognito identity pool ID associated with the caller +- /// +- public string? CognitoIdentityPoolId { get; set; } +- +- /// +- /// The Amazon Cognito identity ID of the caller +- /// +- public string? CognitoIdentityId { get; set; } +- +- /// +- /// The ARN of the IAM user +- /// +- public string? UserArn { get; set; } +- +- /// +- /// Either authenticated or unauthenticated based on the identity type +- /// +- public string? CognitoIdentityAuthType { get; set; } +- +- /// +- /// A comma separated list of external identity provider information used in obtaining the credentials used to sign the request +- /// +- public string? CognitoIdentityAuthProvider { get; set; } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncLambdaIdentity.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncLambdaIdentity.cs +deleted file mode 100644 +index 996aa9a0..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncLambdaIdentity.cs ++++ /dev/null +@@ -1,13 +0,0 @@ +-namespace AWS.Lambda.Powertools.EventHandler.AppSyncEvents; +- +-/// +-/// Represents AWS Lambda authorization identity for AppSync +-/// +-public class AppSyncLambdaIdentity +-{ +- /// +- /// Optional context information that will be passed to subsequent resolvers +- /// Can contain user information, claims, or any other contextual data +- /// +- public Dictionary? ResolverContext { get; set; } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncOidcIdentity.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncOidcIdentity.cs +deleted file mode 100644 +index 8d06db2e..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncOidcIdentity.cs ++++ /dev/null +@@ -1,22 +0,0 @@ +-namespace AWS.Lambda.Powertools.EventHandler.AppSyncEvents; +- +-/// +-/// Represents OpenID Connect authorization identity for AppSync +-/// +-public class AppSyncOidcIdentity +-{ +- /// +- /// Claims from the OIDC token as key-value pairs +- /// +- public Dictionary? Claims { get; set; } +- +- /// +- /// The issuer of the OIDC token +- /// +- public string? Issuer { get; set; } +- +- /// +- /// The UUID of the authenticated user +- /// +- public string? Sub { get; set; } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncRequestContext.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncRequestContext.cs +deleted file mode 100644 +index 3fe5681d..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncRequestContext.cs ++++ /dev/null +@@ -1,40 +0,0 @@ +-namespace AWS.Lambda.Powertools.EventHandler.AppSyncEvents; +- +-/// +-/// Contains contextual information about the AppSync request being authorized. +-/// This class provides details about the API, account, and GraphQL operation. +-/// +-public class AppSyncRequestContext +-{ +- /// +- /// Gets or sets the unique identifier of the AppSync API. +- /// +- public string? ApiId { get; set; } +- +- /// +- /// Gets or sets the AWS account ID where the AppSync API is deployed. +- /// +- public string? AccountId { get; set; } +- +- /// +- /// Gets or sets the unique identifier for this specific request. +- /// +- public string? RequestId { get; set; } +- +- /// +- /// Gets or sets the GraphQL query string containing the operation to be executed. +- /// +- public string? QueryString { get; set; } +- +- /// +- /// Gets or sets the name of the GraphQL operation to be executed. +- /// This corresponds to the operation name in the GraphQL query. +- /// +- public string? OperationName { get; set; } +- +- /// +- /// Gets or sets the variables passed to the GraphQL operation. +- /// Contains key-value pairs of variable names and their values. +- /// +- public Dictionary? Variables { get; set; } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/Channel.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/Channel.cs +deleted file mode 100644 +index 156c736a..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/Channel.cs ++++ /dev/null +@@ -1,21 +0,0 @@ +-using System.Text.Json.Serialization; +- +-namespace AWS.Lambda.Powertools.EventHandler.AppSyncEvents; +- +-/// +-/// Channel details including path and segments +-/// +-public class Channel +-{ +- /// +- /// Provides direct access to the 'Path' attribute within the 'Channel' object. +- /// +- [JsonPropertyName("path")] +- public string? Path { get; set; } +- +- /// +- /// Provides direct access to the 'Segments' attribute within the 'Channel' object. +- /// +- [JsonPropertyName("segments")] +- public string[]? Segments { get; set; } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/ChannelNamespace.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/ChannelNamespace.cs +deleted file mode 100644 +index 9bcc5e6e..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/ChannelNamespace.cs ++++ /dev/null +@@ -1,15 +0,0 @@ +-using System.Text.Json.Serialization; +- +-namespace AWS.Lambda.Powertools.EventHandler.AppSyncEvents; +- +-/// +-/// Namespace configuration for the channel +-/// +-public class ChannelNamespace +-{ +- /// +- /// Name of the channel namespace +- /// +- [JsonPropertyName("name")] +- public string? Name { get; set; } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/Information.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/Information.cs +deleted file mode 100644 +index 79c62a05..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/Information.cs ++++ /dev/null +@@ -1,26 +0,0 @@ +-using System.Text.Json.Serialization; +- +-namespace AWS.Lambda.Powertools.EventHandler.AppSyncEvents; +- +-/// +-/// Represents information about the AppSync event. +-/// +-public class Information +-{ +- /// +- /// The channel being used for the operation +- /// +- [JsonPropertyName("channel")] +- public Channel? Channel { get; set; } +- +- /// +- /// The namespace of the channel +- /// +- public ChannelNamespace? ChannelNamespace { get; set; } +- +- /// +- /// The operation being performed (e.g., Publish, Subscribe) +- /// +- [JsonPropertyName("operation")] +- public AppSyncEventsOperation Operation { get; set; } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/RequestContext.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/RequestContext.cs +deleted file mode 100644 +index 1c289354..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/RequestContext.cs ++++ /dev/null +@@ -1,17 +0,0 @@ +-namespace AWS.Lambda.Powertools.EventHandler.AppSyncEvents; +- +-/// +-/// Represents information about the HTTP request that triggered the event. +-/// +-public class RequestContext +-{ +- /// +- /// Gets or sets the headers of the HTTP request. +- /// +- public Dictionary Headers { get; set; } = new(); +- +- /// +- /// Gets or sets the domain name associated with the request. +- /// +- public string? DomainName { get; set; } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler/Internal/LRUCache.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler/Internal/LRUCache.cs +deleted file mode 100644 +index 37fa4663..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler/Internal/LRUCache.cs ++++ /dev/null +@@ -1,74 +0,0 @@ +-namespace AWS.Lambda.Powertools.EventHandler.Internal; +- +-/// +-/// Basic LRU cache implementation +-/// +-/// +-/// Simple LRU cache implementation for caching route resolutions +-/// +-internal class LruCache where TKey : notnull +-{ +- private readonly int _capacity; +- private readonly Dictionary> _cache; +- private readonly LinkedList _lruList; +- +- internal class CacheItem +- { +- public TKey Key { get; } +- public TValue Value { get; } +- +- public CacheItem(TKey key, TValue value) +- { +- Key = key; +- Value = value; +- } +- } +- +- public LruCache(int capacity) +- { +- _capacity = capacity; +- _cache = new Dictionary>(); +- _lruList = new LinkedList(); +- } +- +- public bool TryGet(TKey key, out TValue? value) +- { +- if (_cache.TryGetValue(key, out var node)) +- { +- // Move to the front of the list (most recently used) +- _lruList.Remove(node); +- _lruList.AddFirst(node); +- value = node.Value.Value; +- return true; +- } +- +- value = default; +- return false; +- } +- +- public void Set(TKey key, TValue value) +- { +- if (_cache.TryGetValue(key, out var existingNode)) +- { +- _lruList.Remove(existingNode); +- _cache.Remove(key); +- } +- else if (_cache.Count >= _capacity) +- { +- // Remove least recently used item +- var lastNode = _lruList.Last; +- _lruList.RemoveLast(); +- if (lastNode != null) _cache.Remove(lastNode.Value.Key); +- } +- +- var newNode = new LinkedListNode(new CacheItem(key, value)); +- _lruList.AddFirst(newNode); +- _cache[key] = newNode; +- } +- +- public void Clear() +- { +- _cache.Clear(); +- _lruList.Clear(); +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler/Internal/RouteHandlerOptions.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler/Internal/RouteHandlerOptions.cs +deleted file mode 100644 +index 06cb2a2a..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler/Internal/RouteHandlerOptions.cs ++++ /dev/null +@@ -1,24 +0,0 @@ +-using Amazon.Lambda.Core; +- +-namespace AWS.Lambda.Powertools.EventHandler.Internal; +- +-/// +-/// Options for registering a route handler +-/// +-internal class RouteHandlerOptions +-{ +- /// +- /// The path pattern to match against (e.g., "/default/*") +- /// +- public string Path { get; set; } = "/default/*"; +- +- /// +- /// The handler function to execute when path matches +- /// +- public required Func> Handler { get; set; } +- +- /// +- /// Whether to aggregate all events into a single handler call +- /// +- public bool Aggregate { get; set; } = false; +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler/Internal/RouteHandlerRegistry.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler/Internal/RouteHandlerRegistry.cs +deleted file mode 100644 +index 78c8ffe2..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler/Internal/RouteHandlerRegistry.cs ++++ /dev/null +@@ -1,141 +0,0 @@ +-namespace AWS.Lambda.Powertools.EventHandler.Internal; +- +-/// +-/// Registry for storing route handlers for path-based routing operations. +-/// Handles path matching, caching, and handler resolution. +-/// +-internal class RouteHandlerRegistry +-{ +- /// +- /// Dictionary of registered handlers +- /// +- private readonly Dictionary> _resolvers = new(); +- +- /// +- /// Cache for resolved routes to improve performance +- /// +- private readonly LruCache> _resolverCache; +- +- /// +- /// Set to track already logged warnings +- /// +- private readonly HashSet _warnedPaths = new(); +- +- /// +- /// Initialize a new registry for route handlers +- /// +- /// Max size of LRU cache (default 100) +- public RouteHandlerRegistry(int cacheSize = 100) +- { +- _resolverCache = new LruCache>(cacheSize); +- } +- +- /// +- /// Register a handler for a specific path pattern. +- /// +- /// Options for the route handler +- public void Register(RouteHandlerOptions options) +- { +- if (!IsValidPath(options.Path)) +- { +- LogWarning($"The path \"{options.Path}\" is not valid and will be skipped. " + +- "Wildcards are allowed only at the end of the path."); +- return; +- } +- +- // Clear cache when registering new handlers +- _resolverCache.Clear(); +- _resolvers[options.Path] = options; +- } +- +- /// +- /// Find the most specific handler for a given path. +- /// +- /// The path to match against registered routes +- /// Most specific matching handler or null if no match +- public RouteHandlerOptions? ResolveFirst(string? path) +- { +- if (path != null && _resolverCache.TryGet(path, out var cachedHandler)) +- { +- return cachedHandler; +- } +- +- // First try for exact match +- if (path != null && _resolvers.TryGetValue(path, out var exactMatch)) +- { +- _resolverCache.Set(path, exactMatch); +- return exactMatch; +- } +- +- // Then try wildcard matches, sorted by specificity (most segments first) +- var wildcardMatches = _resolvers.Keys +- .Where(pattern => path != null && IsWildcardMatch(pattern, path)) +- .OrderByDescending(pattern => pattern.Count(c => c == '/')) +- .ThenByDescending(pattern => pattern.Length); +- +- var bestMatch = wildcardMatches.FirstOrDefault(); +- +- if (bestMatch != null) +- { +- var handler = _resolvers[bestMatch]; +- if (path != null) _resolverCache.Set(path, handler); +- return handler; +- } +- +- return null; +- } +- +- /// +- /// Get all registered handlers +- /// +- public IEnumerable> GetAllHandlers() +- { +- return _resolvers.Values; +- } +- +- /// +- /// Check if a path pattern is valid according to routing rules. +- /// +- private static bool IsValidPath(string path) +- { +- if (string.IsNullOrWhiteSpace(path) || !path.StartsWith('/')) +- return false; +- +- // Check for invalid wildcard usage +- return !path.Contains("*/"); +- } +- +- /// +- /// Check if a wildcard pattern matches the given path +- /// +- private bool IsWildcardMatch(string pattern, string path) +- { +- if (!pattern.Contains('*')) +- return pattern == path; +- +- var patternSegments = pattern.Split('/'); +- var pathSegments = path.Split('/'); +- +- if (patternSegments.Length > pathSegments.Length) +- return false; +- +- for (var i = 0; i < patternSegments.Length; i++) +- { +- // If we've reached the wildcard segment, it matches the rest +- if (patternSegments[i] == "*") +- return true; +- +- // Otherwise, segments must match exactly +- if (patternSegments[i] != pathSegments[i]) +- return false; +- } +- +- return patternSegments.Length == pathSegments.Length; +- } +- +- private void LogWarning(string message) +- { +- if (!_warnedPaths.Add(message)) return; +- Console.WriteLine($"Warning: {message}"); +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler/InternalsVisibleTo.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler/InternalsVisibleTo.cs +deleted file mode 100644 +index a4ee0e7a..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.EventHandler/InternalsVisibleTo.cs ++++ /dev/null +@@ -1,3 +0,0 @@ +-using System.Runtime.CompilerServices; +- +-[assembly: InternalsVisibleTo("AWS.Lambda.Powertools.EventHandler.Tests")] +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Idempotency/Exceptions/IdempotencyItemAlreadyExistsException.cs b/libraries/src/AWS.Lambda.Powertools.Idempotency/Exceptions/IdempotencyItemAlreadyExistsException.cs +index 55144bff..1603bba7 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Idempotency/Exceptions/IdempotencyItemAlreadyExistsException.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Idempotency/Exceptions/IdempotencyItemAlreadyExistsException.cs +@@ -14,7 +14,6 @@ + */ + + using System; +-using AWS.Lambda.Powertools.Idempotency.Persistence; + + namespace AWS.Lambda.Powertools.Idempotency.Exceptions; + +@@ -23,11 +22,6 @@ namespace AWS.Lambda.Powertools.Idempotency.Exceptions; + /// + public class IdempotencyItemAlreadyExistsException : Exception + { +- /// +- /// The record that already exists in the persistence layer. +- /// +- public DataRecord Record { get; set; } +- + /// + /// Creates a new IdempotencyItemAlreadyExistsException + /// +diff --git a/libraries/src/AWS.Lambda.Powertools.Idempotency/Idempotency.cs b/libraries/src/AWS.Lambda.Powertools.Idempotency/Idempotency.cs +index 3e953e73..2e626fb3 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Idempotency/Idempotency.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Idempotency/Idempotency.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.Text.Json.Serialization; + using Amazon.Lambda.Core; +@@ -166,7 +181,6 @@ public sealed class Idempotency + return this; + } + +-#if NET8_0_OR_GREATER + /// + /// Set Customer JsonSerializerContext to append to IdempotencySerializationContext + /// +@@ -177,6 +191,5 @@ public sealed class Idempotency + IdempotencySerializer.AddTypeInfoResolver(context); + return this; + } +-#endif + } + } +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Idempotency/IdempotencyOptions.cs b/libraries/src/AWS.Lambda.Powertools.Idempotency/IdempotencyOptions.cs +index df9645d9..ed1b4a82 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Idempotency/IdempotencyOptions.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Idempotency/IdempotencyOptions.cs +@@ -13,8 +13,6 @@ + * permissions and limitations under the License. + */ + +-using System; +- + namespace AWS.Lambda.Powertools.Idempotency; + + /// +@@ -59,24 +57,32 @@ public class IdempotencyOptions + /// as supported by (eg. SHA1, SHA-256, ...) + /// + public string HashFunction { get; } +- /// +- /// Delegate for manipulating idempotent responses. +- /// +- public Func ResponseHook { get; } + + /// + /// Constructor of . + /// +- /// The builder containing the configuration values +- internal IdempotencyOptions(IdempotencyOptionsBuilder builder) ++ /// ++ /// ++ /// ++ /// ++ /// ++ /// ++ /// ++ internal IdempotencyOptions( ++ string eventKeyJmesPath, ++ string payloadValidationJmesPath, ++ bool throwOnNoIdempotencyKey, ++ bool useLocalCache, ++ int localCacheMaxItems, ++ long expirationInSeconds, ++ string hashFunction) + { +- EventKeyJmesPath = builder.EventKeyJmesPath; +- PayloadValidationJmesPath = builder.PayloadValidationJmesPath; +- ThrowOnNoIdempotencyKey = builder.ThrowOnNoIdempotencyKey; +- UseLocalCache = builder.UseLocalCache; +- LocalCacheMaxItems = builder.LocalCacheMaxItems; +- ExpirationInSeconds = builder.ExpirationInSeconds; +- HashFunction = builder.HashFunction; +- ResponseHook = builder.ResponseHook; ++ EventKeyJmesPath = eventKeyJmesPath; ++ PayloadValidationJmesPath = payloadValidationJmesPath; ++ ThrowOnNoIdempotencyKey = throwOnNoIdempotencyKey; ++ UseLocalCache = useLocalCache; ++ LocalCacheMaxItems = localCacheMaxItems; ++ ExpirationInSeconds = expirationInSeconds; ++ HashFunction = hashFunction; + } + } +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Idempotency/IdempotencyOptionsBuilder.cs b/libraries/src/AWS.Lambda.Powertools.Idempotency/IdempotencyOptionsBuilder.cs +index 55b3141b..3c437d33 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Idempotency/IdempotencyOptionsBuilder.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Idempotency/IdempotencyOptionsBuilder.cs +@@ -43,59 +43,21 @@ public class IdempotencyOptionsBuilder + private string _hashFunction = "MD5"; + + /// +- /// Response hook function +- /// +- private Func _responseHook; +- +- /// +- /// Gets the event key JMESPath expression. +- /// +- internal string EventKeyJmesPath => _eventKeyJmesPath; +- +- /// +- /// Gets the payload validation JMESPath expression. +- /// +- internal string PayloadValidationJmesPath => _payloadValidationJmesPath; +- +- /// +- /// Gets whether to throw exception if no idempotency key is found. +- /// +- internal bool ThrowOnNoIdempotencyKey => _throwOnNoIdempotencyKey; +- +- /// +- /// Gets whether local cache is enabled. +- /// +- internal bool UseLocalCache => _useLocalCache; +- +- /// +- /// Gets the maximum number of items in the local cache. +- /// +- internal int LocalCacheMaxItems => _localCacheMaxItems; +- +- /// +- /// Gets the expiration in seconds. +- /// +- internal long ExpirationInSeconds => _expirationInSeconds; +- +- /// +- /// Gets the hash function. +- /// +- internal string HashFunction => _hashFunction; +- +- /// +- /// Gets the response hook function. +- /// +- internal Func ResponseHook => _responseHook; +- +- /// +- /// Initialize and return an instance of IdempotencyOptions. ++ /// Initialize and return an instance of IdempotencyConfig. + /// Example: +- /// new IdempotencyOptionsBuilder().WithUseLocalCache().Build(); +- /// This instance can then be passed to Idempotency.Configure: +- /// Idempotency.Configure(builder => builder.WithOptions(options)); ++ /// IdempotencyConfig.Builder().WithUseLocalCache().Build(); ++ /// This instance must then be passed to the Idempotency.Config: ++ /// Idempotency.Config().WithConfig(config).Configure(); + /// +- /// an instance of IdempotencyOptions +- public IdempotencyOptions Build() => new(this); ++ /// an instance of IdempotencyConfig ++ public IdempotencyOptions Build() => ++ new(_eventKeyJmesPath, ++ _payloadValidationJmesPath, ++ _throwOnNoIdempotencyKey, ++ _useLocalCache, ++ _localCacheMaxItems, ++ _expirationInSeconds, ++ _hashFunction); + + /// + /// A JMESPath expression to extract the idempotency key from the event record. +@@ -160,26 +122,9 @@ public class IdempotencyOptionsBuilder + /// + /// Can be any algorithm supported by HashAlgorithm.Create + /// the instance of the builder (to chain operations) +-#if NET8_0_OR_GREATER + [Obsolete("Idempotency uses MD5 and does not support other hash algorithms.")] +-#endif + public IdempotencyOptionsBuilder WithHashFunction(string hashFunction) + { +-#if NET6_0 +- // for backward compability keep this code in .net 6 +- _hashFunction = hashFunction; +-#endif +- return this; +- } +- +- /// +- /// Set a response hook function, to be called with the response and the data record. +- /// +- /// The response hook function +- /// the instance of the builder (to chain operations) +- public IdempotencyOptionsBuilder WithResponseHook(Func hook) +- { +- _responseHook = hook; + return this; + } + } +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/IdempotencyAspectHandler.cs b/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/IdempotencyAspectHandler.cs +index e8b67475..a8d7da73 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/IdempotencyAspectHandler.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/IdempotencyAspectHandler.cs +@@ -106,25 +106,9 @@ internal class IdempotencyAspectHandler + // already exists. If it succeeds, there's no need to call getRecord. + await _persistenceStore.SaveInProgress(_data, DateTimeOffset.UtcNow, GetRemainingTimeInMillis()); + } +- catch (IdempotencyItemAlreadyExistsException ex) ++ catch (IdempotencyItemAlreadyExistsException) + { +- DataRecord record; +- +- if(ex.Record != null) +- { +- // If the error includes the existing record, we can use it to validate +- // the record being processed and cache it in memory. +- var existingRecord = _persistenceStore.ProcessExistingRecord(ex.Record, _data); +- record = existingRecord; +- } +- else +- { +- // If the error doesn't include the existing record, we need to fetch +- // it from the persistence layer. In doing so, we also call the processExistingRecord +- // method to validate the record and cache it in memory. +- record = await GetIdempotencyRecord(); +- } +- ++ var record = await GetIdempotencyRecord(); + return await HandleForStatus(record); + } + catch (IdempotencyKeyException) +@@ -208,24 +192,6 @@ internal class IdempotencyAspectHandler + { + throw new IdempotencyPersistenceLayerException("Unable to cast function response as " + typeof(T).Name); + } +- // Response hook logic +- var responseHook = Idempotency.Instance.IdempotencyOptions?.ResponseHook; +- if (responseHook != null) +- { +- try +- { +- var hooked = responseHook(result, record); +- if (hooked is T typedHooked) +- { +- return Task.FromResult(typedHooked); +- } +- // If hook returns wrong type, fallback to original result +- } +- catch (Exception) +- { +- // If hook throws, fallback to original result +- } +- } + return Task.FromResult(result); + } + catch (Exception e) +diff --git a/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/Serializers/IdempotencySerializationContext.cs b/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/Serializers/IdempotencySerializationContext.cs +index 6f77b9cf..4b96bb64 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/Serializers/IdempotencySerializationContext.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/Serializers/IdempotencySerializationContext.cs +@@ -17,8 +17,6 @@ using System.Text.Json.Serialization; + + namespace AWS.Lambda.Powertools.Idempotency.Internal.Serializers; + +-#if NET8_0_OR_GREATER +- + + /// + /// The source generated JsonSerializerContext to be used to Serialize Idempotency types +@@ -28,5 +26,4 @@ namespace AWS.Lambda.Powertools.Idempotency.Internal.Serializers; + public partial class IdempotencySerializationContext : JsonSerializerContext + { + +-} +-#endif +\ No newline at end of file ++} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/Serializers/IdempotencySerializer.cs b/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/Serializers/IdempotencySerializer.cs +index 975a47f4..b7f98419 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/Serializers/IdempotencySerializer.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/Serializers/IdempotencySerializer.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.Diagnostics.CodeAnalysis; + using System.Runtime.Serialization; +@@ -29,22 +44,18 @@ internal static class IdempotencySerializer + { + PropertyNameCaseInsensitive = true + }; +-#if NET8_0_OR_GREATER + if (!RuntimeFeatureWrapper.IsDynamicCodeSupported) + { + _jsonOptions.TypeInfoResolverChain.Add(IdempotencySerializationContext.Default); + } +-#endif + } + +-#if NET8_0_OR_GREATER +- + /// + /// Adds a JsonTypeInfoResolver to the JsonSerializerOptions. + /// + /// The JsonTypeInfoResolver to add. + /// +- /// This method is only available in .NET 8.0 and later versions. ++ /// This method is available in .NET 8.0 and later versions. + /// + internal static void AddTypeInfoResolver(JsonSerializerContext context) + { +@@ -73,7 +84,6 @@ internal static class IdempotencySerializer + { + _jsonOptions = options; + } +-#endif + + /// + /// Serializes the specified object to a JSON string. +@@ -83,9 +93,6 @@ internal static class IdempotencySerializer + /// A JSON string representation of the object. + internal static string Serialize(object value, Type inputType) + { +-#if NET6_0 +- return JsonSerializer.Serialize(value, _jsonOptions); +-#else + if (RuntimeFeatureWrapper.IsDynamicCodeSupported) + { + #pragma warning disable +@@ -100,7 +107,6 @@ internal static class IdempotencySerializer + } + + return JsonSerializer.Serialize(value, typeInfo); +-#endif + } + + /// +@@ -113,15 +119,11 @@ internal static class IdempotencySerializer + [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "False positive")] + internal static T Deserialize(string value) + { +-#if NET6_0 +- return JsonSerializer.Deserialize(value,_jsonOptions); +-#else + if (RuntimeFeatureWrapper.IsDynamicCodeSupported) + { + return JsonSerializer.Deserialize(value, _jsonOptions); + } + + return (T)JsonSerializer.Deserialize(value, GetTypeInfo(typeof(T))); +-#endif + } + } +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Idempotency/Persistence/BasePersistenceStore.cs b/libraries/src/AWS.Lambda.Powertools.Idempotency/Persistence/BasePersistenceStore.cs +index 07a19913..e2006b0c 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Idempotency/Persistence/BasePersistenceStore.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Idempotency/Persistence/BasePersistenceStore.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.Security.Cryptography; + using System.Text; +@@ -324,12 +339,8 @@ public abstract class BasePersistenceStore : IPersistenceStore + /// + internal string GenerateHash(JsonElement data) + { +-#if NET8_0_OR_GREATER + // starting .NET 8 no option to change hash algorithm + using var hashAlgorithm = MD5.Create(); +-#else +- using var hashAlgorithm = HashAlgorithm.Create(_idempotencyOptions.HashFunction); +-#endif + if (hashAlgorithm == null) + { + throw new ArgumentException("Invalid HashAlgorithm"); +@@ -378,20 +389,4 @@ public abstract class BasePersistenceStore : IPersistenceStore + + /// + public abstract Task DeleteRecord(string idempotencyKey); +- +- /// +- /// Validates an existing record against the data payload being processed. +- /// If the payload does not match the stored record, an `IdempotencyValidationError` error is thrown. +- /// Whenever a record is retrieved from the persistence layer, it should be validated against the data payload +- /// being processed. This is to ensure that the data payload being processed is the same as the one that was +- /// used to create the record in the first place. +- /// +- /// The record is also saved to the local cache if local caching is enabled. +- /// +- public virtual DataRecord ProcessExistingRecord(DataRecord exRecord, JsonDocument data) +- { +- ValidatePayload(data, exRecord); +- SaveToCache(exRecord); +- return exRecord; +- } + } +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Idempotency/Persistence/DynamoDBPersistenceStore.cs b/libraries/src/AWS.Lambda.Powertools.Idempotency/Persistence/DynamoDBPersistenceStore.cs +index 34860ea8..9b8cf500 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Idempotency/Persistence/DynamoDBPersistenceStore.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Idempotency/Persistence/DynamoDBPersistenceStore.cs +@@ -191,7 +191,6 @@ public class DynamoDBPersistenceStore : BasePersistenceStore + Item = item, + ConditionExpression = "attribute_not_exists(#id) OR #expiry < :now OR (attribute_exists(#in_progress_expiry) AND #in_progress_expiry < :now_milliseconds AND #status = :inprogress)", + ExpressionAttributeNames = expressionAttributeNames, +- ReturnValuesOnConditionCheckFailure = ReturnValuesOnConditionCheckFailure.ALL_OLD, + ExpressionAttributeValues = new Dictionary + { + {":now", new AttributeValue {N = now.ToUnixTimeSeconds().ToString()}}, +@@ -203,20 +202,8 @@ public class DynamoDBPersistenceStore : BasePersistenceStore + } + catch (ConditionalCheckFailedException e) + { +- var ex = new IdempotencyItemAlreadyExistsException( ++ throw new IdempotencyItemAlreadyExistsException( + "Failed to put record for already existing idempotency key: " + record.IdempotencyKey, e); +- +- if (e.Item != null) +- { +- ex.Record = new DataRecord(e.Item[_keyAttr].S, +- Enum.Parse(e.Item[_statusAttr].S), +- long.Parse(e.Item[_expiryAttr].N), +- e.Item.TryGetValue(_dataAttr, out var data) ? data?.S : null, +- e.Item.TryGetValue(_validationAttr, out var validation) ? validation?.S : null, +- e.Item.TryGetValue(_inProgressExpiryAttr, out var inProgExp) ? long.Parse(inProgExp.N) : null); +- } +- +- throw ex; + } + } + +@@ -370,9 +357,9 @@ public class DynamoDBPersistenceStoreBuilder + private AmazonDynamoDBClient _dynamoDbClient; + + /// +- /// Initialize and return a new instance of . ++ /// Initialize and return a new instance of {@link DynamoDBPersistenceStore}. + /// Example: +- /// new DynamoDBPersistenceStoreBuilder().WithTableName("idempotency_store").Build(); ++ /// DynamoDBPersistenceStore.builder().withTableName("idempotency_store").build(); + /// + /// + /// +diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Serializers/JMESPathSerializationContext.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Serializers/JMESPathSerializationContext.cs +index ecd26686..cb158f88 100644 +--- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Serializers/JMESPathSerializationContext.cs ++++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Serializers/JMESPathSerializationContext.cs +@@ -1,10 +1,23 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System.Text.Json; + using System.Text.Json.Serialization; + + namespace AWS.Lambda.Powertools.JMESPath.Serializers; + +-#if NET8_0_OR_GREATER +- + /// + /// The source generated JsonSerializerContext to be used to Serialize JMESPath types + /// +@@ -17,5 +30,3 @@ namespace AWS.Lambda.Powertools.JMESPath.Serializers; + public partial class JmesPathSerializationContext : JsonSerializerContext + { + } +- +-#endif +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Serializers/JMESPathSerializer.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Serializers/JMESPathSerializer.cs +index 79f1b903..3886a54c 100644 +--- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Serializers/JMESPathSerializer.cs ++++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Serializers/JMESPathSerializer.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.Text.Json; + +@@ -16,12 +31,7 @@ internal static class JmesPathSerializer + /// System.String. + internal static string Serialize(object value, Type inputType) + { +-#if NET6_0 +- return JsonSerializer.Serialize(value); +-#else +- + return JsonSerializer.Serialize(value, inputType, JmesPathSerializationContext.Default); +-#endif + } + + /// +@@ -32,11 +42,6 @@ internal static class JmesPathSerializer + /// T. + internal static T Deserialize(string value) + { +-#if NET6_0 +- return JsonSerializer.Deserialize(value); +-#else +- + return (T)JsonSerializer.Deserialize(value, typeof(T), JmesPathSerializationContext.Default); +-#endif + } + } +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Kafka.Avro/AWS.Lambda.Powertools.Kafka.Avro.csproj b/libraries/src/AWS.Lambda.Powertools.Kafka.Avro/AWS.Lambda.Powertools.Kafka.Avro.csproj +deleted file mode 100644 +index bb074161..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Kafka.Avro/AWS.Lambda.Powertools.Kafka.Avro.csproj ++++ /dev/null +@@ -1,31 +0,0 @@ +- +- +- +- +- +- AWS.Lambda.Powertools.Kafka.Avro +- Powertools for AWS Lambda (.NET) - Kafka Avro consumer package. +- AWS.Lambda.Powertools.Kafka.Avro +- AWS.Lambda.Powertools.Kafka.Avro +- net8.0 +- false +- enable +- enable +- +- +- +- +- true +- $(DefineConstants);KAFKA_AVRO +- +- +- +- +- +- +- +- +- +- +- +- +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Kafka.Avro/PowertoolsKafkaAvroSerializer.cs b/libraries/src/AWS.Lambda.Powertools.Kafka.Avro/PowertoolsKafkaAvroSerializer.cs +deleted file mode 100644 +index 6c2b2aea..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Kafka.Avro/PowertoolsKafkaAvroSerializer.cs ++++ /dev/null +@@ -1,98 +0,0 @@ +-using System.Diagnostics.CodeAnalysis; +-using System.Reflection; +-using System.Text.Json; +-using System.Text.Json.Serialization; +-using Avro; +-using Avro.IO; +-using Avro.Specific; +- +-namespace AWS.Lambda.Powertools.Kafka.Avro; +- +-/// +-/// A Lambda serializer for Kafka events that handles Avro-formatted data. +-/// This serializer automatically deserializes the Avro binary format from base64-encoded strings +-/// in Kafka records and converts them to strongly-typed objects. +-/// +-/// +-/// +-/// [assembly: LambdaSerializer(typeof(PowertoolsKafkaAvroSerializer))] +-/// +-/// // Your Lambda handler will receive properly deserialized objects +-/// public class Function +-/// { +-/// public void Handler(ConsumerRecords<string, Customer> records, ILambdaContext context) +-/// { +-/// foreach (var record in records) +-/// { +-/// Customer customer = record.Value; +-/// context.Logger.LogInformation($"Processed customer {customer.Name}, age {customer.Age}"); +-/// } +-/// } +-/// } +-/// +-/// +-public class PowertoolsKafkaAvroSerializer : PowertoolsKafkaSerializerBase +-{ +- /// +- /// Initializes a new instance of the class +- /// with default JSON serialization options. +- /// +- public PowertoolsKafkaAvroSerializer() : base() +- { +- } +- +- /// +- /// Initializes a new instance of the class +- /// with custom JSON serialization options. +- /// +- /// Custom JSON serializer options to use during deserialization. +- public PowertoolsKafkaAvroSerializer(JsonSerializerOptions jsonOptions) : base(jsonOptions) +- { +- } +- +- /// +- /// Initializes a new instance of the class +- /// with a JSON serializer context for AOT-compatible serialization. +- /// +- /// JSON serializer context for AOT compatibility. +- public PowertoolsKafkaAvroSerializer(JsonSerializerContext serializerContext) : base(serializerContext) +- { +- } +- +- /// +- /// Deserializes complex (non-primitive) types using Avro format. +- /// Requires types to have a public static _SCHEMA field. +- /// +- [RequiresDynamicCode("Avro deserialization might require runtime code generation.")] +- [RequiresUnreferencedCode("Avro deserialization might require types that cannot be statically analyzed.")] +- protected override object? DeserializeComplexTypeFormat(byte[] data, +- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] +- Type targetType, bool isKey, SchemaMetadata? schemaMetadata = null) +- { +- var schema = GetAvroSchema(targetType); +- if (schema == null) +- { +- throw new InvalidOperationException( +- $"Unsupported type for Avro deserialization: {targetType.Name}. " + +- "Avro deserialization requires a type with a static _SCHEMA field. " + +- "Consider using an alternative Deserializer."); +- } +- +- using var stream = new MemoryStream(data); +- var decoder = new BinaryDecoder(stream); +- var reader = new SpecificDatumReader(schema, schema); +- return reader.Read(null!, decoder); +- } +- +- /// +- /// Gets the Avro schema for the specified type from its static _SCHEMA field. +- /// +- [RequiresDynamicCode("Avro schema access requires reflection.")] +- [RequiresUnreferencedCode("Avro schema access requires reflection.")] +- private Schema? GetAvroSchema( +- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] Type payloadType) +- { +- var schemaField = payloadType.GetField("_SCHEMA", BindingFlags.Public | BindingFlags.Static); +- return schemaField?.GetValue(null) as Schema; +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Kafka.Json/AWS.Lambda.Powertools.Kafka.Json.csproj b/libraries/src/AWS.Lambda.Powertools.Kafka.Json/AWS.Lambda.Powertools.Kafka.Json.csproj +deleted file mode 100644 +index 3c5ec81c..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Kafka.Json/AWS.Lambda.Powertools.Kafka.Json.csproj ++++ /dev/null +@@ -1,26 +0,0 @@ +- +- +- +- +- AWS.Lambda.Powertools.Kafka.Json +- Powertools for AWS Lambda (.NET) - Kafka Json consumer package. +- AWS.Lambda.Powertools.Kafka.Json +- AWS.Lambda.Powertools.Kafka.Json +- net8.0 +- false +- enable +- enable +- +- +- +- +- true +- $(DefineConstants);KAFKA_JSON +- +- +- +- +- +- +- +- +diff --git a/libraries/src/AWS.Lambda.Powertools.Kafka.Json/PowertoolsKafkaJsonSerializer.cs b/libraries/src/AWS.Lambda.Powertools.Kafka.Json/PowertoolsKafkaJsonSerializer.cs +deleted file mode 100644 +index 3e3979ad..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Kafka.Json/PowertoolsKafkaJsonSerializer.cs ++++ /dev/null +@@ -1,73 +0,0 @@ +-using System.Diagnostics.CodeAnalysis; +-using System.Text; +-using System.Text.Json; +-using System.Text.Json.Serialization; +- +-namespace AWS.Lambda.Powertools.Kafka.Json; +- +-/// +-/// A Lambda serializer for Kafka events that handles JSON-formatted data. +-/// This serializer automatically deserializes the JSON format from base64-encoded strings +-/// in Kafka records and converts them to strongly-typed objects. +-/// +-public class PowertoolsKafkaJsonSerializer : PowertoolsKafkaSerializerBase +-{ +- /// +- /// Initializes a new instance of the class +- /// with default JSON serialization options. +- /// +- public PowertoolsKafkaJsonSerializer() : base() +- { +- } +- +- /// +- /// Initializes a new instance of the class +- /// with custom JSON serialization options. +- /// +- /// Custom JSON serializer options to use during deserialization. +- public PowertoolsKafkaJsonSerializer(JsonSerializerOptions jsonOptions) : base(jsonOptions) +- { +- } +- +- /// +- /// Initializes a new instance of the class +- /// with a JSON serializer context for AOT-compatible serialization. +- /// +- /// JSON serializer context for AOT compatibility. +- public PowertoolsKafkaJsonSerializer(JsonSerializerContext serializerContext) : base(serializerContext) +- { +- } +- +- /// +- /// Deserializes complex (non-primitive) types using JSON format. +- /// +- [RequiresDynamicCode("JSON deserialization might require runtime code generation.")] +- [RequiresUnreferencedCode("JSON deserialization might require types that cannot be statically analyzed.")] +- protected override object? DeserializeComplexTypeFormat(byte[] data, +- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | +- DynamicallyAccessedMemberTypes.PublicFields)] +- Type targetType, bool isKey, SchemaMetadata? schemaMetadata = null) +- { +- if (data == null || data.Length == 0) +- { +- return targetType.IsValueType ? Activator.CreateInstance(targetType) : null; +- } +- +- var jsonStr = Encoding.UTF8.GetString(data); +- +- // Try context-based deserialization first +- if (SerializerContext != null) +- { +- var typeInfo = SerializerContext.GetTypeInfo(targetType); +- if (typeInfo != null) +- { +- return JsonSerializer.Deserialize(jsonStr, typeInfo); +- } +- } +- +- // Fallback to regular deserialization +-#pragma warning disable IL2026, IL3050 +- return JsonSerializer.Deserialize(jsonStr, targetType, JsonOptions); +-#pragma warning restore IL2026, IL3050 +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Kafka.Protobuf/AWS.Lambda.Powertools.Kafka.Protobuf.csproj b/libraries/src/AWS.Lambda.Powertools.Kafka.Protobuf/AWS.Lambda.Powertools.Kafka.Protobuf.csproj +deleted file mode 100644 +index eef17873..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Kafka.Protobuf/AWS.Lambda.Powertools.Kafka.Protobuf.csproj ++++ /dev/null +@@ -1,31 +0,0 @@ +- +- +- +- +- AWS.Lambda.Powertools.Kafka.Protobuf +- Powertools for AWS Lambda (.NET) - Kafka Protobuf consumer package. +- AWS.Lambda.Powertools.Kafka.Protobuf +- AWS.Lambda.Powertools.Kafka.Protobuf +- net8.0 +- false +- enable +- enable +- +- +- +- +- true +- $(DefineConstants);KAFKA_PROTOBUF +- +- +- +- +- +- +- +- +- +- +- +- +- +diff --git a/libraries/src/AWS.Lambda.Powertools.Kafka.Protobuf/PowertoolsKafkaProtobufSerializer.cs b/libraries/src/AWS.Lambda.Powertools.Kafka.Protobuf/PowertoolsKafkaProtobufSerializer.cs +deleted file mode 100644 +index 2cd7f759..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Kafka.Protobuf/PowertoolsKafkaProtobufSerializer.cs ++++ /dev/null +@@ -1,168 +0,0 @@ +-using System.Diagnostics.CodeAnalysis; +-using System.Reflection; +-using System.Text.Json; +-using System.Text.Json.Serialization; +-using Google.Protobuf; +- +- +-namespace AWS.Lambda.Powertools.Kafka.Protobuf; +- +-/// +-/// A Lambda serializer for Kafka events that handles Protobuf-formatted data. +-/// This serializer automatically deserializes the Protobuf binary format from base64-encoded strings +-/// in Kafka records and converts them to strongly-typed objects. +-/// +-/// +-/// +-/// [assembly: LambdaSerializer(typeof(PowertoolsKafkaProtobufSerializer))] +-/// +-/// // Your Lambda handler will receive properly deserialized objects +-/// public class Function +-/// { +-/// public void Handler(ConsumerRecords<string, Customer> records, ILambdaContext context) +-/// { +-/// foreach (var record in records) +-/// { +-/// Customer customer = record.Value; +-/// context.Logger.LogInformation($"Processed customer {customer.Name}"); +-/// } +-/// } +-/// } +-/// +-/// +-public class PowertoolsKafkaProtobufSerializer : PowertoolsKafkaSerializerBase +-{ +- /// +- /// Initializes a new instance of the class +- /// with default JSON serialization options. +- /// +- public PowertoolsKafkaProtobufSerializer() : base() +- { +- } +- +- /// +- /// Initializes a new instance of the class +- /// with custom JSON serialization options. +- /// +- /// Custom JSON serializer options to use during deserialization. +- public PowertoolsKafkaProtobufSerializer(JsonSerializerOptions jsonOptions) : base(jsonOptions) +- { +- } +- +- /// +- /// Initializes a new instance of the class +- /// with a JSON serializer context for AOT-compatible serialization. +- /// +- /// JSON serializer context for AOT compatibility. +- public PowertoolsKafkaProtobufSerializer(JsonSerializerContext serializerContext) : base(serializerContext) +- { +- } +- +- /// +- /// Deserializes complex (non-primitive) types using Protobuf format. +- /// Handles different parsing strategies based on schema metadata: +- /// - No schema ID: Pure Protobuf deserialization +- /// - UUID schema ID (16+ chars): Glue format - removes magic uint32 +- /// - Short schema ID (≤10 chars): Confluent format - removes message indexes +- /// +- [RequiresDynamicCode("Protobuf deserialization might require runtime code generation.")] +- [RequiresUnreferencedCode("Protobuf deserialization might require types that cannot be statically analyzed.")] +- protected override object? DeserializeComplexTypeFormat(byte[] data, +- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | +- DynamicallyAccessedMemberTypes.PublicFields)] +- Type targetType, bool isKey, SchemaMetadata? schemaMetadata = null) +- { +- if (!typeof(IMessage).IsAssignableFrom(targetType)) +- { +- throw new InvalidOperationException( +- $"Unsupported type for Protobuf deserialization: {targetType.Name}. " + +- "Protobuf deserialization requires a type that implements IMessage. " + +- "Consider using an alternative Deserializer."); +- } +- +- var parser = GetProtobufParser(targetType); +- if (parser == null) +- { +- throw new InvalidOperationException($"Could not find Protobuf parser for type {targetType.Name}"); +- } +- +- return DeserializeByStrategy(data, parser, schemaMetadata); +- } +- +- /// +- /// Deserializes protobuf data using the appropriate strategy based on schema metadata. +- /// +- private IMessage DeserializeByStrategy(byte[] data, MessageParser parser, SchemaMetadata? schemaMetadata) +- { +- var schemaId = schemaMetadata?.SchemaId; +- +- if (string.IsNullOrEmpty(schemaId)) +- { +- // Pure protobuf - no preprocessing needed +- return parser.ParseFrom(data); +- } +- +- if (schemaId.Length > 10) +- { +- // Glue Schema Registry - remove magic uint32 +- return DeserializeGlueFormat(data, parser); +- } +- +- // Confluent Schema Registry - remove message indexes +- return DeserializeConfluentFormat(data, parser); +- } +- +- /// +- /// Deserializes Glue Schema Registry format by removing the magic uint32. +- /// +- private IMessage DeserializeGlueFormat(byte[] data, MessageParser parser) +- { +- using var inputStream = new MemoryStream(data); +- using var codedInput = new CodedInputStream(inputStream); +- +- codedInput.ReadUInt32(); // Skip magic bytes +- return parser.ParseFrom(codedInput); +- } +- +- /// +- /// Deserializes Confluent Schema Registry format by removing message indexes. +- /// Based on Java reference implementation. +- /// +- private IMessage DeserializeConfluentFormat(byte[] data, MessageParser parser) +- { +- using var inputStream = new MemoryStream(data); +- using var codedInput = new CodedInputStream(inputStream); +- +- /* +- ReadSInt32() behavior: +- ReadSInt32() properly handles signed varint encoding using ZigZag encoding +- ZigZag encoding maps signed integers to unsigned integers: (n << 1) ^ (n >> 31) +- This allows both positive and negative numbers to be efficiently encoded +- The key insight is that Confluent Schema Registry uses signed varint encoding for the message index count, not unsigned length encoding. +- The ByteUtils.readVarint() in Java typically reads signed varints, which corresponds to ReadSInt32() in C# Google.Protobuf. +- */ +- +- // Read number of message indexes +- var indexCount = codedInput.ReadSInt32(); +- +- // Skip message indexes if any exist +- if (indexCount > 0) +- { +- for (int i = 0; i < indexCount; i++) +- { +- codedInput.ReadSInt32(); // Read and discard each index +- } +- } +- +- return parser.ParseFrom(codedInput); +- } +- +- /// +- /// Gets the Protobuf parser for the specified type. +- /// +- private MessageParser? GetProtobufParser(Type messageType) +- { +- var parserProperty = messageType.GetProperty("Parser", BindingFlags.Public | BindingFlags.Static); +- return parserProperty?.GetValue(null) as MessageParser; +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Kafka/AWS.Lambda.Powertools.Kafka.csproj b/libraries/src/AWS.Lambda.Powertools.Kafka/AWS.Lambda.Powertools.Kafka.csproj +deleted file mode 100644 +index d947528d..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Kafka/AWS.Lambda.Powertools.Kafka.csproj ++++ /dev/null +@@ -1,21 +0,0 @@ +- +- +- +- +- AWS.Lambda.Powertools.Kafka +- Powertools for AWS Lambda (.NET) - Kafka consumer package. +- AWS.Lambda.Powertools.Kafka +- AWS.Lambda.Powertools.Kafka +- net8.0 +- false +- enable +- enable +- true +- +- +- +- +- +- +- +- +diff --git a/libraries/src/AWS.Lambda.Powertools.Kafka/ConsumerRecord.cs b/libraries/src/AWS.Lambda.Powertools.Kafka/ConsumerRecord.cs +deleted file mode 100644 +index 8e90ec22..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Kafka/ConsumerRecord.cs ++++ /dev/null +@@ -1,78 +0,0 @@ +-#if KAFKA_JSON +-namespace AWS.Lambda.Powertools.Kafka.Json; +-#elif KAFKA_AVRO +-namespace AWS.Lambda.Powertools.Kafka.Avro; +-#elif KAFKA_PROTOBUF +-namespace AWS.Lambda.Powertools.Kafka.Protobuf; +-#else +-namespace AWS.Lambda.Powertools.Kafka; +-#endif +- +-/// +-/// Represents a single record consumed from a Kafka topic. +-/// +-/// The type of the record's value. +-/// The type of the key value +-/// +-/// +-/// var record = new ConsumerRecord<Customer> +-/// { +-/// Topic = "customers", +-/// Partition = 0, +-/// Offset = 42, +-/// Value = new Customer { Id = 123, Name = "John Doe" } +-/// }; +-/// +-/// +-public class ConsumerRecord +-{ +- /// +- /// Gets or sets the Kafka topic name from which the record was consumed. +- /// +- public string Topic { get; internal set; } = null!; +- +- /// +- /// Gets the Kafka partition from which the record was consumed. +- /// +- public int Partition { get; internal set; } +- +- /// +- /// Gets the offset of the record within its Kafka partition. +- /// +- public long Offset { get; internal set; } +- +- /// +- /// Gets the timestamp of the record (typically in Unix time). +- /// +- public long Timestamp { get; internal set; } +- +- /// +- /// Gets the type of timestamp (e.g., "CREATE_TIME" or "LOG_APPEND_TIME"). +- /// +- public string TimestampType { get; internal set; } = null!; +- +- /// +- /// Gets the key of the record (often used for partitioning). +- /// +- public TK Key { get; internal set; } = default!; +- +- /// +- /// Gets the deserialized value of the record. +- /// +- public T Value { get; internal set; } = default!; +- +- /// +- /// Gets the headers associated with the record. +- /// +- public Dictionary Headers { get; internal set; } = null!; +- +- /// +- /// Gets the schema metadata for the record's value. +- /// +- public SchemaMetadata ValueSchemaMetadata { get; internal set; } = null!; +- +- /// +- /// Gets the schema metadata for the record's key. +- /// +- public SchemaMetadata KeySchemaMetadata { get; internal set; } = null!; +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Kafka/ConsumerRecords.cs b/libraries/src/AWS.Lambda.Powertools.Kafka/ConsumerRecords.cs +deleted file mode 100644 +index bb105c44..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Kafka/ConsumerRecords.cs ++++ /dev/null +@@ -1,58 +0,0 @@ +-using System.Collections; +- +-#if KAFKA_JSON +-namespace AWS.Lambda.Powertools.Kafka.Json; +-#elif KAFKA_AVRO +-namespace AWS.Lambda.Powertools.Kafka.Avro; +-#elif KAFKA_PROTOBUF +-namespace AWS.Lambda.Powertools.Kafka.Protobuf; +-#else +-namespace AWS.Lambda.Powertools.Kafka; +-#endif +- +-/// +-/// Represents a collection of Kafka consumer records that can be enumerated. +-/// Contains event metadata and records organized by topics. +-/// +-/// The type of the record values from the event. +-/// The type of Key values from the event. +-public class ConsumerRecords : IEnumerable> +-{ +- /// +- /// Gets the event source (typically "aws:kafka"). +- /// +- public string EventSource { get; internal set; } = null!; +- +- /// +- /// Gets the ARN of the event source (MSK cluster or Self-managed Kafka). +- /// +- public string EventSourceArn { get; internal set; } = null!; +- +- /// +- /// Gets the Kafka bootstrap servers connection string. +- /// +- public string BootstrapServers { get; internal set; } = null!; +- +- internal Dictionary>> Records { get; set; } = new(); +- +- /// +- /// Returns an enumerator that iterates through all consumer records across all topics. +- /// +- /// An enumerator of ConsumerRecord<T> objects. +- public IEnumerator> GetEnumerator() +- { +- foreach (var topicRecords in Records) +- { +- foreach (var record in topicRecords.Value) +- { +- yield return record; +- } +- } +- } +- +- // Implement non-generic IEnumerable (required) +- IEnumerator IEnumerable.GetEnumerator() +- { +- return GetEnumerator(); +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Kafka/HeaderExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Kafka/HeaderExtensions.cs +deleted file mode 100644 +index ea1323db..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Kafka/HeaderExtensions.cs ++++ /dev/null +@@ -1,49 +0,0 @@ +-using System.Text; +- +-#if KAFKA_JSON +-namespace AWS.Lambda.Powertools.Kafka.Json; +-#elif KAFKA_AVRO +-namespace AWS.Lambda.Powertools.Kafka.Avro; +-#elif KAFKA_PROTOBUF +-namespace AWS.Lambda.Powertools.Kafka.Protobuf; +-#else +-namespace AWS.Lambda.Powertools.Kafka; +-#endif +- +-/// +-/// Extension methods for Kafka headers in ConsumerRecord. +-/// +-public static class HeaderExtensions +-{ +- /// +- /// Gets the decoded value of a Kafka header from the ConsumerRecord's Headers dictionary. +- /// +- /// The header key-value pair from ConsumerRecord.Headers +- /// The decoded string value. +- public static Dictionary DecodedValues(this Dictionary headers) +- { +- if (headers == null) +- { +- return new Dictionary(); +- } +- +- return headers.ToDictionary( +- pair => pair.Key, +- pair => pair.Value.DecodedValue() +- ); +- } +- +- /// +- /// Decodes a byte array from a Kafka header into a UTF-8 string. +- /// Returns an empty string if the byte array is null or empty. +- /// +- public static string DecodedValue(this byte[]? headerBytes) +- { +- if (headerBytes == null || headerBytes.Length == 0) +- { +- return string.Empty; +- } +- +- return Encoding.UTF8.GetString(headerBytes); +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Kafka/InternalsVisibleTo.cs b/libraries/src/AWS.Lambda.Powertools.Kafka/InternalsVisibleTo.cs +deleted file mode 100644 +index fbcd85e5..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Kafka/InternalsVisibleTo.cs ++++ /dev/null +@@ -1,3 +0,0 @@ +-using System.Runtime.CompilerServices; +- +-[assembly: InternalsVisibleTo("AWS.Lambda.Powertools.Kafka.Tests")] +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Kafka/PowertoolsKafkaSerializerBase.cs b/libraries/src/AWS.Lambda.Powertools.Kafka/PowertoolsKafkaSerializerBase.cs +deleted file mode 100644 +index 72b0fef3..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Kafka/PowertoolsKafkaSerializerBase.cs ++++ /dev/null +@@ -1,661 +0,0 @@ +-using Amazon.Lambda.Core; +-using System.Diagnostics.CodeAnalysis; +-using System.Reflection; +-using System.Runtime.Serialization; +-using System.Text; +-using System.Text.Json; +-using System.Text.Json.Serialization; +-using System.Text.Json.Serialization.Metadata; +-using AWS.Lambda.Powertools.Common; +- +-#if KAFKA_JSON +-namespace AWS.Lambda.Powertools.Kafka.Json; +-#elif KAFKA_AVRO +-namespace AWS.Lambda.Powertools.Kafka.Avro; +-#elif KAFKA_PROTOBUF +-namespace AWS.Lambda.Powertools.Kafka.Protobuf; +-#else +-namespace AWS.Lambda.Powertools.Kafka; +-#endif +- +-/// +-/// Base class for Kafka event serializers that provides common functionality +-/// for deserializing Kafka event structures in Lambda functions. +-/// +-/// +-/// Inherit from this class to implement specific formats like Avro, Protobuf or JSON. +-/// +-public abstract class PowertoolsKafkaSerializerBase : ILambdaSerializer +-{ +- /// +- /// JSON serializer options used for deserialization. +- /// +- protected readonly JsonSerializerOptions JsonOptions; +- +- /// +- /// JSON serializer context used for AOT-compatible serialization/deserialization. +- /// +- protected readonly JsonSerializerContext? SerializerContext; +- +- /// +- /// Initializes a new instance of the class +- /// with default JSON serialization options. +- /// +- protected PowertoolsKafkaSerializerBase() : this(new JsonSerializerOptions +- { +- PropertyNameCaseInsensitive = true +- }, null) +- { +- } +- +- /// +- /// Initializes a new instance of the class +- /// with custom JSON serialization options. +- /// +- /// Custom JSON serializer options to use during deserialization. +- protected PowertoolsKafkaSerializerBase(JsonSerializerOptions jsonOptions) : this(jsonOptions, null) +- { +- } +- +- /// +- /// Initializes a new instance of the class +- /// with a JSON serializer context for AOT-compatible serialization/deserialization. +- /// +- /// The JSON serializer context for AOT compatibility. +- protected PowertoolsKafkaSerializerBase(JsonSerializerContext serializerContext) : this(serializerContext.Options, +- serializerContext) +- { +- } +- +- /// +- /// Initializes a new instance of the class +- /// with custom JSON serialization options and an optional serializer context. +- /// +- /// Custom JSON serializer options to use during deserialization. +- /// Optional JSON serializer context for AOT compatibility. +- protected PowertoolsKafkaSerializerBase(JsonSerializerOptions jsonOptions, JsonSerializerContext? serializerContext) +- { +- JsonOptions = jsonOptions ?? new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; +- SerializerContext = serializerContext; +- +- PowertoolsEnvironment.Instance.SetExecutionEnvironment(this); +- } +- +- /// +- /// Deserializes the Lambda input stream into the specified type. +- /// Handles Kafka events with various serialization formats. +- /// +- public T Deserialize(Stream requestStream) +- { +- if (SerializerContext != null && typeof(T) != typeof(ConsumerRecords<,>)) +- { +- // Fast path for regular JSON types when serializer context is provided +- var typeInfo = GetJsonTypeInfo(); +- if (typeInfo != null) +- { +- return JsonSerializer.Deserialize(requestStream, typeInfo) ?? throw new InvalidOperationException(); +- } +- } +- +- using var reader = new StreamReader(requestStream); +- var json = reader.ReadToEnd(); +- +- var targetType = typeof(T); +- +- if (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(ConsumerRecords<,>)) +- { +- return DeserializeConsumerRecords(json); +- } +- +- if (SerializerContext != null) +- { +- var typeInfo = SerializerContext.GetTypeInfo(targetType); +- if (typeInfo != null) +- { +- return (T)JsonSerializer.Deserialize(json, typeInfo)!; +- } +- } +- +-#pragma warning disable IL2026, IL3050 +- var result = JsonSerializer.Deserialize(json, JsonOptions); +-#pragma warning restore IL2026, IL3050 +- +- return result ?? throw new InvalidOperationException($"Failed to deserialize to type {typeof(T).Name}"); +- } +- +- /// +- /// Deserializes a Kafka ConsumerRecords event from JSON string. +- /// +- /// The ConsumerRecords type with key and value generics. +- /// The JSON string to deserialize. +- /// The deserialized ConsumerRecords object. +- [RequiresUnreferencedCode("ConsumerRecords deserialization uses reflection and may be incompatible with trimming.")] +- [RequiresDynamicCode( +- "ConsumerRecords deserialization dynamically creates generic types and may be incompatible with NativeAOT.")] +- private T DeserializeConsumerRecords(string json) +- { +- var targetType = typeof(T); +- var typeArgs = targetType.GetGenericArguments(); +- var keyType = typeArgs[0]; +- var valueType = typeArgs[1]; +- +- using var document = JsonDocument.Parse(json); +- var root = document.RootElement; +- +- // Create the typed instance and set basic properties +- var typedEvent = CreateConsumerRecordsInstance(targetType); +- SetBasicProperties(root, typedEvent, targetType); +- +- // Create and populate records dictionary +- if (root.TryGetProperty("records", out var recordsElement)) +- { +- var records = CreateRecordsDictionary(recordsElement, keyType, valueType); +- targetType.GetProperty("Records", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) +- ?.SetValue(typedEvent, records); +- } +- +- return (T)typedEvent; +- } +- +- private object CreateConsumerRecordsInstance(Type targetType) +- { +- return Activator.CreateInstance(targetType) ?? +- throw new InvalidOperationException($"Failed to create instance of {targetType.Name}"); +- } +- +- private void SetBasicProperties(JsonElement root, object instance, Type targetType) +- { +- if (root.TryGetProperty("eventSource", out var eventSource)) +- targetType.GetProperty("EventSource", BindingFlags.Public | BindingFlags.Instance) +- ?.SetValue(instance, eventSource.GetString()); +- +- if (root.TryGetProperty("eventSourceArn", out var eventSourceArn)) +- targetType.GetProperty("EventSourceArn")?.SetValue(instance, eventSourceArn.GetString()); +- +- if (root.TryGetProperty("bootstrapServers", out var bootstrapServers)) +- targetType.GetProperty("BootstrapServers")?.SetValue(instance, bootstrapServers.GetString()); +- } +- +- private object CreateRecordsDictionary(JsonElement recordsElement, Type keyType, Type valueType) +- { +- // Create dictionary with correct generic types +- var dictType = typeof(Dictionary<,>).MakeGenericType( +- typeof(string), +- typeof(List<>).MakeGenericType(typeof(ConsumerRecord<,>).MakeGenericType(keyType, valueType)) +- ); +- var records = Activator.CreateInstance(dictType) ?? +- throw new InvalidOperationException($"Failed to create dictionary of type {dictType.Name}"); +- var dictAddMethod = dictType.GetMethod("Add") ?? +- throw new InvalidOperationException("Add method not found on dictionary type"); +- +- // Process each topic partition +- foreach (var topicPartition in recordsElement.EnumerateObject()) +- { +- var topicName = topicPartition.Name; +- var recordsList = ProcessTopicPartition(topicPartition.Value, keyType, valueType); +- dictAddMethod.Invoke(records, new[] { topicName, recordsList }); +- } +- +- return records; +- } +- +- private object ProcessTopicPartition(JsonElement partitionData, Type keyType, Type valueType) +- { +- // Create list type with correct generics +- var listType = typeof(List<>).MakeGenericType( +- typeof(ConsumerRecord<,>).MakeGenericType(keyType, valueType)); +- var recordsList = Activator.CreateInstance(listType) ?? +- throw new InvalidOperationException($"Failed to create list of type {listType.Name}"); +- var listAddMethod = listType.GetMethod("Add") ?? +- throw new InvalidOperationException("Add method not found on list type"); +- +- // Process each record +- foreach (var recordElement in partitionData.EnumerateArray()) +- { +- var record = CreateAndPopulateRecord(recordElement, keyType, valueType); +- if (record != null) +- { +- listAddMethod.Invoke(recordsList, new[] { record }); +- } +- } +- +- return recordsList; +- } +- +- private object? CreateAndPopulateRecord(JsonElement recordElement, Type keyType, Type valueType) +- { +- // Create record instance +- var recordType = typeof(ConsumerRecord<,>).MakeGenericType(keyType, valueType); +- var record = Activator.CreateInstance(recordType); +- if (record == null) +- return null; +- +- // Set basic properties +- SetProperty(recordType, record, "Topic", recordElement, "topic"); +- SetProperty(recordType, record, "Partition", recordElement, "partition"); +- SetProperty(recordType, record, "Offset", recordElement, "offset"); +- SetProperty(recordType, record, "Timestamp", recordElement, "timestamp"); +- SetProperty(recordType, record, "TimestampType", recordElement, "timestampType"); +- +- // Process schema metadata for both key and value FIRST +- SchemaMetadata? keySchemaMetadata = null; +- SchemaMetadata? valueSchemaMetadata = null; +- +- ProcessSchemaMetadata(recordElement, record, recordType, "keySchemaMetadata", "KeySchemaMetadata"); +- ProcessSchemaMetadata(recordElement, record, recordType, "valueSchemaMetadata", "ValueSchemaMetadata"); +- +- // Get the schema metadata for use in deserialization +- if (recordElement.TryGetProperty("keySchemaMetadata", out var keyMetadataElement)) +- { +- keySchemaMetadata = ExtractSchemaMetadata(keyMetadataElement); +- } +- +- if (recordElement.TryGetProperty("valueSchemaMetadata", out var valueMetadataElement)) +- { +- valueSchemaMetadata = ExtractSchemaMetadata(valueMetadataElement); +- } +- +- // Process key with schema metadata context +- ProcessKey(recordElement, record, recordType, keyType, keySchemaMetadata); +- +- // Process value with schema metadata context +- ProcessValue(recordElement, record, recordType, valueType, valueSchemaMetadata); +- +- // Process headers +- ProcessHeaders(recordElement, record, recordType); +- +- return record; +- } +- +- private SchemaMetadata? ExtractSchemaMetadata(JsonElement metadataElement) +- { +- var schemaMetadata = new SchemaMetadata(); +- var hasData = false; +- +- if (metadataElement.TryGetProperty("dataFormat", out var dataFormatElement)) +- { +- schemaMetadata.DataFormat = dataFormatElement.GetString() ?? string.Empty; +- hasData = true; +- } +- +- if (metadataElement.TryGetProperty("schemaId", out var schemaIdElement)) +- { +- schemaMetadata.SchemaId = schemaIdElement.GetString() ?? string.Empty; +- hasData = true; +- } +- +- return hasData ? schemaMetadata : null; +- } +- +- private void ProcessKey(JsonElement recordElement, object record, Type recordType, Type keyType, SchemaMetadata? keySchemaMetadata) +- { +- if (recordElement.TryGetProperty("key", out var keyElement) && keyElement.ValueKind == JsonValueKind.String) +- { +- var base64Key = keyElement.GetString(); +- if (!string.IsNullOrEmpty(base64Key)) +- { +- try +- { +- var keyBytes = Convert.FromBase64String(base64Key); +- var decodedKey = DeserializeKey(keyBytes, keyType, keySchemaMetadata); +- recordType.GetProperty("Key")?.SetValue(record, decodedKey); +- } +- catch (Exception ex) +- { +- throw new SerializationException($"Failed to deserialize key data: {ex.Message}", ex); +- } +- } +- } +- } +- +- private void ProcessValue(JsonElement recordElement, object record, Type recordType, Type valueType, SchemaMetadata? valueSchemaMetadata) +- { +- if (recordElement.TryGetProperty("value", out var valueElement) && valueElement.ValueKind == JsonValueKind.String) +- { +- var base64Value = valueElement.GetString(); +- var valueProperty = recordType.GetProperty("Value"); +- +- if (base64Value != null && valueProperty != null) +- { +- try +- { +- var deserializedValue = DeserializeValue(base64Value, valueType, valueSchemaMetadata); +- valueProperty.SetValue(record, deserializedValue); +- } +- catch (Exception ex) +- { +- throw new SerializationException($"Failed to deserialize value data: {ex.Message}", ex); +- } +- } +- } +- } +- +- /// +- /// Deserializes a key from bytes based on the specified key type. +- /// +- /// The key bytes to deserialize. +- /// The target type for the key. +- /// Optional schema metadata for the key. +- /// The deserialized key object. +- private object? DeserializeKey(byte[] keyBytes, Type keyType, SchemaMetadata? keySchemaMetadata) +- { +- // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract +- if (keyBytes == null || keyBytes.Length == 0) +- return null; +- +- if (IsPrimitiveOrSimpleType(keyType)) +- { +- return DeserializePrimitiveValue(keyBytes, keyType); +- } +- +- // For complex types, use format-specific deserialization +- return DeserializeFormatSpecific(keyBytes, keyType, isKey: true, keySchemaMetadata); +- } +- +- /// +- /// Sets a property value on an object instance from a JsonElement. +- /// +- /// The type of the object. +- /// The object instance. +- /// The name of the property to set. +- /// The JsonElement containing the source data. +- /// The property name within the JsonElement. +- [RequiresDynamicCode("Dynamically accesses properties which might be trimmed.")] +- [RequiresUnreferencedCode("Dynamically accesses properties which might be trimmed.")] +- private void SetProperty( +- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] +- Type type, object instance, string propertyName, +- JsonElement element, string jsonPropertyName) +- { +- if (!element.TryGetProperty(jsonPropertyName, out var jsonValue) || +- jsonValue.ValueKind == JsonValueKind.Null) +- return; +- +- // Add BindingFlags to find internal properties too +- var property = type.GetProperty(propertyName, +- BindingFlags.Public | BindingFlags.Instance); +- if (property == null) return; +- var propertyType = property.PropertyType; +- +- object value; +- if (propertyType == typeof(int)) value = jsonValue.GetInt32(); +- else if (propertyType == typeof(long)) value = jsonValue.GetInt64(); +- else if (propertyType == typeof(double)) value = jsonValue.GetDouble(); +- else if (propertyType == typeof(string)) value = jsonValue.GetString()!; +- else return; +- +- property.SetValue(instance, value); +- } +- +- /// +- /// Serializes an object to JSON and writes it to the provided stream. +- /// +- public void Serialize(T response, Stream responseStream) +- { +- if (EqualityComparer.Default.Equals(response, default(T))) +- { +- if (responseStream.CanWrite) +- { +- var nullBytes = Encoding.UTF8.GetBytes("null"); +- responseStream.Write(nullBytes, 0, nullBytes.Length); +- } +- return; +- } +- +- if (SerializerContext != null) +- { +- var typeInfo = SerializerContext.GetTypeInfo(response.GetType()) ?? +- SerializerContext.GetTypeInfo(typeof(T)); +- if (typeInfo != null) +- { +- JsonSerializer.Serialize(responseStream, response, typeInfo); +- return; +- } +- } +- +- using var writer = new StreamWriter(responseStream, encoding: Encoding.UTF8, bufferSize: 1024, leaveOpen: true); +-#pragma warning disable IL2026, IL3050 +- var jsonResponse = JsonSerializer.Serialize(response, JsonOptions); +-#pragma warning restore IL2026, IL3050 +- writer.Write(jsonResponse); +- writer.Flush(); +- } +- +- // Helper to get non-generic JsonTypeInfo from context based on a Type argument +- private JsonTypeInfo? GetJsonTypeInfoFromContext(Type type) +- { +- if (SerializerContext == null) +- return null; +- +- return SerializerContext.GetTypeInfo(type); +- } +- +- private JsonTypeInfo? GetJsonTypeInfo() +- { +- if (SerializerContext == null) return null; +- +- foreach (var prop in SerializerContext.GetType().GetProperties()) +- { +- if (prop.PropertyType == typeof(JsonTypeInfo)) +- { +- return prop.GetValue(SerializerContext) as JsonTypeInfo; +- } +- } +- return null; +- } +- +- /// +- /// Deserializes a base64-encoded value into an object using the appropriate format. +- /// +- [RequiresDynamicCode("Deserializing values might require runtime code generation.")] +- [RequiresUnreferencedCode("Deserializing values might require types that cannot be statically analyzed.")] +- protected virtual object DeserializeValue(string base64Value, +- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | +- DynamicallyAccessedMemberTypes.PublicFields)] +- Type valueType, SchemaMetadata? valueSchemaMetadata = null) +- { +- if (IsPrimitiveOrSimpleType(valueType)) +- { +- var bytes = Convert.FromBase64String(base64Value); +- return DeserializePrimitiveValue(bytes, valueType); +- } +- +- var data = Convert.FromBase64String(base64Value); +- return DeserializeFormatSpecific(data, valueType, isKey: false, valueSchemaMetadata); +- } +- +- /// +- /// Deserializes binary data using format-specific implementation. +- /// +- [RequiresDynamicCode("Format-specific deserialization might require runtime code generation.")] +- [RequiresUnreferencedCode("Format-specific deserialization might require types that cannot be statically analyzed.")] +- protected virtual object? DeserializeFormatSpecific(byte[] data, +- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | +- DynamicallyAccessedMemberTypes.PublicFields)] +- Type targetType, bool isKey, SchemaMetadata? schemaMetadata = null) +- { +- if (IsPrimitiveOrSimpleType(targetType)) +- { +- return DeserializePrimitiveValue(data, targetType); +- } +- +- return DeserializeComplexTypeFormat(data, targetType, isKey, schemaMetadata); +- } +- +- /// +- /// Deserializes complex (non-primitive) types using format-specific implementation. +- /// Each derived class must implement this method to handle its specific format. +- /// +- [RequiresDynamicCode("Format-specific deserialization might require runtime code generation.")] +- [RequiresUnreferencedCode("Format-specific deserialization might require types that cannot be statically analyzed.")] +- protected abstract object? DeserializeComplexTypeFormat(byte[] data, +- [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | +- DynamicallyAccessedMemberTypes.PublicFields)] +- Type targetType, bool isKey, SchemaMetadata? schemaMetadata = null); +- +- /// +- /// Checks if the specified type is a primitive or simple type. +- /// +- protected bool IsPrimitiveOrSimpleType(Type type) +- { +- return type.IsPrimitive || +- type == typeof(string) || +- type == typeof(decimal) || +- type == typeof(DateTime) || +- type == typeof(Guid); +- } +- +- /// +- /// Deserializes a primitive value from bytes based on the specified type. +- /// +- protected object? DeserializePrimitiveValue(byte[] bytes, Type valueType) +- { +- if (bytes == null! || bytes.Length == 0) +- return null!; +- +- if (valueType == typeof(string)) +- return Encoding.UTF8.GetString(bytes); +- +- var stringValue = Encoding.UTF8.GetString(bytes); +- +- return valueType.Name switch +- { +- nameof(Int32) => DeserializeIntValue(bytes, stringValue), +- nameof(Int64) => DeserializeLongValue(bytes, stringValue), +- nameof(Double) => DeserializeDoubleValue(bytes, stringValue), +- nameof(Boolean) => DeserializeBoolValue(bytes, stringValue), +- nameof(Guid) => DeserializeGuidValue(bytes, stringValue), +- _ => DeserializeGenericValue(stringValue, valueType) +- }; +- } +- +- private object DeserializeIntValue(byte[] bytes, string stringValue) +- { +- // Try string parsing first +- if (int.TryParse(stringValue, out var parsedValue)) +- return parsedValue; +- +- // Fall back to binary representation +- return bytes.Length switch +- { +- >= 4 => BitConverter.ToInt32(bytes, 0), +- 1 => bytes[0], +- _ => 0 +- }; +- } +- +- private object DeserializeLongValue(byte[] bytes, string stringValue) +- { +- if (long.TryParse(stringValue, out var parsedValue)) +- return parsedValue; +- +- return bytes.Length switch +- { +- >= 8 => BitConverter.ToInt64(bytes, 0), +- >= 4 => BitConverter.ToInt32(bytes, 0), +- _ => 0L +- }; +- } +- +- private object DeserializeDoubleValue(byte[] bytes, string stringValue) +- { +- if (double.TryParse(stringValue, out var doubleValue)) +- return doubleValue; +- +- return bytes.Length >= 8 ? BitConverter.ToDouble(bytes, 0) : 0.0; +- } +- +- private object DeserializeBoolValue(byte[] bytes, string stringValue) +- { +- if (bool.TryParse(stringValue, out var boolValue)) +- return boolValue; +- +- return bytes[0] != 0; +- } +- +- private object? DeserializeGuidValue(byte[] bytes, string stringValue) +- { +- if (bytes.Length < 16) +- return Guid.Empty; +- +- try +- { +- return new Guid(bytes); +- } +- catch +- { +- // If binary parsing fails, try string parsing +- return Guid.TryParse(stringValue, out var guidValue) ? guidValue : Guid.Empty; +- } +- } +- +- private object? DeserializeGenericValue(string stringValue, Type valueType) +- { +- try +- { +- return Convert.ChangeType(stringValue, valueType); +- } +- catch +- { +- return valueType.IsValueType ? Activator.CreateInstance(valueType) : null; +- } +- } +- +- private void ProcessSchemaMetadata(JsonElement recordElement, object record, Type recordType, +- string jsonPropertyName, string recordPropertyName) +- { +- if (recordElement.TryGetProperty(jsonPropertyName, out var metadataElement)) +- { +- var schemaMetadata = new SchemaMetadata(); +- +- if (metadataElement.TryGetProperty("dataFormat", out var dataFormatElement)) +- { +- schemaMetadata.DataFormat = dataFormatElement.GetString() ?? string.Empty; +- } +- +- if (metadataElement.TryGetProperty("schemaId", out var schemaIdElement)) +- { +- schemaMetadata.SchemaId = schemaIdElement.GetString() ?? string.Empty; +- } +- +- recordType.GetProperty(recordPropertyName)?.SetValue(record, schemaMetadata); +- } +- } +- +- private void ProcessHeaders(JsonElement recordElement, object record, Type recordType) +- { +- if (recordElement.TryGetProperty("headers", out var headersElement) && +- headersElement.ValueKind == JsonValueKind.Array) +- { +- var headers = new Dictionary(); +- +- foreach (var headerObj in headersElement.EnumerateArray()) +- { +- foreach (var header in headerObj.EnumerateObject()) +- { +- if (header.Value.ValueKind == JsonValueKind.Array) +- { +- headers[header.Name] = ExtractHeaderBytes(header.Value); +- } +- } +- } +- +- var headersProperty = recordType.GetProperty("Headers", +- BindingFlags.Public | BindingFlags.Instance); +- headersProperty?.SetValue(record, headers); +- } +- } +- +- private byte[] ExtractHeaderBytes(JsonElement headerArray) +- { +- var headerBytes = new byte[headerArray.GetArrayLength()]; +- var i = 0; +- foreach (var byteVal in headerArray.EnumerateArray()) +- { +- headerBytes[i++] = (byte)byteVal.GetInt32(); +- } +- +- return headerBytes; +- } +-} +- +diff --git a/libraries/src/AWS.Lambda.Powertools.Kafka/SchemaMetadata.cs b/libraries/src/AWS.Lambda.Powertools.Kafka/SchemaMetadata.cs +deleted file mode 100644 +index 6947930b..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Kafka/SchemaMetadata.cs ++++ /dev/null +@@ -1,25 +0,0 @@ +-#if KAFKA_JSON +-namespace AWS.Lambda.Powertools.Kafka.Json; +-#elif KAFKA_AVRO +-namespace AWS.Lambda.Powertools.Kafka.Avro; +-#elif KAFKA_PROTOBUF +-namespace AWS.Lambda.Powertools.Kafka.Protobuf; +-#else +-namespace AWS.Lambda.Powertools.Kafka; +-#endif +- +-/// +-/// Represents metadata about the schema used for serializing the record's value or key. +-/// +-public class SchemaMetadata +-{ +- /// +- /// Gets or sets the format of the data (e.g., "JSON", "AVRO" "Protobuf"). +- /// /// +- public string DataFormat { get; internal set; } = null!; +- +- /// +- /// Gets or sets the schema ID associated with the record's value or key. +- /// +- public string SchemaId { get; internal set; } = null!; +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/AWS.Lambda.Powertools.Logging.csproj b/libraries/src/AWS.Lambda.Powertools.Logging/AWS.Lambda.Powertools.Logging.csproj +index ccf8c3ea..a4a1478f 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Logging/AWS.Lambda.Powertools.Logging.csproj ++++ b/libraries/src/AWS.Lambda.Powertools.Logging/AWS.Lambda.Powertools.Logging.csproj +@@ -15,7 +15,6 @@ + + + +- + + + +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/BufferedLogEntry.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/BufferedLogEntry.cs +deleted file mode 100644 +index 2b0aaadb..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/BufferedLogEntry.cs ++++ /dev/null +@@ -1,14 +0,0 @@ +- +-namespace AWS.Lambda.Powertools.Logging.Internal; +- +-internal class BufferedLogEntry +-{ +- public string Entry { get; } +- public int Size { get; } +- +- public BufferedLogEntry(string entry, int calculatedSize) +- { +- Entry = entry; +- Size = calculatedSize; +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/BufferingLoggerProvider.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/BufferingLoggerProvider.cs +deleted file mode 100644 +index eaf70cb3..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/BufferingLoggerProvider.cs ++++ /dev/null +@@ -1,83 +0,0 @@ +-using System.Collections.Concurrent; +-using AWS.Lambda.Powertools.Common; +-using Microsoft.Extensions.Logging; +- +-namespace AWS.Lambda.Powertools.Logging.Internal; +- +-/// +-/// Logger provider that supports buffering logs +-/// +-[ProviderAlias("PowertoolsBuffering")] +-internal class BufferingLoggerProvider : PowertoolsLoggerProvider +-{ +- private readonly IPowertoolsConfigurations _powertoolsConfigurations; +- private readonly ConcurrentDictionary _loggers = new(); +- +- internal BufferingLoggerProvider( +- PowertoolsLoggerConfiguration config, +- IPowertoolsConfigurations powertoolsConfigurations) +- : base(config, powertoolsConfigurations) +- { +- _powertoolsConfigurations = powertoolsConfigurations; +- // Register with the buffer manager +- LogBufferManager.RegisterProvider(this); +- } +- +- public override ILogger CreateLogger(string categoryName) +- { +- return _loggers.GetOrAdd( +- categoryName, +- name => new PowertoolsBufferingLogger( +- base.CreateLogger(name), // Use the parent's logger creation +- GetCurrentConfig, +- _powertoolsConfigurations)); +- } +- +- /// +- /// Flush all buffered logs +- /// +- internal void FlushBuffers() +- { +- foreach (var logger in _loggers.Values) +- { +- logger.FlushBuffer(); +- } +- } +- +- /// +- /// Clear all buffered logs +- /// +- internal void ClearBuffers() +- { +- foreach (var logger in _loggers.Values) +- { +- logger.ClearBuffer(); +- } +- } +- +- /// +- /// Clear buffered logs for the current invocation only +- /// +- internal void ClearCurrentBuffer() +- { +- foreach (var logger in _loggers.Values) +- { +- logger.ClearCurrentInvocation(); +- } +- } +- +- public override void Dispose() +- { +- // Flush all buffers before disposing +- foreach (var logger in _loggers.Values) +- { +- logger.FlushBuffer(); +- } +- +- // Unregister from buffer manager +- LogBufferManager.UnregisterProvider(this); +- +- _loggers.Clear(); +- base.Dispose(); +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/InvocationBuffer.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/InvocationBuffer.cs +deleted file mode 100644 +index a8bec211..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/InvocationBuffer.cs ++++ /dev/null +@@ -1,63 +0,0 @@ +-using System; +-using System.Collections.Concurrent; +-using System.Collections.Generic; +- +-namespace AWS.Lambda.Powertools.Logging.Internal; +- +-/// +-/// Buffer for a specific invocation +-/// +-internal class InvocationBuffer +-{ +- private readonly ConcurrentQueue _buffer = new(); +- private int _currentSize; +- +- public void Add(string logEntry, int maxBytes, int size) +- { +- // If entry size exceeds max buffer size, discard the entry completely +- if (size > maxBytes) +- { +- // Entry is too large to ever fit in buffer, discard it +- return; +- } +- +- if (_currentSize + size > maxBytes) +- { +- // Remove oldest entries until we have enough space +- while (_currentSize + size > maxBytes && _buffer.TryDequeue(out var removed)) +- { +- _currentSize -= removed.Size; +- HasEvictions = true; +- } +- +- if (_currentSize < 0) _currentSize = 0; +- } +- +- _buffer.Enqueue(new BufferedLogEntry(logEntry, size)); +- _currentSize += size; +- } +- +- public IReadOnlyCollection GetAndClear() +- { +- var entries = new List(); +- +- try +- { +- while (_buffer.TryDequeue(out var entry)) +- { +- entries.Add(entry.Entry); +- } +- } +- catch (Exception) +- { +- _buffer.Clear(); +- } +- +- _currentSize = 0; +- return entries; +- } +- +- public bool HasEntries => !_buffer.IsEmpty; +- +- public bool HasEvictions; +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/LogBuffer.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/LogBuffer.cs +deleted file mode 100644 +index db19e096..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/LogBuffer.cs ++++ /dev/null +@@ -1,125 +0,0 @@ +-/* +- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +- * +- * Licensed under the Apache License, Version 2.0 (the "License"). +- * You may not use this file except in compliance with the License. +- * A copy of the License is located at +- * +- * http://aws.amazon.com/apache2.0 +- * +- * or in the "license" file accompanying this file. This file is distributed +- * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +- * express or implied. See the License for the specific language governing +- * permissions and limitations under the License. +- */ +- +-using System; +-using System.Collections.Concurrent; +-using System.Collections.Generic; +-using AWS.Lambda.Powertools.Common; +- +-namespace AWS.Lambda.Powertools.Logging.Internal; +- +-/// +-/// A buffer for storing log entries, with isolation per Lambda invocation +-/// +-internal class LogBuffer +-{ +- private readonly IPowertoolsConfigurations _powertoolsConfigurations; +- +-// Dictionary of buffers by invocation ID +- private readonly ConcurrentDictionary _buffersByInvocation = new(); +- private string _lastInvocationId; +- +- // Get the current invocation ID or create a fallback +- private string CurrentInvocationId => _powertoolsConfigurations.XRayTraceId; +- +- public LogBuffer(IPowertoolsConfigurations powertoolsConfigurations) +- { +- _powertoolsConfigurations = powertoolsConfigurations; +- } +- +- /// +- /// Add a log entry to the buffer for the current invocation +- /// +- public void Add(string logEntry, int maxBytes, int size) +- { +- var invocationId = CurrentInvocationId; +- if (string.IsNullOrEmpty(invocationId)) +- { +- // No invocation ID set, do not buffer +- return; +- } +- +- // If this is a new invocation ID, clear previous buffers +- if (_lastInvocationId != invocationId) +- { +- if (_lastInvocationId != null) +- _buffersByInvocation.Clear(); +- _lastInvocationId = invocationId; +- } +- +- var buffer = _buffersByInvocation.GetOrAdd(invocationId, _ => new InvocationBuffer()); +- buffer.Add(logEntry, maxBytes, size); +- } +- +- /// +- /// Get all entries for the current invocation and clear that buffer +- /// +- public IReadOnlyCollection GetAndClear() +- { +- var invocationId = CurrentInvocationId; +- +- if (string.IsNullOrEmpty(invocationId)) +- { +- // No invocation ID set, return empty +- return Array.Empty(); +- } +- +- // Try to get and remove the buffer for this invocation +- if (_buffersByInvocation.TryRemove(invocationId, out var buffer)) +- { +- return buffer.GetAndClear(); +- } +- +- return Array.Empty(); +- } +- +- /// +- /// Clear all buffers +- /// +- public void Clear() +- { +- _buffersByInvocation.Clear(); +- } +- +- /// +- /// Clear buffer for the current invocation +- /// +- public void ClearCurrentInvocation() +- { +- var invocationId = CurrentInvocationId; +- _buffersByInvocation.TryRemove(invocationId, out _); +- } +- +- /// +- /// Check if the current invocation has any buffered entries +- /// +- public bool HasEntries +- { +- get +- { +- var invocationId = CurrentInvocationId; +- return _buffersByInvocation.TryGetValue(invocationId, out var buffer) && buffer.HasEntries; +- } +- } +- +- public bool HasEvictions +- { +- get +- { +- var invocationId = CurrentInvocationId; +- return _buffersByInvocation.TryGetValue(invocationId, out var buffer) && buffer.HasEvictions; +- } +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/LogBufferManager.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/LogBufferManager.cs +deleted file mode 100644 +index 9e3a3aa8..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/LogBufferManager.cs ++++ /dev/null +@@ -1,89 +0,0 @@ +-/* +- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +- * +- * Licensed under the Apache License, Version 2.0 (the "License"). +- * You may not use this file except in compliance with the License. +- * A copy of the License is located at +- * +- * http://aws.amazon.com/apache2.0 +- * +- * or in the "license" file accompanying this file. This file is distributed +- * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +- * express or implied. See the License for the specific language governing +- * permissions and limitations under the License. +- */ +- +-using System; +-using System.Collections.Generic; +- +-namespace AWS.Lambda.Powertools.Logging.Internal; +- +-/// +-/// Singleton manager for log buffer operations with invocation context awareness +-/// +-internal static class LogBufferManager +-{ +- private static readonly List Providers = new(); +- +- /// +- /// Register a buffering provider with the manager +- /// +- internal static void RegisterProvider(BufferingLoggerProvider provider) +- { +- if (!Providers.Contains(provider)) +- Providers.Add(provider); +- } +- +- /// +- /// Flush buffered logs for the current invocation +- /// +- internal static void FlushCurrentBuffer() +- { +- try +- { +- foreach (var provider in Providers) +- { +- provider?.FlushBuffers(); +- } +- } +- catch (Exception) +- { +- // Suppress errors +- } +- } +- +- /// +- /// Clear buffered logs for the current invocation +- /// +- internal static void ClearCurrentBuffer() +- { +- try +- { +- foreach (var provider in Providers) +- { +- provider?.ClearCurrentBuffer(); +- } +- } +- catch (Exception) +- { +- // Suppress errors +- } +- } +- +- /// +- /// Unregister a buffering provider from the manager +- /// +- /// +- internal static void UnregisterProvider(BufferingLoggerProvider provider) +- { +- Providers.Remove(provider); +- } +- +- /// +- /// Reset the manager state (for testing purposes) +- /// +- internal static void ResetForTesting() +- { +- Providers.Clear(); +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/Logger.Buffer.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/Logger.Buffer.cs +deleted file mode 100644 +index 9e715c55..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/Logger.Buffer.cs ++++ /dev/null +@@ -1,40 +0,0 @@ +-/* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * .cs +-/* +- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +- * +- * Licensed under the Apache License, Version 2.0 (the "License"). +- * You may not use this file except in compliance with the License. +- * A copy of the License is located at +- * +- * http://aws.amazon.com/apache2.0 +- * +- * or in the "license" file accompanying this file. This file is distributed +- * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +- * express or implied. See the License for the specific language governing +- * permissions and limitations under the License. +- */ +- +-using AWS.Lambda.Powertools.Logging.Internal; +- +-namespace AWS.Lambda.Powertools.Logging; +- +-public static partial class Logger +-{ +- /// +- /// Flush any buffered logs +- /// +- public static void FlushBuffer() +- { +- // Use the buffer manager directly +- LogBufferManager.FlushCurrentBuffer(); +- } +- +- /// +- /// Clear any buffered logs without writing them +- /// +- public static void ClearBuffer() +- { +- // Use the buffer manager directly +- LogBufferManager.ClearCurrentBuffer(); +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/PowertoolsBufferingLogger.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/PowertoolsBufferingLogger.cs +deleted file mode 100644 +index 05d24e7b..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/PowertoolsBufferingLogger.cs ++++ /dev/null +@@ -1,149 +0,0 @@ +-using System; +-using AWS.Lambda.Powertools.Common; +-using Microsoft.Extensions.Logging; +- +-namespace AWS.Lambda.Powertools.Logging.Internal; +- +-/// +-/// Logger implementation that supports buffering +-/// +-internal class PowertoolsBufferingLogger : ILogger +-{ +- private readonly ILogger _logger; +- private readonly Func _getCurrentConfig; +- private readonly LogBuffer _buffer; +- +- public PowertoolsBufferingLogger( +- ILogger logger, +- Func getCurrentConfig, +- IPowertoolsConfigurations powertoolsConfigurations) +- { +- _logger = logger; +- _getCurrentConfig = getCurrentConfig; +- _buffer = new LogBuffer(powertoolsConfigurations); +- } +- +- public IDisposable BeginScope(TState state) +- { +- return _logger.BeginScope(state); +- } +- +- public bool IsEnabled(LogLevel logLevel) +- { +- return true; +- } +- +- public void Log( +- LogLevel logLevel, +- EventId eventId, +- TState state, +- Exception exception, +- Func formatter) +- { +- var options = _getCurrentConfig(); +- var bufferOptions = options.LogBuffering; +- +- // Check if this log should be buffered +- bool shouldBuffer = logLevel <= bufferOptions.BufferAtLogLevel; +- +- if (shouldBuffer) +- { +- // Add to buffer instead of logging +- try +- { +- if (_logger is PowertoolsLogger powertoolsLogger) +- { +- var logEntry = powertoolsLogger.LogEntryString(logLevel, state, exception, formatter); +- +- // Check the size of the log entry, log it if too large +- var size = 100 + (logEntry?.Length ?? 0) * 2; +- if (size > bufferOptions.MaxBytes) +- { +- // log the entry directly if it exceeds the buffer size +- powertoolsLogger.LogLine(logEntry); +- ConsoleWrapper.WriteLine(LogLevel.Warning.ToLambdaLogLevel(), "Cannot add item to the buffer"); +- } +- else +- { +- _buffer.Add(logEntry, bufferOptions.MaxBytes, size); +- } +- } +- } +- catch (Exception ex) +- { +- // If buffering fails, try to log an error about it +- try +- { +- _logger.LogError(ex, "Failed to buffer log entry"); +- } +- catch +- { +- // Last resort: if even that fails, just suppress the error +- } +- } +- } +- else +- { +- // If this is an error and we should flush on error +- if (bufferOptions.FlushOnErrorLog && +- logLevel >= LogLevel.Error) +- { +- FlushBuffer(); +- } +- } +- } +- +- /// +- /// Flush buffered logs to the inner logger +- /// +- public void FlushBuffer() +- { +- try +- { +- if (_logger is PowertoolsLogger powertoolsLogger) +- { +- if (_buffer.HasEvictions) +- { +- ConsoleWrapper.WriteLine(LogLevel.Warning.ToLambdaLogLevel(), "Some logs are not displayed because they were evicted from the buffer. Increase buffer size to store more logs in the buffer"); +- } +- +- // Get all buffered entries +- var entries = _buffer.GetAndClear(); +- +- // Log each entry directly +- foreach (var entry in entries) +- { +- powertoolsLogger.LogLine(entry); +- } +- } +- } +- catch (Exception ex) +- { +- // If the entire flush operation fails, try to log an error +- try +- { +- _logger.LogError(ex, "Failed to flush log buffer"); +- } +- catch +- { +- // If even that fails, just suppress the error +- } +- } +- } +- +- /// +- /// Clear the buffer without logging +- /// +- public void ClearBuffer() +- { +- _buffer.Clear(); +- } +- +- /// +- /// Clear buffered logs only for the current invocation +- /// +- public void ClearCurrentInvocation() +- { +- _buffer.ClearCurrentInvocation(); +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/ByteArrayConverter.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/ByteArrayConverter.cs +index b868aa64..b6d7120d 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/ByteArrayConverter.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/ByteArrayConverter.cs +@@ -34,30 +34,31 @@ internal class ByteArrayConverter : JsonConverter + /// + public override byte[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { +- if (reader.TokenType == JsonTokenType.Null) +- return []; +- +- if (reader.TokenType == JsonTokenType.String) +- return Convert.FromBase64String(reader.GetString()!); +- +- throw new JsonException("Expected string value for byte array"); ++ throw new NotSupportedException("Deserializing ByteArray is not allowed"); + } + + /// + /// Write the exception value as JSON. + /// + /// The unicode JsonWriter. +- /// ++ /// The byte array. + /// The JsonSerializer options. +- public override void Write(Utf8JsonWriter writer, byte[] value, JsonSerializerOptions options) ++ public override void Write(Utf8JsonWriter writer, byte[] values, JsonSerializerOptions options) + { +- if (value == null) ++ if (values == null) + { + writer.WriteNullValue(); +- return; + } +- +- string base64 = Convert.ToBase64String(value); +- writer.WriteStringValue(base64); ++ else ++ { ++ writer.WriteStartArray(); ++ ++ foreach (var value in values) ++ { ++ writer.WriteNumberValue(value); ++ } ++ ++ writer.WriteEndArray(); ++ } + } + } +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/ConstantClassConverter.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/ConstantClassConverter.cs +index e6c3aebb..1bc0f6e9 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/ConstantClassConverter.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/ConstantClassConverter.cs +@@ -23,7 +23,7 @@ namespace AWS.Lambda.Powertools.Logging.Internal.Converters; + /// + /// JsonConvert to handle the AWS SDK for .NET custom enum classes that derive from the class called ConstantClass. + /// +-internal class ConstantClassConverter : JsonConverter ++public class ConstantClassConverter : JsonConverter + { + private static readonly HashSet ConstantClassNames = new() + { +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/DateOnlyConverter.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/DateOnlyConverter.cs +index efd9425b..a6f969e5 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/DateOnlyConverter.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/DateOnlyConverter.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.Globalization; + using System.Text.Json; +@@ -8,7 +23,7 @@ namespace AWS.Lambda.Powertools.Logging.Internal.Converters; + /// + /// DateOnly JSON converter + /// +-internal class DateOnlyConverter : JsonConverter ++public class DateOnlyConverter : JsonConverter + { + private const string DateFormat = "yyyy-MM-dd"; + +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/TimeOnlyConverter.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/TimeOnlyConverter.cs +index a97db6ab..737362ca 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/TimeOnlyConverter.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/TimeOnlyConverter.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.Globalization; + using System.Text.Json; +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/LoggerFactoryHelper.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/LoggerFactoryHelper.cs +deleted file mode 100644 +index 17fc402a..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/LoggerFactoryHelper.cs ++++ /dev/null +@@ -1,54 +0,0 @@ +-using Microsoft.Extensions.Logging; +- +-namespace AWS.Lambda.Powertools.Logging.Internal.Helpers; +- +-/// +-/// Helper class for creating and configuring logger factories +-/// +-internal static class LoggerFactoryHelper +-{ +- /// +- /// Creates and configures a logger factory with the provided configuration +- /// +- /// The Powertools logger configuration to apply +- /// The configured logger factory +- internal static ILoggerFactory CreateAndConfigureFactory(PowertoolsLoggerConfiguration configuration) +- { +- var factory = LoggerFactory.Create(builder => +- { +- builder.AddPowertoolsLogger(config => +- { +- config.Service = configuration.Service; +- config.TimestampFormat = configuration.TimestampFormat; +- config.MinimumLogLevel = configuration.MinimumLogLevel; +- config.InitialLogLevel = configuration.InitialLogLevel; +- config.SamplingRate = configuration.SamplingRate; +- config.LoggerOutputCase = configuration.LoggerOutputCase; +- config.LogLevelKey = configuration.LogLevelKey; +- config.LogFormatter = configuration.LogFormatter; +- config.JsonOptions = configuration.JsonOptions; +- config.LogBuffering = configuration.LogBuffering; +- config.LogOutput = configuration.LogOutput; +- config.XRayTraceId = configuration.XRayTraceId; +- config.LogEvent = configuration.LogEvent; +- }); +- +- // When sampling is enabled, set the factory minimum level to Debug +- // so that all logs can reach our PowertoolsLogger for dynamic filtering +- if (configuration.SamplingRate > 0) +- { +- builder.AddFilter(null, LogLevel.Debug); +- builder.SetMinimumLevel(LogLevel.Debug); +- } +- else if (configuration.MinimumLogLevel != LogLevel.None) +- { +- builder.AddFilter(null, configuration.MinimumLogLevel); +- builder.SetMinimumLevel(configuration.MinimumLogLevel); +- } +- }); +- +- LoggerFactoryHolder.SetFactory(factory); +- +- return factory; +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/PowertoolsLoggerHelpers.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/PowertoolsLoggerHelpers.cs +index f682a99f..1245f8d6 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/PowertoolsLoggerHelpers.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/PowertoolsLoggerHelpers.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.Collections.Generic; + using System.Linq; +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerFactoryHolder.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerFactoryHolder.cs +deleted file mode 100644 +index ce96ea73..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerFactoryHolder.cs ++++ /dev/null +@@ -1,78 +0,0 @@ +-/* +- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +- * +- * Licensed under the Apache License, Version 2.0 (the "License"). +- * You may not use this file except in compliance with the License. +- * A copy of the License is located at +- * +- * http://aws.amazon.com/apache2.0 +- * +- * or in the "license" file accompanying this file. This file is distributed +- * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +- * express or implied. See the License for the specific language governing +- * permissions and limitations under the License. +- */ +- +-using System; +-using AWS.Lambda.Powertools.Logging.Internal.Helpers; +-using Microsoft.Extensions.Logging; +- +-namespace AWS.Lambda.Powertools.Logging.Internal; +- +-/// +-/// Holds and manages the shared logger factory instance +-/// +-internal static class LoggerFactoryHolder +-{ +- private static ILoggerFactory _factory; +- private static readonly object _lock = new object(); +- +- /// +- /// Gets or creates the shared logger factory +- /// +- public static ILoggerFactory GetOrCreateFactory() +- { +- lock (_lock) +- { +- if (_factory == null) +- { +- var config = PowertoolsLoggingBuilderExtensions.GetCurrentConfiguration(); +- +- _factory = LoggerFactoryHelper.CreateAndConfigureFactory(config); +- } +- return _factory; +- } +- } +- +- public static void SetFactory(ILoggerFactory factory) +- { +- if (factory == null) throw new ArgumentNullException(nameof(factory)); +- lock (_lock) +- { +- _factory = factory; +- Logger.ClearInstance(); +- } +- } +- +- /// +- /// Resets the factory holder for testing +- /// +- internal static void Reset() +- { +- lock (_lock) +- { +- // Dispose the old factory if it exists +- if (_factory == null) return; +- try +- { +- _factory.Dispose(); +- } +- catch +- { +- // Ignore disposal errors +- } +- +- _factory = null; +- } +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerProvider.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerProvider.cs +new file mode 100644 +index 00000000..94bb1c0d +--- /dev/null ++++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerProvider.cs +@@ -0,0 +1,86 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ ++using System.Collections.Concurrent; ++using AWS.Lambda.Powertools.Common; ++using Microsoft.Extensions.Logging; ++using Microsoft.Extensions.Options; ++ ++namespace AWS.Lambda.Powertools.Logging.Internal; ++ ++/// ++/// Class LoggerProvider. This class cannot be inherited. ++/// Implements the ++/// ++/// ++public sealed class LoggerProvider : ILoggerProvider ++{ ++ /// ++ /// The powertools configurations ++ /// ++ private readonly IPowertoolsConfigurations _powertoolsConfigurations; ++ ++ /// ++ /// The system wrapper ++ /// ++ private readonly ISystemWrapper _systemWrapper; ++ ++ /// ++ /// The loggers ++ /// ++ private readonly ConcurrentDictionary _loggers = new(); ++ ++ ++ /// ++ /// Initializes a new instance of the class. ++ /// ++ /// The configuration. ++ /// ++ /// ++ public LoggerProvider(IOptions config, IPowertoolsConfigurations powertoolsConfigurations, ISystemWrapper systemWrapper) ++ { ++ _powertoolsConfigurations = powertoolsConfigurations; ++ _systemWrapper = systemWrapper; ++ _powertoolsConfigurations.SetCurrentConfig(config?.Value, systemWrapper); ++ } ++ ++ /// ++ /// Initializes a new instance of the class. ++ /// ++ /// The configuration. ++ public LoggerProvider(IOptions config) ++ : this(config, PowertoolsConfigurations.Instance, SystemWrapper.Instance) { } ++ ++ /// ++ /// Creates a new instance. ++ /// ++ /// The category name for messages produced by the logger. ++ /// The instance of that was created. ++ public ILogger CreateLogger(string categoryName) ++ { ++ return _loggers.GetOrAdd(categoryName, ++ name => PowertoolsLogger.CreateLogger(name, ++ _powertoolsConfigurations, ++ _systemWrapper)); ++ } ++ ++ /// ++ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. ++ /// ++ public void Dispose() ++ { ++ _loggers.Clear(); ++ } ++} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs +index 37cdc1c9..c92566e2 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs +@@ -1,10 +1,27 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.IO; + using System.Linq; ++using System.Reflection; + using System.Runtime.ExceptionServices; + using System.Text.Json; ++using AspectInjector.Broker; + using AWS.Lambda.Powertools.Common; +-using AWS.Lambda.Powertools.Logging.Internal.Helpers; ++using AWS.Lambda.Powertools.Logging.Serializers; + using Microsoft.Extensions.Logging; + + namespace AWS.Lambda.Powertools.Logging.Internal; +@@ -14,8 +31,14 @@ namespace AWS.Lambda.Powertools.Logging.Internal; + /// Scope.Global is singleton + /// + /// +-public class LoggingAspect : IMethodAspectHandler ++[Aspect(Scope.Global, Factory = typeof(LoggingAspectFactory))] ++public class LoggingAspect + { ++ /// ++ /// The is cold start ++ /// ++ private bool _isColdStart = true; ++ + /// + /// The initialize context + /// +@@ -26,6 +49,21 @@ public class LoggingAspect : IMethodAspectHandler + /// + private bool _clearState; + ++ /// ++ /// The correlation identifier path ++ /// ++ private string _correlationIdPath; ++ ++ /// ++ /// The Powertools for AWS Lambda (.NET) configurations ++ /// ++ private readonly IPowertoolsConfigurations _powertoolsConfigurations; ++ ++ /// ++ /// The system wrapper ++ /// ++ private readonly ISystemWrapper _systemWrapper; ++ + /// + /// The is context initialized + /// +@@ -36,48 +74,133 @@ public class LoggingAspect : IMethodAspectHandler + /// + private bool _clearLambdaContext; + +- private ILogger _logger; +- private bool _isDebug; +- private bool _bufferingEnabled; +- private PowertoolsLoggerConfiguration _currentConfig; +- private bool _flushBufferOnUncaughtError; ++ /// ++ /// The configuration ++ /// ++ private LoggerConfiguration _config; + + /// + /// Initializes a new instance of the class. + /// +- public LoggingAspect(ILogger logger) ++ /// The Powertools configurations. ++ /// The system wrapper. ++ public LoggingAspect(IPowertoolsConfigurations powertoolsConfigurations, ISystemWrapper systemWrapper) + { +- _logger = logger ?? LoggerFactoryHolder.GetOrCreateFactory().CreatePowertoolsLogger(); ++ _powertoolsConfigurations = powertoolsConfigurations; ++ _systemWrapper = systemWrapper; + } + +- private void InitializeLogger(LoggingAttribute trigger) ++ /// ++ /// Runs before the execution of the method marked with the Logging Attribute ++ /// ++ /// ++ /// ++ /// ++ /// ++ /// ++ /// ++ /// ++ [Advice(Kind.Before)] ++ public void OnEntry( ++ [Argument(Source.Instance)] object instance, ++ [Argument(Source.Name)] string name, ++ [Argument(Source.Arguments)] object[] args, ++ [Argument(Source.Type)] Type hostType, ++ [Argument(Source.Metadata)] MethodBase method, ++ [Argument(Source.ReturnType)] Type returnType, ++ [Argument(Source.Triggers)] Attribute[] triggers) + { +- // Check which settings are explicitly provided in the attribute +- var hasLogLevel = trigger.LogLevel != LogLevel.None; +- var hasService = !string.IsNullOrEmpty(trigger.Service); +- var hasOutputCase = trigger.LoggerOutputCase != LoggerOutputCase.Default; +- var hasSamplingRate = trigger.SamplingRate > 0; ++ // Called before the method ++ var trigger = triggers.OfType().First(); ++ ++ try ++ { ++ var eventArgs = new AspectEventArgs ++ { ++ Instance = instance, ++ Type = hostType, ++ Method = method, ++ Name = name, ++ Args = args, ++ ReturnType = returnType, ++ Triggers = triggers ++ }; ++ ++ _config = new LoggerConfiguration ++ { ++ Service = trigger.Service, ++ LoggerOutputCase = trigger.LoggerOutputCase, ++ SamplingRate = trigger.SamplingRate, ++ MinimumLevel = trigger.LogLevel ++ }; ++ ++ var logEvent = trigger.LogEvent; ++ _correlationIdPath = trigger.CorrelationIdPath; ++ _clearState = trigger.ClearState; + +- // Only update configuration if any settings were provided +- var needsReconfiguration = hasLogLevel || hasService || hasOutputCase || hasSamplingRate; +- _currentConfig = PowertoolsLoggingBuilderExtensions.GetCurrentConfiguration(); ++ Logger.LoggerProvider = new LoggerProvider(_config, _powertoolsConfigurations, _systemWrapper); ++ ++ if (!_initializeContext) ++ return; ++ ++ Logger.AppendKey(LoggingConstants.KeyColdStart, _isColdStart); ++ ++ _isColdStart = false; ++ _initializeContext = false; ++ _isContextInitialized = true; + +- if (needsReconfiguration) ++ var eventObject = eventArgs.Args.FirstOrDefault(); ++ CaptureXrayTraceId(); ++ CaptureLambdaContext(eventArgs); ++ CaptureCorrelationId(eventObject); ++ if (logEvent || _powertoolsConfigurations.LoggerLogEvent) ++ LogEvent(eventObject); ++ } ++ catch (Exception exception) + { +- // Apply each setting directly using the existing Logger static methods +- if (hasLogLevel) _currentConfig.MinimumLogLevel = trigger.LogLevel; +- if (hasService) _currentConfig.Service = trigger.Service; +- if (hasOutputCase) _currentConfig.LoggerOutputCase = trigger.LoggerOutputCase; +- if (hasSamplingRate) _currentConfig.SamplingRate = trigger.SamplingRate; +- +- // Need to refresh the logger after configuration changes +- _logger = LoggerFactoryHelper.CreateAndConfigureFactory(_currentConfig).CreatePowertoolsLogger(); +- Logger.ClearInstance(); ++ // The purpose of ExceptionDispatchInfo.Capture is to capture a potentially mutating exception's StackTrace at a point in time: ++ // https://learn.microsoft.com/en-us/dotnet/standard/exceptions/best-practices-for-exceptions#capture-exceptions-to-rethrow-later ++ ExceptionDispatchInfo.Capture(exception).Throw(); + } ++ } ++ ++ /// ++ /// Handles the Kind.After event. ++ /// ++ [Advice(Kind.After)] ++ public void OnExit() ++ { ++ if (!_isContextInitialized) ++ return; ++ if (_clearLambdaContext) ++ LoggingLambdaContext.Clear(); ++ if (_clearState) ++ Logger.RemoveAllKeys(); ++ _initializeContext = true; ++ } ++ ++ /// ++ /// Determines whether this instance is debug. ++ /// ++ /// true if this instance is debug; otherwise, false. ++ private bool IsDebug() ++ { ++ return LogLevel.Debug >= _powertoolsConfigurations.GetLogLevel(_config.MinimumLevel); ++ } ++ ++ /// ++ /// Captures the xray trace identifier. ++ /// ++ private void CaptureXrayTraceId() ++ { ++ var xRayTraceId = _powertoolsConfigurations.XRayTraceId; ++ if (string.IsNullOrWhiteSpace(xRayTraceId)) ++ return; + +- // Set operational flags based on current configuration +- _isDebug = _currentConfig.MinimumLogLevel <= LogLevel.Debug; +- _bufferingEnabled = _currentConfig.LogBuffering != null; ++ xRayTraceId = xRayTraceId ++ .Split(';', StringSplitOptions.RemoveEmptyEntries)[0].Replace("Root=", ""); ++ ++ Logger.AppendKey(LoggingConstants.KeyXRayTraceId, xRayTraceId); + } + + /// +@@ -90,8 +213,8 @@ public class LoggingAspect : IMethodAspectHandler + private void CaptureLambdaContext(AspectEventArgs eventArgs) + { + _clearLambdaContext = LoggingLambdaContext.Extract(eventArgs); +- if (LoggingLambdaContext.Instance is null && _isDebug) +- ConsoleWrapper.WriteLine(LogLevel.Warning.ToLambdaLogLevel(), ++ if (LoggingLambdaContext.Instance is null && IsDebug()) ++ _systemWrapper.LogLine( + "Skipping Lambda Context injection because ILambdaContext context parameter not found."); + } + +@@ -99,13 +222,12 @@ public class LoggingAspect : IMethodAspectHandler + /// Captures the correlation identifier. + /// + /// The event argument. +- /// +- private void CaptureCorrelationId(object eventArg, string correlationIdPath) ++ private void CaptureCorrelationId(object eventArg) + { +- if (string.IsNullOrWhiteSpace(correlationIdPath)) ++ if (string.IsNullOrWhiteSpace(_correlationIdPath)) + return; + +- var correlationIdPaths = correlationIdPath ++ var correlationIdPaths = _correlationIdPath + .Split(CorrelationIdPaths.Separator, StringSplitOptions.RemoveEmptyEntries); + + if (!correlationIdPaths.Any()) +@@ -113,8 +235,8 @@ public class LoggingAspect : IMethodAspectHandler + + if (eventArg is null) + { +- if (_isDebug) +- ConsoleWrapper.WriteLine(LogLevel.Warning.ToLambdaLogLevel(), ++ if (IsDebug()) ++ _systemWrapper.LogLine( + "Skipping CorrelationId capture because event parameter not found."); + return; + } +@@ -124,16 +246,16 @@ public class LoggingAspect : IMethodAspectHandler + var correlationId = string.Empty; + + var jsonDoc = +- JsonDocument.Parse(_currentConfig.Serializer.Serialize(eventArg, eventArg.GetType())); ++ JsonDocument.Parse(PowertoolsLoggingSerializer.Serialize(eventArg, eventArg.GetType())); + + var element = jsonDoc.RootElement; + + for (var i = 0; i < correlationIdPaths.Length; i++) + { +- // TODO: For casing parsing to be removed from Logging v2 when we get rid of outputcase without this CorrelationIdPaths.ApiGatewayRest would not work +- // TODO: This will be removed and replaced by JMesPath +- +- var pathWithOutputCase = correlationIdPaths[i].ToCase(_currentConfig.LoggerOutputCase); ++ // For casing parsing to be removed from Logging v2 when we get rid of outputcase ++ // without this CorrelationIdPaths.ApiGatewayRest would not work ++ var pathWithOutputCase = ++ _powertoolsConfigurations.ConvertToOutputCase(correlationIdPaths[i], _config.LoggerOutputCase); + if (!element.TryGetProperty(pathWithOutputCase, out var childElement)) + break; + +@@ -143,12 +265,12 @@ public class LoggingAspect : IMethodAspectHandler + } + + if (!string.IsNullOrWhiteSpace(correlationId)) +- _logger.AppendKey(LoggingConstants.KeyCorrelationId, correlationId); ++ Logger.AppendKey(LoggingConstants.KeyCorrelationId, correlationId); + } + catch (Exception e) + { +- if (_isDebug) +- ConsoleWrapper.WriteLine(LogLevel.Warning.ToLambdaLogLevel(), ++ if (IsDebug()) ++ _systemWrapper.LogLine( + $"Skipping CorrelationId capture because of error caused while parsing the event object {e.Message}."); + } + } +@@ -163,30 +285,30 @@ public class LoggingAspect : IMethodAspectHandler + { + case null: + { +- if (_isDebug) +- ConsoleWrapper.WriteLine(LogLevel.Warning.ToLambdaLogLevel(), ++ if (IsDebug()) ++ _systemWrapper.LogLine( + "Skipping Event Log because event parameter not found."); + break; + } + case Stream: + try + { +- _logger.LogInformation(eventArg); ++ Logger.LogInformation(eventArg); + } + catch (Exception e) + { +- _logger.LogError(e, "Failed to log event from supplied input stream."); ++ Logger.LogError(e, "Failed to log event from supplied input stream."); + } + + break; + default: + try + { +- _logger.LogInformation(eventArg); ++ Logger.LogInformation(eventArg); + } + catch (Exception e) + { +- _logger.LogError(e, "Failed to log event from supplied input object."); ++ Logger.LogError(e, "Failed to log event from supplied input object."); + } + + break; +@@ -199,95 +321,8 @@ public class LoggingAspect : IMethodAspectHandler + internal static void ResetForTest() + { + LoggingLambdaContext.Clear(); +- } +- +- /// +- /// Entry point for the aspect. +- /// +- /// +- public void OnEntry(AspectEventArgs eventArgs) +- { +- var trigger = eventArgs.Triggers.OfType().First(); +- try +- { +- _clearState = trigger.ClearState; +- +- InitializeLogger(trigger); +- +- if (!_initializeContext) +- return; +- +- _initializeContext = false; +- _isContextInitialized = true; +- _flushBufferOnUncaughtError = trigger.FlushBufferOnUncaughtError; +- +- var eventObject = eventArgs.Args.FirstOrDefault(); +- CaptureLambdaContext(eventArgs); +- CaptureCorrelationId(eventObject, trigger.CorrelationIdPath); +- +- switch (trigger.IsLogEventSet) +- { +- case true when trigger.LogEvent: +- case false when _currentConfig.LogEvent: +- LogEvent(eventObject); +- break; +- } +- } +- catch (Exception exception) +- { +- if (_bufferingEnabled && _flushBufferOnUncaughtError) +- { +- _logger.FlushBuffer(); +- } +- +- // The purpose of ExceptionDispatchInfo.Capture is to capture a potentially mutating exception's StackTrace at a point in time: +- // https://learn.microsoft.com/en-us/dotnet/standard/exceptions/best-practices-for-exceptions#capture-exceptions-to-rethrow-later +- ExceptionDispatchInfo.Capture(exception).Throw(); +- } +- } +- +- /// +- /// When the method returns successfully, this method is called. +- /// +- /// +- /// +- public void OnSuccess(AspectEventArgs eventArgs, object result) +- { +- +- } +- +- /// +- /// When the method throws an exception, this method is called. +- /// +- /// +- /// +- public void OnException(AspectEventArgs eventArgs, Exception exception) +- { +- if (_bufferingEnabled && _flushBufferOnUncaughtError) +- { +- _logger.FlushBuffer(); +- } +- ExceptionDispatchInfo.Capture(exception).Throw(); +- } +- +- /// +- /// WHen the method exits, this method is called even if it throws an exception. +- /// +- /// +- public void OnExit(AspectEventArgs eventArgs) +- { +- if (!_isContextInitialized) +- return; +- if (_clearLambdaContext) +- LoggingLambdaContext.Clear(); +- if (_clearState) +- _logger.RemoveAllKeys(); +- _initializeContext = true; +- +- if (_bufferingEnabled) +- { +- // clear the buffer after the handler has finished +- _logger.ClearBuffer(); +- } ++ Logger.LoggerProvider = null; ++ Logger.RemoveAllKeys(); ++ Logger.ClearLoggerInstance(); + } + } +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspectFactory.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspectFactory.cs +index 295d8e78..5feae3cf 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspectFactory.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspectFactory.cs +@@ -1,10 +1,25 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using AWS.Lambda.Powertools.Common; + + namespace AWS.Lambda.Powertools.Logging.Internal; + + /// +-/// Class LoggingAspectFactory. For "dependency inject" Aspect ++/// Class LoggingAspectFactory. For "dependency inject" Configuration and SystemWrapper to Aspect + /// + internal static class LoggingAspectFactory + { +@@ -15,6 +30,6 @@ internal static class LoggingAspectFactory + /// An instance of the LoggingAspect class. + public static object GetInstance(Type type) + { +- return new LoggingAspect(LoggerFactoryHolder.GetOrCreateFactory().CreatePowertoolsLogger()); ++ return new LoggingAspect(PowertoolsConfigurations.Instance, SystemWrapper.Instance); + } + } +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingLambdaContext.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingLambdaContext.cs +index 9732bad0..a8846b15 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingLambdaContext.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingLambdaContext.cs +@@ -7,7 +7,7 @@ namespace AWS.Lambda.Powertools.Logging.Internal; + /// + /// Lambda Context + /// +-internal class LoggingLambdaContext ++public class LoggingLambdaContext + { + /// + /// The AWS request ID associated with the request. +@@ -74,21 +74,24 @@ internal class LoggingLambdaContext + return false; + + var index = Array.FindIndex(args.Method.GetParameters(), p => p.ParameterType == typeof(ILambdaContext)); +- if (index < 0 || args.Args[index] == null || args.Args[index] is not ILambdaContext) return false; +- +- var x = (ILambdaContext)args.Args[index]; +- +- Instance = new LoggingLambdaContext ++ if (index >= 0) + { +- AwsRequestId = x.AwsRequestId, +- FunctionName = x.FunctionName, +- FunctionVersion = x.FunctionVersion, +- InvokedFunctionArn = x.InvokedFunctionArn, +- LogGroupName = x.LogGroupName, +- LogStreamName = x.LogStreamName, +- MemoryLimitInMB = x.MemoryLimitInMB +- }; +- return true; ++ var x = (ILambdaContext)args.Args[index]; ++ ++ Instance = new LoggingLambdaContext ++ { ++ AwsRequestId = x.AwsRequestId, ++ FunctionName = x.FunctionName, ++ FunctionVersion = x.FunctionVersion, ++ InvokedFunctionArn = x.InvokedFunctionArn, ++ LogGroupName = x.LogGroupName, ++ LogStreamName = x.LogStreamName, ++ MemoryLimitInMB = x.MemoryLimitInMB ++ }; ++ return true; ++ } ++ ++ return false; + } + + /// +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsConfigurationsExtension.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsConfigurationsExtension.cs +index ee5094d4..148bb540 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsConfigurationsExtension.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsConfigurationsExtension.cs +@@ -1,41 +1,36 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.Collections.Generic; ++using System.Linq; ++using System.Text; + using AWS.Lambda.Powertools.Common; ++using AWS.Lambda.Powertools.Logging.Serializers; + using Microsoft.Extensions.Logging; + + namespace AWS.Lambda.Powertools.Logging.Internal; + +-internal static class LambdaLogLevelMapper +-{ +- public static string ToLambdaLogLevel(this LogLevel logLevel) +- { +- switch (logLevel) +- { +- case LogLevel.Trace: +- return "trace"; +- case LogLevel.Debug: +- return "debug"; +- case LogLevel.Information: +- return "info"; +- case LogLevel.Warning: +- return "warn"; +- case LogLevel.Error: +- return "error"; +- case LogLevel.Critical: +- return "fatal"; +- default: +- return "info"; +- } +- } +-} +- +- +- + /// + /// Class PowertoolsConfigurationsExtension. + /// + internal static class PowertoolsConfigurationsExtension + { ++ private static readonly object _lock = new object(); ++ private static LoggerConfiguration _config; ++ + /// + /// Maps AWS log level to .NET log level + /// +@@ -96,6 +91,88 @@ internal static class PowertoolsConfigurationsExtension + return LoggingConstants.DefaultLoggerOutputCase; + } + ++ /// ++ /// Gets the current configuration. ++ /// ++ /// AWS.Lambda.Powertools.Logging.LoggerConfiguration. ++ internal static void SetCurrentConfig(this IPowertoolsConfigurations powertoolsConfigurations, LoggerConfiguration config, ISystemWrapper systemWrapper) ++ { ++ lock (_lock) ++ { ++ _config = config ?? new LoggerConfiguration(); ++ ++ var logLevel = powertoolsConfigurations.GetLogLevel(_config.MinimumLevel); ++ var lambdaLogLevel = powertoolsConfigurations.GetLambdaLogLevel(); ++ var lambdaLogLevelEnabled = powertoolsConfigurations.LambdaLogLevelEnabled(); ++ ++ if (lambdaLogLevelEnabled && logLevel < lambdaLogLevel) ++ { ++ systemWrapper.LogLine($"Current log level ({logLevel}) does not match AWS Lambda Advanced Logging Controls minimum log level ({lambdaLogLevel}). This can lead to data loss, consider adjusting them."); ++ } ++ ++ // Set service ++ _config.Service = _config.Service ?? powertoolsConfigurations.Service; ++ ++ // Set output case ++ var loggerOutputCase = powertoolsConfigurations.GetLoggerOutputCase(_config.LoggerOutputCase); ++ _config.LoggerOutputCase = loggerOutputCase; ++ PowertoolsLoggingSerializer.ConfigureNamingPolicy(loggerOutputCase); ++ ++ // Set log level ++ var minLogLevel = lambdaLogLevelEnabled ? lambdaLogLevel : logLevel; ++ _config.MinimumLevel = minLogLevel; ++ ++ // Set sampling rate ++ SetSamplingRate(powertoolsConfigurations, systemWrapper, minLogLevel); ++ } ++ } ++ ++ /// ++ /// Set sampling rate ++ /// ++ /// ++ /// ++ /// ++ /// ++ private static void SetSamplingRate(IPowertoolsConfigurations powertoolsConfigurations, ISystemWrapper systemWrapper, LogLevel minLogLevel) ++ { ++ var samplingRate = _config.SamplingRate > 0 ? _config.SamplingRate : powertoolsConfigurations.LoggerSampleRate; ++ samplingRate = ValidateSamplingRate(samplingRate, minLogLevel, systemWrapper); ++ ++ _config.SamplingRate = samplingRate; ++ ++ if (samplingRate > 0) ++ { ++ double sample = systemWrapper.GetRandom(); ++ ++ if (sample <= samplingRate) ++ { ++ systemWrapper.LogLine($"Changed log level to DEBUG based on Sampling configuration. Sampling Rate: {samplingRate}, Sampler Value: {sample}."); ++ _config.MinimumLevel = LogLevel.Debug; ++ } ++ } ++ } ++ ++ /// ++ /// Validate Sampling rate ++ /// ++ /// ++ /// ++ /// ++ /// ++ private static double ValidateSamplingRate(double samplingRate, LogLevel minLogLevel, ISystemWrapper systemWrapper) ++ { ++ if (samplingRate < 0 || samplingRate > 1) ++ { ++ if (minLogLevel is LogLevel.Debug or LogLevel.Trace) ++ { ++ systemWrapper.LogLine($"Skipping sampling rate configuration because of invalid value. Sampling rate: {samplingRate}"); ++ } ++ return 0; ++ } ++ ++ return samplingRate; ++ } + + /// + /// Determines whether [is lambda log level enabled]. +@@ -106,4 +183,149 @@ internal static class PowertoolsConfigurationsExtension + { + return powertoolsConfigurations.GetLambdaLogLevel() != LogLevel.None; + } ++ ++ /// ++ /// Converts the input string to the configured output case. ++ /// ++ /// ++ /// The string to convert. ++ /// ++ /// ++ /// The input string converted to the configured case (camel, pascal, or snake case). ++ /// ++ internal static string ConvertToOutputCase(this IPowertoolsConfigurations powertoolsConfigurations, ++ string correlationIdPath, LoggerOutputCase loggerOutputCase) ++ { ++ return powertoolsConfigurations.GetLoggerOutputCase(loggerOutputCase) switch ++ { ++ LoggerOutputCase.CamelCase => ToCamelCase(correlationIdPath), ++ LoggerOutputCase.PascalCase => ToPascalCase(correlationIdPath), ++ _ => ToSnakeCase(correlationIdPath), // default snake_case ++ }; ++ } ++ ++ /// ++ /// Converts a string to snake_case. ++ /// ++ /// ++ /// The input string converted to snake_case. ++ private static string ToSnakeCase(string input) ++ { ++ if (string.IsNullOrEmpty(input)) ++ return input; ++ ++ var result = new StringBuilder(input.Length + 10); ++ bool lastCharWasUnderscore = false; ++ bool lastCharWasUpper = false; ++ ++ for (int i = 0; i < input.Length; i++) ++ { ++ char currentChar = input[i]; ++ ++ if (currentChar == '_') ++ { ++ result.Append('_'); ++ lastCharWasUnderscore = true; ++ lastCharWasUpper = false; ++ } ++ else if (char.IsUpper(currentChar)) ++ { ++ if (i > 0 && !lastCharWasUnderscore && ++ (!lastCharWasUpper || (i + 1 < input.Length && char.IsLower(input[i + 1])))) ++ { ++ result.Append('_'); ++ } ++ ++ result.Append(char.ToLowerInvariant(currentChar)); ++ lastCharWasUnderscore = false; ++ lastCharWasUpper = true; ++ } ++ else ++ { ++ result.Append(char.ToLowerInvariant(currentChar)); ++ lastCharWasUnderscore = false; ++ lastCharWasUpper = false; ++ } ++ } ++ ++ return result.ToString(); ++ } ++ ++ ++ /// ++ /// Converts a string to PascalCase. ++ /// ++ /// ++ /// The input string converted to PascalCase. ++ private static string ToPascalCase(string input) ++ { ++ if (string.IsNullOrEmpty(input)) ++ return input; ++ ++ var words = input.Split(new[] { '_' }, StringSplitOptions.RemoveEmptyEntries); ++ var result = new StringBuilder(); ++ ++ foreach (var word in words) ++ { ++ if (word.Length > 0) ++ { ++ // Capitalize the first character of each word ++ result.Append(char.ToUpperInvariant(word[0])); ++ ++ // Handle the rest of the characters ++ if (word.Length > 1) ++ { ++ // If the word is all uppercase, convert the rest to lowercase ++ if (word.All(char.IsUpper)) ++ { ++ result.Append(word.Substring(1).ToLowerInvariant()); ++ } ++ else ++ { ++ // Otherwise, keep the original casing ++ result.Append(word.Substring(1)); ++ } ++ } ++ } ++ } ++ ++ return result.ToString(); ++ } ++ ++ /// ++ /// Converts a string to camelCase. ++ /// ++ /// The string to convert. ++ /// The input string converted to camelCase. ++ private static string ToCamelCase(string input) ++ { ++ if (string.IsNullOrEmpty(input)) ++ return input; ++ ++ // First, convert to PascalCase ++ string pascalCase = ToPascalCase(input); ++ ++ // Then convert the first character to lowercase ++ return char.ToLowerInvariant(pascalCase[0]) + pascalCase.Substring(1); ++ } ++ ++ /// ++ /// Determines whether [is log level enabled]. ++ /// ++ /// The Powertools for AWS Lambda (.NET) configurations. ++ /// The log level. ++ /// true if [is log level enabled]; otherwise, false. ++ internal static bool IsLogLevelEnabled(this IPowertoolsConfigurations powertoolsConfigurations, LogLevel logLevel) ++ { ++ return logLevel != LogLevel.None && logLevel >= _config.MinimumLevel; ++ } ++ ++ /// ++ /// Gets the current configuration. ++ /// ++ /// AWS.Lambda.Powertools.Logging.LoggerConfiguration. ++ internal static LoggerConfiguration CurrentConfig(this IPowertoolsConfigurations powertoolsConfigurations) ++ { ++ return _config; ++ } + } +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs +index 6ad3d34c..5e603c02 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs +@@ -1,10 +1,25 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Runtime.CompilerServices; +-using System.Text.RegularExpressions; + using AWS.Lambda.Powertools.Common; + using AWS.Lambda.Powertools.Logging.Internal.Helpers; ++using AWS.Lambda.Powertools.Logging.Serializers; + using Microsoft.Extensions.Logging; + + namespace AWS.Lambda.Powertools.Logging.Internal; +@@ -16,20 +31,21 @@ namespace AWS.Lambda.Powertools.Logging.Internal; + /// + internal sealed class PowertoolsLogger : ILogger + { +- private static string _originalformat = "{OriginalFormat}"; +- + /// + /// The name + /// +- private readonly string _categoryName; ++ private readonly string _name; + + /// + /// The current configuration + /// +- private readonly Func _currentConfig; +- + private readonly IPowertoolsConfigurations _powertoolsConfigurations; + ++ /// ++ /// The system wrapper ++ /// ++ private readonly ISystemWrapper _systemWrapper; ++ + /// + /// The current scope + /// +@@ -38,17 +54,32 @@ internal sealed class PowertoolsLogger : ILogger + /// + /// Private constructor - Is initialized on CreateLogger + /// +- /// The name. +- /// +- /// +- public PowertoolsLogger( +- string categoryName, +- Func getCurrentConfig, +- IPowertoolsConfigurations powertoolsConfigurations) ++ /// The name. ++ /// The Powertools for AWS Lambda (.NET) configurations. ++ /// The system wrapper. ++ private PowertoolsLogger( ++ string name, ++ IPowertoolsConfigurations powertoolsConfigurations, ++ ISystemWrapper systemWrapper) + { +- _categoryName = categoryName; +- _currentConfig = getCurrentConfig; ++ _name = name; + _powertoolsConfigurations = powertoolsConfigurations; ++ _systemWrapper = systemWrapper; ++ ++ _powertoolsConfigurations.SetExecutionEnvironment(this); ++ } ++ ++ /// ++ /// Initializes a new instance of the class. ++ /// ++ /// The name. ++ /// The Powertools for AWS Lambda (.NET) configurations. ++ /// The system wrapper. ++ internal static PowertoolsLogger CreateLogger(string name, ++ IPowertoolsConfigurations powertoolsConfigurations, ++ ISystemWrapper systemWrapper) ++ { ++ return new PowertoolsLogger(name, powertoolsConfigurations, systemWrapper); + } + + /// +@@ -77,43 +108,7 @@ internal sealed class PowertoolsLogger : ILogger + /// The log level. + /// bool. + [MethodImpl(MethodImplOptions.AggressiveInlining)] +- public bool IsEnabled(LogLevel logLevel) +- { +- var config = _currentConfig(); +- return IsEnabledForConfig(logLevel, config); +- } +- +- /// +- /// Determines whether the specified log level is enabled for a specific configuration. +- /// +- /// The log level. +- /// The configuration to check against. +- /// bool. +- [MethodImpl(MethodImplOptions.AggressiveInlining)] +- private bool IsEnabledForConfig(LogLevel logLevel, PowertoolsLoggerConfiguration config) +- { +- //if Buffering is enabled and the log level is below the buffer threshold, skip logging only if bellow error +- if (logLevel <= config.LogBuffering?.BufferAtLogLevel +- && config.LogBuffering?.BufferAtLogLevel != LogLevel.Error +- && config.LogBuffering?.BufferAtLogLevel != LogLevel.Critical) +- { +- return false; +- } +- +- // If we have no explicit minimum level, use the default +- var effectiveMinLevel = config.MinimumLogLevel != LogLevel.None +- ? config.MinimumLogLevel +- : LoggingConstants.DefaultLogLevel; +- +- // Log diagnostic info for Debug/Trace levels +- if (logLevel <= LogLevel.Debug) +- { +- return logLevel >= effectiveMinLevel; +- } +- +- // Standard check +- return logLevel >= effectiveMinLevel; +- } ++ public bool IsEnabled(LogLevel logLevel) => _powertoolsConfigurations.IsLogLevelEnabled(logLevel); + + /// + /// Writes a log entry. +@@ -127,67 +122,23 @@ internal sealed class PowertoolsLogger : ILogger + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, + Func formatter) + { +- var config = _currentConfig(); +- if (config.SamplingRate > 0) +- { +- var samplingActivated = config.RefreshSampleRateCalculation(out double samplerValue); +- if (samplingActivated) +- { +- config.LogOutput.WriteLine($"Changed log level to DEBUG based on Sampling configuration. Sampling Rate: {config.SamplingRate}, Sampler Value: {samplerValue}."); +- } +- } +- +- // Use the same config reference for IsEnabled check to ensure we see the updated MinimumLogLevel +- if (!IsEnabledForConfig(logLevel, config)) +- { +- return; +- } +- +- config.LogOutput.WriteLine(LogEntryString(logLevel, state, exception, formatter)); +- } +- +- internal void LogLine(string message) +- { +- _currentConfig().LogOutput.WriteLine(message); +- } +- +- private void LogDebug(string message) +- { +- if (IsEnabled(LogLevel.Debug)) +- { +- Log(LogLevel.Debug, new EventId(), message, null, (msg, ex) => msg); +- } +- } +- +- internal string LogEntryString(LogLevel logLevel, TState state, Exception exception, +- Func formatter) +- { +- var logEntry = LogEntry(logLevel, state, exception, formatter); +- return _currentConfig().Serializer.Serialize(logEntry, typeof(object)); +- } +- +- internal object LogEntry(LogLevel logLevel, TState state, Exception exception, +- Func formatter) +- { +- var timestamp = DateTime.UtcNow; +- + if (formatter is null) + throw new ArgumentNullException(nameof(formatter)); + +- // Extract structured parameters for template-style logging +- var structuredParameters = ExtractStructuredParameters(state, out _); ++ if (!IsEnabled(logLevel)) ++ return; + +- // Format the message ++ var timestamp = DateTime.UtcNow; + var message = CustomFormatter(state, exception, out var customMessage) && customMessage is not null + ? customMessage + : formatter(state, exception); + +- // Get log entry +- var logFormatter = _currentConfig().LogFormatter; ++ var logFormatter = Logger.GetFormatter(); + var logEntry = logFormatter is null +- ? GetLogEntry(logLevel, timestamp, message, exception, structuredParameters) +- : GetFormattedLogEntry(logLevel, timestamp, message, exception, logFormatter, structuredParameters); +- return logEntry; ++ ? GetLogEntry(logLevel, timestamp, message, exception) ++ : GetFormattedLogEntry(logLevel, timestamp, message, exception, logFormatter); ++ ++ _systemWrapper.LogLine(PowertoolsLoggingSerializer.Serialize(logEntry, typeof(object))); + } + + /// +@@ -197,18 +148,16 @@ internal sealed class PowertoolsLogger : ILogger + /// Entry timestamp. + /// The message to be written. Can be also an object. + /// The exception related to this entry. +- /// The parameters for structured formatting + private Dictionary GetLogEntry(LogLevel logLevel, DateTime timestamp, object message, +- Exception exception, Dictionary structuredParameters = null) ++ Exception exception) + { + var logEntry = new Dictionary(); + +- var config = _currentConfig(); +- logEntry.TryAdd(config.LogLevelKey, logLevel.ToString()); +- logEntry.TryAdd(LoggingConstants.KeyMessage, message); +- logEntry.TryAdd(LoggingConstants.KeyTimestamp, timestamp.ToString(config.TimestampFormat ?? "o")); +- logEntry.TryAdd(LoggingConstants.KeyService, config.Service); +- logEntry.TryAdd(LoggingConstants.KeyColdStart, _powertoolsConfigurations.IsColdStart); ++ // Add Custom Keys ++ foreach (var (key, value) in Logger.GetAllKeys()) ++ { ++ logEntry.TryAdd(key, value); ++ } + + // Add Lambda Context Keys + if (LoggingLambdaContext.Instance is not null) +@@ -216,86 +165,31 @@ internal sealed class PowertoolsLogger : ILogger + AddLambdaContextKeys(logEntry); + } + +- if (!string.IsNullOrWhiteSpace(_powertoolsConfigurations.XRayTraceId)) +- logEntry.TryAdd(LoggingConstants.KeyXRayTraceId, +- _powertoolsConfigurations.XRayTraceId.Split(';', StringSplitOptions.RemoveEmptyEntries)[0] +- .Replace("Root=", "")); +- logEntry.TryAdd(LoggingConstants.KeyLoggerName, _categoryName); +- +- if (config.SamplingRate > 0) +- logEntry.TryAdd(LoggingConstants.KeySamplingRate, config.SamplingRate); +- +- // Add Custom Keys +- foreach (var (key, value) in this.GetAllKeys()) +- { +- // Skip keys that are already defined in LoggingConstants +- if (!IsLogConstantKey(key)) +- { +- logEntry.TryAdd(key, value); +- } +- } +- + // Add Extra Fields + if (CurrentScope?.ExtraKeys is not null) + { + foreach (var (key, value) in CurrentScope.ExtraKeys) + { +- if (string.IsNullOrWhiteSpace(key)) continue; +- if (!IsLogConstantKey(key)) +- { ++ if (!string.IsNullOrWhiteSpace(key)) + logEntry.TryAdd(key, value); +- } + } + } + +- // Add structured parameters +- if (structuredParameters != null && structuredParameters.Count > 0) +- { +- foreach (var (key, value) in structuredParameters) +- { +- if (string.IsNullOrWhiteSpace(key) || key == "json") continue; +- if (!IsLogConstantKey(key)) +- { +- logEntry.TryAdd(key, value); +- } +- } +- } ++ var keyLogLevel = GetLogLevelKey(); + +- // Use the AddExceptionDetails method instead of adding exception directly ++ logEntry.TryAdd(LoggingConstants.KeyTimestamp, timestamp.ToString("o")); ++ logEntry.TryAdd(keyLogLevel, logLevel.ToString()); ++ logEntry.TryAdd(LoggingConstants.KeyService, _powertoolsConfigurations.CurrentConfig().Service); ++ logEntry.TryAdd(LoggingConstants.KeyLoggerName, _name); ++ logEntry.TryAdd(LoggingConstants.KeyMessage, message); ++ if (_powertoolsConfigurations.CurrentConfig().SamplingRate > 0) ++ logEntry.TryAdd(LoggingConstants.KeySamplingRate, _powertoolsConfigurations.CurrentConfig().SamplingRate); + if (exception != null) +- { + logEntry.TryAdd(LoggingConstants.KeyException, exception); +- } + + return logEntry; + } + +- /// +- /// Checks if a key is defined in LoggingConstants +- /// +- /// The key to check +- /// true if the key is a LoggingConstants key +- private bool IsLogConstantKey(string key) +- { +- return string.Equals(key.ToPascal(), LoggingConstants.KeyColdStart, StringComparison.OrdinalIgnoreCase) +- // || string.Equals(key.ToPascal(), LoggingConstants.KeyCorrelationId, StringComparison.OrdinalIgnoreCase) +- || string.Equals(key.ToPascal(), LoggingConstants.KeyException, StringComparison.OrdinalIgnoreCase) +- || string.Equals(key.ToPascal(), LoggingConstants.KeyFunctionArn, StringComparison.OrdinalIgnoreCase) +- || string.Equals(key.ToPascal(), LoggingConstants.KeyFunctionMemorySize, +- StringComparison.OrdinalIgnoreCase) +- || string.Equals(key.ToPascal(), LoggingConstants.KeyFunctionName, StringComparison.OrdinalIgnoreCase) +- || string.Equals(key.ToPascal(), LoggingConstants.KeyFunctionRequestId, +- StringComparison.OrdinalIgnoreCase) +- || string.Equals(key.ToPascal(), LoggingConstants.KeyFunctionVersion, StringComparison.OrdinalIgnoreCase) +- || string.Equals(key.ToPascal(), LoggingConstants.KeyLoggerName, StringComparison.OrdinalIgnoreCase) +- || string.Equals(key.ToPascal(), LoggingConstants.KeyLogLevel, StringComparison.OrdinalIgnoreCase) +- || string.Equals(key.ToPascal(), LoggingConstants.KeyMessage, StringComparison.OrdinalIgnoreCase) +- || string.Equals(key.ToPascal(), LoggingConstants.KeySamplingRate, StringComparison.OrdinalIgnoreCase) +- || string.Equals(key.ToPascal(), LoggingConstants.KeyService, StringComparison.OrdinalIgnoreCase) +- || string.Equals(key.ToPascal(), LoggingConstants.KeyTimestamp, StringComparison.OrdinalIgnoreCase) +- || string.Equals(key.ToPascal(), LoggingConstants.KeyXRayTraceId, StringComparison.OrdinalIgnoreCase); +- } +- + /// + /// Gets a formatted log entry. For custom log formatter + /// +@@ -304,29 +198,27 @@ internal sealed class PowertoolsLogger : ILogger + /// The message to be written. Can be also an object. + /// The exception related to this entry. + /// The custom log entry formatter. +- /// The structured parameters. + private object GetFormattedLogEntry(LogLevel logLevel, DateTime timestamp, object message, +- Exception exception, ILogFormatter logFormatter, Dictionary structuredParameters) ++ Exception exception, ILogFormatter logFormatter) + { + if (logFormatter is null) + return null; + +- var config = _currentConfig(); + var logEntry = new LogEntry + { + Timestamp = timestamp, + Level = logLevel, +- Service = config.Service, +- Name = _categoryName, ++ Service = _powertoolsConfigurations.CurrentConfig().Service, ++ Name = _name, + Message = message, +- Exception = exception, // Keep this to maintain compatibility +- SamplingRate = config.SamplingRate, ++ Exception = exception, ++ SamplingRate = _powertoolsConfigurations.CurrentConfig().SamplingRate, + }; + + var extraKeys = new Dictionary(); + + // Add Custom Keys +- foreach (var (key, value) in this.GetAllKeys()) ++ foreach (var (key, value) in Logger.GetAllKeys()) + { + switch (key) + { +@@ -351,34 +243,7 @@ internal sealed class PowertoolsLogger : ILogger + foreach (var (key, value) in CurrentScope.ExtraKeys) + { + if (!string.IsNullOrWhiteSpace(key)) +- { + extraKeys.TryAdd(key, value); +- } +- } +- } +- +- // Add structured parameters +- if (structuredParameters != null && structuredParameters.Count > 0) +- { +- foreach (var (key, value) in structuredParameters) +- { +- if (!string.IsNullOrWhiteSpace(key) && key != "json") +- { +- extraKeys.TryAdd(key, value); +- } +- } +- } +- +- // Add detailed exception information +- if (exception != null) +- { +- var exceptionDetails = new Dictionary(); +- exceptionDetails.TryAdd(LoggingConstants.KeyException, exception); +- +- // Add exception details to extra keys +- foreach (var (key, value) in exceptionDetails) +- { +- extraKeys.TryAdd(key, value); + } + } + +@@ -396,12 +261,7 @@ internal sealed class PowertoolsLogger : ILogger + var logObject = logFormatter.FormatLogEntry(logEntry); + if (logObject is null) + throw new LogFormatException($"{logFormatter.GetType().FullName} returned Null value."); +- +-#if NET8_0_OR_GREATER + return PowertoolsLoggerHelpers.ObjectToDictionary(logObject); +-#else +- return logObject; +-#endif + } + catch (Exception e) + { +@@ -425,40 +285,36 @@ internal sealed class PowertoolsLogger : ILogger + if (exception is not null) + return false; + +-#if NET8_0_OR_GREATER +- var stateKeys = new Dictionary(); +- if (state is IEnumerable> keyValuePairs) +- { +- foreach (var kvp in keyValuePairs) +- { +- stateKeys[kvp.Key] = PowertoolsLoggerHelpers.ObjectToDictionary(kvp.Value); +- } +- } +-#else +-var stateKeys = new Dictionary(); +-if (state is IEnumerable> keyValuePairs) +-{ +- foreach (var kvp in keyValuePairs) +- { +- stateKeys[kvp.Key] = kvp.Value; +- } +-} +-#endif ++ var stateKeys = (state as IEnumerable>)? ++ .ToDictionary(i => i.Key, i => PowertoolsLoggerHelpers.ObjectToDictionary(i.Value)); + +- if (stateKeys.Count != 2) ++ if (stateKeys is null || stateKeys.Count != 2) + return false; + +- if (!stateKeys.TryGetValue(_originalformat, out var originalFormat)) ++ if (!stateKeys.TryGetValue("{OriginalFormat}", out var originalFormat)) + return false; + + if (originalFormat?.ToString() != LoggingConstants.KeyJsonFormatter) + return false; + +- message = stateKeys.First(k => k.Key != _originalformat).Value; ++ message = stateKeys.First(k => k.Key != "{OriginalFormat}").Value; + + return true; + } + ++ /// ++ /// Gets the log level key. ++ /// ++ /// System.String. ++ [MethodImpl(MethodImplOptions.AggressiveInlining)] ++ private string GetLogLevelKey() ++ { ++ return _powertoolsConfigurations.LambdaLogLevelEnabled() && ++ _powertoolsConfigurations.CurrentConfig().LoggerOutputCase == LoggerOutputCase.PascalCase ++ ? "LogLevel" ++ : LoggingConstants.KeyLogLevel; ++ } ++ + /// + /// Adds the lambda context keys. + /// +@@ -468,10 +324,10 @@ if (state is IEnumerable> keyValuePairs) + { + var context = LoggingLambdaContext.Instance; + logEntry.TryAdd(LoggingConstants.KeyFunctionName, context.FunctionName); ++ logEntry.TryAdd(LoggingConstants.KeyFunctionVersion, context.FunctionVersion); + logEntry.TryAdd(LoggingConstants.KeyFunctionMemorySize, context.MemoryLimitInMB); + logEntry.TryAdd(LoggingConstants.KeyFunctionArn, context.InvokedFunctionArn); + logEntry.TryAdd(LoggingConstants.KeyFunctionRequestId, context.AwsRequestId); +- logEntry.TryAdd(LoggingConstants.KeyFunctionVersion, context.FunctionVersion); + } + + /// +@@ -515,7 +371,6 @@ if (state is IEnumerable> keyValuePairs) + } + + break; +- + case IEnumerable> objectPairs: + foreach (var (key, value) in objectPairs) + { +@@ -524,28 +379,10 @@ if (state is IEnumerable> keyValuePairs) + } + + break; +- + default: +- // Skip property reflection for primitive types, strings and value types +- if (state is string || +- (state.GetType().IsPrimitive) || +- state is ValueType) +- { +- // Don't extract properties from primitives or strings +- break; +- } +- +- // For complex objects, use reflection to get properties + foreach (var property in state.GetType().GetProperties()) + { +- try +- { +- keys.TryAdd(property.Name, property.GetValue(state)); +- } +- catch +- { +- // Safely ignore reflection exceptions +- } ++ keys.TryAdd(property.Name, property.GetValue(state)); + } + + break; +@@ -553,143 +390,4 @@ if (state is IEnumerable> keyValuePairs) + + return keys; + } +- +- /// +- /// Extracts structured parameter key-value pairs from the log state +- /// +- /// Type of the state being logged +- /// The log state containing parameters +- /// Output parameter for the message template +- /// Dictionary of extracted parameter names and values +- private Dictionary ExtractStructuredParameters(TState state, out string messageTemplate) +- { +- messageTemplate = string.Empty; +- var parameters = new Dictionary(); +- +- if (!(state is IEnumerable> stateProps)) +- { +- return parameters; +- } +- +- // Dictionary to store format specifiers for each parameter +- var formatSpecifiers = new Dictionary(); +- var statePropsArray = stateProps.ToArray(); +- +- // First pass - extract message template and identify format specifiers +- ExtractFormatSpecifiers(ref messageTemplate, statePropsArray, formatSpecifiers); +- +- // Second pass - process values with extracted format specifiers +- ProcessValuesWithSpecifiers(statePropsArray, formatSpecifiers, parameters); +- +- return parameters; +- } +- +- private void ProcessValuesWithSpecifiers(KeyValuePair[] statePropsArray, Dictionary formatSpecifiers, +- Dictionary parameters) +- { +- foreach (var prop in statePropsArray) +- { +- if (prop.Key == _originalformat) +- continue; +- +- // Extract parameter name without braces +- var paramName = ExtractParameterName(prop.Key); +- if (string.IsNullOrEmpty(paramName)) +- continue; +- +- // Handle special serialization designators (like @) +- var useStructuredSerialization = paramName.StartsWith('@'); +- var actualParamName = useStructuredSerialization ? paramName.Substring(1) : paramName; +- +- if (!useStructuredSerialization && +- formatSpecifiers.TryGetValue(paramName, out var format) && +- prop.Value is IFormattable formattable) +- { +- // Format the value using the specified format +- var formattedValue = formattable.ToString(format, System.Globalization.CultureInfo.InvariantCulture); +- +- // Try to preserve the numeric type if possible +- if (double.TryParse(formattedValue, out var numericValue)) +- { +- parameters[actualParamName] = numericValue; +- } +- else +- { +- parameters[actualParamName] = formattedValue; +- } +- } +- else if (useStructuredSerialization) +- { +- // Serialize the entire object +- parameters[actualParamName] = prop.Value; +- } +- else +- { +- // Handle regular values appropriately +- if (prop.Value != null && +- !(prop.Value is string) && +- !(prop.Value is ValueType) && +- !(prop.Value.GetType().IsPrimitive)) +- { +- // For complex objects, use ToString() representation +- parameters[actualParamName] = prop.Value.ToString(); +- } +- else +- { +- // For primitives and other simple types, use the value directly +- parameters[actualParamName] = prop.Value; +- } +- } +- } +- } +- +- private static void ExtractFormatSpecifiers(ref string messageTemplate, KeyValuePair[] statePropsArray, +- Dictionary formatSpecifiers) +- { +- foreach (var prop in statePropsArray) +- { +- // The original message template is stored with key "{OriginalFormat}" +- if (prop.Key == _originalformat && prop.Value is string template) +- { +- messageTemplate = template; +- +- // Extract format specifiers using regex pattern for parameters +- var matches = Regex.Matches( +- template, +- @"{([@\w]+)(?::([^{}]+))?}", +- RegexOptions.None, +- TimeSpan.FromSeconds(1)); +- +- foreach (Match match in matches) +- { +- var paramName = match.Groups[1].Value; +- if (match.Groups.Count > 2 && match.Groups[2].Success) +- { +- formatSpecifiers[paramName] = match.Groups[2].Value; +- } +- } +- +- break; +- } +- } +- } +- +- /// +- /// Extracts the parameter name from a template placeholder (e.g. "{paramName}" or "{paramName:format}") +- /// +- private string ExtractParameterName(string key) +- { +- // If it's already a proper parameter name without braces, return it +- if (!key.StartsWith('{') || !key.EndsWith('}')) +- return key; +- +- // Remove the braces +- var nameWithPossibleFormat = key.Substring(1, key.Length - 2); +- +- // If there's a format specifier, remove it +- var colonIndex = nameWithPossibleFormat.IndexOf(':'); +- return colonIndex > 0 +- ? nameWithPossibleFormat.Substring(0, colonIndex) +- : nameWithPossibleFormat; +- } +-} ++} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLoggerProvider.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLoggerProvider.cs +deleted file mode 100644 +index 66536527..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLoggerProvider.cs ++++ /dev/null +@@ -1,151 +0,0 @@ +-using System; +-using System.Collections.Concurrent; +-using AWS.Lambda.Powertools.Common; +-using Microsoft.Extensions.Logging; +- +-namespace AWS.Lambda.Powertools.Logging.Internal; +- +-/// +-/// Class LoggerProvider. This class cannot be inherited. +-/// Implements the +-/// +-/// +-[ProviderAlias("PowertoolsLogger")] +-internal class PowertoolsLoggerProvider : ILoggerProvider +-{ +- private readonly ConcurrentDictionary _loggers = new(StringComparer.OrdinalIgnoreCase); +- private PowertoolsLoggerConfiguration _currentConfig; +- private readonly IPowertoolsConfigurations _powertoolsConfigurations; +- private bool _environmentConfigured; +- +- public PowertoolsLoggerProvider( +- PowertoolsLoggerConfiguration config, +- IPowertoolsConfigurations powertoolsConfigurations) +- { +- _powertoolsConfigurations = powertoolsConfigurations; +- _currentConfig = config; +- +- // Set execution environment +- _powertoolsConfigurations.SetExecutionEnvironment(this); +- +- // Apply environment configurations if available +- ConfigureFromEnvironment(); +- } +- +- public void ConfigureFromEnvironment() +- { +- var logLevel = _powertoolsConfigurations.GetLogLevel(_currentConfig.MinimumLogLevel); +- var lambdaLogLevel = _powertoolsConfigurations.GetLambdaLogLevel(); +- var lambdaLogLevelEnabled = _powertoolsConfigurations.LambdaLogLevelEnabled(); +- +- // Warn if Lambda log level doesn't match +- if (lambdaLogLevelEnabled && logLevel < lambdaLogLevel) +- { +- _currentConfig.LogOutput.WriteLine( +- $"Current log level ({logLevel}) does not match AWS Lambda Advanced Logging Controls minimum log level ({lambdaLogLevel}). This can lead to data loss, consider adjusting them."); +- } +- +- // Set service from environment if not explicitly set +- if (string.IsNullOrEmpty(_currentConfig.Service)) +- { +- _currentConfig.Service = _powertoolsConfigurations.Service; +- } +- +- // Set output case from environment if not explicitly set +- if (_currentConfig.LoggerOutputCase == LoggerOutputCase.Default) +- { +- var loggerOutputCase = _powertoolsConfigurations.GetLoggerOutputCase(_currentConfig.LoggerOutputCase); +- _currentConfig.LoggerOutputCase = loggerOutputCase; +- } +- +- var minLogLevel = lambdaLogLevelEnabled ? lambdaLogLevel : logLevel; +- var effectiveLogLevel = minLogLevel != LogLevel.None ? minLogLevel : LoggingConstants.DefaultLogLevel; +- +- // Only set InitialLogLevel if it hasn't been explicitly configured +- if (_currentConfig.InitialLogLevel == LogLevel.Information) +- { +- _currentConfig.InitialLogLevel = effectiveLogLevel; +- } +- +- _currentConfig.MinimumLogLevel = effectiveLogLevel; +- +- _currentConfig.XRayTraceId = _powertoolsConfigurations.XRayTraceId; +- _currentConfig.LogEvent = _powertoolsConfigurations.LoggerLogEvent; +- +- // Configure the log level key based on output case +- _currentConfig.LogLevelKey = _powertoolsConfigurations.LambdaLogLevelEnabled() && +- _currentConfig.LoggerOutputCase == LoggerOutputCase.PascalCase +- ? "LogLevel" +- : LoggingConstants.KeyLogLevel; +- +- ProcessSamplingRate(_currentConfig, _powertoolsConfigurations); +- _environmentConfigured = true; +- } +- +- /// +- /// Process sampling rate configuration +- /// +- private void ProcessSamplingRate(PowertoolsLoggerConfiguration config, IPowertoolsConfigurations configurations) +- { +- var samplingRate = config.SamplingRate > 0 +- ? config.SamplingRate +- : configurations.LoggerSampleRate; +- +- samplingRate = ValidateSamplingRate(samplingRate, config); +- config.SamplingRate = samplingRate; +- } +- +- /// +- /// Validate sampling rate +- /// +- private double ValidateSamplingRate(double samplingRate, PowertoolsLoggerConfiguration config) +- { +- if (samplingRate < 0 || samplingRate > 1) +- { +- if (config.MinimumLogLevel is LogLevel.Debug or LogLevel.Trace) +- { +- config.LogOutput.WriteLine( +- $"Skipping sampling rate configuration because of invalid value. Sampling rate: {samplingRate}"); +- } +- +- return 0; +- } +- +- return samplingRate; +- } +- +- public virtual ILogger CreateLogger(string categoryName) +- { +- return _loggers.GetOrAdd(categoryName, name => new PowertoolsLogger( +- name, +- GetCurrentConfig, +- _powertoolsConfigurations)); +- } +- +- internal PowertoolsLoggerConfiguration GetCurrentConfig() => _currentConfig; +- +- public void UpdateConfiguration(PowertoolsLoggerConfiguration config) +- { +- _currentConfig = config; +- +- // Apply environment configurations if available +- if (_powertoolsConfigurations != null && !_environmentConfigured) +- { +- ConfigureFromEnvironment(); +- } +- } +- +- /// +- /// Refresh the sampling calculation and update the minimum log level if needed +- /// +- /// True if debug sampling was enabled, false otherwise +- internal bool RefreshSampleRateCalculation() +- { +- return _currentConfig.RefreshSampleRateCalculation(); +- } +- +- public virtual void Dispose() +- { +- _loggers.Clear(); +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/StringCaseExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/StringCaseExtensions.cs +deleted file mode 100644 +index 7e7b390a..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/StringCaseExtensions.cs ++++ /dev/null +@@ -1,132 +0,0 @@ +-using System; +-using System.Linq; +-using System.Text; +- +-namespace AWS.Lambda.Powertools.Logging.Internal; +- +-/// +-/// Extension methods for string case conversion. +-/// +-internal static class StringCaseExtensions +-{ +- /// +- /// Converts a string to camelCase. +- /// +- /// The string to convert. +- /// A camelCase formatted string. +- public static string ToCamel(this string value) +- { +- if (string.IsNullOrEmpty(value)) +- return value; +- +- // Convert to PascalCase first to handle potential snake_case or kebab-case +- string pascalCase = ToPascal(value); +- +- // Convert first char to lowercase +- return char.ToLowerInvariant(pascalCase[0]) + pascalCase.Substring(1); +- } +- +- /// +- /// Converts a string to PascalCase. +- /// +- /// The string to convert. +- /// A PascalCase formatted string. +- public static string ToPascal(this string input) +- { +- if (string.IsNullOrEmpty(input)) +- return input; +- +- var words = input.Split(new[] { '_' }, StringSplitOptions.RemoveEmptyEntries); +- var result = new StringBuilder(); +- +- foreach (var word in words) +- { +- if (word.Length > 0) +- { +- // Capitalize the first character of each word +- result.Append(char.ToUpperInvariant(word[0])); +- +- // Handle the rest of the characters +- if (word.Length > 1) +- { +- // If the word is all uppercase, convert the rest to lowercase +- if (word.All(char.IsUpper)) +- { +- result.Append(word.Substring(1).ToLowerInvariant()); +- } +- else +- { +- // Otherwise, keep the original casing +- result.Append(word.Substring(1)); +- } +- } +- } +- } +- +- return result.ToString(); +- } +- +- /// +- /// Converts a string to snake_case. +- /// +- /// The string to convert. +- /// A snake_case formatted string. +- public static string ToSnake(this string input) +- { +- if (string.IsNullOrEmpty(input)) +- return input; +- +- var result = new StringBuilder(input.Length + 10); +- bool lastCharWasUnderscore = false; +- bool lastCharWasUpper = false; +- +- for (int i = 0; i < input.Length; i++) +- { +- char currentChar = input[i]; +- +- if (currentChar == '_') +- { +- result.Append('_'); +- lastCharWasUnderscore = true; +- lastCharWasUpper = false; +- } +- else if (char.IsUpper(currentChar)) +- { +- if (i > 0 && !lastCharWasUnderscore && +- (!lastCharWasUpper || (i + 1 < input.Length && char.IsLower(input[i + 1])))) +- { +- result.Append('_'); +- } +- +- result.Append(char.ToLowerInvariant(currentChar)); +- lastCharWasUnderscore = false; +- lastCharWasUpper = true; +- } +- else +- { +- result.Append(char.ToLowerInvariant(currentChar)); +- lastCharWasUnderscore = false; +- lastCharWasUpper = false; +- } +- } +- +- return result.ToString(); +- } +- +- /// +- /// Converts a string to the specified case format. +- /// +- /// The string to convert. +- /// The target case format. +- /// A formatted string in the specified case. +- public static string ToCase(this string value, LoggerOutputCase outputCase) +- { +- return outputCase switch +- { +- LoggerOutputCase.CamelCase => value.ToCamel(), +- LoggerOutputCase.PascalCase => value.ToPascal(), +- LoggerOutputCase.SnakeCase => value.ToSnake(), +- _ => value.ToSnake() // Default/unchanged +- }; +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/LogBufferingOptions.cs b/libraries/src/AWS.Lambda.Powertools.Logging/LogBufferingOptions.cs +deleted file mode 100644 +index 9d31471d..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Logging/LogBufferingOptions.cs ++++ /dev/null +@@ -1,44 +0,0 @@ +-/* +- * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +- * +- * Licensed under the Apache License, Version 2.0 (the "License"). +- * You may not use this file except in compliance with the License. +- * A copy of the License is located at +- * +- * http://aws.amazon.com/apache2.0 +- * +- * or in the "license" file accompanying this file. This file is distributed +- * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +- * express or implied. See the License for the specific language governing +- * permissions and limitations under the License. +- */ +- +-using Microsoft.Extensions.Logging; +- +-namespace AWS.Lambda.Powertools.Logging; +- +-/// +-/// Configuration options for log buffering +-/// +-public class LogBufferingOptions +-{ +- /// +- /// Gets or sets the maximum size of the buffer in bytes +- /// +- /// Default is 20KB (20480 bytes) +- /// +- public int MaxBytes { get; set; } = 20480; +- +- /// +- /// Gets or sets the minimum log level to buffer +- /// Defaults to Debug +- /// +- /// Valid values are: Trace, Debug, Information, Warning +- /// +- public LogLevel BufferAtLogLevel { get; set; } = LogLevel.Debug; +- +- /// +- /// Gets or sets whether to flush the buffer when logging an error +- /// +- public bool FlushOnErrorLog { get; set; } = true; +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Formatter.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Formatter.cs +deleted file mode 100644 +index edefc1ac..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Formatter.cs ++++ /dev/null +@@ -1,26 +0,0 @@ +-namespace AWS.Lambda.Powertools.Logging; +- +-public static partial class Logger +-{ +- /// +- /// Set the log formatter. +- /// +- /// The log formatter. +- /// WARNING: This method should not be called when using AOT. ILogFormatter should be passed to PowertoolsSourceGeneratorSerializer constructor +- public static void UseFormatter(ILogFormatter logFormatter) +- { +- Configure(config => { +- config.LogFormatter = logFormatter; +- }); +- } +- +- /// +- /// Set the log formatter to default. +- /// +- public static void UseDefaultFormatter() +- { +- Configure(config => { +- config.LogFormatter = null; +- }); +- } +-} +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.JsonLogs.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.JsonLogs.cs +deleted file mode 100644 +index a593aed8..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.JsonLogs.cs ++++ /dev/null +@@ -1,153 +0,0 @@ +-using System; +-using Microsoft.Extensions.Logging; +- +-namespace AWS.Lambda.Powertools.Logging; +- +-public static partial class Logger +-{ +- #region JSON Logger Methods +- +- /// +- /// Formats and writes a trace log message as JSON. +- /// +- /// The object to be serialized as JSON. +- /// logger.LogTrace(new {User = user, Address = address}) +- public static void LogTrace(object message) +- { +- LoggerInstance.LogTrace(message); +- } +- +- /// +- /// Formats and writes an trace log message. +- /// +- /// The exception to log. +- /// logger.LogTrace(exception) +- public static void LogTrace(Exception exception) +- { +- LoggerInstance.LogTrace(exception); +- } +- +- /// +- /// Formats and writes a debug log message as JSON. +- /// +- /// The object to be serialized as JSON. +- /// logger.LogDebug(new {User = user, Address = address}) +- public static void LogDebug(object message) +- { +- LoggerInstance.LogDebug(message); +- } +- +- /// +- /// Formats and writes an debug log message. +- /// +- /// The exception to log. +- /// logger.LogDebug(exception) +- public static void LogDebug(Exception exception) +- { +- LoggerInstance.LogDebug(exception); +- } +- +- /// +- /// Formats and writes an information log message as JSON. +- /// +- /// The object to be serialized as JSON. +- /// logger.LogInformation(new {User = user, Address = address}) +- public static void LogInformation(object message) +- { +- LoggerInstance.LogInformation(message); +- } +- +- /// +- /// Formats and writes an information log message. +- /// +- /// The exception to log. +- /// logger.LogInformation(exception) +- public static void LogInformation(Exception exception) +- { +- LoggerInstance.LogInformation(exception); +- } +- +- /// +- /// Formats and writes a warning log message as JSON. +- /// +- /// The object to be serialized as JSON. +- /// logger.LogWarning(new {User = user, Address = address}) +- public static void LogWarning(object message) +- { +- LoggerInstance.LogWarning(message); +- } +- +- /// +- /// Formats and writes an warning log message. +- /// +- /// The exception to log. +- /// logger.LogWarning(exception) +- public static void LogWarning(Exception exception) +- { +- LoggerInstance.LogWarning(exception); +- } +- +- /// +- /// Formats and writes a error log message as JSON. +- /// +- /// The object to be serialized as JSON. +- /// logger.LogCritical(new {User = user, Address = address}) +- public static void LogError(object message) +- { +- LoggerInstance.LogError(message); +- } +- +- /// +- /// Formats and writes an error log message. +- /// +- /// The exception to log. +- /// logger.LogError(exception) +- public static void LogError(Exception exception) +- { +- LoggerInstance.LogError(exception); +- } +- +- /// +- /// Formats and writes a critical log message as JSON. +- /// +- /// The object to be serialized as JSON. +- /// logger.LogCritical(new {User = user, Address = address}) +- public static void LogCritical(object message) +- { +- LoggerInstance.LogCritical(message); +- } +- +- /// +- /// Formats and writes an critical log message. +- /// +- /// The exception to log. +- /// logger.LogCritical(exception) +- public static void LogCritical(Exception exception) +- { +- LoggerInstance.LogCritical(exception); +- } +- +- /// +- /// Formats and writes a log message as JSON at the specified log level. +- /// +- /// Entry will be written on this level. +- /// The object to be serialized as JSON. +- /// logger.Log(LogLevel.Information, new {User = user, Address = address}) +- public static void Log(LogLevel logLevel, object message) +- { +- LoggerInstance.Log(logLevel, message); +- } +- +- /// +- /// Formats and writes a log message at the specified log level. +- /// +- /// Entry will be written on this level. +- /// The exception to log. +- /// logger.Log(LogLevel.Information, exception) +- public static void Log(LogLevel logLevel, Exception exception) +- { +- LoggerInstance.Log(logLevel, exception); +- } +- +- #endregion +-} +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Sampling.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Sampling.cs +deleted file mode 100644 +index 0353df18..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Sampling.cs ++++ /dev/null +@@ -1,13 +0,0 @@ +-namespace AWS.Lambda.Powertools.Logging; +- +-public static partial class Logger +-{ +- /// +- /// Refresh the sampling calculation and update the minimum log level if needed +- /// +- /// True if debug sampling was enabled, false otherwise +- public static bool RefreshSampleRateCalculation() +- { +- return _config.RefreshSampleRateCalculation(); +- } +-} +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Scope.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Scope.cs +deleted file mode 100644 +index 4a661da4..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Scope.cs ++++ /dev/null +@@ -1,93 +0,0 @@ +-using System; +-using System.Collections.Generic; +-using System.Linq; +-using AWS.Lambda.Powertools.Logging.Internal.Helpers; +- +-namespace AWS.Lambda.Powertools.Logging; +- +-public static partial class Logger +-{ +- /// +- /// Gets the scope. +- /// +- /// The scope. +- private static IDictionary Scope { get; } = new Dictionary(StringComparer.Ordinal); +- +- /// +- /// Appending additional key to the log context. +- /// +- /// The key. +- /// The value. +- /// key +- /// value +- public static void AppendKey(string key, object value) +- { +- if (string.IsNullOrWhiteSpace(key)) +- throw new ArgumentNullException(nameof(key)); +- +-#if NET8_0_OR_GREATER +- Scope[key] = PowertoolsLoggerHelpers.ObjectToDictionary(value) ?? +- throw new ArgumentNullException(nameof(value)); +-#else +- Scope[key] = value ?? throw new ArgumentNullException(nameof(value)); +-#endif +- } +- +- /// +- /// Appending additional key to the log context. +- /// +- /// The list of keys. +- public static void AppendKeys(IEnumerable> keys) +- { +- foreach (var (key, value) in keys) +- AppendKey(key, value); +- } +- +- /// +- /// Appending additional key to the log context. +- /// +- /// The list of keys. +- public static void AppendKeys(IEnumerable> keys) +- { +- foreach (var (key, value) in keys) +- AppendKey(key, value); +- } +- +- /// +- /// Remove additional keys from the log context. +- /// +- /// The list of keys. +- public static void RemoveKeys(params string[] keys) +- { +- if (keys == null) return; +- foreach (var key in keys) +- if (Scope.ContainsKey(key)) +- Scope.Remove(key); +- } +- +- /// +- /// Returns all additional keys added to the log context. +- /// +- /// IEnumerable<KeyValuePair<System.String, System.Object>>. +- public static IEnumerable> GetAllKeys() +- { +- return Scope.AsEnumerable(); +- } +- +- /// +- /// Removes all additional keys from the log context. +- /// +- internal static void RemoveAllKeys() +- { +- Scope.Clear(); +- } +- +- /// +- /// Removes a key from the log context. +- /// +- public static void RemoveKey(string key) +- { +- if (Scope.ContainsKey(key)) +- Scope.Remove(key); +- } +-} +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.StandardLogs.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.StandardLogs.cs +deleted file mode 100644 +index 0162e0e9..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.StandardLogs.cs ++++ /dev/null +@@ -1,417 +0,0 @@ +-using System; +-using Microsoft.Extensions.Logging; +- +-namespace AWS.Lambda.Powertools.Logging; +- +-public static partial class Logger +-{ +- /// +- /// Formats and writes a debug log message. +- /// +- /// The event id associated with the log. +- /// The exception to log. +- /// +- /// Format string of the log message in message template format. Example: +- /// "User {User} logged in from {Address}" +- /// +- /// An object array that contains zero or more objects to format. +- /// Logger.LogDebug(0, exception, "Error while processing request from {Address}", address) +- public static void LogDebug(EventId eventId, Exception exception, string message, params object[] args) +- { +- LoggerInstance.LogDebug(eventId, exception, message, args); +- } +- +- /// +- /// Formats and writes a debug log message. +- /// +- /// The event id associated with the log. +- /// +- /// Format string of the log message in message template format. Example: +- /// "User {User} logged in from {Address}" +- /// +- /// An object array that contains zero or more objects to format. +- /// Logger.LogDebug(0, "Processing request from {Address}", address) +- public static void LogDebug(EventId eventId, string message, params object[] args) +- { +- LoggerInstance.LogDebug(eventId, message, args); +- } +- +- /// +- /// Formats and writes a debug log message. +- /// +- /// The exception to log. +- /// +- /// Format string of the log message in message template format. Example: +- /// "User {User} logged in from {Address}" +- /// +- /// An object array that contains zero or more objects to format. +- /// Logger.LogDebug(exception, "Error while processing request from {Address}", address) +- public static void LogDebug(Exception exception, string message, params object[] args) +- { +- LoggerInstance.LogDebug(exception, message, args); +- } +- +- /// +- /// Formats and writes a debug log message. +- /// +- /// +- /// Format string of the log message in message template format. Example: +- /// "User {User} logged in from {Address}" +- /// +- /// An object array that contains zero or more objects to format. +- /// Logger.LogDebug("Processing request from {Address}", address) +- public static void LogDebug(string message, params object[] args) +- { +- LoggerInstance.LogDebug(message, args); +- } +- +- /// +- /// Formats and writes a trace log message. +- /// +- /// The event id associated with the log. +- /// The exception to log. +- /// +- /// Format string of the log message in message template format. Example: +- /// "User {User} logged in from {Address}" +- /// +- /// An object array that contains zero or more objects to format. +- /// Logger.LogTrace(0, exception, "Error while processing request from {Address}", address) +- public static void LogTrace(EventId eventId, Exception exception, string message, params object[] args) +- { +- LoggerInstance.LogTrace(eventId, exception, message, args); +- } +- +- /// +- /// Formats and writes a trace log message. +- /// +- /// The event id associated with the log. +- /// +- /// Format string of the log message in message template format. Example: +- /// "User {User} logged in from {Address}" +- /// +- /// An object array that contains zero or more objects to format. +- /// Logger.LogTrace(0, "Processing request from {Address}", address) +- public static void LogTrace(EventId eventId, string message, params object[] args) +- { +- LoggerInstance.LogTrace(eventId, message, args); +- } +- +- /// +- /// Formats and writes a trace log message. +- /// +- /// The exception to log. +- /// +- /// Format string of the log message in message template format. Example: +- /// "User {User} logged in from {Address}" +- /// +- /// An object array that contains zero or more objects to format. +- /// Logger.LogTrace(exception, "Error while processing request from {Address}", address) +- public static void LogTrace(Exception exception, string message, params object[] args) +- { +- LoggerInstance.LogTrace(exception, message, args); +- } +- +- /// +- /// Formats and writes a trace log message. +- /// +- /// +- /// Format string of the log message in message template format. Example: +- /// "User {User} logged in from {Address}" +- /// +- /// An object array that contains zero or more objects to format. +- /// Logger.LogTrace("Processing request from {Address}", address) +- public static void LogTrace(string message, params object[] args) +- { +- LoggerInstance.LogTrace(message, args); +- } +- +- /// +- /// Formats and writes an informational log message. +- /// +- /// The event id associated with the log. +- /// The exception to log. +- /// +- /// Format string of the log message in message template format. Example: +- /// "User {User} logged in from {Address}" +- /// +- /// An object array that contains zero or more objects to format. +- /// Logger.LogInformation(0, exception, "Error while processing request from {Address}", address) +- public static void LogInformation(EventId eventId, Exception exception, string message, params object[] args) +- { +- LoggerInstance.LogInformation(eventId, exception, message, args); +- } +- +- /// +- /// Formats and writes an informational log message. +- /// +- /// The event id associated with the log. +- /// +- /// Format string of the log message in message template format. Example: +- /// "User {User} logged in from {Address}" +- /// +- /// An object array that contains zero or more objects to format. +- /// Logger.LogInformation(0, "Processing request from {Address}", address) +- public static void LogInformation(EventId eventId, string message, params object[] args) +- { +- LoggerInstance.LogInformation(eventId, message, args); +- } +- +- /// +- /// Formats and writes an informational log message. +- /// +- /// The exception to log. +- /// +- /// Format string of the log message in message template format. Example: +- /// "User {User} logged in from {Address}" +- /// +- /// An object array that contains zero or more objects to format. +- /// Logger.LogInformation(exception, "Error while processing request from {Address}", address) +- public static void LogInformation(Exception exception, string message, params object[] args) +- { +- LoggerInstance.LogInformation(exception, message, args); +- } +- +- /// +- /// Formats and writes an informational log message. +- /// +- /// +- /// Format string of the log message in message template format. Example: +- /// "User {User} logged in from {Address}" +- /// +- /// An object array that contains zero or more objects to format. +- /// Logger.LogInformation("Processing request from {Address}", address) +- public static void LogInformation(string message, params object[] args) +- { +- LoggerInstance.LogInformation(message, args); +- } +- +- /// +- /// Formats and writes a warning log message. +- /// +- /// The event id associated with the log. +- /// The exception to log. +- /// +- /// Format string of the log message in message template format. Example: +- /// "User {User} logged in from {Address}" +- /// +- /// An object array that contains zero or more objects to format. +- /// Logger.LogWarning(0, exception, "Error while processing request from {Address}", address) +- public static void LogWarning(EventId eventId, Exception exception, string message, params object[] args) +- { +- LoggerInstance.LogWarning(eventId, exception, message, args); +- } +- +- /// +- /// Formats and writes a warning log message. +- /// +- /// The event id associated with the log. +- /// +- /// Format string of the log message in message template format. Example: +- /// "User {User} logged in from {Address}" +- /// +- /// An object array that contains zero or more objects to format. +- /// Logger.LogWarning(0, "Processing request from {Address}", address) +- public static void LogWarning(EventId eventId, string message, params object[] args) +- { +- LoggerInstance.LogWarning(eventId, message, args); +- } +- +- /// +- /// Formats and writes a warning log message. +- /// +- /// The exception to log. +- /// +- /// Format string of the log message in message template format. Example: +- /// "User {User} logged in from {Address}" +- /// +- /// An object array that contains zero or more objects to format. +- /// Logger.LogWarning(exception, "Error while processing request from {Address}", address) +- public static void LogWarning(Exception exception, string message, params object[] args) +- { +- LoggerInstance.LogWarning(exception, message, args); +- } +- +- /// +- /// Formats and writes a warning log message. +- /// +- /// +- /// Format string of the log message in message template format. Example: +- /// "User {User} logged in from {Address}" +- /// +- /// An object array that contains zero or more objects to format. +- /// Logger.LogWarning("Processing request from {Address}", address) +- public static void LogWarning(string message, params object[] args) +- { +- LoggerInstance.LogWarning(message, args); +- } +- +- /// +- /// Formats and writes an error log message. +- /// +- /// The event id associated with the log. +- /// The exception to log. +- /// +- /// Format string of the log message in message template format. Example: +- /// "User {User} logged in from {Address}" +- /// +- /// An object array that contains zero or more objects to format. +- /// Logger.LogError(0, exception, "Error while processing request from {Address}", address) +- public static void LogError(EventId eventId, Exception exception, string message, params object[] args) +- { +- LoggerInstance.LogError(eventId, exception, message, args); +- } +- +- /// +- /// Formats and writes an error log message. +- /// +- /// The event id associated with the log. +- /// +- /// Format string of the log message in message template format. Example: +- /// "User {User} logged in from {Address}" +- /// +- /// An object array that contains zero or more objects to format. +- /// Logger.LogError(0, "Processing request from {Address}", address) +- public static void LogError(EventId eventId, string message, params object[] args) +- { +- LoggerInstance.LogError(eventId, message, args); +- } +- +- /// +- /// Formats and writes an error log message. +- /// +- /// The exception to log. +- /// +- /// Format string of the log message in message template format. Example: +- /// "User {User} logged in from {Address}" +- /// +- /// An object array that contains zero or more objects to format. +- /// Logger.LogError(exception, "Error while processing request from {Address}", address) +- public static void LogError(Exception exception, string message, params object[] args) +- { +- LoggerInstance.LogError(exception, message, args); +- } +- +- /// +- /// Formats and writes an error log message. +- /// +- /// +- /// Format string of the log message in message template format. Example: +- /// "User {User} logged in from {Address}" +- /// +- /// An object array that contains zero or more objects to format. +- /// Logger.LogError("Processing request from {Address}", address) +- public static void LogError(string message, params object[] args) +- { +- LoggerInstance.LogError(message, args); +- } +- +- /// +- /// Formats and writes a critical log message. +- /// +- /// The event id associated with the log. +- /// The exception to log. +- /// +- /// Format string of the log message in message template format. Example: +- /// "User {User} logged in from {Address}" +- /// +- /// An object array that contains zero or more objects to format. +- /// Logger.LogCritical(0, exception, "Error while processing request from {Address}", address) +- public static void LogCritical(EventId eventId, Exception exception, string message, params object[] args) +- { +- LoggerInstance.LogCritical(eventId, exception, message, args); +- } +- +- /// +- /// Formats and writes a critical log message. +- /// +- /// The event id associated with the log. +- /// +- /// Format string of the log message in message template format. Example: +- /// "User {User} logged in from {Address}" +- /// +- /// An object array that contains zero or more objects to format. +- /// Logger.LogCritical(0, "Processing request from {Address}", address) +- public static void LogCritical(EventId eventId, string message, params object[] args) +- { +- LoggerInstance.LogCritical(eventId, message, args); +- } +- +- /// +- /// Formats and writes a critical log message. +- /// +- /// The exception to log. +- /// +- /// Format string of the log message in message template format. Example: +- /// "User {User} logged in from {Address}" +- /// +- /// An object array that contains zero or more objects to format. +- /// Logger.LogCritical(exception, "Error while processing request from {Address}", address) +- public static void LogCritical(Exception exception, string message, params object[] args) +- { +- LoggerInstance.LogCritical(exception, message, args); +- } +- +- /// +- /// Formats and writes a critical log message. +- /// +- /// +- /// Format string of the log message in message template format. Example: +- /// "User {User} logged in from {Address}" +- /// +- /// An object array that contains zero or more objects to format. +- /// Logger.LogCritical("Processing request from {Address}", address) +- public static void LogCritical(string message, params object[] args) +- { +- LoggerInstance.LogCritical(message, args); +- } +- +- /// +- /// Formats and writes a log message at the specified log level. +- /// +- /// Entry will be written on this level. +- /// Format string of the log message. +- /// An object array that contains zero or more objects to format. +- public static void Log(LogLevel logLevel, string message, params object[] args) +- { +- LoggerInstance.Log(logLevel, message, args); +- } +- +- /// +- /// Formats and writes a log message at the specified log level. +- /// +- /// Entry will be written on this level. +- /// The event id associated with the log. +- /// Format string of the log message. +- /// An object array that contains zero or more objects to format. +- public static void Log(LogLevel logLevel, EventId eventId, string message, params object[] args) +- { +- LoggerInstance.Log(logLevel, eventId, message, args); +- } +- +- /// +- /// Formats and writes a log message at the specified log level. +- /// +- /// Entry will be written on this level. +- /// The exception to log. +- /// Format string of the log message. +- /// An object array that contains zero or more objects to format. +- public static void Log(LogLevel logLevel, Exception exception, string message, params object[] args) +- { +- LoggerInstance.Log(logLevel, exception, message, args); +- } +- +- /// +- /// Formats and writes a log message at the specified log level. +- /// +- /// Entry will be written on this level. +- /// The event id associated with the log. +- /// The exception to log. +- /// Format string of the log message. +- /// An object array that contains zero or more objects to format. +- public static void Log(LogLevel logLevel, EventId eventId, Exception exception, string message, +- params object[] args) +- { +- LoggerInstance.Log(logLevel, eventId, exception, message, args); +- } +- +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs +index 91d786d4..f07464ed 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs +@@ -1,6 +1,21 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; +-using System.Text.Json; +-using AWS.Lambda.Powertools.Common; ++using System.Collections.Generic; ++using System.Linq; + using AWS.Lambda.Powertools.Logging.Internal; + using AWS.Lambda.Powertools.Logging.Internal.Helpers; + using Microsoft.Extensions.Logging; +@@ -10,74 +25,1184 @@ namespace AWS.Lambda.Powertools.Logging; + /// + /// Class Logger. + /// +-public static partial class Logger ++public class Logger + { +- private static PowertoolsLoggerConfiguration _config; ++ /// ++ /// The logger instance ++ /// + private static ILogger _loggerInstance; +- private static readonly object Lock = new object(); + +- // Change this to a property with getter that recreates if needed +- private static ILogger LoggerInstance ++ /// ++ /// Gets the logger instance. ++ /// ++ /// The logger instance. ++ private static ILogger LoggerInstance => _loggerInstance ??= Create(); ++ ++ /// ++ /// Gets or sets the logger provider. ++ /// ++ /// The logger provider. ++ internal static ILoggerProvider LoggerProvider { get; set; } ++ ++ /// ++ /// The logger formatter instance ++ /// ++ private static ILogFormatter _logFormatter; ++ ++ /// ++ /// Gets the scope. ++ /// ++ /// The scope. ++ private static IDictionary Scope { get; } = new Dictionary(StringComparer.Ordinal); ++ ++ /// ++ /// Creates a new instance. ++ /// ++ /// The category name for messages produced by the logger. ++ /// The instance of that was created. ++ /// categoryName ++ public static ILogger Create(string categoryName) + { +- get +- { +- // If we have no instance or configuration has changed, get a new logger +- if (_loggerInstance == null) +- { +- lock (Lock) +- { +- if (_loggerInstance == null) +- { +- _loggerInstance = Initialize(); +- } +- } +- } +- return _loggerInstance; +- } ++ if (string.IsNullOrWhiteSpace(categoryName)) ++ throw new ArgumentNullException(nameof(categoryName)); ++ ++ // Needed for when using Logger directly with decorator ++ LoggerProvider ??= new LoggerProvider(null); ++ ++ return LoggerProvider.CreateLogger(categoryName); + } + +- private static ILogger Initialize() ++ /// ++ /// Creates a new instance. ++ /// ++ /// ++ /// The instance of that was created. ++ public static ILogger Create() + { +- return LoggerFactoryHolder.GetOrCreateFactory().CreatePowertoolsLogger(); ++ return Create(typeof(T).FullName); + } + ++ #region Scope Variables ++ + /// +- /// Configure with an existing logger factory ++ /// Appending additional key to the log context. + /// +- /// The factory to use +- internal static void Configure(ILoggerFactory loggerFactory) ++ /// The key. ++ /// The value. ++ /// key ++ /// value ++ public static void AppendKey(string key, object value) + { +- if (loggerFactory == null) throw new ArgumentNullException(nameof(loggerFactory)); +- LoggerFactoryHolder.SetFactory(loggerFactory); ++ if (string.IsNullOrWhiteSpace(key)) ++ throw new ArgumentNullException(nameof(key)); ++ ++ Scope[key] = PowertoolsLoggerHelpers.ObjectToDictionary(value) ?? ++ throw new ArgumentNullException(nameof(value)); + } + + /// +- /// Configure using a configuration action ++ /// Appending additional key to the log context. + /// +- /// +- public static void Configure(Action configure) ++ /// The list of keys. ++ public static void AppendKeys(IEnumerable> keys) + { +- lock (Lock) +- { +- _config = new PowertoolsLoggerConfiguration(); +- configure(_config); +- _loggerInstance = LoggerFactoryHelper.CreateAndConfigureFactory(_config).CreatePowertoolsLogger(); +- } ++ foreach (var (key, value) in keys) ++ AppendKey(key, value); + } +- +- ++ + /// +- /// Reset the logger for testing ++ /// Appending additional key to the log context. + /// +- internal static void Reset() ++ /// The list of keys. ++ public static void AppendKeys(IEnumerable> keys) + { +- LoggerFactoryHolder.Reset(); +- _loggerInstance = null; +- RemoveAllKeys(); ++ foreach (var (key, value) in keys) ++ AppendKey(key, value); ++ } ++ ++ /// ++ /// Remove additional keys from the log context. ++ /// ++ /// The list of keys. ++ public static void RemoveKeys(params string[] keys) ++ { ++ if (keys == null) return; ++ foreach (var key in keys) ++ if (Scope.ContainsKey(key)) ++ Scope.Remove(key); ++ } ++ ++ /// ++ /// Returns all additional keys added to the log context. ++ /// ++ /// IEnumerable<KeyValuePair<System.String, System.Object>>. ++ public static IEnumerable> GetAllKeys() ++ { ++ return Scope.AsEnumerable(); ++ } ++ ++ /// ++ /// Removes all additional keys from the log context. ++ /// ++ internal static void RemoveAllKeys() ++ { ++ Scope.Clear(); + } +- +- internal static void ClearInstance() ++ ++ internal static void ClearLoggerInstance() + { + _loggerInstance = null; + } ++ ++ #endregion ++ ++ #region Core Logger Methods ++ ++ #region Debug ++ ++ /// ++ /// Formats and writes a debug log message. ++ /// ++ /// The event id associated with the log. ++ /// The exception to log. ++ /// ++ /// Format string of the log message in message template format. Example: ++ /// "User {User} logged in from {Address}" ++ /// ++ /// An object array that contains zero or more objects to format. ++ /// Logger.LogDebug(0, exception, "Error while processing request from {Address}", address) ++ public static void LogDebug(EventId eventId, Exception exception, string message, params object[] args) ++ { ++ LoggerInstance.LogDebug(eventId, exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes a debug log message. ++ /// ++ /// The event id associated with the log. ++ /// ++ /// Format string of the log message in message template format. Example: ++ /// "User {User} logged in from {Address}" ++ /// ++ /// An object array that contains zero or more objects to format. ++ /// Logger.LogDebug(0, "Processing request from {Address}", address) ++ public static void LogDebug(EventId eventId, string message, params object[] args) ++ { ++ LoggerInstance.LogDebug(eventId, message, args); ++ } ++ ++ /// ++ /// Formats and writes a debug log message. ++ /// ++ /// The exception to log. ++ /// ++ /// Format string of the log message in message template format. Example: ++ /// "User {User} logged in from {Address}" ++ /// ++ /// An object array that contains zero or more objects to format. ++ /// Logger.LogDebug(exception, "Error while processing request from {Address}", address) ++ public static void LogDebug(Exception exception, string message, params object[] args) ++ { ++ LoggerInstance.LogDebug(exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes a debug log message. ++ /// ++ /// ++ /// Format string of the log message in message template format. Example: ++ /// "User {User} logged in from {Address}" ++ /// ++ /// An object array that contains zero or more objects to format. ++ /// Logger.LogDebug("Processing request from {Address}", address) ++ public static void LogDebug(string message, params object[] args) ++ { ++ LoggerInstance.LogDebug(message, args); ++ } ++ ++ #endregion ++ ++ #region Trace ++ ++ /// ++ /// Formats and writes a trace log message. ++ /// ++ /// The event id associated with the log. ++ /// The exception to log. ++ /// ++ /// Format string of the log message in message template format. Example: ++ /// "User {User} logged in from {Address}" ++ /// ++ /// An object array that contains zero or more objects to format. ++ /// Logger.LogTrace(0, exception, "Error while processing request from {Address}", address) ++ public static void LogTrace(EventId eventId, Exception exception, string message, params object[] args) ++ { ++ LoggerInstance.LogTrace(eventId, exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes a trace log message. ++ /// ++ /// The event id associated with the log. ++ /// ++ /// Format string of the log message in message template format. Example: ++ /// "User {User} logged in from {Address}" ++ /// ++ /// An object array that contains zero or more objects to format. ++ /// Logger.LogTrace(0, "Processing request from {Address}", address) ++ public static void LogTrace(EventId eventId, string message, params object[] args) ++ { ++ LoggerInstance.LogTrace(eventId, message, args); ++ } ++ ++ /// ++ /// Formats and writes a trace log message. ++ /// ++ /// The exception to log. ++ /// ++ /// Format string of the log message in message template format. Example: ++ /// "User {User} logged in from {Address}" ++ /// ++ /// An object array that contains zero or more objects to format. ++ /// Logger.LogTrace(exception, "Error while processing request from {Address}", address) ++ public static void LogTrace(Exception exception, string message, params object[] args) ++ { ++ LoggerInstance.LogTrace(exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes a trace log message. ++ /// ++ /// ++ /// Format string of the log message in message template format. Example: ++ /// "User {User} logged in from {Address}" ++ /// ++ /// An object array that contains zero or more objects to format. ++ /// Logger.LogTrace("Processing request from {Address}", address) ++ public static void LogTrace(string message, params object[] args) ++ { ++ LoggerInstance.LogTrace(message, args); ++ } ++ ++ #endregion ++ ++ #region Information ++ ++ /// ++ /// Formats and writes an informational log message. ++ /// ++ /// The event id associated with the log. ++ /// The exception to log. ++ /// ++ /// Format string of the log message in message template format. Example: ++ /// "User {User} logged in from {Address}" ++ /// ++ /// An object array that contains zero or more objects to format. ++ /// Logger.LogInformation(0, exception, "Error while processing request from {Address}", address) ++ public static void LogInformation(EventId eventId, Exception exception, string message, params object[] args) ++ { ++ LoggerInstance.LogInformation(eventId, exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes an informational log message. ++ /// ++ /// The event id associated with the log. ++ /// ++ /// Format string of the log message in message template format. Example: ++ /// "User {User} logged in from {Address}" ++ /// ++ /// An object array that contains zero or more objects to format. ++ /// Logger.LogInformation(0, "Processing request from {Address}", address) ++ public static void LogInformation(EventId eventId, string message, params object[] args) ++ { ++ LoggerInstance.LogInformation(eventId, message, args); ++ } ++ ++ /// ++ /// Formats and writes an informational log message. ++ /// ++ /// The exception to log. ++ /// ++ /// Format string of the log message in message template format. Example: ++ /// "User {User} logged in from {Address}" ++ /// ++ /// An object array that contains zero or more objects to format. ++ /// Logger.LogInformation(exception, "Error while processing request from {Address}", address) ++ public static void LogInformation(Exception exception, string message, params object[] args) ++ { ++ LoggerInstance.LogInformation(exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes an informational log message. ++ /// ++ /// ++ /// Format string of the log message in message template format. Example: ++ /// "User {User} logged in from {Address}" ++ /// ++ /// An object array that contains zero or more objects to format. ++ /// Logger.LogInformation("Processing request from {Address}", address) ++ public static void LogInformation(string message, params object[] args) ++ { ++ LoggerInstance.LogInformation(message, args); ++ } ++ ++ #endregion ++ ++ #region Warning ++ ++ /// ++ /// Formats and writes a warning log message. ++ /// ++ /// The event id associated with the log. ++ /// The exception to log. ++ /// ++ /// Format string of the log message in message template format. Example: ++ /// "User {User} logged in from {Address}" ++ /// ++ /// An object array that contains zero or more objects to format. ++ /// Logger.LogWarning(0, exception, "Error while processing request from {Address}", address) ++ public static void LogWarning(EventId eventId, Exception exception, string message, params object[] args) ++ { ++ LoggerInstance.LogWarning(eventId, exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes a warning log message. ++ /// ++ /// The event id associated with the log. ++ /// ++ /// Format string of the log message in message template format. Example: ++ /// "User {User} logged in from {Address}" ++ /// ++ /// An object array that contains zero or more objects to format. ++ /// Logger.LogWarning(0, "Processing request from {Address}", address) ++ public static void LogWarning(EventId eventId, string message, params object[] args) ++ { ++ LoggerInstance.LogWarning(eventId, message, args); ++ } ++ ++ /// ++ /// Formats and writes a warning log message. ++ /// ++ /// The exception to log. ++ /// ++ /// Format string of the log message in message template format. Example: ++ /// "User {User} logged in from {Address}" ++ /// ++ /// An object array that contains zero or more objects to format. ++ /// Logger.LogWarning(exception, "Error while processing request from {Address}", address) ++ public static void LogWarning(Exception exception, string message, params object[] args) ++ { ++ LoggerInstance.LogWarning(exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes a warning log message. ++ /// ++ /// ++ /// Format string of the log message in message template format. Example: ++ /// "User {User} logged in from {Address}" ++ /// ++ /// An object array that contains zero or more objects to format. ++ /// Logger.LogWarning("Processing request from {Address}", address) ++ public static void LogWarning(string message, params object[] args) ++ { ++ LoggerInstance.LogWarning(message, args); ++ } ++ ++ #endregion ++ ++ #region Error ++ ++ /// ++ /// Formats and writes an error log message. ++ /// ++ /// The event id associated with the log. ++ /// The exception to log. ++ /// ++ /// Format string of the log message in message template format. Example: ++ /// "User {User} logged in from {Address}" ++ /// ++ /// An object array that contains zero or more objects to format. ++ /// Logger.LogError(0, exception, "Error while processing request from {Address}", address) ++ public static void LogError(EventId eventId, Exception exception, string message, params object[] args) ++ { ++ LoggerInstance.LogError(eventId, exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes an error log message. ++ /// ++ /// The event id associated with the log. ++ /// ++ /// Format string of the log message in message template format. Example: ++ /// "User {User} logged in from {Address}" ++ /// ++ /// An object array that contains zero or more objects to format. ++ /// Logger.LogError(0, "Processing request from {Address}", address) ++ public static void LogError(EventId eventId, string message, params object[] args) ++ { ++ LoggerInstance.LogError(eventId, message, args); ++ } ++ ++ /// ++ /// Formats and writes an error log message. ++ /// ++ /// The exception to log. ++ /// ++ /// Format string of the log message in message template format. Example: ++ /// "User {User} logged in from {Address}" ++ /// ++ /// An object array that contains zero or more objects to format. ++ /// > ++ /// Logger.LogError(exception, "Error while processing request from {Address}", address) ++ public static void LogError(Exception exception, string message, params object[] args) ++ { ++ LoggerInstance.LogError(exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes an error log message. ++ /// ++ /// ++ /// Format string of the log message in message template format. Example: ++ /// "User {User} logged in from {Address}" ++ /// ++ /// An object array that contains zero or more objects to format. ++ /// Logger.LogError("Processing request from {Address}", address) ++ public static void LogError(string message, params object[] args) ++ { ++ LoggerInstance.LogError(message, args); ++ } ++ ++ #endregion ++ ++ #region Critical ++ ++ /// ++ /// Formats and writes a critical log message. ++ /// ++ /// The event id associated with the log. ++ /// The exception to log. ++ /// ++ /// Format string of the log message in message template format. Example: ++ /// "User {User} logged in from {Address}" ++ /// ++ /// An object array that contains zero or more objects to format. ++ /// Logger.LogCritical(0, exception, "Error while processing request from {Address}", address) ++ public static void LogCritical(EventId eventId, Exception exception, string message, params object[] args) ++ { ++ LoggerInstance.LogCritical(eventId, exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes a critical log message. ++ /// ++ /// The event id associated with the log. ++ /// ++ /// Format string of the log message in message template format. Example: ++ /// "User {User} logged in from {Address}" ++ /// ++ /// An object array that contains zero or more objects to format. ++ /// Logger.LogCritical(0, "Processing request from {Address}", address) ++ public static void LogCritical(EventId eventId, string message, params object[] args) ++ { ++ LoggerInstance.LogCritical(eventId, message, args); ++ } ++ ++ /// ++ /// Formats and writes a critical log message. ++ /// ++ /// The exception to log. ++ /// ++ /// Format string of the log message in message template format. Example: ++ /// "User {User} logged in from {Address}" ++ /// ++ /// An object array that contains zero or more objects to format. ++ /// Logger.LogCritical(exception, "Error while processing request from {Address}", address) ++ public static void LogCritical(Exception exception, string message, params object[] args) ++ { ++ LoggerInstance.LogCritical(exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes a critical log message. ++ /// ++ /// ++ /// Format string of the log message in message template format. Example: ++ /// "User {User} logged in from {Address}" ++ /// ++ /// An object array that contains zero or more objects to format. ++ /// Logger.LogCritical("Processing request from {Address}", address) ++ public static void LogCritical(string message, params object[] args) ++ { ++ LoggerInstance.LogCritical(message, args); ++ } ++ ++ #endregion ++ ++ #region Log ++ ++ /// ++ /// Formats and writes a log message at the specified log level. ++ /// ++ /// Entry will be written on this level. ++ /// Format string of the log message. ++ /// An object array that contains zero or more objects to format. ++ public static void Log(LogLevel logLevel, string message, params object[] args) ++ { ++ LoggerInstance.Log(logLevel, message, args); ++ } ++ ++ /// ++ /// Formats and writes a log message at the specified log level. ++ /// ++ /// Entry will be written on this level. ++ /// The event id associated with the log. ++ /// Format string of the log message. ++ /// An object array that contains zero or more objects to format. ++ public static void Log(LogLevel logLevel, EventId eventId, string message, params object[] args) ++ { ++ LoggerInstance.Log(logLevel, eventId, message, args); ++ } ++ ++ /// ++ /// Formats and writes a log message at the specified log level. ++ /// ++ /// Entry will be written on this level. ++ /// The exception to log. ++ /// Format string of the log message. ++ /// An object array that contains zero or more objects to format. ++ public static void Log(LogLevel logLevel, Exception exception, string message, params object[] args) ++ { ++ LoggerInstance.Log(logLevel, exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes a log message at the specified log level. ++ /// ++ /// Entry will be written on this level. ++ /// The event id associated with the log. ++ /// The exception to log. ++ /// Format string of the log message. ++ /// An object array that contains zero or more objects to format. ++ public static void Log(LogLevel logLevel, EventId eventId, Exception exception, string message, ++ params object[] args) ++ { ++ LoggerInstance.Log(logLevel, eventId, exception, message, args); ++ } ++ ++ /// ++ /// Writes a log entry. ++ /// ++ /// The type of the object to be written. ++ /// Entry will be written on this level. ++ /// Id of the event. ++ /// The entry to be written. Can be also an object. ++ /// The exception related to this entry. ++ /// ++ /// Function to create a message of the ++ /// and . ++ /// ++ public static void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, ++ Func formatter) ++ { ++ LoggerInstance.Log(logLevel, eventId, state, exception, formatter); ++ } ++ ++ #endregion ++ ++ #endregion ++ ++ #region JSON Logger Methods ++ ++ /// ++ /// Formats and writes a trace log message as JSON. ++ /// ++ /// The object to be serialized as JSON. ++ /// logger.LogTrace(new {User = user, Address = address}) ++ public static void LogTrace(object message) ++ { ++ LoggerInstance.LogTrace(message); ++ } ++ ++ /// ++ /// Formats and writes an trace log message. ++ /// ++ /// The exception to log. ++ /// logger.LogTrace(exception) ++ public static void LogTrace(Exception exception) ++ { ++ LoggerInstance.LogTrace(exception); ++ } ++ ++ /// ++ /// Formats and writes a debug log message as JSON. ++ /// ++ /// The object to be serialized as JSON. ++ /// logger.LogDebug(new {User = user, Address = address}) ++ public static void LogDebug(object message) ++ { ++ LoggerInstance.LogDebug(message); ++ } ++ ++ /// ++ /// Formats and writes an debug log message. ++ /// ++ /// The exception to log. ++ /// logger.LogDebug(exception) ++ public static void LogDebug(Exception exception) ++ { ++ LoggerInstance.LogDebug(exception); ++ } ++ ++ /// ++ /// Formats and writes an information log message as JSON. ++ /// ++ /// The object to be serialized as JSON. ++ /// logger.LogInformation(new {User = user, Address = address}) ++ public static void LogInformation(object message) ++ { ++ LoggerInstance.LogInformation(message); ++ } ++ ++ /// ++ /// Formats and writes an information log message. ++ /// ++ /// The exception to log. ++ /// logger.LogInformation(exception) ++ public static void LogInformation(Exception exception) ++ { ++ LoggerInstance.LogInformation(exception); ++ } ++ ++ /// ++ /// Formats and writes a warning log message as JSON. ++ /// ++ /// The object to be serialized as JSON. ++ /// logger.LogWarning(new {User = user, Address = address}) ++ public static void LogWarning(object message) ++ { ++ LoggerInstance.LogWarning(message); ++ } ++ ++ /// ++ /// Formats and writes an warning log message. ++ /// ++ /// The exception to log. ++ /// logger.LogWarning(exception) ++ public static void LogWarning(Exception exception) ++ { ++ LoggerInstance.LogWarning(exception); ++ } ++ ++ /// ++ /// Formats and writes a error log message as JSON. ++ /// ++ /// The object to be serialized as JSON. ++ /// logger.LogCritical(new {User = user, Address = address}) ++ public static void LogError(object message) ++ { ++ LoggerInstance.LogError(message); ++ } ++ ++ /// ++ /// Formats and writes an error log message. ++ /// ++ /// The exception to log. ++ /// logger.LogError(exception) ++ public static void LogError(Exception exception) ++ { ++ LoggerInstance.LogError(exception); ++ } ++ ++ /// ++ /// Formats and writes a critical log message as JSON. ++ /// ++ /// The object to be serialized as JSON. ++ /// logger.LogCritical(new {User = user, Address = address}) ++ public static void LogCritical(object message) ++ { ++ LoggerInstance.LogCritical(message); ++ } ++ ++ /// ++ /// Formats and writes an critical log message. ++ /// ++ /// The exception to log. ++ /// logger.LogCritical(exception) ++ public static void LogCritical(Exception exception) ++ { ++ LoggerInstance.LogCritical(exception); ++ } ++ ++ /// ++ /// Formats and writes a log message as JSON at the specified log level. ++ /// ++ /// Entry will be written on this level. ++ /// The object to be serialized as JSON. ++ /// logger.Log(LogLevel.Information, new {User = user, Address = address}) ++ public static void Log(LogLevel logLevel, object message) ++ { ++ LoggerInstance.Log(logLevel, message); ++ } ++ ++ /// ++ /// Formats and writes a log message at the specified log level. ++ /// ++ /// Entry will be written on this level. ++ /// The exception to log. ++ /// logger.Log(LogLevel.Information, exception) ++ public static void Log(LogLevel logLevel, Exception exception) ++ { ++ LoggerInstance.Log(logLevel, exception); ++ } ++ ++ #endregion ++ ++ #region ExtraKeys Logger Methods ++ ++ #region Debug ++ ++ /// ++ /// Formats and writes a debug log message. ++ /// ++ /// Additional keys will be appended to the log entry. ++ /// The event id associated with the log. ++ /// The exception to log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogDebug(extraKeys, 0, exception, "Error while processing request from {Address}", address) ++ public static void LogDebug(T extraKeys, EventId eventId, Exception exception, string message, ++ params object[] args) where T : class ++ { ++ LoggerInstance.LogDebug(extraKeys, eventId, exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes a debug log message. ++ /// ++ /// Additional keys will be appended to the log entry. ++ /// The event id associated with the log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogDebug(extraKeys, 0, "Processing request from {Address}", address) ++ public static void LogDebug(T extraKeys, EventId eventId, string message, params object[] args) where T : class ++ { ++ LoggerInstance.LogDebug(extraKeys, eventId, message, args); ++ } ++ ++ /// ++ /// Formats and writes a debug log message. ++ /// ++ /// Additional keys will be appended to the log entry. ++ /// The exception to log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogDebug(extraKeys, exception, "Error while processing request from {Address}", address) ++ public static void LogDebug(T extraKeys, Exception exception, string message, params object[] args) ++ where T : class ++ { ++ LoggerInstance.LogDebug(extraKeys, exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes a debug log message. ++ /// ++ /// Additional keys will be appended to the log entry. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogDebug(extraKeys, "Processing request from {Address}", address) ++ public static void LogDebug(T extraKeys, string message, params object[] args) where T : class ++ { ++ LoggerInstance.LogDebug(extraKeys, message, args); ++ } ++ ++ #endregion ++ ++ #region Trace ++ ++ /// ++ /// Formats and writes a trace log message. ++ /// ++ /// Additional keys will be appended to the log entry. ++ /// The event id associated with the log. ++ /// The exception to log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogTrace(extraKeys, 0, exception, "Error while processing request from {Address}", address) ++ public static void LogTrace(T extraKeys, EventId eventId, Exception exception, string message, ++ params object[] args) where T : class ++ { ++ LoggerInstance.LogTrace(extraKeys, eventId, exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes a trace log message. ++ /// ++ /// Additional keys will be appended to the log entry. ++ /// The event id associated with the log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogTrace(extraKeys, 0, "Processing request from {Address}", address) ++ public static void LogTrace(T extraKeys, EventId eventId, string message, params object[] args) where T : class ++ { ++ LoggerInstance.LogTrace(extraKeys, eventId, message, args); ++ } ++ ++ /// ++ /// Formats and writes a trace log message. ++ /// ++ /// Additional keys will be appended to the log entry. ++ /// The exception to log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogTrace(extraKeys, exception, "Error while processing request from {Address}", address) ++ public static void LogTrace(T extraKeys, Exception exception, string message, params object[] args) ++ where T : class ++ { ++ LoggerInstance.LogTrace(extraKeys, exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes a trace log message. ++ /// ++ /// Additional keys will be appended to the log entry. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogTrace(extraKeys, "Processing request from {Address}", address) ++ public static void LogTrace(T extraKeys, string message, params object[] args) where T : class ++ { ++ LoggerInstance.LogTrace(extraKeys, message, args); ++ } ++ ++ #endregion ++ ++ #region Information ++ ++ /// ++ /// Formats and writes an informational log message. ++ /// ++ /// Additional keys will be appended to the log entry. ++ /// The event id associated with the log. ++ /// The exception to log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogInformation(extraKeys, 0, exception, "Error while processing request from {Address}", address) ++ public static void LogInformation(T extraKeys, EventId eventId, Exception exception, string message, ++ params object[] args) where T : class ++ { ++ LoggerInstance.LogInformation(extraKeys, eventId, exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes an informational log message. ++ /// ++ /// Additional keys will be appended to the log entry. ++ /// The event id associated with the log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogInformation(extraKeys, 0, "Processing request from {Address}", address) ++ public static void LogInformation(T extraKeys, EventId eventId, string message, params object[] args) ++ where T : class ++ { ++ LoggerInstance.LogInformation(extraKeys, eventId, message, args); ++ } ++ ++ /// ++ /// Formats and writes an informational log message. ++ /// ++ /// Additional keys will be appended to the log entry. ++ /// The exception to log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogInformation(extraKeys, exception, "Error while processing request from {Address}", address) ++ public static void LogInformation(T extraKeys, Exception exception, string message, params object[] args) ++ where T : class ++ { ++ LoggerInstance.LogInformation(extraKeys, exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes an informational log message. ++ /// ++ /// Additional keys will be appended to the log entry. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogInformation(extraKeys, "Processing request from {Address}", address) ++ public static void LogInformation(T extraKeys, string message, params object[] args) where T : class ++ { ++ LoggerInstance.LogInformation(extraKeys, message, args); ++ } ++ ++ #endregion ++ ++ #region Warning ++ ++ /// ++ /// Formats and writes a warning log message. ++ /// ++ /// Additional keys will be appended to the log entry. ++ /// The event id associated with the log. ++ /// The exception to log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogWarning(extraKeys, 0, exception, "Error while processing request from {Address}", address) ++ public static void LogWarning(T extraKeys, EventId eventId, Exception exception, string message, ++ params object[] args) where T : class ++ { ++ LoggerInstance.LogWarning(extraKeys, eventId, exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes a warning log message. ++ /// ++ /// Additional keys will be appended to the log entry. ++ /// The event id associated with the log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogWarning(extraKeys, 0, "Processing request from {Address}", address) ++ public static void LogWarning(T extraKeys, EventId eventId, string message, params object[] args) where T : class ++ { ++ LoggerInstance.LogWarning(extraKeys, eventId, message, args); ++ } ++ ++ /// ++ /// Formats and writes a warning log message. ++ /// ++ /// Additional keys will be appended to the log entry. ++ /// The exception to log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogWarning(extraKeys, exception, "Error while processing request from {Address}", address) ++ public static void LogWarning(T extraKeys, Exception exception, string message, params object[] args) ++ where T : class ++ { ++ LoggerInstance.LogWarning(extraKeys, exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes a warning log message. ++ /// ++ /// Additional keys will be appended to the log entry. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogWarning(extraKeys, "Processing request from {Address}", address) ++ public static void LogWarning(T extraKeys, string message, params object[] args) where T : class ++ { ++ LoggerInstance.LogWarning(extraKeys, message, args); ++ } ++ ++ #endregion ++ ++ #region Error ++ ++ /// ++ /// Formats and writes an error log message. ++ /// ++ /// Additional keys will be appended to the log entry. ++ /// The event id associated with the log. ++ /// The exception to log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogError(extraKeys, 0, exception, "Error while processing request from {Address}", address) ++ public static void LogError(T extraKeys, EventId eventId, Exception exception, string message, ++ params object[] args) where T : class ++ { ++ LoggerInstance.LogError(extraKeys, eventId, exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes an error log message. ++ /// ++ /// Additional keys will be appended to the log entry. ++ /// The event id associated with the log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogError(extraKeys, 0, "Processing request from {Address}", address) ++ public static void LogError(T extraKeys, EventId eventId, string message, params object[] args) where T : class ++ { ++ LoggerInstance.LogError(extraKeys, eventId, message, args); ++ } ++ ++ /// ++ /// Formats and writes an error log message. ++ /// ++ /// Additional keys will be appended to the log entry. ++ /// The exception to log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogError(extraKeys, exception, "Error while processing request from {Address}", address) ++ public static void LogError(T extraKeys, Exception exception, string message, params object[] args) ++ where T : class ++ { ++ LoggerInstance.LogError(extraKeys, exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes an error log message. ++ /// ++ /// Additional keys will be appended to the log entry. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogError(extraKeys, "Processing request from {Address}", address) ++ public static void LogError(T extraKeys, string message, params object[] args) where T : class ++ { ++ LoggerInstance.LogError(extraKeys, message, args); ++ } ++ ++ #endregion ++ ++ #region Critical ++ ++ /// ++ /// Formats and writes a critical log message. ++ /// ++ /// Additional keys will be appended to the log entry. ++ /// The event id associated with the log. ++ /// The exception to log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogCritical(extraKeys, 0, exception, "Error while processing request from {Address}", address) ++ public static void LogCritical(T extraKeys, EventId eventId, Exception exception, string message, ++ params object[] args) where T : class ++ { ++ LoggerInstance.LogCritical(extraKeys, eventId, exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes a critical log message. ++ /// ++ /// Additional keys will be appended to the log entry. ++ /// The event id associated with the log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogCritical(extraKeys, 0, "Processing request from {Address}", address) ++ public static void LogCritical(T extraKeys, EventId eventId, string message, params object[] args) ++ where T : class ++ { ++ LoggerInstance.LogCritical(extraKeys, eventId, message, args); ++ } ++ ++ /// ++ /// Formats and writes a critical log message. ++ /// ++ /// Additional keys will be appended to the log entry. ++ /// The exception to log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogCritical(extraKeys, exception, "Error while processing request from {Address}", address) ++ public static void LogCritical(T extraKeys, Exception exception, string message, params object[] args) ++ where T : class ++ { ++ LoggerInstance.LogCritical(extraKeys, exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes a critical log message. ++ /// ++ /// Additional keys will be appended to the log entry. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogCritical(extraKeys, "Processing request from {Address}", address) ++ public static void LogCritical(T extraKeys, string message, params object[] args) where T : class ++ { ++ LoggerInstance.LogCritical(extraKeys, message, args); ++ } ++ ++ #endregion ++ ++ #region Log ++ ++ /// ++ /// Formats and writes a log message at the specified log level. ++ /// ++ /// Entry will be written on this level. ++ /// Additional keys will be appended to the log entry. ++ /// The event id associated with the log. ++ /// The exception to log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.Log(LogLevel.Information, extraKeys, 0, exception, "Error while processing request from {Address}", address) ++ public static void Log(LogLevel logLevel, T extraKeys, EventId eventId, Exception exception, string message, ++ params object[] args) where T : class ++ { ++ LoggerInstance.Log(logLevel, extraKeys, eventId, exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes a log message at the specified log level. ++ /// ++ /// Entry will be written on this level. ++ /// Additional keys will be appended to the log entry. ++ /// The event id associated with the log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.Log(LogLevel.Information, extraKeys, 0, "Processing request from {Address}", address) ++ public static void Log(LogLevel logLevel, T extraKeys, EventId eventId, string message, params object[] args) ++ where T : class ++ { ++ LoggerInstance.Log(logLevel, extraKeys, eventId, message, args); ++ } ++ ++ /// ++ /// Formats and writes a log message at the specified log level. ++ /// ++ /// Entry will be written on this level. ++ /// Additional keys will be appended to the log entry. ++ /// The exception to log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.Log(LogLevel.Information, extraKeys, exception, "Error while processing request from {Address}", address) ++ public static void Log(LogLevel logLevel, T extraKeys, Exception exception, string message, params object[] args) ++ where T : class ++ { ++ LoggerInstance.Log(logLevel, extraKeys, exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes a log message at the specified log level. ++ /// ++ /// Entry will be written on this level. ++ /// Additional keys will be appended to the log entry. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.Log(LogLevel.Information, extraKeys, "Processing request from {Address}", address) ++ public static void Log(LogLevel logLevel, T extraKeys, string message, params object[] args) where T : class ++ { ++ LoggerInstance.Log(logLevel, extraKeys, message, args); ++ } ++ ++ #endregion ++ ++ #endregion ++ ++ #region Custom Log Formatter ++ ++ /// ++ /// Set the log formatter. ++ /// ++ /// The log formatter. ++ /// WARNING: This method should not be called when using AOT. ILogFormatter should be passed to PowertoolsSourceGeneratorSerializer constructor ++ public static void UseFormatter(ILogFormatter logFormatter) ++ { ++ _logFormatter = logFormatter ?? throw new ArgumentNullException(nameof(logFormatter)); ++ } ++ ++ /// ++ /// Set the log formatter to default. ++ /// ++ public static void UseDefaultFormatter() ++ { ++ _logFormatter = null; ++ } ++ ++ /// ++ /// Returns the log formatter. ++ /// ++ internal static ILogFormatter GetFormatter() => _logFormatter; ++ ++ #endregion + } +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/LoggerConfiguration.cs b/libraries/src/AWS.Lambda.Powertools.Logging/LoggerConfiguration.cs +new file mode 100644 +index 00000000..aab959af +--- /dev/null ++++ b/libraries/src/AWS.Lambda.Powertools.Logging/LoggerConfiguration.cs +@@ -0,0 +1,62 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ ++using Microsoft.Extensions.Logging; ++using Microsoft.Extensions.Options; ++ ++namespace AWS.Lambda.Powertools.Logging; ++ ++/// ++/// Class LoggerConfiguration. ++/// Implements the ++/// ++/// ++/// ++public class LoggerConfiguration : IOptions ++{ ++ /// ++ /// Service name is used for logging. ++ /// This can be also set using the environment variable POWERTOOLS_SERVICE_NAME. ++ /// ++ /// The service. ++ public string Service { get; set; } ++ ++ /// ++ /// Specify the minimum log level for logging (Information, by default). ++ /// This can be also set using the environment variable POWERTOOLS_LOG_LEVEL. ++ /// ++ /// The minimum level. ++ public LogLevel MinimumLevel { get; set; } = LogLevel.None; ++ ++ /// ++ /// Dynamically set a percentage of logs to DEBUG level. ++ /// This can be also set using the environment variable POWERTOOLS_LOGGER_SAMPLE_RATE. ++ /// ++ /// The sampling rate. ++ public double SamplingRate { get; set; } ++ ++ /// ++ /// The default configured options instance ++ /// ++ /// The value. ++ LoggerConfiguration IOptions.Value => this; ++ ++ /// ++ /// The logger output case. ++ /// This can be also set using the environment variable POWERTOOLS_LOGGER_CASE. ++ /// ++ /// The logger output case. ++ public LoggerOutputCase LoggerOutputCase { get; set; } = LoggerOutputCase.Default; ++} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/LoggerExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Logging/LoggerExtensions.cs +new file mode 100644 +index 00000000..200cf46e +--- /dev/null ++++ b/libraries/src/AWS.Lambda.Powertools.Logging/LoggerExtensions.cs +@@ -0,0 +1,655 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ ++using System; ++using AWS.Lambda.Powertools.Logging.Internal; ++using Microsoft.Extensions.Logging; ++ ++namespace AWS.Lambda.Powertools.Logging; ++ ++/// ++/// Class LoggerExtensions. ++/// ++public static class LoggerExtensions ++{ ++ #region JSON Logger Extentions ++ ++ /// ++ /// Formats and writes a trace log message as JSON. ++ /// ++ /// The to write to. ++ /// The object to be serialized as JSON. ++ /// logger.LogTrace(new {User = user, Address = address}) ++ public static void LogTrace(this ILogger logger, object message) ++ { ++ logger.LogTrace(LoggingConstants.KeyJsonFormatter, message); ++ } ++ ++ /// ++ /// Formats and writes an trace log message. ++ /// ++ /// The to write to. ++ /// The exception to log. ++ /// logger.LogTrace(exception) ++ public static void LogTrace(this ILogger logger, Exception exception) ++ { ++ logger.LogTrace(exception: exception, message: exception.Message); ++ } ++ ++ /// ++ /// Formats and writes a debug log message as JSON. ++ /// ++ /// The to write to. ++ /// The object to be serialized as JSON. ++ /// logger.LogDebug(new {User = user, Address = address}) ++ public static void LogDebug(this ILogger logger, object message) ++ { ++ logger.LogDebug(LoggingConstants.KeyJsonFormatter, message); ++ } ++ ++ /// ++ /// Formats and writes an debug log message. ++ /// ++ /// The to write to. ++ /// The exception to log. ++ /// logger.LogDebug(exception) ++ public static void LogDebug(this ILogger logger, Exception exception) ++ { ++ logger.LogDebug(exception: exception, message: exception.Message); ++ } ++ ++ /// ++ /// Formats and writes an information log message as JSON. ++ /// ++ /// The to write to. ++ /// The object to be serialized as JSON. ++ /// logger.LogInformation(new {User = user, Address = address}) ++ public static void LogInformation(this ILogger logger, object message) ++ { ++ logger.LogInformation(LoggingConstants.KeyJsonFormatter, message); ++ } ++ ++ /// ++ /// Formats and writes an information log message. ++ /// ++ /// The to write to. ++ /// The exception to log. ++ /// logger.LogInformation(exception) ++ public static void LogInformation(this ILogger logger, Exception exception) ++ { ++ logger.LogInformation(exception: exception, message: exception.Message); ++ } ++ ++ /// ++ /// Formats and writes a warning log message as JSON. ++ /// ++ /// The to write to. ++ /// The object to be serialized as JSON. ++ /// logger.LogWarning(new {User = user, Address = address}) ++ public static void LogWarning(this ILogger logger, object message) ++ { ++ logger.LogWarning(LoggingConstants.KeyJsonFormatter, message); ++ } ++ ++ /// ++ /// Formats and writes an warning log message. ++ /// ++ /// The to write to. ++ /// The exception to log. ++ /// logger.LogWarning(exception) ++ public static void LogWarning(this ILogger logger, Exception exception) ++ { ++ logger.LogWarning(exception: exception, message: exception.Message); ++ } ++ ++ /// ++ /// Formats and writes a error log message as JSON. ++ /// ++ /// The to write to. ++ /// The object to be serialized as JSON. ++ /// logger.LogCritical(new {User = user, Address = address}) ++ public static void LogError(this ILogger logger, object message) ++ { ++ logger.LogError(LoggingConstants.KeyJsonFormatter, message); ++ } ++ ++ /// ++ /// Formats and writes an error log message. ++ /// ++ /// The to write to. ++ /// The exception to log. ++ /// logger.LogError(exception) ++ public static void LogError(this ILogger logger, Exception exception) ++ { ++ logger.LogError(exception: exception, message: exception.Message); ++ } ++ ++ /// ++ /// Formats and writes a critical log message as JSON. ++ /// ++ /// The to write to. ++ /// The object to be serialized as JSON. ++ /// logger.LogCritical(new {User = user, Address = address}) ++ public static void LogCritical(this ILogger logger, object message) ++ { ++ logger.LogCritical(LoggingConstants.KeyJsonFormatter, message); ++ } ++ ++ /// ++ /// Formats and writes an critical log message. ++ /// ++ /// The to write to. ++ /// The exception to log. ++ /// logger.LogCritical(exception) ++ public static void LogCritical(this ILogger logger, Exception exception) ++ { ++ logger.LogCritical(exception: exception, message: exception.Message); ++ } ++ ++ /// ++ /// Formats and writes a log message as JSON at the specified log level. ++ /// ++ /// The to write to. ++ /// Entry will be written on this level. ++ /// The object to be serialized as JSON. ++ /// logger.Log(LogLevel.Information, new {User = user, Address = address}) ++ public static void Log(this ILogger logger, LogLevel logLevel, object message) ++ { ++ logger.Log(logLevel, LoggingConstants.KeyJsonFormatter, message); ++ } ++ ++ /// ++ /// Formats and writes a log message at the specified log level. ++ /// ++ /// The to write to. ++ /// Entry will be written on this level. ++ /// The exception to log. ++ /// logger.Log(LogLevel.Information, exception) ++ public static void Log(this ILogger logger, LogLevel logLevel, Exception exception) ++ { ++ logger.Log(logLevel, exception: exception, message: exception.Message); ++ } ++ ++ #endregion ++ ++ #region ExtraKeys Logger Extentions ++ ++ #region Debug ++ ++ /// ++ /// Formats and writes a debug log message. ++ /// ++ /// The to write to. ++ /// Additional keys will be appended to the log entry. ++ /// The event id associated with the log. ++ /// The exception to log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogDebug(extraKeys, 0, exception, "Error while processing request from {Address}", address) ++ public static void LogDebug(this ILogger logger, T extraKeys, EventId eventId, Exception exception, ++ string message, params object[] args) where T : class ++ { ++ Log(logger, LogLevel.Debug, extraKeys, eventId, exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes a debug log message. ++ /// ++ /// The to write to. ++ /// Additional keys will be appended to the log entry. ++ /// The event id associated with the log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogDebug(extraKeys, 0, "Processing request from {Address}", address) ++ public static void LogDebug(this ILogger logger, T extraKeys, EventId eventId, string message, ++ params object[] args) where T : class ++ { ++ Log(logger, LogLevel.Debug, extraKeys, eventId, message, args); ++ } ++ ++ /// ++ /// Formats and writes a debug log message. ++ /// ++ /// The to write to. ++ /// Additional keys will be appended to the log entry. ++ /// The exception to log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogDebug(extraKeys, exception, "Error while processing request from {Address}", address) ++ public static void LogDebug(this ILogger logger, T extraKeys, Exception exception, string message, ++ params object[] args) where T : class ++ { ++ Log(logger, LogLevel.Debug, extraKeys, exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes a debug log message. ++ /// ++ /// The to write to. ++ /// Additional keys will be appended to the log entry. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogDebug(extraKeys, "Processing request from {Address}", address) ++ public static void LogDebug(this ILogger logger, T extraKeys, string message, params object[] args) ++ where T : class ++ { ++ Log(logger, LogLevel.Debug, extraKeys, message, args); ++ } ++ ++ #endregion ++ ++ #region Trace ++ ++ /// ++ /// Formats and writes a trace log message. ++ /// ++ /// The to write to. ++ /// Additional keys will be appended to the log entry. ++ /// The event id associated with the log. ++ /// The exception to log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogTrace(extraKeys, 0, exception, "Error while processing request from {Address}", address) ++ public static void LogTrace(this ILogger logger, T extraKeys, EventId eventId, Exception exception, ++ string message, params object[] args) where T : class ++ { ++ Log(logger, LogLevel.Trace, extraKeys, eventId, exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes a trace log message. ++ /// ++ /// The to write to. ++ /// Additional keys will be appended to the log entry. ++ /// The event id associated with the log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogTrace(extraKeys, 0, "Processing request from {Address}", address) ++ public static void LogTrace(this ILogger logger, T extraKeys, EventId eventId, string message, ++ params object[] args) where T : class ++ { ++ Log(logger, LogLevel.Trace, extraKeys, eventId, message, args); ++ } ++ ++ /// ++ /// Formats and writes a trace log message. ++ /// ++ /// The to write to. ++ /// Additional keys will be appended to the log entry. ++ /// The exception to log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogTrace(extraKeys, exception, "Error while processing request from {Address}", address) ++ public static void LogTrace(this ILogger logger, T extraKeys, Exception exception, string message, ++ params object[] args) where T : class ++ { ++ Log(logger, LogLevel.Trace, extraKeys, exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes a trace log message. ++ /// ++ /// The to write to. ++ /// Additional keys will be appended to the log entry. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogTrace(extraKeys, "Processing request from {Address}", address) ++ public static void LogTrace(this ILogger logger, T extraKeys, string message, params object[] args) ++ where T : class ++ { ++ Log(logger, LogLevel.Trace, extraKeys, message, args); ++ } ++ ++ #endregion ++ ++ #region Information ++ ++ /// ++ /// Formats and writes an informational log message. ++ /// ++ /// The to write to. ++ /// Additional keys will be appended to the log entry. ++ /// The event id associated with the log. ++ /// The exception to log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogInformation(extraKeys, 0, exception, "Error while processing request from {Address}", address) ++ public static void LogInformation(this ILogger logger, T extraKeys, EventId eventId, Exception exception, ++ string message, params object[] args) where T : class ++ { ++ Log(logger, LogLevel.Information, extraKeys, eventId, exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes an informational log message. ++ /// ++ /// The to write to. ++ /// Additional keys will be appended to the log entry. ++ /// The event id associated with the log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogInformation(extraKeys, 0, "Processing request from {Address}", address) ++ public static void LogInformation(this ILogger logger, T extraKeys, EventId eventId, string message, ++ params object[] args) where T : class ++ { ++ Log(logger, LogLevel.Information, extraKeys, eventId, message, args); ++ } ++ ++ /// ++ /// Formats and writes an informational log message. ++ /// ++ /// The to write to. ++ /// Additional keys will be appended to the log entry. ++ /// The exception to log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogInformation(extraKeys, exception, "Error while processing request from {Address}", address) ++ public static void LogInformation(this ILogger logger, T extraKeys, Exception exception, string message, ++ params object[] args) where T : class ++ { ++ Log(logger, LogLevel.Information, extraKeys, exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes an informational log message. ++ /// ++ /// The to write to. ++ /// Additional keys will be appended to the log entry. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogInformation(extraKeys, "Processing request from {Address}", address) ++ public static void LogInformation(this ILogger logger, T extraKeys, string message, params object[] args) ++ where T : class ++ { ++ Log(logger, LogLevel.Information, extraKeys, message, args); ++ } ++ ++ #endregion ++ ++ #region Warning ++ ++ /// ++ /// Formats and writes a warning log message. ++ /// ++ /// The to write to. ++ /// Additional keys will be appended to the log entry. ++ /// The event id associated with the log. ++ /// The exception to log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogWarning(extraKeys, 0, exception, "Error while processing request from {Address}", address) ++ public static void LogWarning(this ILogger logger, T extraKeys, EventId eventId, Exception exception, ++ string message, params object[] args) where T : class ++ { ++ Log(logger, LogLevel.Warning, extraKeys, eventId, exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes a warning log message. ++ /// ++ /// The to write to. ++ /// Additional keys will be appended to the log entry. ++ /// The event id associated with the log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogWarning(extraKeys, 0, "Processing request from {Address}", address) ++ public static void LogWarning(this ILogger logger, T extraKeys, EventId eventId, string message, ++ params object[] args) where T : class ++ { ++ Log(logger, LogLevel.Warning, extraKeys, eventId, message, args); ++ } ++ ++ /// ++ /// Formats and writes a warning log message. ++ /// ++ /// The to write to. ++ /// Additional keys will be appended to the log entry. ++ /// The exception to log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogWarning(extraKeys, exception, "Error while processing request from {Address}", address) ++ public static void LogWarning(this ILogger logger, T extraKeys, Exception exception, string message, ++ params object[] args) where T : class ++ { ++ Log(logger, LogLevel.Warning, extraKeys, exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes a warning log message. ++ /// ++ /// The to write to. ++ /// Additional keys will be appended to the log entry. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogWarning(extraKeys, "Processing request from {Address}", address) ++ public static void LogWarning(this ILogger logger, T extraKeys, string message, params object[] args) ++ where T : class ++ { ++ Log(logger, LogLevel.Warning, extraKeys, message, args); ++ } ++ ++ #endregion ++ ++ #region Error ++ ++ /// ++ /// Formats and writes an error log message. ++ /// ++ /// The to write to. ++ /// Additional keys will be appended to the log entry. ++ /// The event id associated with the log. ++ /// The exception to log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogError(extraKeys, 0, exception, "Error while processing request from {Address}", address) ++ public static void LogError(this ILogger logger, T extraKeys, EventId eventId, Exception exception, ++ string message, params object[] args) where T : class ++ { ++ Log(logger, LogLevel.Error, extraKeys, eventId, exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes an error log message. ++ /// ++ /// The to write to. ++ /// Additional keys will be appended to the log entry. ++ /// The event id associated with the log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogError(extraKeys, 0, "Processing request from {Address}", address) ++ public static void LogError(this ILogger logger, T extraKeys, EventId eventId, string message, ++ params object[] args) where T : class ++ { ++ Log(logger, LogLevel.Error, extraKeys, eventId, message, args); ++ } ++ ++ /// ++ /// Formats and writes an error log message. ++ /// ++ /// The to write to. ++ /// Additional keys will be appended to the log entry. ++ /// The exception to log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogError(extraKeys, exception, "Error while processing request from {Address}", address) ++ public static void LogError(this ILogger logger, T extraKeys, Exception exception, string message, ++ params object[] args) where T : class ++ { ++ Log(logger, LogLevel.Error, extraKeys, exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes an error log message. ++ /// ++ /// The to write to. ++ /// Additional keys will be appended to the log entry. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogError(extraKeys, "Processing request from {Address}", address) ++ public static void LogError(this ILogger logger, T extraKeys, string message, params object[] args) ++ where T : class ++ { ++ Log(logger, LogLevel.Error, extraKeys, message, args); ++ } ++ ++ #endregion ++ ++ #region Critical ++ ++ /// ++ /// Formats and writes a critical log message. ++ /// ++ /// The to write to. ++ /// Additional keys will be appended to the log entry. ++ /// The event id associated with the log. ++ /// The exception to log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogCritical(extraKeys, 0, exception, "Error while processing request from {Address}", address) ++ public static void LogCritical(this ILogger logger, T extraKeys, EventId eventId, Exception exception, ++ string message, params object[] args) where T : class ++ { ++ Log(logger, LogLevel.Critical, extraKeys, eventId, exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes a critical log message. ++ /// ++ /// The to write to. ++ /// Additional keys will be appended to the log entry. ++ /// The event id associated with the log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogCritical(extraKeys, 0, "Processing request from {Address}", address) ++ public static void LogCritical(this ILogger logger, T extraKeys, EventId eventId, string message, ++ params object[] args) where T : class ++ { ++ Log(logger, LogLevel.Critical, extraKeys, eventId, message, args); ++ } ++ ++ /// ++ /// Formats and writes a critical log message. ++ /// ++ /// The to write to. ++ /// Additional keys will be appended to the log entry. ++ /// The exception to log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogCritical(extraKeys, exception, "Error while processing request from {Address}", address) ++ public static void LogCritical(this ILogger logger, T extraKeys, Exception exception, string message, ++ params object[] args) where T : class ++ { ++ Log(logger, LogLevel.Critical, extraKeys, exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes a critical log message. ++ /// ++ /// The to write to. ++ /// Additional keys will be appended to the log entry. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.LogCritical(extraKeys, "Processing request from {Address}", address) ++ public static void LogCritical(this ILogger logger, T extraKeys, string message, params object[] args) ++ where T : class ++ { ++ Log(logger, LogLevel.Critical, extraKeys, message, args); ++ } ++ ++ #endregion ++ ++ #region Log ++ ++ /// ++ /// Formats and writes a log message at the specified log level. ++ /// ++ /// The to write to. ++ /// Entry will be written on this level. ++ /// Additional keys will be appended to the log entry. ++ /// The event id associated with the log. ++ /// The exception to log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.Log(LogLevel.Information, extraKeys, 0, exception, "Error while processing request from {Address}", address) ++ public static void Log(this ILogger logger, LogLevel logLevel, T extraKeys, EventId eventId, Exception exception, ++ string message, params object[] args) where T : class ++ { ++ if (extraKeys is Exception ex && exception is null) ++ logger.Log(logLevel, eventId, ex, message, args); ++ else if (extraKeys is not null) ++ using (logger.BeginScope(extraKeys)) ++ logger.Log(logLevel, eventId, exception, message, args); ++ else ++ logger.Log(logLevel, eventId, exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes a log message at the specified log level. ++ /// ++ /// The to write to. ++ /// Entry will be written on this level. ++ /// Additional keys will be appended to the log entry. ++ /// The event id associated with the log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.Log(LogLevel.Information, extraKeys, 0, "Processing request from {Address}", address) ++ public static void Log(this ILogger logger, LogLevel logLevel, T extraKeys, EventId eventId, string message, ++ params object[] args) where T : class ++ { ++ Log(logger, logLevel, extraKeys, eventId, null, message, args); ++ } ++ ++ /// ++ /// Formats and writes a log message at the specified log level. ++ /// ++ /// The to write to. ++ /// Entry will be written on this level. ++ /// Additional keys will be appended to the log entry. ++ /// The exception to log. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.Log(LogLevel.Information, extraKeys, exception, "Error while processing request from {Address}", address) ++ public static void Log(this ILogger logger, LogLevel logLevel, T extraKeys, Exception exception, string message, ++ params object[] args) where T : class ++ { ++ Log(logger, logLevel, extraKeys, 0, exception, message, args); ++ } ++ ++ /// ++ /// Formats and writes a log message at the specified log level. ++ /// ++ /// The to write to. ++ /// Entry will be written on this level. ++ /// Additional keys will be appended to the log entry. ++ /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" ++ /// An object array that contains zero or more objects to format. ++ /// logger.Log(LogLevel.Information, extraKeys, "Processing request from {Address}", address) ++ public static void Log(this ILogger logger, LogLevel logLevel, T extraKeys, string message, params object[] args) ++ where T : class ++ { ++ try ++ { ++ Log(logger, logLevel, extraKeys, 0, null, message, args); ++ } ++ catch (Exception e) ++ { ++ logger.Log(LogLevel.Error, 0, e, "Powertools internal error"); ++ } ++ } ++ ++ #endregion ++ ++ #endregion ++} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/LoggingAttribute.cs b/libraries/src/AWS.Lambda.Powertools.Logging/LoggingAttribute.cs +index 747cf7db..4a5da930 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Logging/LoggingAttribute.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Logging/LoggingAttribute.cs +@@ -15,7 +15,6 @@ + + using System; + using AspectInjector.Broker; +-using AWS.Lambda.Powertools.Common; + using AWS.Lambda.Powertools.Logging.Internal; + using Microsoft.Extensions.Logging; + +@@ -117,8 +116,8 @@ namespace AWS.Lambda.Powertools.Logging; + /// + /// + [AttributeUsage(AttributeTargets.Method)] +-// [Injection(typeof(LoggingAspect))] +-public class LoggingAttribute : MethodAspectAttribute ++[Injection(typeof(LoggingAspect))] ++public class LoggingAttribute : Attribute + { + /// + /// Service name is used for logging. +@@ -147,19 +146,7 @@ public class LoggingAttribute : MethodAspectAttribute + /// such as a string or any custom data object. + /// + /// true if [log event]; otherwise, false. +- public bool LogEvent +- { +- get => _logEvent; +- set +- { +- _logEvent = value; +- _logEventSet = true; +- } +- } +- +- private bool _logEventSet; +- private bool _logEvent; +- internal bool IsLogEventSet => _logEventSet; ++ public bool LogEvent { get; set; } + + /// + /// Pointer path to extract correlation id from input parameter. +@@ -184,19 +171,4 @@ public class LoggingAttribute : MethodAspectAttribute + /// + /// The log level. + public LoggerOutputCase LoggerOutputCase { get; set; } = LoggerOutputCase.Default; +- +- /// +- /// Flush buffer on uncaught error +- /// When buffering is enabled, this property will flush the buffer on uncaught exceptions +- /// +- public bool FlushBufferOnUncaughtError { get; set; } +- +- /// +- /// Creates the aspect with the Logger +- /// +- /// +- protected override IMethodAspectHandler CreateHandler() +- { +- return new LoggingAspect(LoggerFactoryHolder.GetOrCreateFactory().CreatePowertoolsLogger()); +- } + } +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerBuilder.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerBuilder.cs +deleted file mode 100644 +index e822b3c5..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerBuilder.cs ++++ /dev/null +@@ -1,148 +0,0 @@ +-using System; +-using System.Text.Json; +-using AWS.Lambda.Powertools.Common; +-using AWS.Lambda.Powertools.Logging.Internal.Helpers; +-using Microsoft.Extensions.Logging; +- +-namespace AWS.Lambda.Powertools.Logging; +- +-/// +-/// Builder class for creating configured PowertoolsLogger instances. +-/// Provides a fluent interface for configuring logging options. +-/// +-public class PowertoolsLoggerBuilder +-{ +- private readonly PowertoolsLoggerConfiguration _configuration = new(); +- +- /// +- /// Sets the service name for the logger. +- /// +- /// The service name to be included in logs. +- /// The builder instance for method chaining. +- public PowertoolsLoggerBuilder WithService(string service) +- { +- _configuration.Service = service; +- return this; +- } +- +- /// +- /// Sets the sampling rate for logs. +- /// +- /// The sampling rate between 0 and 1. +- /// The builder instance for method chaining. +- public PowertoolsLoggerBuilder WithSamplingRate(double rate) +- { +- _configuration.SamplingRate = rate; +- return this; +- } +- +- /// +- /// Sets the minimum log level for the logger. +- /// +- /// The minimum LogLevel to capture. +- /// The builder instance for method chaining. +- public PowertoolsLoggerBuilder WithMinimumLogLevel(LogLevel level) +- { +- _configuration.MinimumLogLevel = level; +- return this; +- } +- +- /// +- /// Sets custom JSON serialization options. +- /// +- /// JSON serializer options to use for log formatting. +- /// The builder instance for method chaining. +- public PowertoolsLoggerBuilder WithJsonOptions(JsonSerializerOptions options) +- { +- _configuration.JsonOptions = options; +- return this; +- } +- +- /// +- /// Sets the timestamp format for log entries. +- /// +- /// The timestamp format string. +- /// The builder instance for method chaining. +- public PowertoolsLoggerBuilder WithTimestampFormat(string format) +- { +- _configuration.TimestampFormat = format; +- return this; +- } +- +- /// +- /// Sets the output casing style for log properties. +- /// +- /// The casing style to use for log output. +- /// The builder instance for method chaining. +- public PowertoolsLoggerBuilder WithOutputCase(LoggerOutputCase outputCase) +- { +- _configuration.LoggerOutputCase = outputCase; +- return this; +- } +- +- /// +- /// Sets a custom log formatter. +- /// +- /// The formatter to use for log formatting. +- /// The builder instance for method chaining. +- /// Thrown when formatter is null. +- public PowertoolsLoggerBuilder WithFormatter(ILogFormatter formatter) +- { +- _configuration.LogFormatter = formatter ?? throw new ArgumentNullException(nameof(formatter)); +- return this; +- } +- +- /// +- /// Configures log buffering with custom options. +- /// +- /// Action to configure the log buffering options. +- /// The builder instance for method chaining. +- public PowertoolsLoggerBuilder WithLogBuffering(Action configure) +- { +- _configuration.LogBuffering = new LogBufferingOptions(); +- configure?.Invoke(_configuration.LogBuffering); +- return this; +- } +- +- /// +- /// Specifies the console output wrapper used for writing logs. This property allows +- /// redirecting log output for testing or specialized handling scenarios. +- /// Defaults to standard console output via ConsoleWrapper. +- /// +- /// +- /// +- /// // Using TestLoggerOutput +- /// .WithLogOutput(new TestLoggerOutput()); +- /// +- /// // Custom console output for testing +- /// .WithLogOutput(new TestConsoleWrapper()); +- /// +- /// // Example implementation for testing: +- /// public class TestConsoleWrapper : IConsoleWrapper +- /// { +- /// public List<string> CapturedOutput { get; } = new(); +- /// +- /// public void WriteLine(string message) +- /// { +- /// CapturedOutput.Add(message); +- /// } +- /// } +- /// +- /// +- public PowertoolsLoggerBuilder WithLogOutput(IConsoleWrapper console) +- { +- _configuration.LogOutput = console ?? throw new ArgumentNullException(nameof(console)); +- return this; +- } +- +- +- /// +- /// Builds and returns a configured logger instance. +- /// +- /// An ILogger configured with the specified options. +- public ILogger Build() +- { +- var factory = LoggerFactoryHelper.CreateAndConfigureFactory(_configuration); +- return factory.CreatePowertoolsLogger(); +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs +deleted file mode 100644 +index 9b25d653..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs ++++ /dev/null +@@ -1,396 +0,0 @@ +-using System; +-using System.Security.Cryptography; +-using System.Text.Json; +-using Microsoft.Extensions.Logging; +-using Microsoft.Extensions.Options; +-using AWS.Lambda.Powertools.Common; +-using AWS.Lambda.Powertools.Logging.Serializers; +- +-namespace AWS.Lambda.Powertools.Logging; +- +-/// +-/// Configuration for the Powertools Logger. +-/// +-/// +-/// +-/// Basic logging configuration: +-/// +-/// builder.Logging.AddPowertoolsLogger(options => +-/// { +-/// options.Service = "OrderService"; +-/// options.MinimumLogLevel = LogLevel.Information; +-/// options.LoggerOutputCase = LoggerOutputCase.CamelCase; +-/// }); +-/// +-/// +-/// Using with log buffering: +-/// +-/// builder.Logging.AddPowertoolsLogger(options => +-/// { +-/// options.LogBuffering = new LogBufferingOptions +-/// { +-/// Enabled = true, +-/// BufferAtLogLevel = LogLevel.Debug, +-/// FlushOnErrorLog = true +-/// }; +-/// }); +-/// +-/// +-/// Custom JSON formatting: +-/// +-/// builder.Logging.AddPowertoolsLogger(options => +-/// { +-/// options.JsonOptions = new JsonSerializerOptions +-/// { +-/// PropertyNamingPolicy = JsonNamingPolicy.CamelCase, +-/// WriteIndented = true +-/// }; +-/// }); +-/// +-/// +-public class PowertoolsLoggerConfiguration : IOptions +-{ +- /// +- /// The configuration section name used when retrieving configuration from appsettings.json +- /// or other configuration providers. +- /// +- public const string ConfigurationSectionName = "AWS.Lambda.Powertools.Logging.Logger"; +- +- /// +- /// Specifies the service name that will be added to all logs to improve discoverability. +- /// This value can also be set using the environment variable POWERTOOLS_SERVICE_NAME. +- /// +- /// +- /// +- /// options.Service = "OrderProcessingService"; +- /// +- /// +- public string Service { get; set; } = null; +- +- /// +- /// Defines the format for timestamps in log entries. Supports standard .NET date format strings. +- /// When not specified, the default ISO 8601 format is used. +- /// +- /// +- /// +- /// // Use specific format +- /// options.TimestampFormat = "yyyy-MM-dd HH:mm:ss"; +- /// +- /// // Use ISO 8601 with milliseconds +- /// options.TimestampFormat = "o"; +- /// +- /// +- public string TimestampFormat { get; set; } +- +- /// +- /// Defines the minimum log level that will be processed by the logger. +- /// Messages below this level will be ignored. Defaults to LogLevel.None, which means +- /// the minimum level is determined by other configuration mechanisms. +- /// This can also be set using the environment variable POWERTOOLS_LOG_LEVEL. +- /// +- /// +- /// +- /// // Only log warnings and above +- /// options.MinimumLogLevel = LogLevel.Warning; +- /// +- /// // Log everything including trace messages +- /// options.MinimumLogLevel = LogLevel.Trace; +- /// +- /// +- public LogLevel MinimumLogLevel { get; set; } = LogLevel.None; +- +- /// +- /// Sets a percentage (0.0 to 1.0) of logs that will be dynamically elevated to DEBUG level, +- /// allowing for production debugging without increasing log verbosity for all requests. +- /// This can also be set using the environment variable POWERTOOLS_LOGGER_SAMPLE_RATE. +- /// +- /// +- /// +- /// // Sample 10% of logs to DEBUG level +- /// options.SamplingRate = 0.1; +- /// +- /// // Sample 100% (all logs) to DEBUG level +- /// options.SamplingRate = 1.0; +- /// +- /// +- public double SamplingRate { get; set; } +- +- /// +- /// Controls the case format used for log field names in the JSON output. +- /// Available options are Default, CamelCase, PascalCase, or SnakeCase. +- /// This can also be set using the environment variable POWERTOOLS_LOGGER_CASE. +- /// +- /// +- /// +- /// // Use camelCase for JSON field names +- /// options.LoggerOutputCase = LoggerOutputCase.CamelCase; +- /// +- /// // Use snake_case for JSON field names +- /// options.LoggerOutputCase = LoggerOutputCase.SnakeCase; +- /// +- /// +- public LoggerOutputCase LoggerOutputCase { get; set; } = LoggerOutputCase.Default; +- +- /// +- /// Internal key used for log level in output +- /// +- internal string LogLevelKey { get; set; } = "level"; +- +- /// +- /// Provides a custom log formatter implementation to control how log entries are formatted. +- /// Set this to override the default JSON formatting with your own custom format. +- /// +- /// +- /// +- /// // Use a custom formatter implementation +- /// options.LogFormatter = new MyCustomLogFormatter(); +- /// +- /// // Example with a simple custom formatter class this will just return a string: +- /// public class MyCustomLogFormatter : ILogFormatter +- /// { +- /// public object FormatLog(LogEntry entry) +- /// { +- /// // Custom formatting logic here +- /// return $"{logEntry.Timestamp}: [{logEntry.Level}] {logEntry.Message}"; +- /// } +- /// } +- /// // Example with a complete formatter class this will just return a json object: +- /// public object FormatLogEntry(LogEntry logEntry) +- /// { +- /// return new +- /// { +- /// Message = logEntry.Message, +- /// Service = logEntry.Service, +- /// CorrelationIds = new +- /// { +- /// AwsRequestId = logEntry.LambdaContext?.AwsRequestId, +- /// XRayTraceId = logEntry.XRayTraceId, +- /// CorrelationId = logEntry.CorrelationId +- /// }, +- /// LambdaFunction = new +- /// { +- /// Name = logEntry.LambdaContext?.FunctionName, +- /// Arn = logEntry.LambdaContext?.InvokedFunctionArn, +- /// MemoryLimitInMB = logEntry.LambdaContext?.MemoryLimitInMB, +- /// Version = logEntry.LambdaContext?.FunctionVersion, +- /// ColdStart = true, +- /// }, +- /// Level = logEntry.Level.ToString(), +- /// Timestamp = new DateTime(2024, 1, 1).ToString("o"), +- /// Logger = new +- /// { +- /// Name = logEntry.Name, +- /// SampleRate = logEntry.SamplingRate +- /// }, +- /// }; +- /// } +- /// +- /// +- public ILogFormatter LogFormatter { get; set; } +- +- private JsonSerializerOptions _jsonOptions; +- +- /// +- /// Configures the JSON serialization options used when converting log entries to JSON. +- /// This allows customization of property naming, indentation, and other serialization behaviors. +- /// Setting this property automatically updates the internal serializer. +- /// +- /// +- /// +- /// // DictionaryNamingPolicy allows you to control the naming policy for dictionary keys +- /// options.JsonOptions = new JsonSerializerOptions +- /// { +- /// DictionaryNamingPolicy = JsonNamingPolicy.CamelCase +- /// }; +- /// // Pretty-print JSON logs with indentation +- /// options.JsonOptions = new JsonSerializerOptions +- /// { +- /// WriteIndented = true, +- /// PropertyNamingPolicy = JsonNamingPolicy.CamelCase +- /// }; +- /// +- /// // Configure to ignore null values in output +- /// options.JsonOptions = new JsonSerializerOptions +- /// { +- /// DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull +- /// }; +- /// +- /// +- public JsonSerializerOptions JsonOptions +- { +- get => _jsonOptions; +- set +- { +- _jsonOptions = value; +- if (_jsonOptions != null && _serializer != null) +- { +- _serializer.SetOptions(_jsonOptions); +- } +- } +- } +- +- /// +- /// Enables or disables log buffering. Logs below the specified level will be buffered +- /// until the buffer is flushed or an error occurs. +- /// Buffer logs at the WARNING, INFO, and DEBUG levels and reduce CloudWatch costs by decreasing the number of emitted log messages +- /// +- /// +- /// +- /// // Enable buffering for debug logs +- /// options.LogBuffering = new LogBufferingOptions +- /// { +- /// Enabled = true, +- /// BufferAtLogLevel = LogLevel.Debug, +- /// FlushOnErrorLog = true +- /// }; +- /// +- /// // Buffer all logs below Error level +- /// options.LogBuffering = new LogBufferingOptions +- /// { +- /// Enabled = true, +- /// BufferAtLogLevel = LogLevel.Warning, +- /// FlushOnErrorLog = true +- /// }; +- /// +- /// +- public LogBufferingOptions LogBuffering { get; set; } +- +- /// +- /// Serializer instance for this configuration +- /// +- private PowertoolsLoggingSerializer _serializer; +- +- /// +- /// Gets the serializer instance for this configuration +- /// +- internal PowertoolsLoggingSerializer Serializer => _serializer ??= InitializeSerializer(); +- +- /// +- /// Specifies the console output wrapper used for writing logs. This property allows +- /// redirecting log output for testing or specialized handling scenarios. +- /// Defaults to standard console output via ConsoleWrapper. +- /// +- /// +- /// +- /// // Using TestLoggerOutput +- /// options.LogOutput = new TestLoggerOutput(); +- /// +- /// // Custom console output for testing +- /// options.LogOutput = new TestConsoleWrapper(); +- /// +- /// // Example implementation for testing: +- /// public class TestConsoleWrapper : IConsoleWrapper +- /// { +- /// public List<string> CapturedOutput { get; } = new(); +- /// +- /// public void WriteLine(string message) +- /// { +- /// CapturedOutput.Add(message); +- /// } +- /// } +- /// +- /// +- public IConsoleWrapper LogOutput { get; set; } = new ConsoleWrapper(); +- +- /// +- /// Initialize serializer with the current configuration +- /// +- private PowertoolsLoggingSerializer InitializeSerializer() +- { +- var serializer = new PowertoolsLoggingSerializer(); +- if (_jsonOptions != null) +- { +- serializer.SetOptions(_jsonOptions); +- } +- +- serializer.ConfigureNamingPolicy(LoggerOutputCase); +- return serializer; +- } +- +- // IOptions implementation +- PowertoolsLoggerConfiguration IOptions.Value => this; +- +- internal string XRayTraceId { get; set; } +- internal bool LogEvent { get; set; } +- +- internal int SamplingRefreshCount { get; set; } = 0; +- internal LogLevel InitialLogLevel { get; set; } = LogLevel.Information; +- +- /// +- /// Gets random number +- /// +- /// System.Double. +- internal virtual double GetRandom() +- { +- return GetSafeRandom(); +- } +- +- /// +- /// Refresh the sampling calculation and update the minimum log level if needed +- /// +- /// True if debug sampling was enabled, false otherwise +- internal bool RefreshSampleRateCalculation() +- { +- return RefreshSampleRateCalculation(out _); +- } +- +- /// +- /// Refresh the sampling calculation and update the minimum log level if needed +- /// +- /// +- /// True if debug sampling was enabled, false otherwise +- internal bool RefreshSampleRateCalculation(out double samplerValue) +- { +- samplerValue = 0.0; +- +- if (SamplingRate <= 0) +- return false; +- +- // Increment counter at the beginning for proper cold start protection +- SamplingRefreshCount++; +- +- // Skip first call for cold start protection +- if (SamplingRefreshCount == 1) +- { +- return false; +- } +- +- var shouldEnableDebugSampling = ShouldEnableDebugSampling(out samplerValue); +- +- if (shouldEnableDebugSampling && MinimumLogLevel > LogLevel.Debug) +- { +- MinimumLogLevel = LogLevel.Debug; +- return true; +- } +- else if (!shouldEnableDebugSampling) +- { +- MinimumLogLevel = InitialLogLevel; +- } +- +- return shouldEnableDebugSampling; +- } +- +- +- internal bool ShouldEnableDebugSampling() +- { +- return ShouldEnableDebugSampling(out _); +- } +- +- internal bool ShouldEnableDebugSampling(out double samplerValue) +- { +- samplerValue = 0.0; +- if (SamplingRate <= 0) return false; +- +- samplerValue = GetRandom(); +- return samplerValue <= SamplingRate; +- } +- +- internal static double GetSafeRandom() +- { +- var randomGenerator = RandomNumberGenerator.Create(); +- byte[] data = new byte[4]; +- randomGenerator.GetBytes(data); +- uint randomUInt = BitConverter.ToUInt32(data, 0); +- return (double)randomUInt / uint.MaxValue; +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerExtensions.cs +deleted file mode 100644 +index 9a6cda81..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerExtensions.cs ++++ /dev/null +@@ -1,269 +0,0 @@ +-using System; +-using System.Collections.Generic; +-using AWS.Lambda.Powertools.Logging.Internal; +-using Microsoft.Extensions.Logging; +- +-namespace AWS.Lambda.Powertools.Logging; +- +-/// +-/// Class LoggerExtensions. +-/// +-public static class PowertoolsLoggerExtensions +-{ +- #region JSON Logger Extentions +- +- /// +- /// Formats and writes a trace log message as JSON. +- /// +- /// The to write to. +- /// The object to be serialized as JSON. +- /// logger.LogTrace(new {User = user, Address = address}) +- public static void LogTrace(this ILogger logger, object message) +- { +- logger.LogTrace(LoggingConstants.KeyJsonFormatter, message); +- } +- +- /// +- /// Formats and writes an trace log message. +- /// +- /// The to write to. +- /// The exception to log. +- /// logger.LogTrace(exception) +- public static void LogTrace(this ILogger logger, Exception exception) +- { +- logger.LogTrace(exception: exception, message: exception.Message); +- } +- +- /// +- /// Formats and writes a debug log message as JSON. +- /// +- /// The to write to. +- /// The object to be serialized as JSON. +- /// logger.LogDebug(new {User = user, Address = address}) +- public static void LogDebug(this ILogger logger, object message) +- { +- logger.LogDebug(LoggingConstants.KeyJsonFormatter, message); +- } +- +- /// +- /// Formats and writes an debug log message. +- /// +- /// The to write to. +- /// The exception to log. +- /// logger.LogDebug(exception) +- public static void LogDebug(this ILogger logger, Exception exception) +- { +- logger.LogDebug(exception: exception, message: exception.Message); +- } +- +- /// +- /// Formats and writes an information log message as JSON. +- /// +- /// The to write to. +- /// The object to be serialized as JSON. +- /// logger.LogInformation(new {User = user, Address = address}) +- public static void LogInformation(this ILogger logger, object message) +- { +- logger.LogInformation(LoggingConstants.KeyJsonFormatter, message); +- } +- +- /// +- /// Formats and writes an information log message. +- /// +- /// The to write to. +- /// The exception to log. +- /// logger.LogInformation(exception) +- public static void LogInformation(this ILogger logger, Exception exception) +- { +- logger.LogInformation(exception: exception, message: exception.Message); +- } +- +- /// +- /// Formats and writes a warning log message as JSON. +- /// +- /// The to write to. +- /// The object to be serialized as JSON. +- /// logger.LogWarning(new {User = user, Address = address}) +- public static void LogWarning(this ILogger logger, object message) +- { +- logger.LogWarning(LoggingConstants.KeyJsonFormatter, message); +- } +- +- /// +- /// Formats and writes an warning log message. +- /// +- /// The to write to. +- /// The exception to log. +- /// logger.LogWarning(exception) +- public static void LogWarning(this ILogger logger, Exception exception) +- { +- logger.LogWarning(exception: exception, message: exception.Message); +- } +- +- /// +- /// Formats and writes a error log message as JSON. +- /// +- /// The to write to. +- /// The object to be serialized as JSON. +- /// logger.LogCritical(new {User = user, Address = address}) +- public static void LogError(this ILogger logger, object message) +- { +- logger.LogError(LoggingConstants.KeyJsonFormatter, message); +- } +- +- /// +- /// Formats and writes an error log message. +- /// +- /// The to write to. +- /// The exception to log. +- /// logger.LogError(exception) +- public static void LogError(this ILogger logger, Exception exception) +- { +- logger.LogError(exception: exception, message: exception.Message); +- } +- +- /// +- /// Formats and writes a critical log message as JSON. +- /// +- /// The to write to. +- /// The object to be serialized as JSON. +- /// logger.LogCritical(new {User = user, Address = address}) +- public static void LogCritical(this ILogger logger, object message) +- { +- logger.LogCritical(LoggingConstants.KeyJsonFormatter, message); +- } +- +- /// +- /// Formats and writes an critical log message. +- /// +- /// The to write to. +- /// The exception to log. +- /// logger.LogCritical(exception) +- public static void LogCritical(this ILogger logger, Exception exception) +- { +- logger.LogCritical(exception: exception, message: exception.Message); +- } +- +- /// +- /// Formats and writes a log message as JSON at the specified log level. +- /// +- /// The to write to. +- /// Entry will be written on this level. +- /// The object to be serialized as JSON. +- /// logger.Log(LogLevel.Information, new {User = user, Address = address}) +- public static void Log(this ILogger logger, LogLevel logLevel, object message) +- { +- logger.Log(logLevel, LoggingConstants.KeyJsonFormatter, message); +- } +- +- /// +- /// Formats and writes a log message at the specified log level. +- /// +- /// The to write to. +- /// Entry will be written on this level. +- /// The exception to log. +- /// logger.Log(LogLevel.Information, exception) +- public static void Log(this ILogger logger, LogLevel logLevel, Exception exception) +- { +- logger.Log(logLevel, exception: exception, message: exception.Message); +- } +- +- #endregion +- +- /// +- /// Appending additional key to the log context. +- /// +- /// +- /// The list of keys. +- public static void AppendKeys(this ILogger logger,IEnumerable> keys) +- { +- Logger.AppendKeys(keys); +- } +- +- /// +- /// Appending additional key to the log context. +- /// +- /// +- /// The list of keys. +- public static void AppendKeys(this ILogger logger,IEnumerable> keys) +- { +- Logger.AppendKeys(keys); +- } +- +- /// +- /// Appending additional key to the log context. +- /// +- /// +- /// The key. +- /// The value. +- /// key +- /// value +- public static void AppendKey(this ILogger logger, string key, object value) +- { +- Logger.AppendKey(key, value); +- } +- +- /// +- /// Returns all additional keys added to the log context. +- /// +- /// IEnumerable<KeyValuePair<System.String, System.Object>>. +- public static IEnumerable> GetAllKeys(this ILogger logger) +- { +- return Logger.GetAllKeys(); +- } +- +- /// +- /// Removes all additional keys from the log context. +- /// +- internal static void RemoveAllKeys(this ILogger logger) +- { +- Logger.RemoveAllKeys(); +- } +- +- /// +- /// Remove additional keys from the log context. +- /// +- /// +- /// The list of keys. +- public static void RemoveKeys(this ILogger logger, params string[] keys) +- { +- Logger.RemoveKeys(keys); +- } +- +- /// +- /// Removes a key from the log context. +- /// +- public static void RemoveKey(this ILogger logger, string key) +- { +- Logger.RemoveKey(key); +- } +- +- // Replace the buffer methods with direct calls to the manager +- +- /// +- /// Flush any buffered logs +- /// +- public static void FlushBuffer(this ILogger logger) +- { +- // Direct call to the buffer manager to avoid any recursion +- LogBufferManager.FlushCurrentBuffer(); +- } +- +- /// +- /// Clear any buffered logs without writing them +- /// +- public static void ClearBuffer(this ILogger logger) +- { +- // Direct call to the buffer manager to avoid any recursion +- LogBufferManager.ClearCurrentBuffer(); +- } +- +- /// +- /// Refresh the sampling calculation and update the minimum log level if needed +- /// +- /// +- public static bool RefreshSampleRateCalculation(this ILogger logger) +- { +- return Logger.RefreshSampleRateCalculation(); +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactory.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactory.cs +deleted file mode 100644 +index 062a7c15..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactory.cs ++++ /dev/null +@@ -1,55 +0,0 @@ +-using System; +-using AWS.Lambda.Powertools.Logging.Internal.Helpers; +-using Microsoft.Extensions.Logging; +- +-namespace AWS.Lambda.Powertools.Logging; +- +-internal sealed class PowertoolsLoggerFactory : IDisposable +-{ +- private readonly ILoggerFactory _factory; +- +- internal PowertoolsLoggerFactory(ILoggerFactory loggerFactory) +- { +- _factory = loggerFactory; +- } +- +- internal PowertoolsLoggerFactory() : this(LoggerFactory.Create(builder => { builder.AddPowertoolsLogger(); })) +- { +- } +- +- internal static PowertoolsLoggerFactory Create(Action configureOptions) +- { +- var options = new PowertoolsLoggerConfiguration(); +- configureOptions(options); +- var factory = Create(options); +- return new PowertoolsLoggerFactory(factory); +- } +- +- internal static ILoggerFactory Create(PowertoolsLoggerConfiguration options) +- { +- return LoggerFactoryHelper.CreateAndConfigureFactory(options); +- } +- +- // Add builder pattern support +- internal static PowertoolsLoggerBuilder CreateBuilder() +- { +- return new PowertoolsLoggerBuilder(); +- } +- +- internal ILogger CreateLogger() => CreateLogger(typeof(T).FullName ?? typeof(T).Name); +- +- internal ILogger CreateLogger(string category) +- { +- return _factory.CreateLogger(category); +- } +- +- internal ILogger CreatePowertoolsLogger() +- { +- return _factory.CreatePowertoolsLogger(); +- } +- +- public void Dispose() +- { +- _factory?.Dispose(); +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactoryExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactoryExtensions.cs +deleted file mode 100644 +index edec07fd..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactoryExtensions.cs ++++ /dev/null +@@ -1,19 +0,0 @@ +-using Microsoft.Extensions.Logging; +- +-namespace AWS.Lambda.Powertools.Logging; +- +-/// +-/// Extensions for ILoggerFactory +-/// +-public static class PowertoolsLoggerFactoryExtensions +-{ +- /// +- /// Creates a new Powertools Logger instance using the Powertools full name. +- /// +- /// The factory. +- /// The that was created. +- public static ILogger CreatePowertoolsLogger(this ILoggerFactory factory) +- { +- return new PowertoolsLoggerFactory(factory).CreateLogger(PowertoolsLoggerConfiguration.ConfigurationSectionName); +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggingBuilderExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggingBuilderExtensions.cs +deleted file mode 100644 +index 73046197..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggingBuilderExtensions.cs ++++ /dev/null +@@ -1,237 +0,0 @@ +-using System; +-using System.Collections.Concurrent; +-using AWS.Lambda.Powertools.Common; +-using AWS.Lambda.Powertools.Logging.Internal; +-using Microsoft.Extensions.DependencyInjection; +-using Microsoft.Extensions.DependencyInjection.Extensions; +-using Microsoft.Extensions.Logging; +-using Microsoft.Extensions.Logging.Configuration; +- +-namespace AWS.Lambda.Powertools.Logging; +- +-/// +-/// Extension methods to configure and add the Powertools logger to an . +-/// +-/// +-/// This class provides methods to integrate the AWS Lambda Powertools logging capabilities +-/// with the standard .NET logging framework. +-/// +-/// +-/// Basic usage: +-/// +-/// builder.Logging.AddPowertoolsLogger(); +-/// +-/// +-public static class PowertoolsLoggingBuilderExtensions +-{ +- private static readonly ConcurrentBag AllProviders = new(); +- private static readonly object Lock = new(); +- private static PowertoolsLoggerConfiguration _currentConfig = new(); +- +- internal static void UpdateConfiguration(PowertoolsLoggerConfiguration config) +- { +- lock (Lock) +- { +- // Update the shared configuration +- _currentConfig = config; +- +- // Notify all providers about the change +- foreach (var provider in AllProviders) +- { +- provider.UpdateConfiguration(config); +- } +- } +- } +- +- internal static PowertoolsLoggerConfiguration GetCurrentConfiguration() +- { +- lock (Lock) +- { +- // Return a copy to prevent external modification +- return _currentConfig; +- } +- } +- +- /// +- /// Adds the Powertools logger to the logging builder with default configuration. +- /// +- /// The logging builder to configure. +- /// Opt-in to clear providers for Powertools-only output +- /// The logging builder for further configuration. +- /// +- /// This method registers the Powertools logger with default settings. The logger will output +- /// structured JSON logs that integrate well with AWS CloudWatch and other log analysis tools. +- /// +- /// +- /// Add the Powertools logger to your Lambda function: +- /// +- /// var builder = new HostBuilder() +- /// .ConfigureLogging(logging => +- /// { +- /// logging.AddPowertoolsLogger(); +- /// }); +- /// +- /// +- /// Using with minimal API: +- /// +- /// var builder = WebApplication.CreateBuilder(args); +- /// builder.Logging.AddPowertoolsLogger(); +- /// +- /// +- public static ILoggingBuilder AddPowertoolsLogger( +- this ILoggingBuilder builder, +- bool clearExistingProviders = false) +- { +- if (clearExistingProviders) +- { +- builder.ClearProviders(); +- } +- +- builder.AddConfiguration(); +- +- builder.Services.TryAddSingleton(); +- builder.Services.TryAddSingleton(sp => +- new PowertoolsConfigurations(sp.GetRequiredService())); +- +- // automatically register ILogger +- builder.Services.TryAddSingleton(provider => +- provider.GetRequiredService().CreatePowertoolsLogger()); +- +- builder.Services.TryAddEnumerable( +- ServiceDescriptor.Singleton(provider => +- { +- var powertoolsConfigurations = provider.GetRequiredService(); +- +- var loggerProvider = new PowertoolsLoggerProvider( +- _currentConfig, +- powertoolsConfigurations); +- +- lock (Lock) +- { +- AllProviders.Add(loggerProvider); +- } +- +- return loggerProvider; +- })); +- +- return builder; +- } +- +- /// +- /// Adds the Powertools logger to the logging builder with default configuration. +- /// +- /// The logging builder to configure. +- /// +- /// Opt-in to clear providers for Powertools-only output +- /// The logging builder for further configuration. +- /// +- /// This method registers the Powertools logger with default settings. The logger will output +- /// structured JSON logs that integrate well with AWS CloudWatch and other log analysis tools. +- /// +- /// +- /// Add the Powertools logger to your Lambda function: +- /// +- /// var builder = new HostBuilder() +- /// .ConfigureLogging(logging => +- /// { +- /// logging.AddPowertoolsLogger(); +- /// }); +- /// +- /// +- /// Using with minimal API: +- /// +- /// var builder = WebApplication.CreateBuilder(args); +- /// builder.Logging.AddPowertoolsLogger(); +- /// +- /// With custom configuration: +- /// +- /// builder.Logging.AddPowertoolsLogger(options => +- /// { +- /// options.MinimumLogLevel = LogLevel.Information; +- /// options.LoggerOutputCase = LoggerOutputCase.PascalCase; +- /// options.IncludeLogLevel = true; +- /// }); +- /// +- /// +- /// With log buffering: +- /// +- /// builder.Logging.AddPowertoolsLogger(options => +- /// { +- /// options.LogBuffering = new LogBufferingOptions +- /// { +- /// Enabled = true, +- /// BufferAtLogLevel = LogLevel.Debug +- /// }; +- /// }); +- /// +- /// +- public static ILoggingBuilder AddPowertoolsLogger( +- this ILoggingBuilder builder, +- Action configure, +- bool clearExistingProviders = false) +- { +- // Add configuration +- builder.AddPowertoolsLogger(clearExistingProviders); +- +- // Create initial configuration +- var options = new PowertoolsLoggerConfiguration(); +- configure(options); +- +- // IMPORTANT: Set the minimum level directly on the builder +- if (options.MinimumLogLevel != LogLevel.None) +- { +- builder.SetMinimumLevel(options.MinimumLogLevel); +- } +- +- builder.Services.Configure(configure); +- +- UpdateConfiguration(options); +- +- // If buffering is enabled, register buffer providers +- if (options.LogBuffering != null) +- { +- // Add a filter for the buffer provider +- builder.AddFilter( +- null, +- LogLevel.Trace); +- +- // Register the buffer provider as an enumerable service +- // Using singleton to ensure it's properly tracked +- builder.Services.TryAddEnumerable( +- ServiceDescriptor.Singleton(provider => +- { +- var powertoolsConfigurations = provider.GetRequiredService(); +- +- var bufferingProvider = new BufferingLoggerProvider( +- _currentConfig, powertoolsConfigurations +- ); +- +- lock (Lock) +- { +- AllProviders.Add(bufferingProvider); +- } +- +- return bufferingProvider; +- })); +- } +- +- +- return builder; +- } +- +- /// +- /// Resets all providers and clears the configuration. +- /// This is useful for testing purposes to ensure a clean state. +- /// +- internal static void ResetAllProviders() +- { +- lock (Lock) +- { +- // Clear the provider collection +- AllProviders.Clear(); +- +- // Reset the current configuration to default +- _currentConfig = new PowertoolsLoggerConfiguration(); +- } +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/CompositeJsonTypeInfoResolver.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/CompositeJsonTypeInfoResolver.cs +deleted file mode 100644 +index 028c1cb4..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/CompositeJsonTypeInfoResolver.cs ++++ /dev/null +@@ -1,44 +0,0 @@ +-#if NET8_0_OR_GREATER +- +-using System; +-using System.Text.Json; +-using System.Text.Json.Serialization.Metadata; +- +-namespace AWS.Lambda.Powertools.Logging.Serializers +-{ +- /// +- /// Combines multiple IJsonTypeInfoResolver instances into one +- /// +- internal class CompositeJsonTypeInfoResolver : IJsonTypeInfoResolver +- { +- private readonly IJsonTypeInfoResolver[] _resolvers; +- +- /// +- /// Creates a new composite resolver from multiple resolvers +- /// +- /// Array of resolvers to use +- public CompositeJsonTypeInfoResolver(IJsonTypeInfoResolver[] resolvers) +- { +- _resolvers = resolvers ?? throw new ArgumentNullException(nameof(resolvers)); +- } +- +- +- /// +- /// Gets JSON type info by trying each resolver in order (.NET Standard 2.0 version) +- /// +- public JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options) +- { +- foreach (var resolver in _resolvers) +- { +- var typeInfo = resolver?.GetTypeInfo(type, options); +- if (typeInfo != null) +- { +- return typeInfo; +- } +- } +- +- return null; +- } +- } +-} +-#endif +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/LoggingSerializationContext.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/LoggingSerializationContext.cs +index d4d918e0..459a3b0f 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/LoggingSerializationContext.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/LoggingSerializationContext.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.Collections.Generic; + using System.IO; +@@ -5,8 +20,6 @@ using System.Text.Json.Serialization; + + namespace AWS.Lambda.Powertools.Logging.Serializers; + +-#if NET8_0_OR_GREATER +- + /// + /// Custom JSON serializer context for AWS.Lambda.Powertools.Logging + /// +@@ -29,5 +42,3 @@ public partial class PowertoolsLoggingSerializationContext : JsonSerializerConte + { + } + +- +-#endif +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs +index 22afec8f..b13fb6c1 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; +@@ -10,64 +25,36 @@ using Amazon.Lambda.Serialization.SystemTextJson; + using AWS.Lambda.Powertools.Common; + using AWS.Lambda.Powertools.Common.Utils; + using AWS.Lambda.Powertools.Logging.Internal.Converters; ++using Microsoft.Extensions.Logging; + + namespace AWS.Lambda.Powertools.Logging.Serializers; + + /// + /// Provides serialization functionality for Powertools logging. + /// +-internal class PowertoolsLoggingSerializer ++internal static class PowertoolsLoggingSerializer + { +- private JsonSerializerOptions _currentOptions; +- private LoggerOutputCase _currentOutputCase; +- private JsonSerializerOptions _jsonOptions; +- private readonly object _lock = new(); ++ private static LoggerOutputCase _currentOutputCase; ++ private static JsonSerializerOptions _jsonOptions; + +-#if NET8_0_OR_GREATER +- private readonly ConcurrentBag _additionalContexts = new(); +- private static JsonSerializerContext _staticAdditionalContexts; +- private IJsonTypeInfoResolver _customTypeInfoResolver; +-#endif ++ private static readonly ConcurrentBag AdditionalContexts = ++ new ConcurrentBag(); + + /// + /// Gets the JsonSerializerOptions instance. + /// +- internal JsonSerializerOptions GetSerializerOptions() ++ internal static JsonSerializerOptions GetSerializerOptions() + { +- // Double-checked locking pattern for thread safety while ensuring we only build once +- if (_jsonOptions == null) +- { +- lock (_lock) +- { +- if (_jsonOptions == null) +- { +- BuildJsonSerializerOptions(_currentOptions); +- } +- } +- } +- +- return _jsonOptions; ++ return _jsonOptions ?? BuildJsonSerializerOptions(); + } + + /// + /// Configures the naming policy for the serializer. + /// + /// The case to use for serialization. +- internal void ConfigureNamingPolicy(LoggerOutputCase loggerOutputCase) ++ internal static void ConfigureNamingPolicy(LoggerOutputCase loggerOutputCase) + { +- if (_currentOutputCase != loggerOutputCase) +- { +- lock (_lock) +- { +- _currentOutputCase = loggerOutputCase; +- +- // Only rebuild options if they already exist +- if (_jsonOptions != null) +- { +- SetOutputCase(); +- } +- } +- } ++ _currentOutputCase = loggerOutputCase; + } + + /// +@@ -77,20 +64,15 @@ internal class PowertoolsLoggingSerializer + /// The type of the object to serialize. + /// A JSON string representation of the object. + /// Thrown when the input type is not known to the serializer. +- internal string Serialize(object value, Type inputType) ++ internal static string Serialize(object value, Type inputType) + { +-#if NET6_0 +- var options = GetSerializerOptions(); +- return JsonSerializer.Serialize(value, options); +-#else + if (RuntimeFeatureWrapper.IsDynamicCodeSupported) + { +- var jsonSerializerOptions = GetSerializerOptions(); ++ var options = GetSerializerOptions(); + #pragma warning disable +- return JsonSerializer.Serialize(value, jsonSerializerOptions); ++ return JsonSerializer.Serialize(value, options); + } + +- // Try to serialize using the configured TypeInfoResolver + var typeInfo = GetTypeInfo(inputType); + if (typeInfo == null) + { +@@ -99,149 +81,42 @@ internal class PowertoolsLoggingSerializer + } + + return JsonSerializer.Serialize(value, typeInfo); +- +-#endif + } + +-#if NET8_0_OR_GREATER +- + /// + /// Adds a JsonSerializerContext to the serializer options. + /// + /// The JsonSerializerContext to add. + /// Thrown when the context is null. +- internal void AddSerializerContext(JsonSerializerContext context) ++ internal static void AddSerializerContext(JsonSerializerContext context) + { + ArgumentNullException.ThrowIfNull(context); + +- // Don't add duplicates +- if (!_additionalContexts.Contains(context)) ++ if (!AdditionalContexts.Contains(context)) + { +- _additionalContexts.Add(context); +- +- // If we have existing JSON options, update their type resolver +- if (_jsonOptions != null && !RuntimeFeatureWrapper.IsDynamicCodeSupported) +- { +- // Reset the type resolver chain to rebuild it +- _jsonOptions.TypeInfoResolver = GetCompositeResolver(); +- } ++ AdditionalContexts.Add(context); + } + } + +- internal static void AddStaticSerializerContext(JsonSerializerContext context) +- { +- ArgumentNullException.ThrowIfNull(context); +- +- _staticAdditionalContexts = context; +- } +- +- /// +- /// Get a composite resolver that includes all configured resolvers +- /// +- private IJsonTypeInfoResolver GetCompositeResolver() +- { +- var resolvers = new List(); +- +- // Add custom resolver if provided +- if (_customTypeInfoResolver != null) +- { +- resolvers.Add(_customTypeInfoResolver); +- } +- +- // add any static resolvers +- if (_staticAdditionalContexts != null) +- { +- resolvers.Add(_staticAdditionalContexts); +- } +- +- // Add default context +- resolvers.Add(PowertoolsLoggingSerializationContext.Default); +- +- // Add additional contexts +- foreach (var context in _additionalContexts) +- { +- resolvers.Add(context); +- } +- +- return new CompositeJsonTypeInfoResolver(resolvers.ToArray()); +- } +- + /// + /// Gets the JsonTypeInfo for a given type. + /// + /// The type to get information for. + /// The JsonTypeInfo for the specified type, or null if not found. +- private JsonTypeInfo GetTypeInfo(Type type) ++ internal static JsonTypeInfo GetTypeInfo(Type type) + { + var options = GetSerializerOptions(); + return options.TypeInfoResolver?.GetTypeInfo(type, options); + } + +-#endif +- + /// + /// Builds and configures the JsonSerializerOptions. + /// + /// A configured JsonSerializerOptions instance. +- private void BuildJsonSerializerOptions(JsonSerializerOptions options = null) ++ private static JsonSerializerOptions BuildJsonSerializerOptions() + { +- lock (_lock) +- { +- // Create a completely new options instance regardless +- _jsonOptions = new JsonSerializerOptions(); +- +- // Copy any properties from the original options if provided +- if (options != null) +- { +- // Copy standard properties +- _jsonOptions.DefaultIgnoreCondition = options.DefaultIgnoreCondition; +- _jsonOptions.PropertyNameCaseInsensitive = options.PropertyNameCaseInsensitive; +- _jsonOptions.PropertyNamingPolicy = options.PropertyNamingPolicy; +- _jsonOptions.DictionaryKeyPolicy = options.DictionaryKeyPolicy; +- _jsonOptions.WriteIndented = options.WriteIndented; +- _jsonOptions.ReferenceHandler = options.ReferenceHandler; +- _jsonOptions.MaxDepth = options.MaxDepth; +- _jsonOptions.IgnoreReadOnlyFields = options.IgnoreReadOnlyFields; +- _jsonOptions.IgnoreReadOnlyProperties = options.IgnoreReadOnlyProperties; +- _jsonOptions.IncludeFields = options.IncludeFields; +- _jsonOptions.NumberHandling = options.NumberHandling; +- _jsonOptions.ReadCommentHandling = options.ReadCommentHandling; +- _jsonOptions.UnknownTypeHandling = options.UnknownTypeHandling; +- _jsonOptions.AllowTrailingCommas = options.AllowTrailingCommas; +- +-#if NET8_0_OR_GREATER +- // Handle type resolver extraction without setting it yet +- if (options.TypeInfoResolver != null) +- { +- _customTypeInfoResolver = options.TypeInfoResolver; +- +- // If it's a JsonSerializerContext, also add it to our contexts +- if (_customTypeInfoResolver is JsonSerializerContext jsonContext) +- { +- AddSerializerContext(jsonContext); +- } +- } +-#endif +- } +- +- // Set output case and other properties +- SetOutputCase(); +- AddConverters(); +- _jsonOptions.Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping; +- _jsonOptions.PropertyNameCaseInsensitive = true; ++ _jsonOptions = new JsonSerializerOptions(); + +-#if NET8_0_OR_GREATER +- // Set TypeInfoResolver last, as this makes options read-only +- if (!RuntimeFeatureWrapper.IsDynamicCodeSupported) +- { +- _jsonOptions.TypeInfoResolver = GetCompositeResolver(); +- } +-#endif +- } +- } +- +- internal void SetOutputCase() +- { + switch (_currentOutputCase) + { + case LoggerOutputCase.CamelCase: +@@ -253,20 +128,11 @@ internal class PowertoolsLoggingSerializer + _jsonOptions.DictionaryKeyPolicy = PascalCaseNamingPolicy.Instance; + break; + default: // Snake case +-#if NET8_0_OR_GREATER +- // If is default (Not Set) and JsonOptions provided with DictionaryKeyPolicy or PropertyNamingPolicy, use it +- _jsonOptions.DictionaryKeyPolicy ??= JsonNamingPolicy.SnakeCaseLower; +- _jsonOptions.PropertyNamingPolicy ??= JsonNamingPolicy.SnakeCaseLower; +-#else +- _jsonOptions.PropertyNamingPolicy = SnakeCaseNamingPolicy.Instance; +- _jsonOptions.DictionaryKeyPolicy = SnakeCaseNamingPolicy.Instance; +-#endif ++ _jsonOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower; ++ _jsonOptions.DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower; + break; + } +- } + +- private void AddConverters() +- { + _jsonOptions.Converters.Add(new ByteArrayConverter()); + _jsonOptions.Converters.Add(new ExceptionConverter()); + _jsonOptions.Converters.Add(new MemoryStreamConverter()); +@@ -274,15 +140,38 @@ internal class PowertoolsLoggingSerializer + _jsonOptions.Converters.Add(new DateOnlyConverter()); + _jsonOptions.Converters.Add(new TimeOnlyConverter()); + +-#if NET8_0_OR_GREATER +- _jsonOptions.Converters.Add(new LogLevelJsonConverter()); +-#elif NET6_0 + _jsonOptions.Converters.Add(new LogLevelJsonConverter()); +-#endif ++ ++ _jsonOptions.Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping; ++ _jsonOptions.PropertyNameCaseInsensitive = true; ++ ++ // Only add TypeInfoResolver if AOT mode ++ if (!RuntimeFeatureWrapper.IsDynamicCodeSupported) ++ { ++ _jsonOptions.TypeInfoResolverChain.Add(PowertoolsLoggingSerializationContext.Default); ++ foreach (var context in AdditionalContexts) ++ { ++ _jsonOptions.TypeInfoResolverChain.Add(context); ++ } ++ } ++ return _jsonOptions; + } + +- internal void SetOptions(JsonSerializerOptions options) ++ internal static bool HasContext(JsonSerializerContext customContext) ++ { ++ return AdditionalContexts.Contains(customContext); ++ } ++ ++ internal static void ClearContext() ++ { ++ AdditionalContexts.Clear(); ++ } ++ ++ /// ++ /// Clears options for tests ++ /// ++ internal static void ClearOptions() + { +- _currentOptions = options; ++ _jsonOptions = null; + } + } +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsSourceGeneratorSerializer.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsSourceGeneratorSerializer.cs +index 95bd749e..8871fb95 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsSourceGeneratorSerializer.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsSourceGeneratorSerializer.cs +@@ -1,4 +1,17 @@ +-#if NET8_0_OR_GREATER ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ + + using System; + using System.Diagnostics.CodeAnalysis; +@@ -59,8 +72,6 @@ public sealed class PowertoolsSourceGeneratorSerializer< + } + + var jsonSerializerContext = constructor.Invoke(new object[] { options }) as TSgContext; +- PowertoolsLoggingSerializer.AddStaticSerializerContext(jsonSerializerContext); ++ PowertoolsLoggingSerializer.AddSerializerContext(jsonSerializerContext); + } + } +- +-#endif +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/AWS.Lambda.Powertools.Metrics.AspNetCore.csproj b/libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/AWS.Lambda.Powertools.Metrics.AspNetCore.csproj +deleted file mode 100644 +index 976fe8b0..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/AWS.Lambda.Powertools.Metrics.AspNetCore.csproj ++++ /dev/null +@@ -1,25 +0,0 @@ +- +- +- +- +- AWS.Lambda.Powertools.Metrics.AspNetCore +- Powertools for AWS Lambda (.NET) - Metrics AspNetCore package. +- AWS.Lambda.Powertools.Metrics.AspNetCore +- AWS.Lambda.Powertools.Metrics.AspNetCore +- net8.0 +- false +- enable +- enable +- +- +- +- +- +- +- +- +- +- +- +- +- +diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/Http/ColdStartTracker.cs b/libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/Http/ColdStartTracker.cs +deleted file mode 100644 +index 7d6473d7..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/Http/ColdStartTracker.cs ++++ /dev/null +@@ -1,61 +0,0 @@ +-using Amazon.Lambda.Core; +-using Microsoft.AspNetCore.Http; +- +-namespace AWS.Lambda.Powertools.Metrics.AspNetCore.Http; +- +- +-/// +-/// Tracks and manages cold start metrics for Lambda functions in ASP.NET Core applications. +-/// +-/// +-/// This class is responsible for detecting and recording the first invocation (cold start) of a Lambda function. +-/// It ensures thread-safe tracking of cold starts and proper metric capture using the provided IMetrics implementation. +-/// +-internal class ColdStartTracker : IDisposable +-{ +- private readonly IMetrics _metrics; +- private static bool _coldStart = true; +- private static readonly object _lock = new(); +- +- /// +- /// Initializes a new instance of the class. +- /// +- /// The metrics implementation to use for capturing cold start metrics. +- public ColdStartTracker(IMetrics metrics) +- { +- _metrics = metrics; +- } +- +- /// +- /// Tracks the cold start of the Lambda function. +- /// +- /// The current HTTP context. +- internal void TrackColdStart(HttpContext context) +- { +- if (!_coldStart) return; +- +- lock (_lock) +- { +- if (!_coldStart) return; +- _metrics.CaptureColdStartMetric(context.Items["LambdaContext"] as ILambdaContext); +- _coldStart = false; +- } +- } +- +- /// +- /// Resets the cold start tracking state. +- /// +- internal static void ResetColdStart() +- { +- lock (_lock) +- { +- _coldStart = true; +- } +- } +- +- /// +- public void Dispose() +- { +- ResetColdStart(); +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/Http/MetricsEndpointExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/Http/MetricsEndpointExtensions.cs +deleted file mode 100644 +index 2ca74c05..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/Http/MetricsEndpointExtensions.cs ++++ /dev/null +@@ -1,22 +0,0 @@ +-using Microsoft.AspNetCore.Builder; +-using Microsoft.AspNetCore.Http; +- +-namespace AWS.Lambda.Powertools.Metrics.AspNetCore.Http; +- +-/// +-/// Provides extension methods for adding metrics to route handlers. +-/// +-public static class MetricsEndpointExtensions +-{ +- /// +- /// Adds a metrics filter to the specified route handler builder. +- /// This will capture cold start (if CaptureColdStart is enabled) metrics and flush metrics on function exit. +- /// +- /// The route handler builder to add the metrics filter to. +- /// The route handler builder with the metrics filter added. +- public static RouteHandlerBuilder WithMetrics(this RouteHandlerBuilder builder) +- { +- builder.AddEndpointFilter(); +- return builder; +- } +-} +diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/Http/MetricsFilter.cs b/libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/Http/MetricsFilter.cs +deleted file mode 100644 +index 8c84836b..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/Http/MetricsFilter.cs ++++ /dev/null +@@ -1,53 +0,0 @@ +-using Microsoft.AspNetCore.Http; +- +-namespace AWS.Lambda.Powertools.Metrics.AspNetCore.Http; +- +-/// +-/// Represents a filter that captures and records metrics for HTTP endpoints. +-/// +-/// +-/// This filter is responsible for tracking cold starts and capturing metrics during HTTP request processing. +-/// It integrates with the ASP.NET Core endpoint routing system to inject metrics collection at the endpoint level. +-/// +-/// +-/// +-public class MetricsFilter : IEndpointFilter, IDisposable +-{ +- private readonly ColdStartTracker _coldStartTracker; +- +- /// +- /// Initializes a new instance of the class. +- /// +- public MetricsFilter(IMetrics metrics) +- { +- _coldStartTracker = new ColdStartTracker(metrics); +- } +- +- /// +- /// Invokes the filter asynchronously. +- /// +- /// The context for the endpoint filter invocation. +- /// The delegate to invoke the next filter or endpoint. +- /// A task that represents the asynchronous operation, containing the result of the endpoint invocation. +- public async ValueTask InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next) +- { +- try +- { +- _coldStartTracker.TrackColdStart(context.HttpContext); +- } +- catch +- { +- // ignored +- } +- +- return await next(context); +- } +- +- /// +- /// Disposes of the resources used by the filter. +- /// +- public void Dispose() +- { +- _coldStartTracker.Dispose(); +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/Http/MetricsMiddlewareExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/Http/MetricsMiddlewareExtensions.cs +deleted file mode 100644 +index 0d74b2fa..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/Http/MetricsMiddlewareExtensions.cs ++++ /dev/null +@@ -1,35 +0,0 @@ +-using Microsoft.AspNetCore.Builder; +-using Microsoft.Extensions.DependencyInjection; +- +-namespace AWS.Lambda.Powertools.Metrics.AspNetCore.Http; +- +-/// +-/// Provides extension methods for adding metrics middleware to the application pipeline. +-/// +-public static class MetricsMiddlewareExtensions +-{ +- /// +- /// Adds middleware to capture and record metrics for HTTP requests, including cold start tracking. +- /// +- /// The application builder instance used to configure the request pipeline. +- /// The application builder with the metrics middleware added. +- /// +- /// This middleware tracks cold starts and captures request metrics. To use this middleware, ensure you have registered +- /// the required services using builder.Services.AddSingleton<IMetrics>() in your service configuration. +- /// +- /// +- /// +- /// app.UseMetrics(); +- /// +- /// +- public static IApplicationBuilder UseMetrics(this IApplicationBuilder app) +- { +- return app.Use(async (context, next) => +- { +- var metrics = context.RequestServices.GetRequiredService(); +- using var metricsHelper = new ColdStartTracker(metrics); +- metricsHelper.TrackColdStart(context); +- await next(); +- }); +- } +-} +diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/InternalsVisibleTo.cs b/libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/InternalsVisibleTo.cs +deleted file mode 100644 +index 7e99bec2..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/InternalsVisibleTo.cs ++++ /dev/null +@@ -1,3 +0,0 @@ +-using System.Runtime.CompilerServices; +- +-[assembly: InternalsVisibleTo("AWS.Lambda.Powertools.Metrics.AspNetCore.Tests")] +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/IMetrics.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/IMetrics.cs +index f9b1d261..6f0b868a 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Metrics/IMetrics.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Metrics/IMetrics.cs +@@ -1,113 +1,104 @@ +-using System.Collections.Generic; +-using Amazon.Lambda.Core; ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ ++using System; ++using System.Collections.Generic; + + namespace AWS.Lambda.Powertools.Metrics; + + /// +-/// Interface for metrics operations. ++/// Interface IMetrics ++/// Implements the + /// + /// +-public interface IMetrics ++public interface IMetrics + { + /// +- /// Adds a metric to the collection. ++ /// Adds metric + /// +- /// The metric key. +- /// The metric value. +- /// The metric unit. +- /// The metric resolution. +- void AddMetric(string key, double value, MetricUnit unit = MetricUnit.None, +- MetricResolution resolution = MetricResolution.Default); ++ /// Metric key ++ /// Metric value ++ /// Metric unit ++ /// ++ void AddMetric(string key, double value, MetricUnit unit, MetricResolution metricResolution); + + /// +- /// Adds a dimension to the collection. ++ /// Adds a dimension + /// +- /// The dimension key. +- /// The dimension value. ++ /// Dimension key ++ /// Dimension value + void AddDimension(string key, string value); + + /// +- /// Adds metadata to the collection. +- /// +- /// The metadata key. +- /// The metadata value. +- void AddMetadata(string key, object value); +- +- /// +- /// Sets the default dimensions. ++ /// Sets the default dimensions + /// +- /// The default dimensions. +- void SetDefaultDimensions(Dictionary defaultDimensions); ++ /// Default dimensions ++ void SetDefaultDimensions(Dictionary defaultDimension); + + /// +- /// Sets the namespace for the metrics. ++ /// Adds metadata + /// +- /// The namespace. +- void SetNamespace(string nameSpace); ++ /// Metadata key ++ /// Metadata value ++ void AddMetadata(string key, object value); + + /// +- /// Sets the service name for the metrics. ++ /// Pushes a single metric with custom namespace, service and dimensions. + /// +- /// The service name. +- void SetService(string service); ++ /// Name of the metric ++ /// Metric value ++ /// Metric unit ++ /// Metric namespace ++ /// Metric service ++ /// Metric default dimensions ++ /// Metrics resolution ++ void PushSingleMetric(string metricName, double value, MetricUnit unit, string nameSpace = null, ++ string service = null, Dictionary defaultDimensions = null, MetricResolution metricResolution = MetricResolution.Default); + + /// +- /// Sets whether to raise an event on empty metrics. ++ /// Sets the namespace + /// +- /// If set to true, raises an event on empty metrics. +- void SetRaiseOnEmptyMetrics(bool raiseOnEmptyMetrics); ++ /// Metrics namespace ++ void SetNamespace(string nameSpace); + + /// +- /// Sets whether to capture cold start metrics. ++ /// Gets the namespace + /// +- /// If set to true, captures cold start metrics. +- void SetCaptureColdStart(bool captureColdStart); ++ /// System.String. ++ string GetNamespace(); + + /// +- /// Pushes a single metric to the collection. ++ /// Gets the service + /// +- /// The metric name. +- /// The metric value. +- /// The metric unit. +- /// The namespace. +- /// The service name. +- /// The default dimensions. +- /// The metric resolution. +- void PushSingleMetric(string name, double value, MetricUnit unit, string nameSpace = null, string service = null, +- Dictionary dimensions = null, MetricResolution resolution = MetricResolution.Default); ++ /// System.String. ++ string GetService(); + + /// +- /// Clears the default dimensions. ++ /// Serializes metrics instance + /// +- void ClearDefaultDimensions(); ++ /// System.String. ++ string Serialize(); + + /// +- /// Flushes the metrics. ++ /// Flushes metrics to CloudWatch + /// +- /// If set to true, indicates a metrics overflow. ++ /// if set to true [metrics overflow]. + void Flush(bool metricsOverflow = false); +- +- /// +- /// Gets the metrics options. +- /// +- /// The metrics options. +- public MetricsOptions Options { get; } +- +- /// +- /// Sets the function name. +- /// +- /// +- void SetFunctionName(string functionName); + + /// +- /// Captures the cold start metric. ++ /// Clears both default dimensions and dimensions lists + /// +- /// +- void CaptureColdStartMetric(ILambdaContext context); +- +- /// +- /// Adds multiple dimensions at once. +- /// +- /// Array of key-value tuples representing dimensions. +- void AddDimensions(params (string key, string value)[] dimensions); +-} +\ No newline at end of file ++ void ClearDefaultDimensions(); ++} +diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/Internal/MetricsAspect.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/Internal/MetricsAspect.cs +index ed81742c..a72e205e 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Metrics/Internal/MetricsAspect.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Metrics/Internal/MetricsAspect.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.Collections.Generic; + using System.Linq; +@@ -5,7 +20,6 @@ using System.Reflection; + using Amazon.Lambda.Core; + using AspectInjector.Broker; + using AWS.Lambda.Powertools.Common; +-using AWS.Lambda.Powertools.Common.Core; + + namespace AWS.Lambda.Powertools.Metrics; + +@@ -16,12 +30,22 @@ namespace AWS.Lambda.Powertools.Metrics; + [Aspect(Scope.Global)] + public class MetricsAspect + { ++ /// ++ /// The is cold start ++ /// ++ private static bool _isColdStart; ++ + /// + /// Gets the metrics instance. + /// + /// The metrics instance. + private static IMetrics _metricsInstance; + ++ static MetricsAspect() ++ { ++ _isColdStart = true; ++ } ++ + /// + /// Runs before the execution of the method marked with the Metrics Attribute + /// +@@ -46,13 +70,13 @@ public class MetricsAspect + + var trigger = triggers.OfType().First(); + +- _metricsInstance ??= Metrics.Configure(options => { +- options.Namespace = trigger.Namespace; +- options.Service = trigger.Service; +- options.RaiseOnEmptyMetrics = trigger.IsRaiseOnEmptyMetricsSet ? trigger.RaiseOnEmptyMetrics : null; +- options.CaptureColdStart = trigger.IsCaptureColdStartSet ? trigger.CaptureColdStart : null; +- options.FunctionName = trigger.FunctionName; +- }); ++ _metricsInstance ??= new Metrics( ++ PowertoolsConfigurations.Instance, ++ trigger.Namespace, ++ trigger.Service, ++ trigger.RaiseOnEmptyMetrics, ++ trigger.CaptureColdStart ++ ); + + var eventArgs = new AspectEventArgs + { +@@ -65,9 +89,32 @@ public class MetricsAspect + Triggers = triggers + }; + +- if (LambdaLifecycleTracker.IsColdStart) ++ if (trigger.CaptureColdStart && _isColdStart) + { +- _metricsInstance.CaptureColdStartMetric(GetContext(eventArgs)); ++ _isColdStart = false; ++ ++ var nameSpace = _metricsInstance.GetNamespace(); ++ var service = _metricsInstance.GetService(); ++ Dictionary dimensions = null; ++ ++ var context = GetContext(eventArgs); ++ ++ if (context is not null) ++ { ++ dimensions = new Dictionary ++ { ++ { "FunctionName", context.FunctionName } ++ }; ++ } ++ ++ _metricsInstance.PushSingleMetric( ++ "ColdStart", ++ 1.0, ++ MetricUnit.Count, ++ nameSpace, ++ service, ++ dimensions ++ ); + } + } + +@@ -87,10 +134,10 @@ public class MetricsAspect + internal static void ResetForTest() + { + _metricsInstance = null; +- LambdaLifecycleTracker.Reset(); ++ _isColdStart = true; + Metrics.ResetForTest(); + } +- ++ + /// + /// Gets the Lambda context + /// +@@ -98,7 +145,6 @@ public class MetricsAspect + /// + private static ILambdaContext GetContext(AspectEventArgs args) + { +- if (args == null || args.Method == null) return null; + var index = Array.FindIndex(args.Method.GetParameters(), p => p.ParameterType == typeof(ILambdaContext)); + if (index >= 0) + { +diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/InternalsVisibleTo.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/InternalsVisibleTo.cs +index a1b53257..0e44da1c 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Metrics/InternalsVisibleTo.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Metrics/InternalsVisibleTo.cs +@@ -15,6 +15,4 @@ + + using System.Runtime.CompilerServices; + +-[assembly: InternalsVisibleTo("AWS.Lambda.Powertools.Metrics.Tests")] +-[assembly: InternalsVisibleTo("AWS.Lambda.Powertools.Metrics.AspNetCore")] +-[assembly: InternalsVisibleTo("AWS.Lambda.Powertools.Metrics.AspNetCore.Tests")] +\ No newline at end of file ++[assembly: InternalsVisibleTo("AWS.Lambda.Powertools.Metrics.Tests")] +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs +index 9cf95ec5..51b86e37 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs +@@ -1,7 +1,21 @@ +-using System; ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ ++using System; + using System.Collections.Generic; + using System.Linq; +-using Amazon.Lambda.Core; + using AWS.Lambda.Powertools.Common; + + namespace AWS.Lambda.Powertools.Metrics; +@@ -13,47 +27,11 @@ namespace AWS.Lambda.Powertools.Metrics; + /// + public class Metrics : IMetrics, IDisposable + { +- /// +- /// Gets or sets the instance. +- /// +- public static IMetrics Instance +- { +- get => _instance ?? new Metrics(PowertoolsConfigurations.Instance, consoleWrapper: new ConsoleWrapper()); +- private set => _instance = value; +- } +- +- /// +- /// Gets DefaultDimensions +- /// +- public static Dictionary DefaultDimensions => Instance.Options.DefaultDimensions; +- +- /// +- /// Gets Namespace +- /// +- public static string Namespace => Instance.Options.Namespace; +- +- /// +- /// Gets Service +- /// +- public static string Service => Instance.Options.Service; +- +- /// +- public MetricsOptions Options => _options ?? +- new() +- { +- CaptureColdStart = _captureColdStartEnabled, +- Namespace = GetNamespace(), +- Service = GetService(), +- RaiseOnEmptyMetrics = _raiseOnEmptyMetrics, +- DefaultDimensions = GetDefaultDimensions(), +- FunctionName = _functionName +- }; +- + /// + /// The instance + /// + private static IMetrics _instance; +- ++ + /// + /// The context + /// +@@ -67,76 +45,17 @@ public class Metrics : IMetrics, IDisposable + /// + /// If true, Powertools for AWS Lambda (.NET) will throw an exception on empty metrics when trying to flush + /// +- private bool _raiseOnEmptyMetrics; +- ++ private readonly bool _raiseOnEmptyMetrics; ++ + /// + /// The capture cold start enabled + /// +- private bool _captureColdStartEnabled; ++ private readonly bool _captureColdStartEnabled; + +- /// +- /// Shared synchronization object +- /// ++ // ++ // Shared synchronization object ++ // + private readonly object _lockObj = new(); +- +- /// +- /// Function name is used for metric dimension across all metrics. +- /// +- private string _functionName; +- +- /// +- /// The options +- /// +- private readonly MetricsOptions _options; +- +- /// +- /// The console wrapper for console output +- /// +- private readonly IConsoleWrapper _consoleWrapper; +- +- /// +- /// Gets a value indicating whether metrics are disabled. +- /// +- private bool _disabled; +- +- /// +- /// Initializes a new instance of the class. +- /// +- /// +- /// +- public static IMetrics Configure(Action configure) +- { +- var options = new MetricsOptions(); +- configure(options); +- +- if (!string.IsNullOrEmpty(options.Namespace)) +- SetNamespace(options.Namespace); +- +- if (!string.IsNullOrEmpty(options.Service)) +- Instance.SetService(options.Service); +- +- if (options.RaiseOnEmptyMetrics.HasValue) +- Instance.SetRaiseOnEmptyMetrics(options.RaiseOnEmptyMetrics.Value); +- if (options.CaptureColdStart.HasValue) +- Instance.SetCaptureColdStart(options.CaptureColdStart.Value); +- +- if (options.DefaultDimensions != null) +- SetDefaultDimensions(options.DefaultDimensions); +- +- if (!string.IsNullOrEmpty(options.FunctionName)) +- Instance.SetFunctionName(options.FunctionName); +- +- return Instance; +- } +- +- /// +- /// Sets the function name. +- /// +- /// +- void IMetrics.SetFunctionName(string functionName) +- { +- _functionName = functionName; +- } + + /// + /// Creates a Metrics object that provides features to send metrics to Amazon Cloudwatch using the Embedded metric +@@ -148,99 +67,94 @@ public class Metrics : IMetrics, IDisposable + /// Metrics Service Name + /// Instructs metrics validation to throw exception if no metrics are provided + /// Instructs metrics capturing the ColdStart is enabled +- /// For console output +- /// MetricsOptions + internal Metrics(IPowertoolsConfigurations powertoolsConfigurations, string nameSpace = null, string service = null, +- bool raiseOnEmptyMetrics = false, bool captureColdStartEnabled = false, IConsoleWrapper consoleWrapper = null, MetricsOptions options = null) ++ bool raiseOnEmptyMetrics = false, bool captureColdStartEnabled = false) + { ++ _instance ??= this; ++ + _powertoolsConfigurations = powertoolsConfigurations; +- _consoleWrapper = consoleWrapper; +- _context = new MetricsContext(); + _raiseOnEmptyMetrics = raiseOnEmptyMetrics; + _captureColdStartEnabled = captureColdStartEnabled; +- _options = options; +- +- _disabled = _powertoolsConfigurations.MetricsDisabled; ++ _context = InitializeContext(nameSpace, service, null); + +- Instance = this; + _powertoolsConfigurations.SetExecutionEnvironment(this); +- +- // set namespace and service always +- SetNamespace(nameSpace); +- SetService(service); ++ + } + +- /// +- void IMetrics.AddMetric(string key, double value, MetricUnit unit, MetricResolution resolution) ++ /// ++ /// Implements interface that adds new metric to memory. ++ /// ++ /// Metric Key ++ /// Metric Value ++ /// Metric Unit ++ /// Metric resolution ++ /// ++ /// 'AddMetric' method requires a valid metrics key. 'Null' or empty values ++ /// are not allowed. ++ /// ++ void IMetrics.AddMetric(string key, double value, MetricUnit unit, MetricResolution metricResolution) + { +- if(_disabled) +- return; ++ if (string.IsNullOrWhiteSpace(key)) ++ throw new ArgumentNullException( ++ nameof(key), "'AddMetric' method requires a valid metrics key. 'Null' or empty values are not allowed."); + +- if (Instance != null) ++ if (value < 0) { ++ throw new ArgumentException( ++ "'AddMetric' method requires a valid metrics value. Value must be >= 0.", nameof(value)); ++ } ++ ++ lock (_lockObj) + { +- if (string.IsNullOrWhiteSpace(key)) +- throw new ArgumentNullException( +- nameof(key), +- "'AddMetric' method requires a valid metrics key. 'Null' or empty values are not allowed."); +- if (key.Length > 255) +- throw new ArgumentOutOfRangeException( +- nameof(key), +- "'AddMetric' method requires a valid metrics key. Key exceeds the allowed length constraint."); +- +- if (value < 0) ++ var metrics = _context.GetMetrics(); ++ ++ if (metrics.Count > 0 && ++ (metrics.Count == PowertoolsConfigurations.MaxMetrics || ++ metrics.FirstOrDefault(x => x.Name == key) ++ ?.Values.Count == PowertoolsConfigurations.MaxMetrics)) + { +- throw new ArgumentException( +- "'AddMetric' method requires a valid metrics value. Value must be >= 0.", nameof(value)); ++ _instance.Flush(true); + } + +- lock (_lockObj) +- { +- var metrics = _context.GetMetrics(); +- +- if (metrics.Count > 0 && +- (metrics.Count == PowertoolsConfigurations.MaxMetrics || +- metrics.FirstOrDefault(x => x.Name == key) +- ?.Values.Count == PowertoolsConfigurations.MaxMetrics)) +- { +- Instance.Flush(true); +- } +- +- _context.AddMetric(key, value, unit, resolution); +- } +- } +- else +- { +- _consoleWrapper.Debug( +- $"##WARNING##: Metrics should be initialized in Handler method before calling {nameof(AddMetric)} method."); ++ _context.AddMetric(key, value, unit, metricResolution); + } + } + +- /// ++ /// ++ /// Implements interface that sets metrics namespace identifier. ++ /// ++ /// Metrics Namespace Identifier + void IMetrics.SetNamespace(string nameSpace) + { +- _context.SetNamespace(!string.IsNullOrWhiteSpace(nameSpace) +- ? nameSpace +- : GetNamespace() ?? _powertoolsConfigurations.MetricsNamespace); ++ _context.SetNamespace(nameSpace); + } + ++ /// ++ /// Implements interface that allows retrieval of namespace identifier. ++ /// ++ /// Namespace identifier ++ string IMetrics.GetNamespace() ++ { ++ return _context.GetNamespace(); ++ } + + /// + /// Implements interface to get service name + /// + /// System.String. +- private string GetService() ++ string IMetrics.GetService() + { +- try +- { +- return _context.GetService(); +- } +- catch +- { +- return null; +- } ++ return _context.GetService(); + } + +- /// ++ /// ++ /// Implements interface that adds a dimension. ++ /// ++ /// Dimension key. Must not be null, empty or whitespace ++ /// Dimension value ++ /// ++ /// 'AddDimension' method requires a valid dimension key. 'Null' or empty ++ /// values are not allowed. ++ /// + void IMetrics.AddDimension(string key, string value) + { + if (string.IsNullOrWhiteSpace(key)) +@@ -250,7 +164,15 @@ public class Metrics : IMetrics, IDisposable + _context.AddDimension(key, value); + } + +- /// ++ /// ++ /// Implements interface that adds metadata. ++ /// ++ /// Metadata key. Must not be null, empty or whitespace ++ /// Metadata value ++ /// ++ /// 'AddMetadata' method requires a valid metadata key. 'Null' or empty ++ /// values are not allowed. ++ /// + void IMetrics.AddMetadata(string key, object value) + { + if (string.IsNullOrWhiteSpace(key)) +@@ -260,23 +182,32 @@ public class Metrics : IMetrics, IDisposable + _context.AddMetadata(key, value); + } + +- /// +- void IMetrics.SetDefaultDimensions(Dictionary defaultDimensions) ++ /// ++ /// Implements interface that sets default dimension list ++ /// ++ /// Default Dimension List ++ /// ++ /// 'SetDefaultDimensions' method requires a valid key pair. 'Null' or empty ++ /// values are not allowed. ++ /// ++ void IMetrics.SetDefaultDimensions(Dictionary defaultDimension) + { +- foreach (var item in defaultDimensions) ++ foreach (var item in defaultDimension) + if (string.IsNullOrWhiteSpace(item.Key) || string.IsNullOrWhiteSpace(item.Value)) + throw new ArgumentNullException(nameof(item.Key), + "'SetDefaultDimensions' method requires a valid key pair. 'Null' or empty values are not allowed."); + +- _context.SetDefaultDimensions(DictionaryToList(defaultDimensions)); ++ _context.SetDefaultDimensions(DictionaryToList(defaultDimension)); + } + +- /// ++ /// ++ /// Flushes metrics in Embedded Metric Format (EMF) to Standard Output. In Lambda, this output is collected ++ /// automatically and sent to Cloudwatch. ++ /// ++ /// If enabled, non-default dimensions are cleared after flushing metrics ++ /// true + void IMetrics.Flush(bool metricsOverflow) + { +- if(_disabled) +- return; +- + if (_context.GetMetrics().Count == 0 + && _raiseOnEmptyMetrics) + throw new SchemaValidationException(true); +@@ -285,7 +216,7 @@ public class Metrics : IMetrics, IDisposable + { + var emfPayload = _context.Serialize(); + +- _consoleWrapper.WriteLine(emfPayload); ++ Console.WriteLine(emfPayload); + + _context.ClearMetrics(); + +@@ -294,91 +225,56 @@ public class Metrics : IMetrics, IDisposable + else + { + if (!_captureColdStartEnabled) +- _consoleWrapper.WriteLine( +- "##User-WARNING## No application metrics to publish. The cold-start metric may be published if enabled. If application metrics should never be empty, consider using 'RaiseOnEmptyMetrics = true'"); ++ Console.WriteLine( ++ "##WARNING## Metrics and Metadata have not been specified. No data will be sent to Cloudwatch Metrics."); + } + } +- +- /// ++ ++ /// ++ /// Clears both default dimensions and dimensions lists ++ /// + void IMetrics.ClearDefaultDimensions() + { + _context.ClearDefaultDimensions(); + } + +- /// +- void IMetrics.SetService(string service) +- { +- // this needs to check if service is set through code or env variables +- // the default value service_undefined has to be ignored and return null so it is not added as default +- var parsedService = !string.IsNullOrWhiteSpace(service) +- ? service +- : _powertoolsConfigurations.Service == "service_undefined" +- ? null +- : _powertoolsConfigurations.Service; +- +- if (parsedService != null) +- { +- _context.SetService(parsedService); +- _context.SetDefaultDimensions(new List(new[] +- { new DimensionSet("Service", GetService()) })); +- } +- } +- +- /// +- public void SetRaiseOnEmptyMetrics(bool raiseOnEmptyMetrics) +- { +- _raiseOnEmptyMetrics = raiseOnEmptyMetrics; +- } +- +- /// +- public void SetCaptureColdStart(bool captureColdStart) +- { +- _captureColdStartEnabled = captureColdStart; +- } +- +- private Dictionary GetDefaultDimensions() ++ /// ++ /// Serialize global context object ++ /// ++ /// Serialized global context object ++ public string Serialize() + { +- return ListToDictionary(_context.GetDefaultDimensions()); ++ return _context.Serialize(); + } + +- /// +- void IMetrics.PushSingleMetric(string name, double value, MetricUnit unit, string nameSpace, +- string service, Dictionary dimensions, MetricResolution resolution) +- { +- if(_disabled) +- return; +- +- if (string.IsNullOrWhiteSpace(name)) +- throw new ArgumentNullException(nameof(name), ++ /// ++ /// Implements the interface that pushes single metric to CloudWatch using Embedded Metric Format. This can be used to ++ /// push metrics with a different context. ++ /// ++ /// Metric Name. Metric key cannot be null, empty or whitespace ++ /// Metric Value ++ /// Metric Unit ++ /// Metric Namespace ++ /// Service Name ++ /// Default dimensions list ++ /// Metrics resolution ++ /// ++ /// 'PushSingleMetric' method requires a valid metrics key. 'Null' or empty ++ /// values are not allowed. ++ /// ++ void IMetrics.PushSingleMetric(string metricName, double value, MetricUnit unit, string nameSpace, string service, ++ Dictionary defaultDimensions, MetricResolution metricResolution) ++ { ++ if (string.IsNullOrWhiteSpace(metricName)) ++ throw new ArgumentNullException(nameof(metricName), + "'PushSingleMetric' method requires a valid metrics key. 'Null' or empty values are not allowed."); + +- var context = new MetricsContext(); +- context.SetNamespace(nameSpace ?? GetNamespace()); +- +- var parsedService = !string.IsNullOrWhiteSpace(service) +- ? service +- : _powertoolsConfigurations.Service == "service_undefined" +- ? null +- : _powertoolsConfigurations.Service; +- +- if (!string.IsNullOrWhiteSpace(parsedService)) +- { +- context.SetService(parsedService); +- context.AddDimension("Service", parsedService); +- } +- +- if (dimensions != null) +- { +- var dimensionsList = DictionaryToList(dimensions); +- context.AddDimensions(dimensionsList); +- } +- +- context.AddMetric(name, value, unit, resolution); ++ using var context = InitializeContext(nameSpace, service, defaultDimensions); ++ context.AddMetric(metricName, value, unit, metricResolution); + + Flush(context); + } + +- + /// + /// Implementation of IDisposable interface + /// +@@ -387,7 +283,7 @@ public class Metrics : IMetrics, IDisposable + Dispose(true); + GC.SuppressFinalize(this); + } +- ++ + /// + /// + /// +@@ -397,7 +293,7 @@ public class Metrics : IMetrics, IDisposable + // Cleanup + if (disposing) + { +- Instance.Flush(); ++ _instance.Flush(); + } + } + +@@ -407,11 +303,11 @@ public class Metrics : IMetrics, IDisposable + /// Metric Key. Must not be null, empty or whitespace + /// Metric Value + /// Metric Unit +- /// ++ /// + public static void AddMetric(string key, double value, MetricUnit unit = MetricUnit.None, +- MetricResolution resolution = MetricResolution.Default) ++ MetricResolution metricResolution = MetricResolution.Default) + { +- Instance.AddMetric(key, value, unit, resolution); ++ _instance.AddMetric(key, value, unit, metricResolution); + } + + /// +@@ -420,32 +316,16 @@ public class Metrics : IMetrics, IDisposable + /// Metrics Namespace Identifier + public static void SetNamespace(string nameSpace) + { +- Instance.SetNamespace(nameSpace); +- } +- +- /// +- /// Sets the service name for the metrics. +- /// +- /// The service name. +- public static void SetService(string service) +- { +- Instance.SetService(service); ++ _instance.SetNamespace(nameSpace); + } + + /// + /// Retrieves namespace identifier. + /// + /// Namespace identifier +- public string GetNamespace() ++ public static string GetNamespace() + { +- try +- { +- return _context.GetNamespace() ?? _powertoolsConfigurations.MetricsNamespace; +- } +- catch +- { +- return null; +- } ++ return _instance.GetNamespace(); + } + + /// +@@ -455,7 +335,7 @@ public class Metrics : IMetrics, IDisposable + /// Dimension value + public static void AddDimension(string key, string value) + { +- Instance.AddDimension(key, value); ++ _instance.AddDimension(key, value); + } + + /// +@@ -465,7 +345,7 @@ public class Metrics : IMetrics, IDisposable + /// Metadata value + public static void AddMetadata(string key, object value) + { +- Instance.AddMetadata(key, value); ++ _instance.AddMetadata(key, value); + } + + /// +@@ -474,15 +354,15 @@ public class Metrics : IMetrics, IDisposable + /// Default Dimension List + public static void SetDefaultDimensions(Dictionary defaultDimensions) + { +- Instance.SetDefaultDimensions(defaultDimensions); ++ _instance.SetDefaultDimensions(defaultDimensions); + } +- ++ + /// + /// Clears both default dimensions and dimensions lists + /// + public static void ClearDefaultDimensions() + { +- Instance.ClearDefaultDimensions(); ++ _instance.ClearDefaultDimensions(); + } + + /// +@@ -494,134 +374,70 @@ public class Metrics : IMetrics, IDisposable + { + var emfPayload = context.Serialize(); + +- _consoleWrapper.WriteLine(emfPayload); ++ Console.WriteLine(emfPayload); + } + + /// + /// Pushes single metric to CloudWatch using Embedded Metric Format. This can be used to push metrics with a different + /// context. + /// +- /// Metric Name. Metric key cannot be null, empty or whitespace ++ /// Metric Name. Metric key cannot be null, empty or whitespace + /// Metric Value + /// Metric Unit + /// Metric Namespace + /// Service Name +- /// Default dimensions list +- /// Metrics resolution +- public static void PushSingleMetric(string name, double value, MetricUnit unit, string nameSpace = null, +- string service = null, Dictionary dimensions = null, +- MetricResolution resolution = MetricResolution.Default) ++ /// Default dimensions list ++ /// Metrics resolution ++ public static void PushSingleMetric(string metricName, double value, MetricUnit unit, string nameSpace = null, ++ string service = null, Dictionary defaultDimensions = null, MetricResolution metricResolution = MetricResolution.Default) + { +- Instance.PushSingleMetric(name, value, unit, nameSpace, service, dimensions, +- resolution); ++ _instance.PushSingleMetric(metricName, value, unit, nameSpace, service, defaultDimensions, metricResolution); + } + + /// +- /// Helper method to convert default dimensions dictionary to list ++ /// Sets global namespace, service name and default dimensions list. Service name is automatically added as a default ++ /// dimension + /// +- /// Default dimensions dictionary +- /// Default dimensions list +- private List DictionaryToList(Dictionary defaultDimensions) ++ /// Metrics namespace ++ /// Service Name ++ /// Default Dimensions List ++ /// MetricsContext. ++ private MetricsContext InitializeContext(string nameSpace, string service, ++ Dictionary defaultDimensions) + { +- var dimensionsList = new List(); +- if (defaultDimensions != null) +- foreach (var item in defaultDimensions) +- dimensionsList.Add(new DimensionSet(item.Key, item.Value)); +- +- return dimensionsList; +- } ++ var context = new MetricsContext(); + +- private Dictionary ListToDictionary(List dimensions) +- { +- var dictionary = new Dictionary(); +- try +- { +- return dimensions != null +- ? new Dictionary(dimensions.SelectMany(x => x.Dimensions)) +- : dictionary; +- } +- catch (Exception e) +- { +- _consoleWrapper.Debug("Error converting list to dictionary: " + e.Message); +- return dictionary; +- } +- } +- +- /// +- /// Captures the cold start metric. +- /// +- /// The ILambdaContext. +- void IMetrics.CaptureColdStartMetric(ILambdaContext context) +- { +- if (Options.CaptureColdStart == null || !Options.CaptureColdStart.Value) return; +- +- // bring default dimensions if exist +- var dimensions = Options?.DefaultDimensions; +- +- var functionName = Options?.FunctionName ?? context?.FunctionName ?? ""; +- if (!string.IsNullOrWhiteSpace(functionName)) +- { +- dimensions ??= new Dictionary(); +- dimensions.Add("FunctionName", functionName); +- } ++ context.SetNamespace(!string.IsNullOrWhiteSpace(nameSpace) ++ ? nameSpace ++ : _powertoolsConfigurations.MetricsNamespace); + +- PushSingleMetric( +- "ColdStart", +- 1.0, +- MetricUnit.Count, +- Options?.Namespace ?? "", +- Options?.Service ?? "", +- dimensions +- ); +- } +- +- /// +- void IMetrics.AddDimensions(params (string key, string value)[] dimensions) +- { +- if (dimensions == null || dimensions.Length == 0) +- return; ++ context.SetService(!string.IsNullOrWhiteSpace(service) ++ ? service ++ : _powertoolsConfigurations.Service); + +- // Validate all dimensions first +- foreach (var (key, value) in dimensions) +- { +- if (string.IsNullOrWhiteSpace(key)) +- throw new ArgumentNullException(nameof(dimensions), +- "'AddDimensions' method requires valid dimension keys. 'Null' or empty values are not allowed."); ++ var defaultDimensionsList = DictionaryToList(defaultDimensions); + +- if (string.IsNullOrWhiteSpace(value)) +- throw new ArgumentNullException(nameof(dimensions), +- "'AddDimensions' method requires valid dimension values. 'Null' or empty values are not allowed."); +- } ++ // Add service as a default dimension ++ defaultDimensionsList.Add(new DimensionSet("Service", context.GetService())); + +- // Create a new dimension set with all dimensions +- var dimensionSet = new DimensionSet(dimensions[0].key, dimensions[0].value); +- +- // Add remaining dimensions to the same set +- for (var i = 1; i < dimensions.Length; i++) +- { +- dimensionSet.Dimensions.Add(dimensions[i].key, dimensions[i].value); +- } ++ context.SetDefaultDimensions(defaultDimensionsList); + +- // Add the dimensionSet to a list and pass it to AddDimensions +- _context.AddDimensions([dimensionSet]); ++ return context; + } +- +- /// +- /// Adds multiple dimensions at once. +- /// +- /// Array of key-value tuples representing dimensions. +- public static void AddDimensions(params (string key, string value)[] dimensions) +- { +- Instance.AddDimensions(dimensions); +- } +- ++ + /// +- /// Flushes the metrics. ++ /// Helper method to convert default dimensions dictionary to list + /// +- /// If set to true, indicates a metrics overflow. +- public static void Flush(bool metricsOverflow = false) ++ /// Default dimensions dictionary ++ /// Default dimensions list ++ private List DictionaryToList(Dictionary defaultDimensions) + { +- Instance.Flush(metricsOverflow); ++ var defaultDimensionsList = new List(); ++ if (defaultDimensions != null) ++ foreach (var item in defaultDimensions) ++ defaultDimensionsList.Add(new DimensionSet(item.Key, item.Value)); ++ ++ return defaultDimensionsList; + } + + /// +@@ -629,15 +445,6 @@ public class Metrics : IMetrics, IDisposable + /// + internal static void ResetForTest() + { +- Instance = null; +- } +- +- /// +- /// For testing purposes, resets the Instance to the provided metrics instance. +- /// +- /// +- public static void UseMetricsForTests(IMetrics metricsInstance) +- { +- Instance = metricsInstance; ++ _instance = null; + } +-} +\ No newline at end of file ++} +diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/MetricsAttribute.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/MetricsAttribute.cs +index 3e47c1e0..761bf7bd 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Metrics/MetricsAttribute.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Metrics/MetricsAttribute.cs +@@ -66,7 +66,7 @@ namespace AWS.Lambda.Powertools.Metrics; + /// + /// + /// Service +-/// string, service name is used for metric dimension ++/// string, service name is used for metric dimension across all metrics, by default service_undefined + /// + /// + /// Namespace +@@ -112,13 +112,6 @@ public class MetricsAttribute : Attribute + /// The namespace. + public string Namespace { get; set; } + +- /// +- /// Function name is used for metric dimension across all metrics. +- /// This can be also set using the environment variable LAMBDA_FUNCTION_NAME. +- /// If not set, the function name will be automatically set to the Lambda function name. +- /// +- public string FunctionName { get; set; } +- + /// + /// Service name is used for metric dimension across all metrics. + /// This can be also set using the environment variable POWERTOOLS_SERVICE_NAME. +@@ -126,41 +119,15 @@ public class MetricsAttribute : Attribute + /// The service. + public string Service { get; set; } + +- private bool _captureColdStartSet; +- private bool _captureColdStart; +- + /// + /// Captures cold start during Lambda execution + /// + /// true if [capture cold start]; otherwise, false. +- public bool CaptureColdStart +- { +- get => _captureColdStart; +- set +- { +- _captureColdStart = value; +- _captureColdStartSet = true; +- } +- } +- +- internal bool IsCaptureColdStartSet => _captureColdStartSet; +- +- private bool _raiseOnEmptyMetricsSet; +- private bool _raiseOnEmptyMetrics; ++ public bool CaptureColdStart { get; set; } + + /// + /// Instructs metrics validation to throw exception if no metrics are provided. + /// + /// true if [raise on empty metrics]; otherwise, false. +- public bool RaiseOnEmptyMetrics +- { +- get => _raiseOnEmptyMetrics; +- set +- { +- _raiseOnEmptyMetrics = value; +- _raiseOnEmptyMetricsSet = true; +- } +- } +- +- internal bool IsRaiseOnEmptyMetricsSet => _raiseOnEmptyMetricsSet; ++ public bool RaiseOnEmptyMetrics { get; set; } + } +diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/MetricsBuilder.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/MetricsBuilder.cs +deleted file mode 100644 +index 388226a1..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Metrics/MetricsBuilder.cs ++++ /dev/null +@@ -1,94 +0,0 @@ +-using System.Collections.Generic; +- +-namespace AWS.Lambda.Powertools.Metrics; +- +-/// +-/// Provides a builder for configuring metrics. +-/// +-public class MetricsBuilder +-{ +- private readonly MetricsOptions _options = new(); +- +- /// +- /// Sets the namespace for the metrics. +- /// +- /// The namespace identifier. +- /// The current instance of . +- public MetricsBuilder WithNamespace(string nameSpace) +- { +- _options.Namespace = nameSpace; +- return this; +- } +- +- /// +- /// Sets the service name for the metrics. +- /// +- /// The service name. +- /// The current instance of . +- public MetricsBuilder WithService(string service) +- { +- _options.Service = service; +- return this; +- } +- +- /// +- /// Sets whether to raise an exception if no metrics are captured. +- /// +- /// If true, raises an exception when no metrics are captured. +- /// The current instance of . +- public MetricsBuilder WithRaiseOnEmptyMetrics(bool raiseOnEmptyMetrics) +- { +- _options.RaiseOnEmptyMetrics = raiseOnEmptyMetrics; +- return this; +- } +- +- /// +- /// Sets whether to capture cold start metrics. +- /// +- /// If true, captures cold start metrics. +- /// The current instance of . +- public MetricsBuilder WithCaptureColdStart(bool captureColdStart) +- { +- _options.CaptureColdStart = captureColdStart; +- return this; +- } +- +- /// +- /// Sets the default dimensions for the metrics. +- /// +- /// A dictionary of default dimensions. +- /// The current instance of . +- public MetricsBuilder WithDefaultDimensions(Dictionary defaultDimensions) +- { +- _options.DefaultDimensions = defaultDimensions; +- return this; +- } +- +- /// +- /// Sets the function name for the metrics dimension. +- /// +- /// +- /// +- public MetricsBuilder WithFunctionName(string functionName) +- { +- _options.FunctionName = !string.IsNullOrWhiteSpace(functionName) ? functionName : null; +- return this; +- } +- +- /// +- /// Builds and configures the metrics instance. +- /// +- /// An instance of . +- public IMetrics Build() +- { +- return Metrics.Configure(opt => +- { +- opt.Namespace = _options.Namespace; +- opt.Service = _options.Service; +- opt.RaiseOnEmptyMetrics = _options.RaiseOnEmptyMetrics; +- opt.CaptureColdStart = _options.CaptureColdStart; +- opt.DefaultDimensions = _options.DefaultDimensions; +- opt.FunctionName = _options.FunctionName; +- }); +- } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/MetricsOptions.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/MetricsOptions.cs +deleted file mode 100644 +index 71adc557..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Metrics/MetricsOptions.cs ++++ /dev/null +@@ -1,39 +0,0 @@ +-using System.Collections.Generic; +- +-namespace AWS.Lambda.Powertools.Metrics; +- +-/// +-/// Configuration options for AWS Lambda Powertools Metrics. +-/// +-public class MetricsOptions +-{ +- /// +- /// Gets or sets the CloudWatch metrics namespace. +- /// +- public string Namespace { get; set; } +- +- /// +- /// Gets or sets the service name to be used as a metric dimension. +- /// +- public string Service { get; set; } +- +- /// +- /// Gets or sets whether to throw an exception when no metrics are emitted. +- /// +- public bool? RaiseOnEmptyMetrics { get; set; } +- +- /// +- /// Gets or sets whether to capture cold start metrics. +- /// +- public bool? CaptureColdStart { get; set; } +- +- /// +- /// Gets or sets the default dimensions to be added to all metrics. +- /// +- public Dictionary DefaultDimensions { get; set; } +- +- /// +- /// Gets or sets the function name to be used as a metric dimension. +- /// +- public string FunctionName { get; set; } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/Model/Metadata.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/Model/Metadata.cs +index 847b0dc8..ee3d0605 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Metrics/Model/Metadata.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Metrics/Model/Metadata.cs +@@ -1,4 +1,19 @@ +-using System; ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ ++using System; + using System.Collections.Generic; + using System.Text.Json.Serialization; + +@@ -114,19 +129,10 @@ public class Metadata + /// Adds new Dimension + /// + /// Dimension to add +- internal void AddDimension(DimensionSet dimension) ++ internal void AddDimensionSet(DimensionSet dimension) + { + _metricDirective.AddDimension(dimension); + } +- +- /// +- /// Adds new List of Dimensions +- /// +- /// Dimensions to add +- internal void AddDimensionSet(List dimension) +- { +- _metricDirective.AddDimensionSet(dimension); +- } + + /// + /// Sets default dimensions list +@@ -178,12 +184,4 @@ public class Metadata + { + _metricDirective.ClearDefaultDimensions(); + } +- +- /// +- /// Retrieves default dimensions list +- /// +- internal List GetDefaultDimensions() +- { +- return _metricDirective.DefaultDimensions; +- } + } +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricDirective.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricDirective.cs +index 0d300d5e..f8ccad76 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricDirective.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricDirective.cs +@@ -105,39 +105,25 @@ public class MetricDirective + /// + /// All dimension keys. + [JsonPropertyName("Dimensions")] +- public List> AllDimensionKeys ++ public List AllDimensionKeys + { + get + { +- var result = new List>(); +- var allDimKeys = new List(); ++ var defaultKeys = DefaultDimensions ++ .Where(d => d.DimensionKeys.Any()) ++ .SelectMany(s => s.DimensionKeys) ++ .ToList(); + +- // Add default dimensions keys +- if (DefaultDimensions.Any()) +- { +- foreach (var dimensionSet in DefaultDimensions) +- { +- foreach (var key in dimensionSet.DimensionKeys.Where(key => !allDimKeys.Contains(key))) +- { +- allDimKeys.Add(key); +- } +- } +- } ++ var keys = Dimensions ++ .Where(d => d.DimensionKeys.Any()) ++ .SelectMany(s => s.DimensionKeys) ++ .ToList(); + +- // Add all regular dimensions to the same array +- foreach (var dimensionSet in Dimensions) +- { +- foreach (var key in dimensionSet.DimensionKeys.Where(key => !allDimKeys.Contains(key))) +- { +- allDimKeys.Add(key); +- } +- } ++ defaultKeys.AddRange(keys); + +- // Add non-empty dimension arrays +- // When no dimensions exist, add an empty array +- result.Add(allDimKeys.Any() ? allDimKeys : []); ++ if (defaultKeys.Count == 0) defaultKeys = new List(); + +- return result; ++ return defaultKeys; + } + } + +@@ -205,37 +191,19 @@ public class MetricDirective + /// Dimensions - Cannot add more than 9 dimensions at the same time. + internal void AddDimension(DimensionSet dimension) + { +- // Check if we already have any dimensions +- if (Dimensions.Count > 0) ++ if (Dimensions.Count < PowertoolsConfigurations.MaxDimensions) + { +- // Get the first dimension set where we now store all dimensions +- var firstDimensionSet = Dimensions[0]; +- +- // Check the actual dimension count inside the first dimension set +- if (firstDimensionSet.Dimensions.Count >= PowertoolsConfigurations.MaxDimensions) +- { +- throw new ArgumentOutOfRangeException(nameof(dimension), +- $"Cannot add more than {PowertoolsConfigurations.MaxDimensions} dimensions at the same time."); +- } +- +- // Add to the first dimension set instead of creating a new one +- foreach (var pair in dimension.Dimensions) +- { +- if (!firstDimensionSet.Dimensions.ContainsKey(pair.Key)) +- { +- firstDimensionSet.Dimensions.Add(pair.Key, pair.Value); +- } +- else +- { +- Console.WriteLine( +- $"##WARNING##: Failed to Add dimension '{pair.Key}'. Dimension already exists."); +- } +- } ++ var matchingKeys = AllDimensionKeys.Where(x => x.Contains(dimension.DimensionKeys[0])); ++ if (!matchingKeys.Any()) ++ Dimensions.Add(dimension); ++ else ++ Console.WriteLine( ++ $"##WARNING##: Failed to Add dimension '{dimension.DimensionKeys[0]}'. Dimension already exists."); + } + else + { +- // No dimensions yet, add the new one +- Dimensions.Add(dimension); ++ throw new ArgumentOutOfRangeException(nameof(Dimensions), ++ "Cannot add more than 9 dimensions at the same time."); + } + } + +@@ -259,44 +227,18 @@ public class MetricDirective + /// Dictionary with dimension and default dimension list appended + internal Dictionary ExpandAllDimensionSets() + { +- // if a key appears multiple times, the last value will be the one that's used in the output. + var dimensions = new Dictionary(); + + foreach (var dimensionSet in DefaultDimensions) + foreach (var (key, value) in dimensionSet.Dimensions) +- dimensions[key] = value; ++ dimensions.TryAdd(key, value); + + foreach (var dimensionSet in Dimensions) + foreach (var (key, value) in dimensionSet.Dimensions) +- dimensions[key] = value; ++ dimensions.TryAdd(key, value); + + return dimensions; + } +- +- /// +- /// Adds multiple dimensions as a complete dimension set to memory. +- /// +- /// List of dimension sets to add +- internal void AddDimensionSet(List dimensionSets) +- { +- if (dimensionSets == null || !dimensionSets.Any()) +- return; +- +- if (Dimensions.Count + dimensionSets.Count <= PowertoolsConfigurations.MaxDimensions) +- { +- // Simply add the dimension sets without checking for existing keys +- // This ensures dimensions added together stay together +- foreach (var dimensionSet in dimensionSets.Where(dimensionSet => dimensionSet.DimensionKeys.Any())) +- { +- Dimensions.Add(dimensionSet); +- } +- } +- else +- { +- throw new ArgumentOutOfRangeException(nameof(Dimensions), +- $"Cannot add more than {PowertoolsConfigurations.MaxDimensions} dimensions at the same time."); +- } +- } + + /// + /// Clears both default dimensions and dimensions lists +diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricUnit.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricUnit.cs +index 2fbb389d..ddf091c5 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricUnit.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricUnit.cs +@@ -21,11 +21,7 @@ namespace AWS.Lambda.Powertools.Metrics; + /// + /// Enum MetricUnit + /// +-#if NET8_0_OR_GREATER + [JsonConverter(typeof(JsonStringEnumConverter))] +-#else +-[JsonConverter(typeof(JsonStringEnumConverter))] +-#endif + public enum MetricUnit + { + /// +diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricsContext.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricsContext.cs +index d43d059b..ba77d0ed 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricsContext.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricsContext.cs +@@ -132,17 +132,7 @@ public class MetricsContext : IDisposable + /// Dimension value + public void AddDimension(string key, string value) + { +- _rootNode.AWS.AddDimension(new DimensionSet(key, value)); +- } +- +- /// +- /// Adds new dimensions to memory +- /// +- /// List of dimensions +- public void AddDimensions(List dimensions) +- { +- // Call the AddDimensionSet method on the MetricDirective to add as a set +- _rootNode.AWS.AddDimensionSet(dimensions); ++ _rootNode.AWS.AddDimensionSet(new DimensionSet(key, value)); + } + + /// +@@ -180,12 +170,4 @@ public class MetricsContext : IDisposable + { + _rootNode.AWS.ClearDefaultDimensions(); + } +- +- /// +- /// Retrieves default dimensions list +- /// +- internal List GetDefaultDimensions() +- { +- return _rootNode.AWS.GetDefaultDimensions(); +- } + } +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/Model/RootNode.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/Model/RootNode.cs +index 496606cd..91ccdc9f 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Metrics/Model/RootNode.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Metrics/Model/RootNode.cs +@@ -65,11 +65,6 @@ public class RootNode + { + if (string.IsNullOrWhiteSpace(AWS.GetNamespace())) throw new SchemaValidationException("namespace"); + +-#if NET8_0_OR_GREATER +- + return JsonSerializer.Serialize(this, typeof(RootNode), MetricsSerializationContext.Default); +-#else +- return JsonSerializer.Serialize(this); +-#endif + } + } +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/Serializer/MetricsSerializationContext.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/Serializer/MetricsSerializationContext.cs +index df77cc58..05317c4a 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Metrics/Serializer/MetricsSerializationContext.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Metrics/Serializer/MetricsSerializationContext.cs +@@ -1,9 +1,23 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System.Collections.Generic; + using System.Text.Json.Serialization; + + namespace AWS.Lambda.Powertools.Metrics; + +-#if NET8_0_OR_GREATER + /// + /// Source generator for Metrics types + /// +@@ -21,5 +35,4 @@ namespace AWS.Lambda.Powertools.Metrics; + public partial class MetricsSerializationContext : JsonSerializerContext + { + +-} +-#endif +\ No newline at end of file ++} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/Transform/JsonTransformer.cs b/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/Transform/JsonTransformer.cs +index 136f889f..36f19a46 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/Transform/JsonTransformer.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/Transform/JsonTransformer.cs +@@ -23,31 +23,12 @@ namespace AWS.Lambda.Powertools.Parameters.Internal.Transform; + /// + internal class JsonTransformer : ITransformer + { +- private readonly JsonSerializerOptions _options; +- +- /// +- /// Initializes a new instance of the class. +- /// +- public JsonTransformer() +- { +- _options = new JsonSerializerOptions +- { +- PropertyNameCaseInsensitive = true +- }; +- } +- + /// + /// Deserialize a JSON value from a JSON string. + /// + /// JSON string. + /// JSON value type. + /// JSON value. +-#if NET6_0_OR_GREATER +- [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("AOT", "IL3050:RequiresDynamicCode", +- Justification = "Types are expected to be known at compile time")] +- [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode", +- Justification = "Types are expected to be preserved")] +-#endif + public T? Transform(string value) + { + if (typeof(T) == typeof(string)) +@@ -56,6 +37,6 @@ internal class JsonTransformer : ITransformer + if (string.IsNullOrWhiteSpace(value)) + return default; + +- return JsonSerializer.Deserialize(value, _options); ++ return JsonSerializer.Deserialize(value); + } + } +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingAspect.cs b/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingAspect.cs +index 84bf0246..6052bb68 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingAspect.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingAspect.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.Linq; + using System.Runtime.ExceptionServices; +@@ -5,7 +20,6 @@ using System.Text; + using System.Threading.Tasks; + using AspectInjector.Broker; + using AWS.Lambda.Powertools.Common; +-using AWS.Lambda.Powertools.Common.Core; + using AWS.Lambda.Powertools.Common.Utils; + + namespace AWS.Lambda.Powertools.Tracing.Internal; +@@ -27,6 +41,11 @@ public class TracingAspect + /// + private readonly IXRayRecorder _xRayRecorder; + ++ /// ++ /// If true, then is cold start ++ /// ++ private static bool _isColdStart = true; ++ + /// + /// If true, capture annotations + /// +@@ -129,7 +148,7 @@ public class TracingAspect + + if (_captureAnnotations) + { +- _xRayRecorder.AddAnnotation("ColdStart", LambdaLifecycleTracker.IsColdStart); ++ _xRayRecorder.AddAnnotation("ColdStart", _isColdStart); + + _captureAnnotations = false; + _isAnnotationsCaptured = true; +@@ -137,6 +156,8 @@ public class TracingAspect + if (_powertoolsConfigurations.IsServiceDefined) + _xRayRecorder.AddAnnotation("Service", _powertoolsConfigurations.Service); + } ++ ++ _isColdStart = false; + } + + private void HandleResponse(string name, object result, TracingCaptureMode captureMode, string @namespace) +@@ -147,7 +168,6 @@ public class TracingAspect + // Skip if the result is VoidTaskResult + if (result.GetType().Name == "VoidTaskResult") return; + +-#if NET8_0_OR_GREATER + if (!RuntimeFeatureWrapper.IsDynamicCodeSupported) // is AOT + { + _xRayRecorder.AddMetadata( +@@ -157,7 +177,6 @@ public class TracingAspect + ); + return; + } +-#endif + + _xRayRecorder.AddMetadata( + @namespace, +@@ -232,7 +251,7 @@ public class TracingAspect + + internal static void ResetForTest() + { +- LambdaLifecycleTracker.Reset(); ++ _isColdStart = true; + _captureAnnotations = true; + } + } +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingAspectFactory.cs b/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingAspectFactory.cs +index f1e17c5c..b013fde7 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingAspectFactory.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingAspectFactory.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + + using System; + using AWS.Lambda.Powertools.Common; +diff --git a/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingSubsegment.cs b/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingSubsegment.cs +deleted file mode 100644 +index 68622858..00000000 +--- a/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingSubsegment.cs ++++ /dev/null +@@ -1,17 +0,0 @@ +-using Amazon.XRay.Recorder.Core.Internal.Entities; +- +-namespace AWS.Lambda.Powertools.Tracing.Internal; +- +-/// +-/// Class TracingSubsegment. +-/// It's a wrapper for Subsegment from Amazon.XRay.Recorder.Core.Internal. +-/// +-/// +-public class TracingSubsegment : Subsegment +-{ +- /// +- /// Wrapper constructor +- /// +- /// +- public TracingSubsegment(string name) : base(name) { } +-} +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/XRayRecorder.cs b/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/XRayRecorder.cs +index 0d4aa658..53bface3 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/XRayRecorder.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/XRayRecorder.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using Amazon.XRay.Recorder.Core; + using Amazon.XRay.Recorder.Core.Internal.Emitters; +diff --git a/libraries/src/AWS.Lambda.Powertools.Tracing/Serializers/PowertoolsTracingSerializer.cs b/libraries/src/AWS.Lambda.Powertools.Tracing/Serializers/PowertoolsTracingSerializer.cs +index 9f2e9e8f..fca46725 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Tracing/Serializers/PowertoolsTracingSerializer.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Tracing/Serializers/PowertoolsTracingSerializer.cs +@@ -1,5 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ + +-#if NET8_0_OR_GREATER + + using System; + using System.Collections.Generic; +@@ -89,5 +102,3 @@ public static class PowertoolsTracingSerializer + } + } + } +- +-#endif +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Tracing/Serializers/TracingSerializerExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Tracing/Serializers/TracingSerializerExtensions.cs +index e18368cb..9287c697 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Tracing/Serializers/TracingSerializerExtensions.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Tracing/Serializers/TracingSerializerExtensions.cs +@@ -1,4 +1,17 @@ +-#if NET8_0_OR_GREATER ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ + + using System; + using System.Diagnostics.CodeAnalysis; +@@ -40,5 +53,3 @@ public static class TracingSerializerExtensions + return serializer; + } + } +- +-#endif +\ No newline at end of file +diff --git a/libraries/src/AWS.Lambda.Powertools.Tracing/Tracing.cs b/libraries/src/AWS.Lambda.Powertools.Tracing/Tracing.cs +index 3d7d1aa8..29e5b88a 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Tracing/Tracing.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Tracing/Tracing.cs +@@ -130,7 +130,7 @@ public static class Tracing + /// The name of the subsegment. + /// The AWS X-Ray subsegment for the wrapped consumer. + /// Thrown when the name is not provided. +- public static void WithSubsegment(string name, Action subsegment) ++ public static void WithSubsegment(string name, Action subsegment) + { + WithSubsegment(null, name, subsegment); + } +@@ -145,7 +145,7 @@ public static class Tracing + /// The name of the subsegment. + /// The AWS X-Ray subsegment for the wrapped consumer. + /// name +- public static void WithSubsegment(string nameSpace, string name, Action subsegment) ++ public static void WithSubsegment(string nameSpace, string name, Action subsegment) + { + if (string.IsNullOrWhiteSpace(name)) + throw new ArgumentNullException(nameof(name)); +@@ -154,8 +154,7 @@ public static class Tracing + XRayRecorder.Instance.SetNamespace(GetNamespaceOrDefault(nameSpace)); + try + { +- var entity = XRayRecorder.Instance.GetEntity() as TracingSubsegment; +- subsegment?.Invoke(entity); ++ subsegment?.Invoke((Subsegment) XRayRecorder.Instance.GetEntity()); + } + finally + { +@@ -175,7 +174,7 @@ public static class Tracing + /// The AWS X-Ray subsegment for the wrapped consumer. + /// Thrown when the name is not provided. + /// Thrown when the entity is not provided. +- public static void WithSubsegment(string name, Entity entity, Action subsegment) ++ public static void WithSubsegment(string name, Entity entity, Action subsegment) + { + WithSubsegment(null, name, subsegment); + } +@@ -192,7 +191,7 @@ public static class Tracing + /// The AWS X-Ray subsegment for the wrapped consumer. + /// name + /// entity +- public static void WithSubsegment(string nameSpace, string name, Entity entity, Action subsegment) ++ public static void WithSubsegment(string nameSpace, string name, Entity entity, Action subsegment) + { + if (string.IsNullOrWhiteSpace(name)) + throw new ArgumentNullException(nameof(name)); +@@ -200,7 +199,7 @@ public static class Tracing + if (entity is null) + throw new ArgumentNullException(nameof(entity)); + +- var childSubsegment = new TracingSubsegment($"## {name}"); ++ var childSubsegment = new Subsegment($"## {name}"); + entity.AddSubsegment(childSubsegment); + childSubsegment.Sampled = entity.Sampled; + childSubsegment.SetStartTimeToNow(); +@@ -241,7 +240,7 @@ public static class Tracing + + return PowertoolsConfigurations.Instance.Service; + } +- ++ + /// + /// Registers X-Ray for all instances of . + /// +diff --git a/libraries/src/AWS.Lambda.Powertools.Tracing/TracingAttribute.cs b/libraries/src/AWS.Lambda.Powertools.Tracing/TracingAttribute.cs +index 5cbfc495..c144d038 100644 +--- a/libraries/src/AWS.Lambda.Powertools.Tracing/TracingAttribute.cs ++++ b/libraries/src/AWS.Lambda.Powertools.Tracing/TracingAttribute.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using AspectInjector.Broker; + using AWS.Lambda.Powertools.Tracing.Internal; +diff --git a/libraries/src/Directory.Build.props b/libraries/src/Directory.Build.props +index 7d1e38a6..7d7d5306 100644 +--- a/libraries/src/Directory.Build.props ++++ b/libraries/src/Directory.Build.props +@@ -1,9 +1,9 @@ + + +- net6.0;net8.0 ++ net8.0 + default + +- $(Version) ++ 0.0.1 + Amazon Web Services + Amazon.com, Inc + Powertools for AWS Lambda (.NET) +@@ -16,7 +16,6 @@ + AWSLogo128x128.png + true + true +- $(VersionSuffix) + + + +diff --git a/libraries/src/Directory.Packages.props b/libraries/src/Directory.Packages.props +index be5d5685..56d0fba9 100644 +--- a/libraries/src/Directory.Packages.props ++++ b/libraries/src/Directory.Packages.props +@@ -4,20 +4,14 @@ + + + +- +- ++ + +- ++ + + +- + + +- +- +- +- +- ++ + + + +@@ -26,6 +20,5 @@ + + + +- + + +\ No newline at end of file +diff --git a/libraries/src/KafkaDependencies.props b/libraries/src/KafkaDependencies.props +deleted file mode 100644 +index 1034529a..00000000 +--- a/libraries/src/KafkaDependencies.props ++++ /dev/null +@@ -1,20 +0,0 @@ +- +- +- false +- +- +- +- +- +- +- +- +- Kafka\%(RecursiveDir)%(Filename)%(Extension) +- +- +- Common\%(RecursiveDir)%(Filename)%(Extension) +- +- +- +- +- +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/AotCompatibilityTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/AotCompatibilityTests.cs +deleted file mode 100644 +index fb8b976c..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/AotCompatibilityTests.cs ++++ /dev/null +@@ -1,385 +0,0 @@ +- +- +-using System; +-using System.Runtime.CompilerServices; +-using System.Text.Json; +-using System.Text.Json.Serialization; +-using AWS.Lambda.Powertools.BatchProcessing.Exceptions; +-using AWS.Lambda.Powertools.BatchProcessing.Internal; +-using Xunit; +- +-namespace AWS.Lambda.Powertools.BatchProcessing.Tests; +- +-[Collection("Sequential")] +-public class AotCompatibilityTests +-{ +- #region Test Models +- +- public class TestAotModel +- { +- public int Id { get; set; } +- public string Name { get; set; } +- public DateTime CreatedAt { get; set; } +- } +- +- public class UnregisteredModel +- { +- public string Value { get; set; } +- } +- +- #endregion +- +- #region AotCompatibilityHelper Tests +- +- [Fact] +- public void IsAotMode_ReturnsExpectedValue() +- { +- // Act +- var isAotMode = AotCompatibilityHelper.IsAotMode(); +- +- // Assert +- // In test environment, this should return false (not AOT compiled) +- // The actual value depends on the runtime, but we can test that it returns a boolean +- Assert.IsType(isAotMode); +- } +- +- [Fact] +- public void ValidateTypeInContext_WithValidType_ReturnsTrue() +- { +- // Arrange +- var context = TestAotJsonSerializerContext.Default; +- +- // Act +- var result = AotCompatibilityHelper.ValidateTypeInContext(context, false); +- +- // Assert +- Assert.True(result); +- } +- +- [Fact] +- public void ValidateTypeInContext_WithUnregisteredType_ReturnsFalse() +- { +- // Arrange +- var context = TestAotJsonSerializerContext.Default; +- +- // Act +- var result = AotCompatibilityHelper.ValidateTypeInContext(context, false); +- +- // Assert +- Assert.False(result); +- } +- +- [Fact] +- public void ValidateTypeInContext_WithUnregisteredTypeAndThrow_ThrowsException() +- { +- // Arrange +- var context = TestAotJsonSerializerContext.Default; +- +- // Act & Assert +- var exception = Assert.Throws(() => +- AotCompatibilityHelper.ValidateTypeInContext(context, true)); +- +- Assert.Equal(typeof(UnregisteredModel), exception.TargetType); +- Assert.Contains("UnregisteredModel", exception.Message); +- Assert.Contains("JsonSerializable", exception.Message); +- } +- +- [Fact] +- public void ValidateTypeInContext_WithNullContext_ThrowsException() +- { +- // Act & Assert +- var exception = Assert.Throws(() => +- AotCompatibilityHelper.ValidateTypeInContext(null, true)); +- +- Assert.Equal(typeof(TestAotModel), exception.TargetType); +- Assert.Contains("JsonSerializerContext is null", exception.Message); +- } +- +- [Fact] +- public void ValidateTypeInContext_WithNullContextAndNoThrow_ReturnsFalse() +- { +- // Act +- var result = AotCompatibilityHelper.ValidateTypeInContext(null, false); +- +- // Assert +- Assert.False(result); +- } +- +- [Fact] +- public void GetAotCompatibilityErrorMessage_WithoutContext_ReturnsCorrectMessage() +- { +- // Act +- var message = AotCompatibilityHelper.GetAotCompatibilityErrorMessage(typeof(TestAotModel), false); +- +- // Assert +- Assert.Contains("AOT compilation requires a JsonSerializerContext", message); +- Assert.Contains("TestAotModel", message); +- Assert.Contains("JsonSerializable", message); +- } +- +- [Fact] +- public void GetAotCompatibilityErrorMessage_WithContext_ReturnsCorrectMessage() +- { +- // Act +- var message = AotCompatibilityHelper.GetAotCompatibilityErrorMessage(typeof(TestAotModel), true); +- +- // Assert +- Assert.Contains("does not contain type information", message); +- Assert.Contains("TestAotModel", message); +- Assert.Contains("JsonSerializable", message); +- } +- +- [Fact] +- public void ValidateAotCompatibility_InNonAotMode_DoesNotThrow() +- { +- // Arrange +- var options = new DeserializationOptions(); +- +- // Act & Assert - Should not throw in non-AOT mode +- AotCompatibilityHelper.ValidateAotCompatibility(options); +- } +- +- [Fact] +- public void ValidateAotCompatibility_WithJsonSerializerContext_ValidatesType() +- { +- // Arrange +- var options = new DeserializationOptions(TestAotJsonSerializerContext.Default); +- +- // Act & Assert - Should not throw for registered type +- AotCompatibilityHelper.ValidateAotCompatibility(options); +- } +- +- [Fact] +- public void ValidateAotCompatibility_WithUnregisteredType_ThrowsException() +- { +- // Arrange +- var options = new DeserializationOptions(TestAotJsonSerializerContext.Default); +- +- // Act & Assert +- var exception = Assert.Throws(() => +- AotCompatibilityHelper.ValidateAotCompatibility(options)); +- +- Assert.Equal(typeof(UnregisteredModel), exception.TargetType); +- Assert.Contains("UnregisteredModel", exception.Message); +- } +- +- [Fact] +- public void FallbackDeserialize_WithJsonSerializerOptions_Succeeds() +- { +- // Arrange +- var json = """{"Id":1,"Name":"Test","CreatedAt":"2023-01-01T00:00:00Z"}"""; +- var options = new DeserializationOptions +- { +- JsonSerializerOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true } +- }; +- +- // Act +- var result = AotCompatibilityHelper.FallbackDeserialize(json, options); +- +- // Assert +- Assert.NotNull(result); +- Assert.Equal(1, result.Id); +- Assert.Equal("Test", result.Name); +- } +- +- [Fact] +- public void FallbackDeserialize_WithNullOptions_Succeeds() +- { +- // Arrange +- var json = """{"Id":1,"Name":"Test","CreatedAt":"2023-01-01T00:00:00Z"}"""; +- +- // Act +- var result = AotCompatibilityHelper.FallbackDeserialize(json, null); +- +- // Assert +- Assert.NotNull(result); +- Assert.Equal(1, result.Id); +- Assert.Equal("Test", result.Name); +- } +- +- #endregion +- +- #region JsonDeserializationService AOT Tests +- +- [Fact] +- public void JsonDeserializationService_WithValidJsonSerializerContext_Succeeds() +- { +- // Arrange +- var service = new JsonDeserializationService(); +- var json = """{"Id":1,"Name":"Test","CreatedAt":"2023-01-01T00:00:00Z"}"""; +- var options = new DeserializationOptions(TestAotJsonSerializerContext.Default); +- +- // Act +- var result = service.Deserialize(json, options); +- +- // Assert +- Assert.NotNull(result); +- Assert.Equal(1, result.Id); +- Assert.Equal("Test", result.Name); +- Assert.Equal(new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc), result.CreatedAt); +- } +- +- [Fact] +- public void JsonDeserializationService_WithUnregisteredTypeInContext_ThrowsException() +- { +- // Arrange +- var service = new JsonDeserializationService(); +- var json = """{"Value":"test"}"""; +- var options = new DeserializationOptions(TestAotJsonSerializerContext.Default); +- +- // Act & Assert +- var exception = Assert.Throws(() => +- service.Deserialize(json, options)); +- +- Assert.Equal(typeof(UnregisteredModel), exception.TargetType); +- Assert.Contains("UnregisteredModel", exception.Message); +- } +- +- [Fact] +- public void JsonDeserializationService_TryDeserialize_WithUnregisteredType_ReturnsFalse() +- { +- // Arrange +- var service = new JsonDeserializationService(); +- var json = """{"Value":"test"}"""; +- var options = new DeserializationOptions(TestAotJsonSerializerContext.Default); +- +- // Act +- var success = service.TryDeserialize(json, out var result, out var exception, options); +- +- // Assert +- Assert.False(success); +- Assert.Null(result); +- Assert.NotNull(exception); +- Assert.IsType(exception); +- } +- +- [Fact] +- public void JsonDeserializationService_WithPrimitiveTypes_WorksWithContext() +- { +- // Arrange +- var service = new JsonDeserializationService(); +- var options = new DeserializationOptions(TestAotJsonSerializerContext.Default); +- +- // Act & Assert +- Assert.Equal(42, service.Deserialize("42", options)); +- Assert.Equal("test", service.Deserialize("\"test\"", options)); +- } +- +- #endregion +- +- #region Exception Tests +- +- [Fact] +- public void AotCompatibilityException_ConstructorWithMessage_SetsProperties() +- { +- // Arrange +- var targetType = typeof(TestAotModel); +- var message = "Test message"; +- +- // Act +- var exception = new AotCompatibilityException(targetType, message); +- +- // Assert +- Assert.Equal(targetType, exception.TargetType); +- Assert.Equal(message, exception.Message); +- Assert.Null(exception.InnerException); +- } +- +- [Fact] +- public void AotCompatibilityException_ConstructorWithInnerException_SetsProperties() +- { +- // Arrange +- var targetType = typeof(TestAotModel); +- var message = "Test message"; +- var innerException = new InvalidOperationException("Inner"); +- +- // Act +- var exception = new AotCompatibilityException(targetType, message, innerException); +- +- // Assert +- Assert.Equal(targetType, exception.TargetType); +- Assert.Equal(message, exception.Message); +- Assert.Equal(innerException, exception.InnerException); +- } +- +- [Fact] +- public void AotTypeValidationException_ConstructorWithMessage_SetsProperties() +- { +- // Arrange +- var targetType = typeof(TestAotModel); +- var message = "Test message"; +- +- // Act +- var exception = new AotTypeValidationException(targetType, message); +- +- // Assert +- Assert.Equal(targetType, exception.TargetType); +- Assert.Equal(message, exception.Message); +- Assert.Null(exception.InnerException); +- } +- +- [Fact] +- public void AotTypeValidationException_ConstructorWithInnerException_SetsProperties() +- { +- // Arrange +- var targetType = typeof(TestAotModel); +- var message = "Test message"; +- var innerException = new NotSupportedException("Inner"); +- +- // Act +- var exception = new AotTypeValidationException(targetType, message, innerException); +- +- // Assert +- Assert.Equal(targetType, exception.TargetType); +- Assert.Equal(message, exception.Message); +- Assert.Equal(innerException, exception.InnerException); +- } +- +- #endregion +- +- #region Integration Tests +- +- [Fact] +- public void DeserializationOptions_WithJsonSerializerContext_PreservesContext() +- { +- // Arrange +- var context = TestAotJsonSerializerContext.Default; +- +- // Act +- var options = new DeserializationOptions(context); +- +- // Assert +- Assert.Equal(context, options.JsonSerializerContext); +- Assert.Null(options.JsonSerializerOptions); +- } +- +- [Fact] +- public void JsonDeserializationService_ContextTakesPrecedenceOverOptions() +- { +- // Arrange +- var service = new JsonDeserializationService(); +- var json = """{"Id":1,"Name":"Test","CreatedAt":"2023-01-01T00:00:00Z"}"""; +- var options = new DeserializationOptions +- { +- JsonSerializerContext = TestAotJsonSerializerContext.Default, +- JsonSerializerOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = false } +- }; +- +- // Act +- var result = service.Deserialize(json, options); +- +- // Assert - Should succeed because JsonSerializerContext is used instead of JsonSerializerOptions +- Assert.NotNull(result); +- Assert.Equal(1, result.Id); +- } +- +- #endregion +-} +- +-// JsonSerializerContext needs to be outside the test class and partial for source generation +-[JsonSerializable(typeof(AotCompatibilityTests.TestAotModel))] +-[JsonSerializable(typeof(string))] +-[JsonSerializable(typeof(int))] +-public partial class TestAotJsonSerializerContext : JsonSerializerContext +-{ +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/BackwardCompatibilityIntegrationTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/BackwardCompatibilityIntegrationTests.cs +deleted file mode 100644 +index a0f9ac85..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/BackwardCompatibilityIntegrationTests.cs ++++ /dev/null +@@ -1,549 +0,0 @@ +- +- +-using System; +-using System.Collections.Generic; +-using System.IO; +-using System.Linq; +-using System.Text; +-using System.Text.Json; +-using System.Threading; +-using System.Threading.Tasks; +-using Amazon.Lambda.Core; +-using Amazon.Lambda.DynamoDBEvents; +-using Amazon.Lambda.KinesisEvents; +-using Amazon.Lambda.SQSEvents; +-using AWS.Lambda.Powertools.BatchProcessing.DynamoDb; +-using AWS.Lambda.Powertools.BatchProcessing.Exceptions; +-using AWS.Lambda.Powertools.BatchProcessing.Kinesis; +-using AWS.Lambda.Powertools.BatchProcessing.Sqs; +-using AWS.Lambda.Powertools.BatchProcessing.Tests.Handlers.SQS.Custom; +-using Xunit; +- +-namespace AWS.Lambda.Powertools.BatchProcessing.Tests; +- +-/// +-/// Integration tests to verify backward compatibility with existing batch processing functionality. +-/// These tests ensure that all existing IRecordHandler implementations, BatchProcessorAttribute usage, +-/// static access patterns, and ProcessingResult structure remain unchanged. +-/// +-[Collection("Sequential")] +-public class BackwardCompatibilityIntegrationTests +-{ +- #region Static Access Pattern Tests +- +- [Fact] +- public void SqsBatchProcessor_StaticInstance_ShouldBeAccessible() +- { +- // Arrange & Act +- var instance = SqsBatchProcessor.Instance; +- +- // Assert +- Assert.NotNull(instance); +- Assert.IsAssignableFrom(instance); +- Assert.IsAssignableFrom>(instance); +- } +- +- [Fact] +- public void KinesisEventBatchProcessor_StaticInstance_ShouldBeAccessible() +- { +- // Arrange & Act +- var instance = KinesisEventBatchProcessor.Instance; +- +- // Assert +- Assert.NotNull(instance); +- Assert.IsAssignableFrom(instance); +- Assert.IsAssignableFrom>(instance); +- } +- +- [Fact] +- public void DynamoDbStreamBatchProcessor_StaticInstance_ShouldBeAccessible() +- { +- // Arrange & Act +- var instance = DynamoDbStreamBatchProcessor.Instance; +- +- // Assert +- Assert.NotNull(instance); +- Assert.IsAssignableFrom(instance); +- Assert.IsAssignableFrom>(instance); +- } +- +- [Fact] +- public void SqsBatchProcessor_StaticResult_ShouldBeAccessible() +- { +- // Arrange & Act +- var result = SqsBatchProcessor.Result; +- +- // Assert +- Assert.NotNull(result); +- Assert.IsType>(result); +- } +- +- [Fact] +- public async Task KinesisEventBatchProcessor_StaticResult_ShouldBeAccessible() +- { +- // Arrange - First use the processor to initialize the result +- var processor = KinesisEventBatchProcessor.Instance; +- var handler = new TraditionalKinesisRecordHandler(); +- var kinesisEvent = CreateSampleKinesisEvent(); +- +- // Act - Process to initialize the static result +- await processor.ProcessAsync(kinesisEvent, handler); +- var result = KinesisEventBatchProcessor.Result; +- +- // Assert +- Assert.NotNull(result); +- Assert.IsType>(result); +- } +- +- [Fact] +- public void DynamoDbStreamBatchProcessor_StaticResult_ShouldBeAccessible() +- { +- // Arrange & Act +- var result = DynamoDbStreamBatchProcessor.Result; +- +- // Assert +- Assert.NotNull(result); +- Assert.IsType>(result); +- } +- +- #endregion +- +- #region Traditional IRecordHandler Tests +- +- [Fact] +- public async Task SqsBatchProcessor_WithTraditionalRecordHandler_ShouldProcessSuccessfully() +- { +- // Arrange +- var processor = SqsBatchProcessor.Instance; +- var handler = new TraditionalSqsRecordHandler(); +- var sqsEvent = CreateSampleSqsEvent(); +- +- // Act +- var result = await processor.ProcessAsync(sqsEvent, handler); +- +- // Assert +- Assert.NotNull(result); +- Assert.IsType>(result); +- Assert.Equal(2, result.BatchRecords.Count); +- Assert.Equal(2, result.SuccessRecords.Count); +- Assert.Empty(result.FailureRecords); +- Assert.Empty(result.BatchItemFailuresResponse.BatchItemFailures); +- } +- +- [Fact] +- public async Task KinesisEventBatchProcessor_WithTraditionalRecordHandler_ShouldProcessSuccessfully() +- { +- // Arrange +- var processor = KinesisEventBatchProcessor.Instance; +- var handler = new TraditionalKinesisRecordHandler(); +- var kinesisEvent = CreateSampleKinesisEvent(); +- +- // Act +- var result = await processor.ProcessAsync(kinesisEvent, handler); +- +- // Assert +- Assert.NotNull(result); +- Assert.IsType>(result); +- Assert.Equal(2, result.BatchRecords.Count); +- Assert.Equal(2, result.SuccessRecords.Count); +- Assert.Empty(result.FailureRecords); +- Assert.Empty(result.BatchItemFailuresResponse.BatchItemFailures); +- } +- +- [Fact] +- public async Task DynamoDbStreamBatchProcessor_WithTraditionalRecordHandler_ShouldProcessSuccessfully() +- { +- // Arrange +- var processor = DynamoDbStreamBatchProcessor.Instance; +- var handler = new TraditionalDynamoDbRecordHandler(); +- var dynamoDbEvent = CreateSampleDynamoDbEvent(); +- +- // Act +- var result = await processor.ProcessAsync(dynamoDbEvent, handler); +- +- // Assert +- Assert.NotNull(result); +- Assert.IsType>(result); +- Assert.Equal(2, result.BatchRecords.Count); +- Assert.Equal(2, result.SuccessRecords.Count); +- Assert.Empty(result.FailureRecords); +- Assert.Empty(result.BatchItemFailuresResponse.BatchItemFailures); +- } +- +- #endregion +- +- #region ProcessingResult Structure Compatibility Tests +- +- [Fact] +- public async Task ProcessingResult_Structure_ShouldRemainUnchanged() +- { +- // Arrange +- var processor = SqsBatchProcessor.Instance; +- var handler = new TraditionalSqsRecordHandler(); +- var sqsEvent = CreateSampleSqsEvent(); +- +- // Act +- var result = await processor.ProcessAsync(sqsEvent, handler); +- +- // Assert - Verify all expected properties exist and have correct types +- Assert.NotNull(result.BatchItemFailuresResponse); +- Assert.IsType(result.BatchItemFailuresResponse); +- +- Assert.NotNull(result.BatchRecords); +- Assert.IsType>(result.BatchRecords); +- +- Assert.NotNull(result.SuccessRecords); +- Assert.IsType>>(result.SuccessRecords); +- +- Assert.NotNull(result.FailureRecords); +- Assert.IsType>>(result.FailureRecords); +- +- // Verify BatchItemFailuresResponse structure +- Assert.NotNull(result.BatchItemFailuresResponse.BatchItemFailures); +- Assert.IsType>(result.BatchItemFailuresResponse.BatchItemFailures); +- } +- +- [Fact] +- public async Task ProcessingResult_WithFailures_ShouldMaintainStructure() +- { +- // Arrange +- var processor = SqsBatchProcessor.Instance; +- var handler = new FailingTraditionalSqsRecordHandler(); +- var sqsEvent = CreateSampleSqsEvent(); +- var processingOptions = new ProcessingOptions +- { +- ThrowOnFullBatchFailure = false // Disable throwing on full batch failure for this test +- }; +- +- // Act +- var result = await processor.ProcessAsync(sqsEvent, handler, processingOptions); +- +- // Assert +- Assert.NotNull(result); +- Assert.Equal(2, result.BatchRecords.Count); +- Assert.Empty(result.SuccessRecords); +- Assert.Equal(2, result.FailureRecords.Count); +- Assert.Equal(2, result.BatchItemFailuresResponse.BatchItemFailures.Count); +- +- // Verify failure record structure +- var failureRecord = result.FailureRecords.First(); +- Assert.NotNull(failureRecord.Record); +- Assert.NotNull(failureRecord.Exception); +- Assert.NotNull(failureRecord.RecordId); +- +- // Verify batch item failure structure +- var batchItemFailure = result.BatchItemFailuresResponse.BatchItemFailures.First(); +- Assert.NotNull(batchItemFailure.ItemIdentifier); +- } +- +- #endregion +- +- #region BatchProcessorAttribute Compatibility Tests +- +- [Fact] +- public void BatchProcessorAttribute_WithTraditionalHandler_ShouldCreateAspectHandler() +- { +- // Arrange +- var attribute = new BatchProcessorAttribute +- { +- RecordHandler = typeof(TraditionalSqsRecordHandler) +- }; +- +- // Act +- var handler = attribute.CreateAspectHandler(new object[] { new SQSEvent() }); +- +- // Assert +- Assert.NotNull(handler); +- } +- +- [Fact] +- public void BatchProcessorAttribute_WithRecordHandlerProvider_ShouldCreateAspectHandler() +- { +- // Arrange +- var attribute = new BatchProcessorAttribute +- { +- RecordHandlerProvider = typeof(TraditionalSqsRecordHandlerProvider) +- }; +- +- // Act +- var handler = attribute.CreateAspectHandler(new object[] { new SQSEvent() }); +- +- // Assert +- Assert.NotNull(handler); +- } +- +- [Fact] +- public void BatchProcessorAttribute_WithCustomBatchProcessor_ShouldCreateAspectHandler() +- { +- // Arrange +- var attribute = new BatchProcessorAttribute +- { +- BatchProcessor = typeof(CustomSqsBatchProcessor), +- RecordHandler = typeof(TraditionalSqsRecordHandler) +- }; +- +- // Act +- var handler = attribute.CreateAspectHandler(new object[] { new SQSEvent() }); +- +- // Assert +- Assert.NotNull(handler); +- } +- +- [Fact] +- public void BatchProcessorAttribute_WithBatchProcessorProvider_ShouldCreateAspectHandler() +- { +- // Arrange +- var attribute = new BatchProcessorAttribute +- { +- BatchProcessorProvider = typeof(CustomSqsBatchProcessorProvider), +- RecordHandler = typeof(TraditionalSqsRecordHandler) +- }; +- +- // Act +- var handler = attribute.CreateAspectHandler(new object[] { new SQSEvent() }); +- +- // Assert +- Assert.NotNull(handler); +- } +- +- #endregion +- +- #region Mixed Usage Compatibility Tests +- +- [Fact] +- public async Task BatchProcessor_MixedUsage_TraditionalAndDirectCalls_ShouldWork() +- { +- // Arrange +- var processor = SqsBatchProcessor.Instance; +- var traditionalHandler = new TraditionalSqsRecordHandler(); +- var sqsEvent = CreateSampleSqsEvent(); +- +- // Act - First use traditional handler +- var result1 = await processor.ProcessAsync(sqsEvent, traditionalHandler); +- +- // Then use direct processor call (simulating mixed usage) +- var result2 = await processor.ProcessAsync(sqsEvent, traditionalHandler, CancellationToken.None); +- +- // Assert +- Assert.NotNull(result1); +- Assert.NotNull(result2); +- Assert.Equal(result1.BatchRecords.Count, result2.BatchRecords.Count); +- Assert.Equal(result1.SuccessRecords.Count, result2.SuccessRecords.Count); +- } +- +- #endregion +- +- #region Error Handling Compatibility Tests +- +- [Fact] +- public async Task BatchProcessor_ErrorHandling_ShouldMaintainExistingBehavior() +- { +- // Arrange +- var processor = SqsBatchProcessor.Instance; +- var handler = new PartiallyFailingTraditionalSqsRecordHandler(); +- var sqsEvent = CreateSampleSqsEvent(); +- +- // Act +- var result = await processor.ProcessAsync(sqsEvent, handler); +- +- // Assert +- Assert.NotNull(result); +- Assert.Equal(2, result.BatchRecords.Count); +- Assert.Single(result.SuccessRecords); +- Assert.Single(result.FailureRecords); +- Assert.Single(result.BatchItemFailuresResponse.BatchItemFailures); +- +- // Verify error structure remains the same +- var failureRecord = result.FailureRecords.First(); +- Assert.Contains("Failed processing record", failureRecord.Exception.Message); +- Assert.IsType(failureRecord.Exception); +- } +- +- #endregion +- +- #region Performance Compatibility Tests +- +- [Fact] +- public async Task BatchProcessor_ParallelProcessing_ShouldMaintainCompatibility() +- { +- // Arrange +- var processor = SqsBatchProcessor.Instance; +- var handler = new TraditionalSqsRecordHandler(); +- var sqsEvent = CreateLargeSampleSqsEvent(10); +- var processingOptions = new ProcessingOptions +- { +- BatchParallelProcessingEnabled = true, +- MaxDegreeOfParallelism = 4 +- }; +- +- // Act +- var result = await processor.ProcessAsync(sqsEvent, handler, processingOptions); +- +- // Assert +- Assert.NotNull(result); +- Assert.Equal(10, result.BatchRecords.Count); +- Assert.Equal(10, result.SuccessRecords.Count); +- Assert.Empty(result.FailureRecords); +- } +- +- #endregion +- +- #region Helper Methods and Classes +- +- private static SQSEvent CreateSampleSqsEvent() +- { +- return new SQSEvent +- { +- Records = new List +- { +- new() +- { +- MessageId = "msg-1", +- Body = JsonSerializer.Serialize(new { Id = 1, Name = "Product 1" }), +- EventSourceArn = "arn:aws:sqs:us-east-1:123456789012:test-queue" +- }, +- new() +- { +- MessageId = "msg-2", +- Body = JsonSerializer.Serialize(new { Id = 2, Name = "Product 2" }), +- EventSourceArn = "arn:aws:sqs:us-east-1:123456789012:test-queue" +- } +- } +- }; +- } +- +- private static SQSEvent CreateLargeSampleSqsEvent(int recordCount) +- { +- var records = new List(); +- for (int i = 1; i <= recordCount; i++) +- { +- records.Add(new SQSEvent.SQSMessage +- { +- MessageId = $"msg-{i}", +- Body = JsonSerializer.Serialize(new { Id = i, Name = $"Product {i}" }), +- EventSourceArn = "arn:aws:sqs:us-east-1:123456789012:test-queue" +- }); +- } +- +- return new SQSEvent { Records = records }; +- } +- +- private static KinesisEvent CreateSampleKinesisEvent() +- { +- return new KinesisEvent +- { +- Records = new List +- { +- new() +- { +- Kinesis = new KinesisEvent.Record +- { +- SequenceNumber = "1", +- Data = new MemoryStream(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(new { Id = 1, Name = "Event 1" }))) +- } +- }, +- new() +- { +- Kinesis = new KinesisEvent.Record +- { +- SequenceNumber = "2", +- Data = new MemoryStream(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(new { Id = 2, Name = "Event 2" }))) +- } +- } +- } +- }; +- } +- +- private static DynamoDBEvent CreateSampleDynamoDbEvent() +- { +- return new DynamoDBEvent +- { +- Records = new List +- { +- new() +- { +- Dynamodb = new DynamoDBEvent.StreamRecord +- { +- SequenceNumber = "seq-1" +- } +- }, +- new() +- { +- Dynamodb = new DynamoDBEvent.StreamRecord +- { +- SequenceNumber = "seq-2" +- } +- } +- } +- }; +- } +- +- // Traditional record handlers for testing +- public class TraditionalSqsRecordHandler : IRecordHandler +- { +- public Task HandleAsync(SQSEvent.SQSMessage record, CancellationToken cancellationToken) +- { +- return Task.FromResult(RecordHandlerResult.None); +- } +- } +- +- public class TraditionalKinesisRecordHandler : IRecordHandler +- { +- public Task HandleAsync(KinesisEvent.KinesisEventRecord record, CancellationToken cancellationToken) +- { +- return Task.FromResult(RecordHandlerResult.None); +- } +- } +- +- public class TraditionalDynamoDbRecordHandler : IRecordHandler +- { +- public Task HandleAsync(DynamoDBEvent.DynamodbStreamRecord record, CancellationToken cancellationToken) +- { +- return Task.FromResult(RecordHandlerResult.None); +- } +- } +- +- public class FailingTraditionalSqsRecordHandler : IRecordHandler +- { +- public Task HandleAsync(SQSEvent.SQSMessage record, CancellationToken cancellationToken) +- { +- throw new InvalidOperationException("Simulated failure"); +- } +- } +- +- public class PartiallyFailingTraditionalSqsRecordHandler : IRecordHandler +- { +- public Task HandleAsync(SQSEvent.SQSMessage record, CancellationToken cancellationToken) +- { +- if (record.MessageId == "msg-1") +- { +- throw new InvalidOperationException("Simulated failure for first record"); +- } +- return Task.FromResult(RecordHandlerResult.None); +- } +- } +- +- // Provider classes for testing +- public class TraditionalSqsRecordHandlerProvider : IRecordHandlerProvider +- { +- public IRecordHandler Create() +- { +- return new TraditionalSqsRecordHandler(); +- } +- } +- +- public class CustomSqsBatchProcessor : SqsBatchProcessor +- { +- // Custom implementation for testing +- } +- +- public class CustomSqsBatchProcessorProvider : IBatchProcessorProvider +- { +- public IBatchProcessor Create() +- { +- return new CustomSqsBatchProcessor(); +- } +- } +- +- #endregion +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/BatchProcessingAttributeTest.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/BatchProcessingAttributeTest.cs +index d5078234..db2bee36 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/BatchProcessingAttributeTest.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/BatchProcessingAttributeTest.cs +@@ -1,224 +1,31 @@ +- ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ + +-using System; +-using System.Text.Json.Serialization; +-using System.Threading; +-using System.Threading.Tasks; +-using Amazon.Lambda.Core; +-using Amazon.Lambda.SQSEvents; +-using AWS.Lambda.Powertools.BatchProcessing.Tests.Handlers.SQS.Custom; + using Xunit; + + namespace AWS.Lambda.Powertools.BatchProcessing.Tests + { + [Collection("Sequential")] +- public partial class BatchProcessingAttributeTest ++ public class BatchProcessingAttributeTest + { + [Fact] +- public void BatchProcessorAttribute_WithTypedRecordHandler_ThrowsNotSupportedException() ++ public void OnEntry_Test() + { + // Arrange +- var attribute = new BatchProcessorAttribute +- { +- TypedRecordHandler = typeof(TestTypedRecordHandler) +- }; +- +- // Act & Assert +- var exception = Assert.Throws(() => +- attribute.CreateAspectHandler(new object[] { new SQSEvent() })); +- +- Assert.Contains("Typed record handlers are not yet fully supported with BatchProcessorAttribute", exception.Message); +- } +- +- [Fact] +- public void BatchProcessorAttribute_WithTypedRecordHandlerProvider_ThrowsNotSupportedException() +- { +- // Arrange +- var attribute = new BatchProcessorAttribute +- { +- TypedRecordHandlerProvider = typeof(TestTypedRecordHandlerProvider) +- }; +- +- // Act & Assert +- var exception = Assert.Throws(() => +- attribute.CreateAspectHandler(new object[] { new SQSEvent() })); +- +- Assert.Contains("Typed record handlers are not yet fully supported with BatchProcessorAttribute", exception.Message); +- } +- +- [Fact] +- public void BatchProcessorAttribute_WithTypedRecordHandlerWithContext_ThrowsNotSupportedException() +- { +- // Arrange +- var attribute = new BatchProcessorAttribute +- { +- TypedRecordHandlerWithContext = typeof(TestTypedRecordHandlerWithContext) +- }; +- +- // Act & Assert +- var exception = Assert.Throws(() => +- attribute.CreateAspectHandler(new object[] { new SQSEvent() })); +- +- Assert.Contains("Typed record handlers are not yet fully supported with BatchProcessorAttribute", exception.Message); +- } +- +- [Fact] +- public void BatchProcessorAttribute_WithTypedRecordHandlerWithContextProvider_ThrowsNotSupportedException() +- { +- // Arrange +- var attribute = new BatchProcessorAttribute +- { +- TypedRecordHandlerWithContextProvider = typeof(TestTypedRecordHandlerWithContextProvider) +- }; +- +- // Act & Assert +- var exception = Assert.Throws(() => +- attribute.CreateAspectHandler(new object[] { new SQSEvent() })); +- +- Assert.Contains("Typed record handlers are not yet fully supported with BatchProcessorAttribute", exception.Message); +- } +- +- [Fact] +- public void BatchProcessorAttribute_WithMultipleHandlerTypes_ThrowsInvalidOperationException() +- { +- // Arrange +- var attribute = new BatchProcessorAttribute +- { +- RecordHandler = typeof(CustomSqsRecordHandler), +- TypedRecordHandler = typeof(TestTypedRecordHandler) +- }; +- +- // Act & Assert +- var exception = Assert.Throws(() => +- attribute.CreateAspectHandler(new object[] { new SQSEvent() })); +- +- Assert.Contains("Only one type of handler (traditional or typed) can be configured at a time", exception.Message); +- } +- +- [Fact] +- public void BatchProcessorAttribute_WithNoHandlers_ThrowsInvalidOperationException() +- { +- // Arrange +- var attribute = new BatchProcessorAttribute(); +- +- // Act & Assert +- var exception = Assert.Throws(() => +- attribute.CreateAspectHandler(new object[] { new SQSEvent() })); +- +- Assert.Contains("A record handler, record handler provider, typed record handler, or typed record handler provider is required", exception.Message); +- } +- +- [Fact] +- public void BatchProcessorAttribute_WithInvalidJsonSerializerContext_ThrowsInvalidOperationException() +- { +- // Arrange +- var attribute = new BatchProcessorAttribute +- { +- RecordHandler = typeof(CustomSqsRecordHandler), +- JsonSerializerContext = typeof(string) // Invalid type +- }; +- +- // Act & Assert +- var exception = Assert.Throws(() => +- attribute.CreateAspectHandler(new object[] { new SQSEvent() })); +- +- Assert.Contains("The provided JsonSerializerContext must inherit from", exception.Message); +- } +- +- [Fact] +- public void BatchProcessorAttribute_WithValidJsonSerializerContext_DoesNotThrow() +- { +- // Arrange - Use a mock type that inherits from JsonSerializerContext for validation +- var attribute = new BatchProcessorAttribute +- { +- RecordHandler = typeof(CustomSqsRecordHandler), +- // We'll skip this test since it requires complex source generation setup +- // The validation logic is tested in the invalid case above +- }; +- +- // Act & Assert - Should not throw during validation +- // The actual processing would still work with traditional handlers +- Assert.NotNull(attribute); +- } +- +- [Fact] +- public void BatchProcessorAttribute_DeserializationErrorPolicy_DefaultValue() +- { +- // Arrange & Act +- var attribute = new BatchProcessorAttribute(); +- +- // Assert +- Assert.Equal(DeserializationErrorPolicy.FailRecord, attribute.DeserializationErrorPolicy); +- } +- +- [Fact] +- public void BatchProcessorAttribute_DeserializationErrorPolicy_CanBeSet() +- { +- // Arrange +- var attribute = new BatchProcessorAttribute +- { +- DeserializationErrorPolicy = DeserializationErrorPolicy.IgnoreRecord +- }; +- +- // Act & Assert +- Assert.Equal(DeserializationErrorPolicy.IgnoreRecord, attribute.DeserializationErrorPolicy); +- } +- +- [Fact] +- public void BatchProcessorAttribute_BackwardCompatibility_WithTraditionalHandler() +- { +- // Arrange +- var attribute = new BatchProcessorAttribute +- { +- RecordHandler = typeof(CustomSqsRecordHandler) +- }; +- +- // Act - This should work as before (traditional processing) +- var handler = attribute.CreateAspectHandler(new object[] { new SQSEvent() }); +- ++ // Act + // Assert +- Assert.NotNull(handler); +- } +- +- // Test helper classes +- private class TestTypedRecordHandler : ITypedRecordHandler +- { +- public Task HandleAsync(TestData data, CancellationToken cancellationToken) +- { +- return Task.FromResult(RecordHandlerResult.None); +- } + } +- +- private class TestTypedRecordHandlerProvider : ITypedRecordHandlerProvider +- { +- public ITypedRecordHandler Create() +- { +- return new TestTypedRecordHandler(); +- } +- } +- +- private class TestTypedRecordHandlerWithContext : ITypedRecordHandlerWithContext +- { +- public Task HandleAsync(TestData data, ILambdaContext context, CancellationToken cancellationToken) +- { +- return Task.FromResult(RecordHandlerResult.None); +- } +- } +- +- private class TestTypedRecordHandlerWithContextProvider : ITypedRecordHandlerWithContextProvider +- { +- public ITypedRecordHandlerWithContext Create() +- { +- return new TestTypedRecordHandlerWithContext(); +- } +- } +- +- private class TestData +- { +- public string Message { get; set; } +- public int Id { get; set; } +- } +- +- // Removed TestJsonSerializerContext to avoid source generation conflicts + } + } +diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/BatchProcessingTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/BatchProcessingTests.cs +index 6d204052..a59866e7 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/BatchProcessingTests.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/BatchProcessingTests.cs +@@ -1,4 +1,17 @@ +- ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ + + using System; + using System.Collections.Generic; +diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/DeserializationErrorHandlingTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/DeserializationErrorHandlingTests.cs +deleted file mode 100644 +index 755e7712..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/DeserializationErrorHandlingTests.cs ++++ /dev/null +@@ -1,225 +0,0 @@ +- +- +-using System; +-using System.Threading; +-using System.Threading.Tasks; +-using AWS.Lambda.Powertools.BatchProcessing; +-using AWS.Lambda.Powertools.BatchProcessing.Exceptions; +-using Xunit; +- +-namespace AWS.Lambda.Powertools.BatchProcessing.Tests; +- +-public class DeserializationErrorHandlingTests +-{ +- [Fact] +- public void DeserializationException_WithAllParameters_SetsPropertiesCorrectly() +- { +- // Arrange +- var recordData = "{\"invalid\": json}"; +- var targetType = typeof(TestModel); +- var recordId = "test-record-123"; +- var innerException = new ArgumentException("Invalid JSON"); +- +- // Act +- var exception = new DeserializationException(recordData, targetType, recordId, innerException); +- +- // Assert +- Assert.Equal(recordData, exception.RecordData); +- Assert.Equal(targetType, exception.TargetType); +- Assert.Equal(recordId, exception.RecordId); +- Assert.Equal(innerException, exception.InnerException); +- Assert.Contains("Failed to deserialize record 'test-record-123' to type 'TestModel'", exception.Message); +- } +- +- [Fact] +- public void DeserializationException_WithoutRecordId_UsesUnknownAsDefault() +- { +- // Arrange +- var recordData = "{\"invalid\": json}"; +- var targetType = typeof(TestModel); +- var innerException = new ArgumentException("Invalid JSON"); +- +- // Act +- var exception = new DeserializationException(recordData, targetType, innerException); +- +- // Assert +- Assert.Equal(recordData, exception.RecordData); +- Assert.Equal(targetType, exception.TargetType); +- Assert.Equal("Unknown", exception.RecordId); +- Assert.Equal(innerException, exception.InnerException); +- Assert.Contains("Failed to deserialize record 'Unknown' to type 'TestModel'", exception.Message); +- } +- +- [Fact] +- public void DeserializationException_WithNullTargetType_HandlesGracefully() +- { +- // Arrange +- var recordData = "{\"invalid\": json}"; +- Type targetType = null; +- var recordId = "test-record-123"; +- var innerException = new ArgumentException("Invalid JSON"); +- +- // Act +- var exception = new DeserializationException(recordData, targetType, recordId, innerException); +- +- // Assert +- Assert.Equal(recordData, exception.RecordData); +- Assert.Null(exception.TargetType); +- Assert.Equal(recordId, exception.RecordId); +- Assert.Contains("Failed to deserialize record 'test-record-123' to type 'Unknown'", exception.Message); +- } +- +- [Fact] +- public void DeserializationException_WithMessageOnly_CreatesCorrectly() +- { +- // Arrange +- var message = "Custom error message"; +- +- // Act +- var exception = new DeserializationException(message); +- +- // Assert +- Assert.Equal(message, exception.Message); +- Assert.Null(exception.InnerException); +- } +- +- [Fact] +- public void DeserializationException_WithMessageAndInnerException_CreatesCorrectly() +- { +- // Arrange +- var message = "Custom error message"; +- var innerException = new ArgumentException("Inner error"); +- +- // Act +- var exception = new DeserializationException(message, innerException); +- +- // Assert +- Assert.Equal(message, exception.Message); +- Assert.Equal(innerException, exception.InnerException); +- } +- +- [Theory] +- [InlineData(DeserializationErrorPolicy.FailRecord)] +- [InlineData(DeserializationErrorPolicy.IgnoreRecord)] +- [InlineData(DeserializationErrorPolicy.CustomHandler)] +- public void DeserializationErrorPolicy_AllValuesAreDefined(DeserializationErrorPolicy policy) +- { +- // Act & Assert - Should not throw +- var policyName = policy.ToString(); +- Assert.NotNull(policyName); +- Assert.NotEmpty(policyName); +- } +- +- [Fact] +- public void DeserializationOptions_DefaultErrorPolicy_IsFailRecord() +- { +- // Act +- var options = new DeserializationOptions(); +- +- // Assert +- Assert.Equal(DeserializationErrorPolicy.FailRecord, options.ErrorPolicy); +- } +- +- [Fact] +- public void DeserializationOptions_CanSetErrorPolicy() +- { +- // Arrange +- var options = new DeserializationOptions(); +- +- // Act +- options.ErrorPolicy = DeserializationErrorPolicy.IgnoreRecord; +- +- // Assert +- Assert.Equal(DeserializationErrorPolicy.IgnoreRecord, options.ErrorPolicy); +- } +- +- [Fact] +- public void DeserializationOptions_BackwardCompatibility_IgnoreDeserializationErrorsStillWorks() +- { +- // Arrange +- var options = new DeserializationOptions(); +- +- // Act +- #pragma warning disable CS0618 // Type or member is obsolete +- options.IgnoreDeserializationErrors = true; +- #pragma warning restore CS0618 // Type or member is obsolete +- +- // Assert +- #pragma warning disable CS0618 // Type or member is obsolete +- Assert.True(options.IgnoreDeserializationErrors); +- #pragma warning restore CS0618 // Type or member is obsolete +- } +- +- [Fact] +- public async Task TestDeserializationErrorHandler_HandleDeserializationError_ReturnsExpectedResult() +- { +- // Arrange +- var handler = new TestDeserializationErrorHandler(); +- var record = new TestRecord { Id = "test-123", Data = "test-data" }; +- var exception = new DeserializationException("Test error"); +- var cancellationToken = CancellationToken.None; +- +- // Act +- var result = await handler.HandleDeserializationError(record, exception, cancellationToken); +- +- // Assert +- Assert.NotNull(result); +- Assert.Equal("Error handling record: test-123", result.Data); +- } +- +- [Fact] +- public async Task TestDeserializationErrorHandler_WithNullRecord_HandlesGracefully() +- { +- // Arrange +- var handler = new TestDeserializationErrorHandler(); +- TestRecord record = null; +- var exception = new DeserializationException("Test error"); +- var cancellationToken = CancellationToken.None; +- +- // Act +- var result = await handler.HandleDeserializationError(record, exception, cancellationToken); +- +- // Assert +- Assert.NotNull(result); +- Assert.Equal("Error handling record: unknown", result.Data); +- } +- +- [Fact] +- public async Task TestDeserializationErrorHandler_WithCancellation_RespectsCancellationToken() +- { +- // Arrange +- var handler = new TestDeserializationErrorHandler(); +- var record = new TestRecord { Id = "test-123", Data = "test-data" }; +- var exception = new DeserializationException("Test error"); +- var cancellationTokenSource = new CancellationTokenSource(); +- cancellationTokenSource.Cancel(); +- +- // Act & Assert +- await Assert.ThrowsAsync( +- () => handler.HandleDeserializationError(record, exception, cancellationTokenSource.Token)); +- } +- +- // Test models and helpers +- public class TestModel +- { +- public string Name { get; set; } +- public int Value { get; set; } +- } +- +- public class TestRecord +- { +- public string Id { get; set; } +- public string Data { get; set; } +- } +- +- public class TestDeserializationErrorHandler : IDeserializationErrorHandler +- { +- public Task HandleDeserializationError(TestRecord record, Exception exception, CancellationToken cancellationToken) +- { +- cancellationToken.ThrowIfCancellationRequested(); +- +- var recordId = record?.Id ?? "unknown"; +- return Task.FromResult(RecordHandlerResult.FromData($"Error handling record: {recordId}")); +- } +- } +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/Custom/CustomDynamoDbStreamBatchProcessors.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/Custom/CustomDynamoDbStreamBatchProcessors.cs +index ca84f0a3..1abf5658 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/Custom/CustomDynamoDbStreamBatchProcessors.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/Custom/CustomDynamoDbStreamBatchProcessors.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.Collections.Generic; + using System.Linq; +diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/Custom/CustomDynamoDbStreamHandlers.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/Custom/CustomDynamoDbStreamHandlers.cs +index 033f5941..767c3b1d 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/Custom/CustomDynamoDbStreamHandlers.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/Custom/CustomDynamoDbStreamHandlers.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.Text.Json; + using System.Threading; +diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/CustomProcessorTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/CustomProcessorTests.cs +index e2b4d8ac..bce9ed57 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/CustomProcessorTests.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/CustomProcessorTests.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using Xunit; + using TestHelper = AWS.Lambda.Powertools.BatchProcessing.Tests.Helpers.Helpers; + using System.Threading.Tasks; +diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/Handler/HandlerFunction.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/Handler/HandlerFunction.cs +index abce8320..52d6f087 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/Handler/HandlerFunction.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/Handler/HandlerFunction.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.Text.Json; + using System.Threading.Tasks; +diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/HandlerTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/HandlerTests.cs +index f7b3b193..68d6d083 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/HandlerTests.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/HandlerTests.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.Threading.Tasks; + using Amazon.Lambda.DynamoDBEvents; +diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/HandlerValidationTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/HandlerValidationTests.cs +index c7d79ab4..9152ab16 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/HandlerValidationTests.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/HandlerValidationTests.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using TestHelper = AWS.Lambda.Powertools.BatchProcessing.Tests.Helpers.Helpers; + using Xunit; + using System; +diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/Services.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/Services.cs +index 9475b9d5..224a2eec 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/Services.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/Services.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using AWS.Lambda.Powertools.BatchProcessing.DynamoDb; + using AWS.Lambda.Powertools.BatchProcessing.Tests.Handlers.DynamoDB.Custom; +diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/Custom/CustomKinesisBatchProcessors.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/Custom/CustomKinesisBatchProcessors.cs +index f0082a21..473bef73 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/Custom/CustomKinesisBatchProcessors.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/Custom/CustomKinesisBatchProcessors.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.Collections.Generic; + using System.Linq; +diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/Custom/CustomKinesisHandlers.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/Custom/CustomKinesisHandlers.cs +index 1dde72b8..27a517e9 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/Custom/CustomKinesisHandlers.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/Custom/CustomKinesisHandlers.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.Text.Json; + using System.Threading; +diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/CustomProcessorTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/CustomProcessorTests.cs +index 07284bc3..fe094737 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/CustomProcessorTests.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/CustomProcessorTests.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System.Threading.Tasks; + using Amazon.Lambda.KinesisEvents; + using AWS.Lambda.Powertools.BatchProcessing.Kinesis; +diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/Handler/HandlerFunction.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/Handler/HandlerFunction.cs +index 8d9864ef..233b52de 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/Handler/HandlerFunction.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/Handler/HandlerFunction.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.Text.Json; + using System.Threading.Tasks; +diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/HandlerTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/HandlerTests.cs +index c0f0f7c6..07758fd3 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/HandlerTests.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/HandlerTests.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.Threading.Tasks; + using Amazon.Lambda.KinesisEvents; +diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/HandlerValidationTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/HandlerValidationTests.cs +index 88cee6d1..9f9398e6 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/HandlerValidationTests.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/HandlerValidationTests.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.Threading.Tasks; + using Amazon.Lambda.KinesisEvents; +diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/Services.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/Services.cs +index 613d076f..0c7fbc79 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/Services.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/Services.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using AWS.Lambda.Powertools.BatchProcessing.Kinesis; + using AWS.Lambda.Powertools.BatchProcessing.Tests.Handlers.Kinesis.Custom; +diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/Custom/CustomSqsBatchProcessors.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/Custom/CustomSqsBatchProcessors.cs +index 19e95d83..af477753 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/Custom/CustomSqsBatchProcessors.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/Custom/CustomSqsBatchProcessors.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.Collections.Generic; + using System.Linq; +diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/Custom/CustomSqsHandlers.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/Custom/CustomSqsHandlers.cs +index 1479b08d..06c87acd 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/Custom/CustomSqsHandlers.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/Custom/CustomSqsHandlers.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.Text.Json; + using System.Threading; +diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/CustomProcessorTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/CustomProcessorTests.cs +index 5cf5ee91..aba71da0 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/CustomProcessorTests.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/CustomProcessorTests.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System.Threading.Tasks; + using Amazon.Lambda.SQSEvents; + using AWS.Lambda.Powertools.BatchProcessing.Sqs; +diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/Handler/HandlerFunction.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/Handler/HandlerFunction.cs +index f7a73de3..9bfbf90b 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/Handler/HandlerFunction.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/Handler/HandlerFunction.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.Text.Json; + using System.Threading.Tasks; +@@ -132,7 +147,7 @@ public class HandlerFunction + public BatchItemFailuresResponse HandlerUsingAttributeAllFail_ThrowOnFullBatchFailureFalseEnv(SQSEvent _) + { + return SqsBatchProcessor.Result.BatchItemFailuresResponse; +- } ++ } + + public async Task HandlerUsingUtilityAllFail_ThrowOnFullBatchFailureFalseOption(SQSEvent sqsEvent) + { +@@ -160,7 +175,7 @@ public class HandlerFunction + public BatchItemFailuresResponse HandlerUsingAttributeFailAll_StopOnFirstErrorAttr_ThrowOnFullBatchFailureFalseEnv(SQSEvent _) + { + return SqsBatchProcessor.Result.BatchItemFailuresResponse; +- } ++ } + + public async Task HandlerUsingUtility_StopOnFirstErrorOption_ThrowOnFullBatchFailureFalseOption(SQSEvent sqsEvent) + { +diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/HandlerTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/HandlerTests.cs +index 2c31eb2f..04bd0b21 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/HandlerTests.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/HandlerTests.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.Threading.Tasks; + using Amazon.Lambda.SQSEvents; +@@ -28,8 +43,8 @@ public class HandlerTests : IDisposable + Assert.Equal("4", response.BatchItemFailures[1].ItemIdentifier); + + return Task.CompletedTask; +- } +- ++ } ++ + [Fact] + public Task Sqs_Handler_All_Fail_Using_Attribute_Should_Throw_BatchProcessingException() + { +@@ -194,7 +209,7 @@ public class HandlerTests : IDisposable + Assert.Equal("5", response.BatchItemFailures[4].ItemIdentifier); + + return Task.CompletedTask; +- } ++ } + + [Fact] + public Task Sqs_Handler_Using_Attribute_All_Fail_Should_Not_Throw_BatchProcessingException_With_Throw_On_Full_Batch_Failure_False_Env() +diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/HandlerValidationTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/HandlerValidationTests.cs +index 07574214..6996a769 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/HandlerValidationTests.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/HandlerValidationTests.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.Threading.Tasks; + using Amazon.Lambda.SQSEvents; +diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/Services.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/Services.cs +index 9339824d..f6b4227b 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/Services.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/Services.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using AWS.Lambda.Powertools.BatchProcessing.Sqs; + using AWS.Lambda.Powertools.BatchProcessing.Tests.Handlers.SQS.Custom; +diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Helpers/Helpers.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Helpers/Helpers.cs +index 79231499..da090c51 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Helpers/Helpers.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Helpers/Helpers.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System.Collections.Generic; + using System.IO; + using System.Linq; +diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/ITypedBatchProcessorIntegrationTest.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/ITypedBatchProcessorIntegrationTest.cs +deleted file mode 100644 +index d139bdf9..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/ITypedBatchProcessorIntegrationTest.cs ++++ /dev/null +@@ -1,192 +0,0 @@ +- +- +-using System; +-using System.Threading; +-using System.Threading.Tasks; +-using Amazon.Lambda.Core; +-using Amazon.Lambda.SQSEvents; +-using Xunit; +- +-namespace AWS.Lambda.Powertools.BatchProcessing.Tests; +- +-/// +-/// Integration tests to verify ITypedBatchProcessor interface accessibility and usage. +-/// +-public class ITypedBatchProcessorIntegrationTest +-{ +- [Fact] +- public void ITypedBatchProcessor_ShouldBeAccessibleFromNamespace() +- { +- // Arrange & Act +- var interfaceType = typeof(ITypedBatchProcessor); +- +- // Assert +- Assert.NotNull(interfaceType); +- Assert.True(interfaceType.IsInterface); +- Assert.Equal("AWS.Lambda.Powertools.BatchProcessing", interfaceType.Namespace); +- } +- +- [Fact] +- public void ITypedBatchProcessor_ShouldHaveCorrectGenericParameters() +- { +- // Arrange & Act +- var interfaceType = typeof(ITypedBatchProcessor<,>); +- var genericParameters = interfaceType.GetGenericArguments(); +- +- // Assert +- Assert.Equal(2, genericParameters.Length); +- Assert.Equal("TEvent", genericParameters[0].Name); +- Assert.Equal("TRecord", genericParameters[1].Name); +- } +- +- [Fact] +- public void ITypedBatchProcessor_ShouldBeImplementableByConcreteClass() +- { +- // Arrange & Act +- var concreteType = typeof(TestTypedBatchProcessor); +- var interfaceType = typeof(ITypedBatchProcessor); +- +- // Assert +- Assert.True(interfaceType.IsAssignableFrom(concreteType)); +- } +- +- [Fact] +- public async Task ITypedBatchProcessor_ShouldSupportTypedRecordHandlers() +- { +- // Arrange +- var processor = new TestTypedBatchProcessor(); +- var handler = new TestTypedRecordHandler(); +- var sqsEvent = new SQSEvent(); +- +- // Act +- var result = await processor.ProcessAsync(sqsEvent, handler); +- +- // Assert +- Assert.NotNull(result); +- Assert.IsType>(result); +- } +- +- [Fact] +- public async Task ITypedBatchProcessor_ShouldSupportTypedRecordHandlersWithContext() +- { +- // Arrange +- var processor = new TestTypedBatchProcessor(); +- var handler = new TestTypedRecordHandlerWithContext(); +- var sqsEvent = new SQSEvent(); +- var context = new TestLambdaContext(); +- +- // Act +- var result = await processor.ProcessAsync(sqsEvent, handler, context); +- +- // Assert +- Assert.NotNull(result); +- Assert.IsType>(result); +- } +- +- /// +- /// Test implementation of ITypedBatchProcessor for integration testing. +- /// +- public class TestTypedBatchProcessor : ITypedBatchProcessor +- { +- public ProcessingResult ProcessingResult { get; } = new ProcessingResult(); +- +- public Task> ProcessAsync(SQSEvent @event, ITypedRecordHandler recordHandler) +- { +- return Task.FromResult(ProcessingResult); +- } +- +- public Task> ProcessAsync(SQSEvent @event, ITypedRecordHandler recordHandler, DeserializationOptions deserializationOptions) +- { +- return Task.FromResult(ProcessingResult); +- } +- +- public Task> ProcessAsync(SQSEvent @event, ITypedRecordHandler recordHandler, CancellationToken cancellationToken) +- { +- return Task.FromResult(ProcessingResult); +- } +- +- public Task> ProcessAsync(SQSEvent @event, ITypedRecordHandler recordHandler, DeserializationOptions deserializationOptions, CancellationToken cancellationToken) +- { +- return Task.FromResult(ProcessingResult); +- } +- +- public Task> ProcessAsync(SQSEvent @event, ITypedRecordHandler recordHandler, DeserializationOptions deserializationOptions, ProcessingOptions processingOptions) +- { +- return Task.FromResult(ProcessingResult); +- } +- +- public Task> ProcessAsync(SQSEvent @event, ITypedRecordHandlerWithContext recordHandler, ILambdaContext context) +- { +- return Task.FromResult(ProcessingResult); +- } +- +- public Task> ProcessAsync(SQSEvent @event, ITypedRecordHandlerWithContext recordHandler, ILambdaContext context, DeserializationOptions deserializationOptions) +- { +- return Task.FromResult(ProcessingResult); +- } +- +- public Task> ProcessAsync(SQSEvent @event, ITypedRecordHandlerWithContext recordHandler, ILambdaContext context, CancellationToken cancellationToken) +- { +- return Task.FromResult(ProcessingResult); +- } +- +- public Task> ProcessAsync(SQSEvent @event, ITypedRecordHandlerWithContext recordHandler, ILambdaContext context, DeserializationOptions deserializationOptions, CancellationToken cancellationToken) +- { +- return Task.FromResult(ProcessingResult); +- } +- +- public Task> ProcessAsync(SQSEvent @event, ITypedRecordHandlerWithContext recordHandler, ILambdaContext context, DeserializationOptions deserializationOptions, ProcessingOptions processingOptions) +- { +- return Task.FromResult(ProcessingResult); +- } +- } +- +- /// +- /// Test typed record handler. +- /// +- public class TestTypedRecordHandler : ITypedRecordHandler +- { +- public Task HandleAsync(TestData data, CancellationToken cancellationToken) +- { +- return Task.FromResult(RecordHandlerResult.FromData("Success")); +- } +- } +- +- /// +- /// Test typed record handler with context. +- /// +- public class TestTypedRecordHandlerWithContext : ITypedRecordHandlerWithContext +- { +- public Task HandleAsync(TestData data, ILambdaContext context, CancellationToken cancellationToken) +- { +- return Task.FromResult(RecordHandlerResult.FromData("Success with context")); +- } +- } +- +- /// +- /// Test data class. +- /// +- public class TestData +- { +- public string Id { get; set; } +- public string Name { get; set; } +- } +- +- /// +- /// Test Lambda context. +- /// +- public class TestLambdaContext : ILambdaContext +- { +- public string AwsRequestId { get; set; } = "test-request-id"; +- public IClientContext ClientContext { get; set; } +- public string FunctionName { get; set; } = "test-function"; +- public string FunctionVersion { get; set; } = "1.0"; +- public ICognitoIdentity Identity { get; set; } +- public string InvokedFunctionArn { get; set; } = "arn:aws:lambda:us-east-1:123456789012:function:test-function"; +- public ILambdaLogger Logger { get; set; } +- public string LogGroupName { get; set; } = "/aws/lambda/test-function"; +- public string LogStreamName { get; set; } = "2023/01/01/[$LATEST]abcdef123456"; +- public int MemoryLimitInMB { get; set; } = 128; +- public TimeSpan RemainingTime { get; set; } = TimeSpan.FromMinutes(5); +- } +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/ITypedBatchProcessorTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/ITypedBatchProcessorTests.cs +deleted file mode 100644 +index 5b68dfe7..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/ITypedBatchProcessorTests.cs ++++ /dev/null +@@ -1,382 +0,0 @@ +- +- +-using System; +-using System.Threading; +-using System.Threading.Tasks; +-using Amazon.Lambda.Core; +-using Amazon.Lambda.SQSEvents; +-using Xunit; +- +-namespace AWS.Lambda.Powertools.BatchProcessing.Tests; +- +-/// +-/// Tests for ITypedBatchProcessor interface contracts. +-/// +-public class ITypedBatchProcessorTests +-{ +- /// +- /// Test data class for testing typed record handlers. +- /// +- public class TestData +- { +- public string Id { get; set; } +- public string Name { get; set; } +- public int Value { get; set; } +- } +- +- /// +- /// Mock implementation of ITypedBatchProcessor for testing interface contracts. +- /// +- public class MockTypedBatchProcessor : ITypedBatchProcessor +- { +- public ProcessingResult ProcessingResult { get; private set; } +- +- public MockTypedBatchProcessor() +- { +- ProcessingResult = new ProcessingResult(); +- } +- +- public Task> ProcessAsync(SQSEvent @event, ITypedRecordHandler recordHandler) +- { +- return Task.FromResult(ProcessingResult); +- } +- +- public Task> ProcessAsync(SQSEvent @event, ITypedRecordHandler recordHandler, DeserializationOptions deserializationOptions) +- { +- return Task.FromResult(ProcessingResult); +- } +- +- public Task> ProcessAsync(SQSEvent @event, ITypedRecordHandler recordHandler, CancellationToken cancellationToken) +- { +- return Task.FromResult(ProcessingResult); +- } +- +- public Task> ProcessAsync(SQSEvent @event, ITypedRecordHandler recordHandler, DeserializationOptions deserializationOptions, CancellationToken cancellationToken) +- { +- return Task.FromResult(ProcessingResult); +- } +- +- public Task> ProcessAsync(SQSEvent @event, ITypedRecordHandler recordHandler, DeserializationOptions deserializationOptions, ProcessingOptions processingOptions) +- { +- return Task.FromResult(ProcessingResult); +- } +- +- public Task> ProcessAsync(SQSEvent @event, ITypedRecordHandlerWithContext recordHandler, ILambdaContext context) +- { +- return Task.FromResult(ProcessingResult); +- } +- +- public Task> ProcessAsync(SQSEvent @event, ITypedRecordHandlerWithContext recordHandler, ILambdaContext context, DeserializationOptions deserializationOptions) +- { +- return Task.FromResult(ProcessingResult); +- } +- +- public Task> ProcessAsync(SQSEvent @event, ITypedRecordHandlerWithContext recordHandler, ILambdaContext context, CancellationToken cancellationToken) +- { +- return Task.FromResult(ProcessingResult); +- } +- +- public Task> ProcessAsync(SQSEvent @event, ITypedRecordHandlerWithContext recordHandler, ILambdaContext context, DeserializationOptions deserializationOptions, CancellationToken cancellationToken) +- { +- return Task.FromResult(ProcessingResult); +- } +- +- public Task> ProcessAsync(SQSEvent @event, ITypedRecordHandlerWithContext recordHandler, ILambdaContext context, DeserializationOptions deserializationOptions, ProcessingOptions processingOptions) +- { +- return Task.FromResult(ProcessingResult); +- } +- } +- +- /// +- /// Mock typed record handler for testing. +- /// +- public class MockTypedRecordHandler : ITypedRecordHandler +- { +- public Task HandleAsync(TestData data, CancellationToken cancellationToken) +- { +- return Task.FromResult(RecordHandlerResult.FromData($"Processed: {data?.Name}")); +- } +- } +- +- /// +- /// Mock typed record handler with context for testing. +- /// +- public class MockTypedRecordHandlerWithContext : ITypedRecordHandlerWithContext +- { +- public Task HandleAsync(TestData data, ILambdaContext context, CancellationToken cancellationToken) +- { +- return Task.FromResult(RecordHandlerResult.FromData($"Processed: {data?.Name} with context")); +- } +- } +- +- /// +- /// Mock Lambda context for testing. +- /// +- public class MockLambdaContext : ILambdaContext +- { +- public string AwsRequestId { get; set; } = "test-request-id"; +- public IClientContext ClientContext { get; set; } +- public string FunctionName { get; set; } = "test-function"; +- public string FunctionVersion { get; set; } = "1.0"; +- public ICognitoIdentity Identity { get; set; } +- public string InvokedFunctionArn { get; set; } = "arn:aws:lambda:us-east-1:123456789012:function:test-function"; +- public ILambdaLogger Logger { get; set; } +- public string LogGroupName { get; set; } = "/aws/lambda/test-function"; +- public string LogStreamName { get; set; } = "2023/01/01/[$LATEST]abcdef123456"; +- public int MemoryLimitInMB { get; set; } = 128; +- public TimeSpan RemainingTime { get; set; } = TimeSpan.FromMinutes(5); +- } +- +- [Fact] +- public void ProcessingResult_Property_ShouldBeAccessible() +- { +- // Arrange +- var processor = new MockTypedBatchProcessor(); +- +- // Act +- var result = processor.ProcessingResult; +- +- // Assert +- Assert.NotNull(result); +- Assert.IsType>(result); +- } +- +- [Fact] +- public async Task ProcessAsync_WithTypedHandler_ShouldReturnProcessingResult() +- { +- // Arrange +- var processor = new MockTypedBatchProcessor(); +- var handler = new MockTypedRecordHandler(); +- var testEvent = new SQSEvent(); +- +- // Act +- var result = await processor.ProcessAsync(testEvent, handler); +- +- // Assert +- Assert.NotNull(result); +- Assert.IsType>(result); +- } +- +- [Fact] +- public async Task ProcessAsync_WithTypedHandlerAndDeserializationOptions_ShouldReturnProcessingResult() +- { +- // Arrange +- var processor = new MockTypedBatchProcessor(); +- var handler = new MockTypedRecordHandler(); +- var testEvent = new SQSEvent(); +- var deserializationOptions = new DeserializationOptions(); +- +- // Act +- var result = await processor.ProcessAsync(testEvent, handler, deserializationOptions); +- +- // Assert +- Assert.NotNull(result); +- Assert.IsType>(result); +- } +- +- [Fact] +- public async Task ProcessAsync_WithTypedHandlerAndCancellationToken_ShouldReturnProcessingResult() +- { +- // Arrange +- var processor = new MockTypedBatchProcessor(); +- var handler = new MockTypedRecordHandler(); +- var testEvent = new SQSEvent(); +- var cancellationToken = new CancellationToken(); +- +- // Act +- var result = await processor.ProcessAsync(testEvent, handler, cancellationToken); +- +- // Assert +- Assert.NotNull(result); +- Assert.IsType>(result); +- } +- +- [Fact] +- public async Task ProcessAsync_WithTypedHandlerDeserializationOptionsAndCancellationToken_ShouldReturnProcessingResult() +- { +- // Arrange +- var processor = new MockTypedBatchProcessor(); +- var handler = new MockTypedRecordHandler(); +- var testEvent = new SQSEvent(); +- var deserializationOptions = new DeserializationOptions(); +- var cancellationToken = new CancellationToken(); +- +- // Act +- var result = await processor.ProcessAsync(testEvent, handler, deserializationOptions, cancellationToken); +- +- // Assert +- Assert.NotNull(result); +- Assert.IsType>(result); +- } +- +- [Fact] +- public async Task ProcessAsync_WithTypedHandlerDeserializationOptionsAndProcessingOptions_ShouldReturnProcessingResult() +- { +- // Arrange +- var processor = new MockTypedBatchProcessor(); +- var handler = new MockTypedRecordHandler(); +- var testEvent = new SQSEvent(); +- var deserializationOptions = new DeserializationOptions(); +- var processingOptions = new ProcessingOptions(); +- +- // Act +- var result = await processor.ProcessAsync(testEvent, handler, deserializationOptions, processingOptions); +- +- // Assert +- Assert.NotNull(result); +- Assert.IsType>(result); +- } +- +- [Fact] +- public async Task ProcessAsync_WithTypedHandlerWithContext_ShouldReturnProcessingResult() +- { +- // Arrange +- var processor = new MockTypedBatchProcessor(); +- var handler = new MockTypedRecordHandlerWithContext(); +- var testEvent = new SQSEvent(); +- var context = new MockLambdaContext(); +- +- // Act +- var result = await processor.ProcessAsync(testEvent, handler, context); +- +- // Assert +- Assert.NotNull(result); +- Assert.IsType>(result); +- } +- +- [Fact] +- public async Task ProcessAsync_WithTypedHandlerWithContextAndDeserializationOptions_ShouldReturnProcessingResult() +- { +- // Arrange +- var processor = new MockTypedBatchProcessor(); +- var handler = new MockTypedRecordHandlerWithContext(); +- var testEvent = new SQSEvent(); +- var context = new MockLambdaContext(); +- var deserializationOptions = new DeserializationOptions(); +- +- // Act +- var result = await processor.ProcessAsync(testEvent, handler, context, deserializationOptions); +- +- // Assert +- Assert.NotNull(result); +- Assert.IsType>(result); +- } +- +- [Fact] +- public async Task ProcessAsync_WithTypedHandlerWithContextAndCancellationToken_ShouldReturnProcessingResult() +- { +- // Arrange +- var processor = new MockTypedBatchProcessor(); +- var handler = new MockTypedRecordHandlerWithContext(); +- var testEvent = new SQSEvent(); +- var context = new MockLambdaContext(); +- var cancellationToken = new CancellationToken(); +- +- // Act +- var result = await processor.ProcessAsync(testEvent, handler, context, cancellationToken); +- +- // Assert +- Assert.NotNull(result); +- Assert.IsType>(result); +- } +- +- [Fact] +- public async Task ProcessAsync_WithTypedHandlerWithContextDeserializationOptionsAndCancellationToken_ShouldReturnProcessingResult() +- { +- // Arrange +- var processor = new MockTypedBatchProcessor(); +- var handler = new MockTypedRecordHandlerWithContext(); +- var testEvent = new SQSEvent(); +- var context = new MockLambdaContext(); +- var deserializationOptions = new DeserializationOptions(); +- var cancellationToken = new CancellationToken(); +- +- // Act +- var result = await processor.ProcessAsync(testEvent, handler, context, deserializationOptions, cancellationToken); +- +- // Assert +- Assert.NotNull(result); +- Assert.IsType>(result); +- } +- +- [Fact] +- public async Task ProcessAsync_WithTypedHandlerWithContextDeserializationOptionsAndProcessingOptions_ShouldReturnProcessingResult() +- { +- // Arrange +- var processor = new MockTypedBatchProcessor(); +- var handler = new MockTypedRecordHandlerWithContext(); +- var testEvent = new SQSEvent(); +- var context = new MockLambdaContext(); +- var deserializationOptions = new DeserializationOptions(); +- var processingOptions = new ProcessingOptions(); +- +- // Act +- var result = await processor.ProcessAsync(testEvent, handler, context, deserializationOptions, processingOptions); +- +- // Assert +- Assert.NotNull(result); +- Assert.IsType>(result); +- } +- +- [Fact] +- public void Interface_ShouldHaveCorrectGenericConstraints() +- { +- // Arrange & Act +- var interfaceType = typeof(ITypedBatchProcessor<,>); +- var genericParameters = interfaceType.GetGenericArguments(); +- +- // Assert +- Assert.Equal(2, genericParameters.Length); +- +- // TEvent should be contravariant (in) +- Assert.True(genericParameters[0].GenericParameterAttributes.HasFlag(System.Reflection.GenericParameterAttributes.Contravariant)); +- +- // TRecord should be invariant (no variance) +- Assert.False(genericParameters[1].GenericParameterAttributes.HasFlag(System.Reflection.GenericParameterAttributes.Contravariant)); +- Assert.False(genericParameters[1].GenericParameterAttributes.HasFlag(System.Reflection.GenericParameterAttributes.Covariant)); +- } +- +- [Fact] +- public void Interface_ShouldInheritFromCorrectNamespace() +- { +- // Arrange & Act +- var interfaceType = typeof(ITypedBatchProcessor<,>); +- +- // Assert +- Assert.Equal("AWS.Lambda.Powertools.BatchProcessing", interfaceType.Namespace); +- } +- +- [Fact] +- public void Interface_ShouldHaveCorrectMethodSignatures() +- { +- // Arrange & Act +- var interfaceType = typeof(ITypedBatchProcessor<,>); +- var methods = interfaceType.GetMethods(); +- +- // Assert +- Assert.True(methods.Length >= 10); // Should have at least 10 ProcessAsync overloads plus ProcessingResult property getter +- +- // Check that all ProcessAsync methods are generic +- var processAsyncMethods = Array.FindAll(methods, m => m.Name == "ProcessAsync"); +- Assert.True(processAsyncMethods.Length >= 10); +- +- foreach (var method in processAsyncMethods) +- { +- Assert.True(method.IsGenericMethodDefinition); +- Assert.Equal(1, method.GetGenericArguments().Length); // Should have one generic parameter T +- } +- } +- +- [Fact] +- public void Interface_ProcessingResult_Property_ShouldBeReadOnly() +- { +- // Arrange & Act +- var interfaceType = typeof(ITypedBatchProcessor<,>); +- var property = interfaceType.GetProperty("ProcessingResult"); +- +- // Assert +- Assert.NotNull(property); +- Assert.True(property.CanRead); +- Assert.False(property.CanWrite); // Should be read-only +- } +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Internal/BatchProcessingInternalTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Internal/BatchProcessingInternalTests.cs +index ed12994a..299956ec 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Internal/BatchProcessingInternalTests.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Internal/BatchProcessingInternalTests.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using AWS.Lambda.Powertools.BatchProcessing.DynamoDb; + using AWS.Lambda.Powertools.BatchProcessing.Kinesis; + using AWS.Lambda.Powertools.BatchProcessing.Sqs; +@@ -13,15 +28,25 @@ public class BatchProcessingInternalTests + public void BatchProcessing_Set_Execution_Environment_Context_SQS() + { + // Arrange +- var env = new PowertoolsEnvironment(); +- var conf = new PowertoolsConfigurations(env); ++ var assemblyName = "AWS.Lambda.Powertools.BatchProcessing"; ++ var assemblyVersion = "1.0.0"; ++ ++ var env = Substitute.For(); ++ env.GetAssemblyName(Arg.Any()).Returns(assemblyName); ++ env.GetAssemblyVersion(Arg.Any()).ReturnsForAnyArgs(assemblyVersion); ++ ++ var conf = new PowertoolsConfigurations(new SystemWrapper(env)); + + // Act + var sqsBatchProcessor = new SqsBatchProcessor(conf); + + // Assert +- Assert.Contains($"{Constants.FeatureContextIdentifier}/BatchProcessing/", +- env.GetEnvironmentVariable("AWS_EXECUTION_ENV")); ++ env.Received(1).SetEnvironmentVariable( ++ "AWS_EXECUTION_ENV", ++ $"{Constants.FeatureContextIdentifier}/BatchProcessing/{assemblyVersion}" ++ ); ++ ++ env.Received(1).GetEnvironmentVariable("AWS_EXECUTION_ENV"); + + Assert.NotNull(sqsBatchProcessor); + } +@@ -30,15 +55,25 @@ public class BatchProcessingInternalTests + public void BatchProcessing_Set_Execution_Environment_Context_Kinesis() + { + // Arrange +- var env = new PowertoolsEnvironment(); +- var conf = new PowertoolsConfigurations(env); ++ var assemblyName = "AWS.Lambda.Powertools.BatchProcessing"; ++ var assemblyVersion = "1.0.0"; ++ ++ var env = Substitute.For(); ++ env.GetAssemblyName(Arg.Any()).Returns(assemblyName); ++ env.GetAssemblyVersion(Arg.Any()).ReturnsForAnyArgs(assemblyVersion); ++ ++ var conf = new PowertoolsConfigurations(new SystemWrapper(env)); + + // Act + var KinesisEventBatchProcessor = new KinesisEventBatchProcessor(conf); + + // Assert +- Assert.Contains($"{Constants.FeatureContextIdentifier}/BatchProcessing/", +- env.GetEnvironmentVariable("AWS_EXECUTION_ENV")); ++ env.Received(1).SetEnvironmentVariable( ++ "AWS_EXECUTION_ENV", ++ $"{Constants.FeatureContextIdentifier}/BatchProcessing/{assemblyVersion}" ++ ); ++ ++ env.Received(1).GetEnvironmentVariable("AWS_EXECUTION_ENV"); + + Assert.NotNull(KinesisEventBatchProcessor); + } +@@ -47,15 +82,25 @@ public class BatchProcessingInternalTests + public void BatchProcessing_Set_Execution_Environment_Context_DynamoDB() + { + // Arrange +- var env = new PowertoolsEnvironment(); +- var conf = new PowertoolsConfigurations(env); ++ var assemblyName = "AWS.Lambda.Powertools.BatchProcessing"; ++ var assemblyVersion = "1.0.0"; ++ ++ var env = Substitute.For(); ++ env.GetAssemblyName(Arg.Any()).Returns(assemblyName); ++ env.GetAssemblyVersion(Arg.Any()).ReturnsForAnyArgs(assemblyVersion); ++ ++ var conf = new PowertoolsConfigurations(new SystemWrapper(env)); + + // Act + var dynamoDbStreamBatchProcessor = new DynamoDbStreamBatchProcessor(conf); + + // Assert +- Assert.Contains($"{Constants.FeatureContextIdentifier}/BatchProcessing/", +- env.GetEnvironmentVariable("AWS_EXECUTION_ENV")); ++ env.Received(1).SetEnvironmentVariable( ++ "AWS_EXECUTION_ENV", ++ $"{Constants.FeatureContextIdentifier}/BatchProcessing/{assemblyVersion}" ++ ); ++ ++ env.Received(1).GetEnvironmentVariable("AWS_EXECUTION_ENV"); + + Assert.NotNull(dynamoDbStreamBatchProcessor); + } +diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/JsonDeserializationServiceTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/JsonDeserializationServiceTests.cs +deleted file mode 100644 +index feed5254..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/JsonDeserializationServiceTests.cs ++++ /dev/null +@@ -1,357 +0,0 @@ +- +- +-using System; +-using System.Text.Json; +-using System.Text.Json.Serialization; +-using AWS.Lambda.Powertools.BatchProcessing.Exceptions; +-using Xunit; +- +-namespace AWS.Lambda.Powertools.BatchProcessing.Tests; +- +-[Collection("Sequential")] +-public partial class JsonDeserializationServiceTests +-{ +- private readonly JsonDeserializationService _service; +- +- public JsonDeserializationServiceTests() +- { +- _service = new JsonDeserializationService(); +- } +- +- #region Test Models +- +- public class TestProduct +- { +- public int Id { get; set; } +- public string Name { get; set; } +- public decimal Price { get; set; } +- } +- +- public class TestOrder +- { +- public string OrderId { get; set; } +- public DateTime OrderDate { get; set; } +- public TestProduct[] Items { get; set; } +- } +- +- [JsonSerializable(typeof(TestProduct))] +- [JsonSerializable(typeof(TestOrder))] +- [JsonSerializable(typeof(string))] +- [JsonSerializable(typeof(int))] +- public partial class TestJsonSerializerContext : JsonSerializerContext +- { +- } +- +- #endregion +- +- #region Deserialize Tests +- +- [Fact] +- public void Deserialize_ValidJson_ReturnsDeserializedObject() +- { +- // Arrange +- var json = """{"Id":1,"Name":"Test Product","Price":99.99}"""; +- +- // Act +- var result = _service.Deserialize(json); +- +- // Assert +- Assert.NotNull(result); +- Assert.Equal(1, result.Id); +- Assert.Equal("Test Product", result.Name); +- Assert.Equal(99.99m, result.Price); +- } +- +- [Fact] +- public void Deserialize_WithJsonSerializerOptions_ReturnsDeserializedObject() +- { +- // Arrange +- var json = """{"id":1,"name":"Test Product","price":99.99}"""; +- var options = new DeserializationOptions(new JsonSerializerOptions +- { +- PropertyNameCaseInsensitive = true +- }); +- +- // Act +- var result = _service.Deserialize(json, options); +- +- // Assert +- Assert.NotNull(result); +- Assert.Equal(1, result.Id); +- Assert.Equal("Test Product", result.Name); +- Assert.Equal(99.99m, result.Price); +- } +- +- [Fact] +- public void Deserialize_WithJsonSerializerContext_ReturnsDeserializedObject() +- { +- // Arrange +- var json = """{"Id":1,"Name":"Test Product","Price":99.99}"""; +- var options = new DeserializationOptions(TestJsonSerializerContext.Default); +- +- // Act +- var result = _service.Deserialize(json, options); +- +- // Assert +- Assert.NotNull(result); +- Assert.Equal(1, result.Id); +- Assert.Equal("Test Product", result.Name); +- Assert.Equal(99.99m, result.Price); +- } +- +- [Fact] +- public void Deserialize_ComplexObject_ReturnsDeserializedObject() +- { +- // Arrange +- var json = """{"OrderId":"ORD-123","OrderDate":"2023-01-01T00:00:00Z","Items":[{"Id":1,"Name":"Product 1","Price":10.00},{"Id":2,"Name":"Product 2","Price":20.00}]}"""; +- +- // Act +- var result = _service.Deserialize(json); +- +- // Assert +- Assert.NotNull(result); +- Assert.Equal("ORD-123", result.OrderId); +- Assert.Equal(new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc), result.OrderDate); +- Assert.NotNull(result.Items); +- Assert.Equal(2, result.Items.Length); +- Assert.Equal("Product 1", result.Items[0].Name); +- Assert.Equal("Product 2", result.Items[1].Name); +- } +- +- [Fact] +- public void Deserialize_PrimitiveTypes_ReturnsDeserializedValues() +- { +- // Arrange & Act & Assert +- Assert.Equal(42, _service.Deserialize("42")); +- Assert.Equal("test", _service.Deserialize("\"test\"")); +- Assert.True(_service.Deserialize("true")); +- Assert.Equal(3.14, _service.Deserialize("3.14")); +- } +- +- [Fact] +- public void Deserialize_NullData_ThrowsDeserializationException() +- { +- // Act & Assert +- var exception = Assert.Throws(() => _service.Deserialize(null)); +- Assert.Contains("Data cannot be null or empty", exception.Message); +- Assert.IsType(exception.InnerException); +- } +- +- [Fact] +- public void Deserialize_EmptyData_ThrowsDeserializationException() +- { +- // Act & Assert +- var exception = Assert.Throws(() => _service.Deserialize("")); +- Assert.Contains("Data cannot be null or empty", exception.Message); +- Assert.IsType(exception.InnerException); +- } +- +- [Fact] +- public void Deserialize_InvalidJson_ThrowsDeserializationException() +- { +- // Arrange +- var invalidJson = "{invalid json}"; +- +- // Act & Assert +- var exception = Assert.Throws(() => _service.Deserialize(invalidJson)); +- Assert.Equal(invalidJson, exception.RecordData); +- Assert.Equal(typeof(TestProduct), exception.TargetType); +- Assert.IsType(exception.InnerException); +- } +- +- [Fact] +- public void Deserialize_InvalidJsonWithIgnoreErrors_ReturnsDefault() +- { +- // Arrange +- var invalidJson = "{invalid json}"; +- var options = new DeserializationOptions { IgnoreDeserializationErrors = true }; +- +- // Act +- var result = _service.Deserialize(invalidJson, options); +- +- // Assert +- Assert.Null(result); +- } +- +- #endregion +- +- #region TryDeserialize Tests +- +- [Fact] +- public void TryDeserialize_ValidJson_ReturnsTrue() +- { +- // Arrange +- var json = """{"Id":1,"Name":"Test Product","Price":99.99}"""; +- +- // Act +- var success = _service.TryDeserialize(json, out var result); +- +- // Assert +- Assert.True(success); +- Assert.NotNull(result); +- Assert.Equal(1, result.Id); +- Assert.Equal("Test Product", result.Name); +- Assert.Equal(99.99m, result.Price); +- } +- +- [Fact] +- public void TryDeserialize_InvalidJson_ReturnsFalse() +- { +- // Arrange +- var invalidJson = "{invalid json}"; +- +- // Act +- var success = _service.TryDeserialize(invalidJson, out var result); +- +- // Assert +- Assert.False(success); +- Assert.Null(result); +- } +- +- [Fact] +- public void TryDeserialize_NullData_ReturnsFalse() +- { +- // Act +- var success = _service.TryDeserialize(null, out var result); +- +- // Assert +- Assert.False(success); +- Assert.Null(result); +- } +- +- [Fact] +- public void TryDeserialize_WithException_ReturnsFalseAndException() +- { +- // Arrange +- var invalidJson = "{invalid json}"; +- +- // Act +- var success = _service.TryDeserialize(invalidJson, out var result, out var exception); +- +- // Assert +- Assert.False(success); +- Assert.Null(result); +- Assert.NotNull(exception); +- Assert.IsType(exception); +- } +- +- [Fact] +- public void TryDeserialize_WithJsonSerializerContext_ReturnsTrue() +- { +- // Arrange +- var json = """{"Id":1,"Name":"Test Product","Price":99.99}"""; +- var options = new DeserializationOptions(TestJsonSerializerContext.Default); +- +- // Act +- var success = _service.TryDeserialize(json, out var result, options); +- +- // Assert +- Assert.True(success); +- Assert.NotNull(result); +- Assert.Equal(1, result.Id); +- } +- +- #endregion +- +- #region DeserializationOptions Tests +- +- [Fact] +- public void DeserializationOptions_DefaultConstructor_SetsDefaults() +- { +- // Act +- var options = new DeserializationOptions(); +- +- // Assert +- Assert.Null(options.JsonSerializerContext); +- Assert.Null(options.JsonSerializerOptions); +- Assert.False(options.IgnoreDeserializationErrors); +- } +- +- [Fact] +- public void DeserializationOptions_JsonSerializerContextConstructor_SetsContext() +- { +- // Arrange +- var context = TestJsonSerializerContext.Default; +- +- // Act +- var options = new DeserializationOptions(context); +- +- // Assert +- Assert.Equal(context, options.JsonSerializerContext); +- Assert.Null(options.JsonSerializerOptions); +- Assert.False(options.IgnoreDeserializationErrors); +- } +- +- [Fact] +- public void DeserializationOptions_JsonSerializerOptionsConstructor_SetsOptions() +- { +- // Arrange +- var jsonOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; +- +- // Act +- var options = new DeserializationOptions(jsonOptions); +- +- // Assert +- Assert.Null(options.JsonSerializerContext); +- Assert.Equal(jsonOptions, options.JsonSerializerOptions); +- Assert.False(options.IgnoreDeserializationErrors); +- } +- +- #endregion +- +- #region Singleton Tests +- +- [Fact] +- public void Instance_ReturnsSameInstance() +- { +- // Act +- var instance1 = JsonDeserializationService.Instance; +- var instance2 = JsonDeserializationService.Instance; +- +- // Assert +- Assert.Same(instance1, instance2); +- } +- +- [Fact] +- public void Instance_IsNotNull() +- { +- // Act +- var instance = JsonDeserializationService.Instance; +- +- // Assert +- Assert.NotNull(instance); +- } +- +- #endregion +- +- #region Edge Cases +- +- [Fact] +- public void Deserialize_JsonSerializerContextTakesPrecedence_OverJsonSerializerOptions() +- { +- // Arrange +- var json = """{"Id":1,"Name":"Test Product","Price":99.99}"""; +- var options = new DeserializationOptions +- { +- JsonSerializerContext = TestJsonSerializerContext.Default, +- JsonSerializerOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = false } +- }; +- +- // Act +- var result = _service.Deserialize(json, options); +- +- // Assert - Should succeed because JsonSerializerContext is used instead of JsonSerializerOptions +- Assert.NotNull(result); +- Assert.Equal(1, result.Id); +- } +- +- [Fact] +- public void Deserialize_WhitespaceOnlyData_ThrowsDeserializationException() +- { +- // Act & Assert +- var exception = Assert.Throws(() => _service.Deserialize(" ")); +- Assert.Contains("Data cannot be null or empty", exception.Message); +- } +- +- #endregion +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/RecordDataExtractorTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/RecordDataExtractorTests.cs +deleted file mode 100644 +index 3735e101..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/RecordDataExtractorTests.cs ++++ /dev/null +@@ -1,317 +0,0 @@ +- +- +-using System; +-using System.Collections.Generic; +-using System.IO; +-using System.Text; +-using System.Text.Json; +-using Amazon.Lambda.DynamoDBEvents; +-using Amazon.Lambda.KinesisEvents; +-using Amazon.Lambda.SQSEvents; +-using AWS.Lambda.Powertools.BatchProcessing.DynamoDb; +-using AWS.Lambda.Powertools.BatchProcessing.Kinesis; +-using AWS.Lambda.Powertools.BatchProcessing.Sqs; +-using Xunit; +- +-namespace AWS.Lambda.Powertools.BatchProcessing.Tests; +- +-public class RecordDataExtractorTests +-{ +- #region SQS Record Data Extractor Tests +- +- [Fact] +- public void SqsRecordDataExtractor_ExtractData_ReturnsMessageBody() +- { +- // Arrange +- var extractor = SqsRecordDataExtractor.Instance; +- var messageBody = "{\"orderId\": \"12345\", \"amount\": 99.99}"; +- var sqsMessage = new SQSEvent.SQSMessage +- { +- Body = messageBody, +- MessageId = "test-message-id" +- }; +- +- // Act +- var result = extractor.ExtractData(sqsMessage); +- +- // Assert +- Assert.Equal(messageBody, result); +- } +- +- [Fact] +- public void SqsRecordDataExtractor_ExtractData_WithNullRecord_ReturnsEmptyString() +- { +- // Arrange +- var extractor = SqsRecordDataExtractor.Instance; +- +- // Act +- var result = extractor.ExtractData(null); +- +- // Assert +- Assert.Equal(string.Empty, result); +- } +- +- [Fact] +- public void SqsRecordDataExtractor_ExtractData_WithNullBody_ReturnsEmptyString() +- { +- // Arrange +- var extractor = SqsRecordDataExtractor.Instance; +- var sqsMessage = new SQSEvent.SQSMessage +- { +- Body = null, +- MessageId = "test-message-id" +- }; +- +- // Act +- var result = extractor.ExtractData(sqsMessage); +- +- // Assert +- Assert.Equal(string.Empty, result); +- } +- +- [Fact] +- public void SqsRecordDataExtractor_Instance_IsSingleton() +- { +- // Arrange & Act +- var instance1 = SqsRecordDataExtractor.Instance; +- var instance2 = SqsRecordDataExtractor.Instance; +- +- // Assert +- Assert.Same(instance1, instance2); +- } +- +- #endregion +- +- #region Kinesis Record Data Extractor Tests +- +- [Fact] +- public void KinesisRecordDataExtractor_ExtractData_ReadsFromMemoryStream() +- { +- // Arrange +- var extractor = KinesisRecordDataExtractor.Instance; +- var originalData = "{\"userId\": \"user123\", \"action\": \"login\"}"; +- var dataStream = new MemoryStream(Encoding.UTF8.GetBytes(originalData)); +- +- var kinesisRecord = new KinesisEvent.KinesisEventRecord +- { +- Kinesis = new KinesisEvent.Record +- { +- Data = dataStream, +- SequenceNumber = "12345" +- } +- }; +- +- // Act +- var result = extractor.ExtractData(kinesisRecord); +- +- // Assert +- Assert.Equal(originalData, result); +- } +- +- [Fact] +- public void KinesisRecordDataExtractor_ExtractData_WithEmptyStream_ReturnsEmptyString() +- { +- // Arrange +- var extractor = KinesisRecordDataExtractor.Instance; +- var emptyStream = new MemoryStream(); +- +- var kinesisRecord = new KinesisEvent.KinesisEventRecord +- { +- Kinesis = new KinesisEvent.Record +- { +- Data = emptyStream, +- SequenceNumber = "12345" +- } +- }; +- +- // Act +- var result = extractor.ExtractData(kinesisRecord); +- +- // Assert +- Assert.Equal(string.Empty, result); +- } +- +- [Fact] +- public void KinesisRecordDataExtractor_ExtractData_WithNullRecord_ReturnsEmptyString() +- { +- // Arrange +- var extractor = KinesisRecordDataExtractor.Instance; +- +- // Act +- var result = extractor.ExtractData(null); +- +- // Assert +- Assert.Equal(string.Empty, result); +- } +- +- [Fact] +- public void KinesisRecordDataExtractor_ExtractData_WithNullKinesisData_ReturnsEmptyString() +- { +- // Arrange +- var extractor = KinesisRecordDataExtractor.Instance; +- var kinesisRecord = new KinesisEvent.KinesisEventRecord +- { +- Kinesis = new KinesisEvent.Record +- { +- Data = null, +- SequenceNumber = "12345" +- } +- }; +- +- // Act +- var result = extractor.ExtractData(kinesisRecord); +- +- // Assert +- Assert.Equal(string.Empty, result); +- } +- +- [Fact] +- public void KinesisRecordDataExtractor_ExtractData_WithNullKinesis_ReturnsEmptyString() +- { +- // Arrange +- var extractor = KinesisRecordDataExtractor.Instance; +- var kinesisRecord = new KinesisEvent.KinesisEventRecord +- { +- Kinesis = null +- }; +- +- // Act +- var result = extractor.ExtractData(kinesisRecord); +- +- // Assert +- Assert.Equal(string.Empty, result); +- } +- +- [Fact] +- public void KinesisRecordDataExtractor_Instance_IsSingleton() +- { +- // Arrange & Act +- var instance1 = KinesisRecordDataExtractor.Instance; +- var instance2 = KinesisRecordDataExtractor.Instance; +- +- // Assert +- Assert.Same(instance1, instance2); +- } +- +- #endregion +- +- #region DynamoDB Record Data Extractor Tests +- +- [Fact] +- public void DynamoDbRecordDataExtractor_ExtractData_SerializesDynamoDbRecord() +- { +- // Arrange +- var extractor = DynamoDbRecordDataExtractor.Instance; +- var dynamoDbRecord = new DynamoDBEvent.DynamodbStreamRecord +- { +- EventName = "INSERT", +- Dynamodb = new DynamoDBEvent.StreamRecord +- { +- Keys = new Dictionary +- { +- ["id"] = new DynamoDBEvent.AttributeValue { S = "123" } +- }, +- NewImage = new Dictionary +- { +- ["id"] = new DynamoDBEvent.AttributeValue { S = "123" }, +- ["name"] = new DynamoDBEvent.AttributeValue { S = "Test Item" } +- }, +- SequenceNumber = "12345", +- SizeBytes = 100, +- StreamViewType = "NEW_AND_OLD_IMAGES" +- } +- }; +- +- // Act +- var result = extractor.ExtractData(dynamoDbRecord); +- +- // Assert +- Assert.NotEmpty(result); +- +- // Verify the result is valid JSON +- var deserializedResult = JsonSerializer.Deserialize(result); +- Assert.Equal("INSERT", deserializedResult.GetProperty("EventName").GetString()); +- Assert.Equal("12345", deserializedResult.GetProperty("SequenceNumber").GetString()); +- Assert.Equal(100, deserializedResult.GetProperty("SizeBytes").GetInt32()); +- } +- +- [Fact] +- public void DynamoDbRecordDataExtractor_ExtractData_WithRemoveEvent_IncludesOldImage() +- { +- // Arrange +- var extractor = DynamoDbRecordDataExtractor.Instance; +- var dynamoDbRecord = new DynamoDBEvent.DynamodbStreamRecord +- { +- EventName = "REMOVE", +- Dynamodb = new DynamoDBEvent.StreamRecord +- { +- Keys = new Dictionary +- { +- ["id"] = new DynamoDBEvent.AttributeValue { S = "123" } +- }, +- OldImage = new Dictionary +- { +- ["id"] = new DynamoDBEvent.AttributeValue { S = "123" }, +- ["name"] = new DynamoDBEvent.AttributeValue { S = "Deleted Item" } +- }, +- SequenceNumber = "12345", +- StreamViewType = "NEW_AND_OLD_IMAGES" +- } +- }; +- +- // Act +- var result = extractor.ExtractData(dynamoDbRecord); +- +- // Assert +- Assert.NotEmpty(result); +- +- // Verify the result contains the old image +- var deserializedResult = JsonSerializer.Deserialize(result); +- Assert.Equal("REMOVE", deserializedResult.GetProperty("EventName").GetString()); +- Assert.True(deserializedResult.GetProperty("OldImage").ValueKind != JsonValueKind.Null); +- } +- +- [Fact] +- public void DynamoDbRecordDataExtractor_ExtractData_WithNullRecord_ReturnsEmptyString() +- { +- // Arrange +- var extractor = DynamoDbRecordDataExtractor.Instance; +- +- // Act +- var result = extractor.ExtractData(null); +- +- // Assert +- Assert.Equal(string.Empty, result); +- } +- +- [Fact] +- public void DynamoDbRecordDataExtractor_ExtractData_WithNullDynamoDb_ReturnsEmptyString() +- { +- // Arrange +- var extractor = DynamoDbRecordDataExtractor.Instance; +- var dynamoDbRecord = new DynamoDBEvent.DynamodbStreamRecord +- { +- EventName = "INSERT", +- Dynamodb = null +- }; +- +- // Act +- var result = extractor.ExtractData(dynamoDbRecord); +- +- // Assert +- Assert.Equal(string.Empty, result); +- } +- +- [Fact] +- public void DynamoDbRecordDataExtractor_Instance_IsSingleton() +- { +- // Arrange & Act +- var instance1 = DynamoDbRecordDataExtractor.Instance; +- var instance2 = DynamoDbRecordDataExtractor.Instance; +- +- // Assert +- Assert.Same(instance1, instance2); +- } +- +- #endregion +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/TypedDynamoDbStreamBatchProcessorTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/TypedDynamoDbStreamBatchProcessorTests.cs +deleted file mode 100644 +index bf79930b..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/TypedDynamoDbStreamBatchProcessorTests.cs ++++ /dev/null +@@ -1,443 +0,0 @@ +- +- +-using System; +-using System.Collections.Generic; +-using System.Linq; +-using System.Text.Json; +-using System.Text.Json.Serialization; +-using System.Threading; +-using System.Threading.Tasks; +-using Amazon.Lambda.Core; +-using Amazon.Lambda.DynamoDBEvents; +-using AWS.Lambda.Powertools.BatchProcessing.DynamoDb; +-using AWS.Lambda.Powertools.BatchProcessing.Exceptions; +-using AWS.Lambda.Powertools.Common; +-using NSubstitute; +-using Xunit; +- +-namespace AWS.Lambda.Powertools.BatchProcessing.Tests; +- +-[Collection("Sequential")] +-public class TypedDynamoDbStreamBatchProcessorTests +-{ +- private readonly IPowertoolsConfigurations _mockConfigurations; +- private readonly TypedDynamoDbStreamBatchProcessor _processor; +- +- public TypedDynamoDbStreamBatchProcessorTests() +- { +- _mockConfigurations = Substitute.For(); +- _processor = new TypedDynamoDbStreamBatchProcessor(_mockConfigurations); +- } +- +- [Fact] +- public async Task ProcessAsync_WithTypedHandler_DeserializesAndProcessesSuccessfully() +- { +- // Arrange +- var @event = CreateDynamoDbEvent("INSERT", "seq-1", "1", "Test Record"); +- +- TestDynamoDbRecord capturedRecord = null; +- var handler = Substitute.For>(); +- handler.HandleAsync(Arg.Any(), Arg.Any()) +- .Returns(callInfo => +- { +- capturedRecord = callInfo.Arg(); +- return RecordHandlerResult.None; +- }); +- +- // Act +- var result = await _processor.ProcessAsync(@event, handler); +- +- // Assert +- Assert.Single(result.SuccessRecords); +- Assert.Empty(result.FailureRecords); +- Assert.Empty(result.BatchItemFailuresResponse.BatchItemFailures); +- Assert.NotNull(capturedRecord); +- Assert.Equal("INSERT", capturedRecord.EventName); +- Assert.Equal("seq-1", capturedRecord.SequenceNumber); +- +- await handler.Received(1).HandleAsync(Arg.Any(), Arg.Any()); +- } +- +- [Fact] +- public async Task ProcessAsync_WithTypedHandlerWithContext_PassesContextCorrectly() +- { +- // Arrange +- var context = Substitute.For(); +- context.AwsRequestId.Returns("test-request-id"); +- +- var @event = CreateDynamoDbEvent("MODIFY", "seq-2", "2", "Context Test"); +- +- TestDynamoDbRecord capturedRecord = null; +- ILambdaContext capturedContext = null; +- var handler = Substitute.For>(); +- handler.HandleAsync(Arg.Any(), Arg.Any(), Arg.Any()) +- .Returns(callInfo => +- { +- capturedRecord = callInfo.Arg(); +- capturedContext = callInfo.Arg(); +- return RecordHandlerResult.None; +- }); +- +- // Act +- var result = await _processor.ProcessAsync(@event, handler, context); +- +- // Assert +- Assert.Single(result.SuccessRecords); +- Assert.Empty(result.FailureRecords); +- Assert.NotNull(capturedRecord); +- Assert.Equal("MODIFY", capturedRecord.EventName); +- Assert.Equal("seq-2", capturedRecord.SequenceNumber); +- Assert.NotNull(capturedContext); +- Assert.Equal("test-request-id", capturedContext.AwsRequestId); +- +- await handler.Received(1).HandleAsync(Arg.Any(), Arg.Any(), Arg.Any()); +- } +- +- [Fact] +- public async Task ProcessAsync_WithDeserializationOptions_UsesCustomOptions() +- { +- // Arrange +- var @event = CreateDynamoDbEvent("INSERT", "seq-3", "3", "Custom Options Test"); +- +- var deserializationOptions = new DeserializationOptions +- { +- JsonSerializerOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true } +- }; +- +- TestDynamoDbRecord capturedRecord = null; +- var handler = Substitute.For>(); +- handler.HandleAsync(Arg.Any(), Arg.Any()) +- .Returns(callInfo => +- { +- capturedRecord = callInfo.Arg(); +- return RecordHandlerResult.None; +- }); +- +- // Act +- var result = await _processor.ProcessAsync(@event, handler, deserializationOptions); +- +- // Assert +- Assert.Single(result.SuccessRecords); +- Assert.Empty(result.FailureRecords); +- Assert.NotNull(capturedRecord); +- Assert.Equal("INSERT", capturedRecord.EventName); +- Assert.Equal("seq-3", capturedRecord.SequenceNumber); +- Assert.NotNull(capturedRecord.Keys); +- Assert.NotNull(capturedRecord.NewImage); +- +- await handler.Received(1).HandleAsync(Arg.Any(), Arg.Any()); +- } +- +- [Fact] +- public async Task ProcessAsync_WithJsonSerializerContext_UsesAOTCompatibleDeserialization() +- { +- // Arrange +- var @event = CreateDynamoDbEvent("INSERT", "seq-4", "4", "AOT Test"); +- +- var deserializationOptions = new DeserializationOptions(TypedDynamoDbTestJsonSerializerContext.Default); +- +- TestDynamoDbRecord capturedRecord = null; +- var handler = Substitute.For>(); +- handler.HandleAsync(Arg.Any(), Arg.Any()) +- .Returns(callInfo => +- { +- capturedRecord = callInfo.Arg(); +- return RecordHandlerResult.None; +- }); +- +- // Act +- var result = await _processor.ProcessAsync(@event, handler, deserializationOptions); +- +- // Assert +- Assert.Single(result.SuccessRecords); +- Assert.Empty(result.FailureRecords); +- Assert.NotNull(capturedRecord); +- Assert.Equal("INSERT", capturedRecord.EventName); +- Assert.Equal("seq-4", capturedRecord.SequenceNumber); +- +- await handler.Received(1).HandleAsync(Arg.Any(), Arg.Any()); +- } +- +- [Fact] +- public async Task ProcessAsync_WithInvalidJson_FailsRecordByDefault() +- { +- // Arrange - Create a record that will produce invalid JSON for our TestDynamoDbRecord type +- // We'll use a custom deserializer that always throws to simulate deserialization failure +- var @event = CreateDynamoDbEvent("INSERT", "seq-5", "5", "Invalid Test"); +- +- var mockDeserializationService = Substitute.For(); +- mockDeserializationService.Deserialize(Arg.Any(), Arg.Any()) +- .Returns(callInfo => throw new DeserializationException("Test deserialization failure", typeof(TestDynamoDbRecord), "seq-5", new JsonException("Invalid JSON"))); +- +- var processor = new TypedDynamoDbStreamBatchProcessor(_mockConfigurations, mockDeserializationService); +- var handler = Substitute.For>(); +- +- // Act & Assert +- var exception = await Assert.ThrowsAsync(() => processor.ProcessAsync(@event, handler)); +- +- // Verify the exception contains the expected details +- Assert.Contains("Failed processing record: 'seq-5'", exception.Message); +- Assert.Single(exception.InnerExceptions); +- +- await handler.DidNotReceive().HandleAsync(Arg.Any(), Arg.Any()); +- } +- +- [Fact] +- public async Task ProcessAsync_WithInvalidJsonAndIgnorePolicy_IgnoresRecord() +- { +- // Arrange - Create a record that will produce invalid JSON for our TestDynamoDbRecord type +- // We'll use a custom deserializer that always throws to simulate deserialization failure +- var @event = CreateDynamoDbEvent("INSERT", "seq-6", "6", "Invalid Test"); +- +- var mockDeserializationService = Substitute.For(); +- mockDeserializationService.Deserialize(Arg.Any(), Arg.Any()) +- .Returns(callInfo => throw new DeserializationException("Test deserialization failure", typeof(TestDynamoDbRecord), "seq-6", new JsonException("Invalid JSON"))); +- +- var processor = new TypedDynamoDbStreamBatchProcessor(_mockConfigurations, mockDeserializationService); +- +- var deserializationOptions = new DeserializationOptions +- { +- ErrorPolicy = DeserializationErrorPolicy.IgnoreRecord +- }; +- +- var handler = Substitute.For>(); +- +- // Act +- var result = await processor.ProcessAsync(@event, handler, deserializationOptions); +- +- // Assert +- Assert.Single(result.SuccessRecords); +- Assert.Empty(result.FailureRecords); +- Assert.Empty(result.BatchItemFailuresResponse.BatchItemFailures); +- +- await handler.DidNotReceive().HandleAsync(Arg.Any(), Arg.Any()); +- } +- +- [Fact] +- public async Task ProcessAsync_WithMultipleRecords_ProcessesAllSuccessfully() +- { +- // Arrange +- var @event = new DynamoDBEvent +- { +- Records = new List +- { +- CreateDynamoDbRecord("INSERT", "seq-7", "7", "Record 1"), +- CreateDynamoDbRecord("MODIFY", "seq-8", "8", "Record 2") +- } +- }; +- +- var handler = Substitute.For>(); +- handler.HandleAsync(Arg.Any(), Arg.Any()) +- .Returns(RecordHandlerResult.None); +- +- // Act +- var result = await _processor.ProcessAsync(@event, handler); +- +- // Assert +- Assert.Equal(2, result.SuccessRecords.Count); +- Assert.Empty(result.FailureRecords); +- Assert.Empty(result.BatchItemFailuresResponse.BatchItemFailures); +- +- await handler.Received(2).HandleAsync(Arg.Any(), Arg.Any()); +- } +- +- [Fact] +- public async Task ProcessAsync_WithHandlerException_FailsRecord() +- { +- // Arrange +- var @event = CreateDynamoDbEvent("INSERT", "seq-9", "9", "Exception Test"); +- +- var handler = Substitute.For>(); +- handler.WhenForAnyArgs(x => x.HandleAsync(Arg.Any(), Arg.Any())) +- .Do(callInfo => throw new InvalidOperationException("Handler failed")); +- +- // Act & Assert +- var exception = await Assert.ThrowsAsync(() => _processor.ProcessAsync(@event, handler)); +- +- // Verify the exception contains the expected details +- Assert.Contains("Failed processing record: 'seq-9'", exception.Message); +- Assert.Single(exception.InnerExceptions); +- } +- +- [Fact] +- public async Task ProcessAsync_WithCancellationToken_PropagatesCancellation() +- { +- // Arrange +- var @event = CreateDynamoDbEvent("INSERT", "seq-10", "10", "Cancellation Test"); +- +- var handler = Substitute.For>(); +- var cancellationTokenSource = new CancellationTokenSource(); +- cancellationTokenSource.Cancel(); +- +- // Act & Assert +- var exception = await Assert.ThrowsAsync(() => +- _processor.ProcessAsync(@event, handler, cancellationTokenSource.Token)); +- +- // Verify the cancellation was the root cause +- Assert.Contains("Failed processing record: 'seq-10'", exception.Message); +- Assert.Single(exception.InnerExceptions); +- Assert.IsType(exception.InnerExceptions.First()); +- Assert.IsType(exception.InnerExceptions.First().InnerException); +- } +- +- +- +- [Fact] +- public async Task ProcessAsync_WithNullContext_HandlesGracefully() +- { +- // Arrange +- var @event = CreateDynamoDbEvent("INSERT", "seq-11", "11", "Null Context Test"); +- +- TestDynamoDbRecord capturedRecord = null; +- ILambdaContext capturedContext = null; +- var handler = Substitute.For>(); +- handler.HandleAsync(Arg.Any(), Arg.Any(), Arg.Any()) +- .Returns(callInfo => +- { +- capturedRecord = callInfo.Arg(); +- capturedContext = callInfo.Arg(); +- return RecordHandlerResult.None; +- }); +- +- // Act +- var result = await _processor.ProcessAsync(@event, handler, context: null); +- +- // Assert +- Assert.Single(result.SuccessRecords); +- Assert.Empty(result.FailureRecords); +- Assert.NotNull(capturedRecord); +- Assert.Equal("INSERT", capturedRecord.EventName); +- Assert.Equal("seq-11", capturedRecord.SequenceNumber); +- Assert.Null(capturedContext); +- +- await handler.Received(1).HandleAsync(Arg.Any(), Arg.Any(), Arg.Any()); +- } +- +- [Fact] +- public async Task ProcessAsync_WithRemoveEvent_ProcessesOldImageCorrectly() +- { +- // Arrange +- var @event = CreateDynamoDbEvent("REMOVE", "seq-12", "12", "Remove Test"); +- +- TestDynamoDbRecord capturedRecord = null; +- var handler = Substitute.For>(); +- handler.HandleAsync(Arg.Any(), Arg.Any()) +- .Returns(callInfo => +- { +- capturedRecord = callInfo.Arg(); +- return RecordHandlerResult.None; +- }); +- +- // Act +- var result = await _processor.ProcessAsync(@event, handler); +- +- // Assert +- Assert.Single(result.SuccessRecords); +- Assert.Empty(result.FailureRecords); +- Assert.NotNull(capturedRecord); +- Assert.Equal("REMOVE", capturedRecord.EventName); +- Assert.Equal("seq-12", capturedRecord.SequenceNumber); +- +- await handler.Received(1).HandleAsync(Arg.Any(), Arg.Any()); +- } +- +- [Fact] +- public async Task ProcessAsync_WithMixedEventTypes_ProcessesAllCorrectly() +- { +- // Arrange +- var @event = new DynamoDBEvent +- { +- Records = new List +- { +- CreateDynamoDbRecord("INSERT", "seq-13", "13", "Insert Record"), +- CreateDynamoDbRecord("MODIFY", "seq-14", "14", "Modify Record"), +- CreateDynamoDbRecord("REMOVE", "seq-15", "15", "Remove Record") +- } +- }; +- +- var handler = Substitute.For>(); +- handler.HandleAsync(Arg.Any(), Arg.Any()) +- .Returns(RecordHandlerResult.None); +- +- // Act +- var result = await _processor.ProcessAsync(@event, handler); +- +- // Assert +- Assert.Equal(3, result.SuccessRecords.Count); +- Assert.Empty(result.FailureRecords); +- Assert.Empty(result.BatchItemFailuresResponse.BatchItemFailures); +- +- await handler.Received(3).HandleAsync(Arg.Any(), Arg.Any()); +- } +- +- // Helper methods +- private DynamoDBEvent CreateDynamoDbEvent(string eventName, string sequenceNumber, string id, string name) +- { +- return new DynamoDBEvent +- { +- Records = new List +- { +- CreateDynamoDbRecord(eventName, sequenceNumber, id, name) +- } +- }; +- } +- +- private DynamoDBEvent.DynamodbStreamRecord CreateDynamoDbRecord(string eventName, string sequenceNumber, string id, string name) +- { +- var record = new DynamoDBEvent.DynamodbStreamRecord +- { +- EventName = eventName, +- Dynamodb = new DynamoDBEvent.StreamRecord +- { +- SequenceNumber = sequenceNumber, +- Keys = new Dictionary +- { +- ["Id"] = new DynamoDBEvent.AttributeValue { N = id } +- }, +- StreamViewType = "NEW_AND_OLD_IMAGES" +- } +- }; +- +- // For INSERT and MODIFY events, include NewImage +- if (eventName == "INSERT" || eventName == "MODIFY") +- { +- record.Dynamodb.NewImage = new Dictionary +- { +- ["Id"] = new DynamoDBEvent.AttributeValue { N = id }, +- ["Name"] = new DynamoDBEvent.AttributeValue { S = name } +- }; +- } +- +- // For REMOVE and MODIFY events, include OldImage +- if (eventName == "REMOVE" || eventName == "MODIFY") +- { +- record.Dynamodb.OldImage = new Dictionary +- { +- ["Id"] = new DynamoDBEvent.AttributeValue { N = id }, +- ["Name"] = new DynamoDBEvent.AttributeValue { S = name } +- }; +- } +- +- return record; +- } +- +- +- +- // Test data classes that match the DynamoDbRecordDataExtractor output structure +- public class TestDynamoDbRecord +- { +- public string EventName { get; set; } +- public Dictionary Keys { get; set; } +- public Dictionary NewImage { get; set; } +- public Dictionary OldImage { get; set; } +- public string SequenceNumber { get; set; } +- public long SizeBytes { get; set; } +- public string StreamViewType { get; set; } +- } +-} +- +-// JsonSerializerContext needs to be outside the test class and partial for source generation +-[JsonSerializable(typeof(TypedDynamoDbStreamBatchProcessorTests.TestDynamoDbRecord))] +-public partial class TypedDynamoDbTestJsonSerializerContext : JsonSerializerContext +-{ +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/TypedKinesisEventBatchProcessorTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/TypedKinesisEventBatchProcessorTests.cs +deleted file mode 100644 +index e38aba96..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/TypedKinesisEventBatchProcessorTests.cs ++++ /dev/null +@@ -1,526 +0,0 @@ +- +- +-using System; +-using System.Collections.Generic; +-using System.IO; +-using System.Linq; +-using System.Text; +-using System.Text.Json; +-using System.Text.Json.Serialization; +-using System.Threading; +-using System.Threading.Tasks; +-using Amazon.Lambda.Core; +-using Amazon.Lambda.KinesisEvents; +-using AWS.Lambda.Powertools.BatchProcessing.Exceptions; +-using AWS.Lambda.Powertools.BatchProcessing.Kinesis; +-using AWS.Lambda.Powertools.Common; +-using NSubstitute; +-using Xunit; +- +-namespace AWS.Lambda.Powertools.BatchProcessing.Tests; +- +-[Collection("Sequential")] +-public class TypedKinesisEventBatchProcessorTests +-{ +- private readonly IPowertoolsConfigurations _mockConfigurations; +- private readonly TypedKinesisEventBatchProcessor _processor; +- +- public TypedKinesisEventBatchProcessorTests() +- { +- _mockConfigurations = Substitute.For(); +- _processor = new TypedKinesisEventBatchProcessor(_mockConfigurations); +- } +- +- [Fact] +- public async Task ProcessAsync_WithTypedHandler_DeserializesAndProcessesSuccessfully() +- { +- // Arrange +- var testData = new TestKinesisMessage { Id = 1, Name = "Test Kinesis Message" }; +- var messageBody = JsonSerializer.Serialize(testData); +- var dataStream = new MemoryStream(Encoding.UTF8.GetBytes(messageBody)); +- +- var @event = new KinesisEvent +- { +- Records = new List +- { +- new() +- { +- Kinesis = new KinesisEvent.Record +- { +- SequenceNumber = "12345", +- Data = dataStream, +- PartitionKey = "partition-1" +- } +- } +- } +- }; +- +- var handler = Substitute.For>(); +- handler.HandleAsync(Arg.Any(), Arg.Any()) +- .Returns(RecordHandlerResult.None); +- +- // Act +- var result = await _processor.ProcessAsync(@event, handler); +- +- // Assert +- Assert.Single(result.SuccessRecords); +- Assert.Empty(result.FailureRecords); +- Assert.Empty(result.BatchItemFailuresResponse.BatchItemFailures); +- +- await handler.Received(1).HandleAsync( +- Arg.Is(m => m.Id == testData.Id && m.Name == testData.Name), +- Arg.Any()); +- } +- +- [Fact] +- public async Task ProcessAsync_WithTypedHandlerWithContext_PassesContextCorrectly() +- { +- // Arrange +- var testData = new TestKinesisMessage { Id = 2, Name = "Context Test" }; +- var messageBody = JsonSerializer.Serialize(testData); +- var dataStream = new MemoryStream(Encoding.UTF8.GetBytes(messageBody)); +- var context = Substitute.For(); +- context.AwsRequestId.Returns("test-request-id"); +- +- var @event = new KinesisEvent +- { +- Records = new List +- { +- new() +- { +- Kinesis = new KinesisEvent.Record +- { +- SequenceNumber = "12346", +- Data = dataStream, +- PartitionKey = "partition-2" +- } +- } +- } +- }; +- +- var handler = Substitute.For>(); +- handler.HandleAsync(Arg.Any(), Arg.Any(), Arg.Any()) +- .Returns(RecordHandlerResult.None); +- +- // Act +- var result = await _processor.ProcessAsync(@event, handler, context); +- +- // Assert +- Assert.Single(result.SuccessRecords); +- Assert.Empty(result.FailureRecords); +- +- await handler.Received(1).HandleAsync( +- Arg.Is(m => m.Id == testData.Id && m.Name == testData.Name), +- Arg.Is(c => c.AwsRequestId == "test-request-id"), +- Arg.Any()); +- } +- +- [Fact] +- public async Task ProcessAsync_WithDeserializationOptions_UsesCustomOptions() +- { +- // Arrange +- var testData = new TestKinesisMessage { Id = 3, Name = "Custom Options Test" }; +- var messageBody = JsonSerializer.Serialize(testData, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); +- var dataStream = new MemoryStream(Encoding.UTF8.GetBytes(messageBody)); +- +- var @event = new KinesisEvent +- { +- Records = new List +- { +- new() +- { +- Kinesis = new KinesisEvent.Record +- { +- SequenceNumber = "12347", +- Data = dataStream, +- PartitionKey = "partition-3" +- } +- } +- } +- }; +- +- var deserializationOptions = new DeserializationOptions +- { +- JsonSerializerOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase } +- }; +- +- var handler = Substitute.For>(); +- handler.HandleAsync(Arg.Any(), Arg.Any()) +- .Returns(RecordHandlerResult.None); +- +- // Act +- var result = await _processor.ProcessAsync(@event, handler, deserializationOptions); +- +- // Assert +- Assert.Single(result.SuccessRecords); +- Assert.Empty(result.FailureRecords); +- +- await handler.Received(1).HandleAsync( +- Arg.Is(m => m.Id == testData.Id && m.Name == testData.Name), +- Arg.Any()); +- } +- +- [Fact] +- public async Task ProcessAsync_WithJsonSerializerContext_UsesAOTCompatibleDeserialization() +- { +- // Arrange +- var testData = new TestKinesisMessage { Id = 4, Name = "AOT Test" }; +- var messageBody = JsonSerializer.Serialize(testData, TypedKinesisTestJsonSerializerContext.Default.TestKinesisMessage); +- var dataStream = new MemoryStream(Encoding.UTF8.GetBytes(messageBody)); +- +- var @event = new KinesisEvent +- { +- Records = new List +- { +- new() +- { +- Kinesis = new KinesisEvent.Record +- { +- SequenceNumber = "12348", +- Data = dataStream, +- PartitionKey = "partition-4" +- } +- } +- } +- }; +- +- var deserializationOptions = new DeserializationOptions(TypedKinesisTestJsonSerializerContext.Default); +- +- var handler = Substitute.For>(); +- handler.HandleAsync(Arg.Any(), Arg.Any()) +- .Returns(RecordHandlerResult.None); +- +- // Act +- var result = await _processor.ProcessAsync(@event, handler, deserializationOptions); +- +- // Assert +- Assert.Single(result.SuccessRecords); +- Assert.Empty(result.FailureRecords); +- +- await handler.Received(1).HandleAsync( +- Arg.Is(m => m.Id == testData.Id && m.Name == testData.Name), +- Arg.Any()); +- } +- +- [Fact] +- public async Task ProcessAsync_WithInvalidJson_FailsRecordByDefault() +- { +- // Arrange +- var invalidJson = "invalid json"; +- var dataStream = new MemoryStream(Encoding.UTF8.GetBytes(invalidJson)); +- +- var @event = new KinesisEvent +- { +- Records = new List +- { +- new() +- { +- Kinesis = new KinesisEvent.Record +- { +- SequenceNumber = "12349", +- Data = dataStream, +- PartitionKey = "partition-5" +- } +- } +- } +- }; +- +- var handler = Substitute.For>(); +- +- // Act & Assert +- var exception = await Assert.ThrowsAsync(() => _processor.ProcessAsync(@event, handler)); +- +- // Verify the exception contains the expected details +- Assert.Contains("Failed processing record: '12349'", exception.Message); +- Assert.Single(exception.InnerExceptions); +- +- await handler.DidNotReceive().HandleAsync(Arg.Any(), Arg.Any()); +- } +- +- [Fact] +- public async Task ProcessAsync_WithInvalidJsonAndIgnorePolicy_IgnoresRecord() +- { +- // Arrange +- var invalidJson = "invalid json"; +- var dataStream = new MemoryStream(Encoding.UTF8.GetBytes(invalidJson)); +- +- var @event = new KinesisEvent +- { +- Records = new List +- { +- new() +- { +- Kinesis = new KinesisEvent.Record +- { +- SequenceNumber = "12350", +- Data = dataStream, +- PartitionKey = "partition-6" +- } +- } +- } +- }; +- +- var deserializationOptions = new DeserializationOptions +- { +- ErrorPolicy = DeserializationErrorPolicy.IgnoreRecord +- }; +- +- var handler = Substitute.For>(); +- +- // Act +- var result = await _processor.ProcessAsync(@event, handler, deserializationOptions); +- +- // Assert +- Assert.Single(result.SuccessRecords); +- Assert.Empty(result.FailureRecords); +- Assert.Empty(result.BatchItemFailuresResponse.BatchItemFailures); +- +- await handler.DidNotReceive().HandleAsync(Arg.Any(), Arg.Any()); +- } +- +- [Fact] +- public async Task ProcessAsync_WithMultipleRecords_ProcessesAllSuccessfully() +- { +- // Arrange +- var testData1 = new TestKinesisMessage { Id = 7, Name = "Message 1" }; +- var testData2 = new TestKinesisMessage { Id = 8, Name = "Message 2" }; +- var dataStream1 = new MemoryStream(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(testData1))); +- var dataStream2 = new MemoryStream(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(testData2))); +- +- var @event = new KinesisEvent +- { +- Records = new List +- { +- new() +- { +- Kinesis = new KinesisEvent.Record +- { +- SequenceNumber = "12351", +- Data = dataStream1, +- PartitionKey = "partition-7" +- } +- }, +- new() +- { +- Kinesis = new KinesisEvent.Record +- { +- SequenceNumber = "12352", +- Data = dataStream2, +- PartitionKey = "partition-8" +- } +- } +- } +- }; +- +- var handler = Substitute.For>(); +- handler.HandleAsync(Arg.Any(), Arg.Any()) +- .Returns(RecordHandlerResult.None); +- +- // Act +- var result = await _processor.ProcessAsync(@event, handler); +- +- // Assert +- Assert.Equal(2, result.SuccessRecords.Count); +- Assert.Empty(result.FailureRecords); +- Assert.Empty(result.BatchItemFailuresResponse.BatchItemFailures); +- +- await handler.Received(2).HandleAsync(Arg.Any(), Arg.Any()); +- } +- +- [Fact] +- public async Task ProcessAsync_WithHandlerException_FailsRecord() +- { +- // Arrange +- var testData = new TestKinesisMessage { Id = 9, Name = "Exception Test" }; +- var messageBody = JsonSerializer.Serialize(testData); +- var dataStream = new MemoryStream(Encoding.UTF8.GetBytes(messageBody)); +- +- var @event = new KinesisEvent +- { +- Records = new List +- { +- new() +- { +- Kinesis = new KinesisEvent.Record +- { +- SequenceNumber = "12353", +- Data = dataStream, +- PartitionKey = "partition-9" +- } +- } +- } +- }; +- +- var handler = Substitute.For>(); +- handler.WhenForAnyArgs(x => x.HandleAsync(Arg.Any(), Arg.Any())) +- .Do(callInfo => throw new InvalidOperationException("Handler failed")); +- +- // Act & Assert +- var exception = await Assert.ThrowsAsync(() => _processor.ProcessAsync(@event, handler)); +- +- // Verify the exception contains the expected details +- Assert.Contains("Failed processing record: '12353'", exception.Message); +- Assert.Single(exception.InnerExceptions); +- } +- +- [Fact] +- public async Task ProcessAsync_WithCancellationToken_PropagatesCancellation() +- { +- // Arrange +- var testData = new TestKinesisMessage { Id = 12, Name = "Cancellation Test" }; +- var messageBody = JsonSerializer.Serialize(testData); +- var dataStream = new MemoryStream(Encoding.UTF8.GetBytes(messageBody)); +- +- var @event = new KinesisEvent +- { +- Records = new List +- { +- new() +- { +- Kinesis = new KinesisEvent.Record +- { +- SequenceNumber = "12354", +- Data = dataStream, +- PartitionKey = "partition-12" +- } +- } +- } +- }; +- +- var handler = Substitute.For>(); +- var cancellationTokenSource = new CancellationTokenSource(); +- cancellationTokenSource.Cancel(); +- +- // Act & Assert +- var exception = await Assert.ThrowsAsync(() => +- _processor.ProcessAsync(@event, handler, cancellationTokenSource.Token)); +- +- // Verify the cancellation was the root cause +- Assert.Contains("Failed processing record: '12354'", exception.Message); +- Assert.Single(exception.InnerExceptions); +- Assert.IsType(exception.InnerExceptions.First()); +- Assert.IsType(exception.InnerExceptions.First().InnerException); +- } +- +- +- +- [Fact] +- public async Task ProcessAsync_WithNullContext_HandlesGracefully() +- { +- // Arrange +- var testData = new TestKinesisMessage { Id = 13, Name = "Null Context Test" }; +- var messageBody = JsonSerializer.Serialize(testData); +- var dataStream = new MemoryStream(Encoding.UTF8.GetBytes(messageBody)); +- +- var @event = new KinesisEvent +- { +- Records = new List +- { +- new() +- { +- Kinesis = new KinesisEvent.Record +- { +- SequenceNumber = "12355", +- Data = dataStream, +- PartitionKey = "partition-13" +- } +- } +- } +- }; +- +- var handler = Substitute.For>(); +- handler.HandleAsync(Arg.Any(), Arg.Any(), Arg.Any()) +- .Returns(RecordHandlerResult.None); +- +- // Act +- var result = await _processor.ProcessAsync(@event, handler, context: null); +- +- // Assert +- Assert.Single(result.SuccessRecords); +- Assert.Empty(result.FailureRecords); +- +- await handler.Received(1).HandleAsync( +- Arg.Any(), +- Arg.Is(c => c == null), +- Arg.Any()); +- } +- +- [Fact] +- public async Task ProcessAsync_WithEmptyKinesisData_HandlesGracefully() +- { +- // Arrange +- var dataStream = new MemoryStream(Encoding.UTF8.GetBytes(string.Empty)); +- +- var @event = new KinesisEvent +- { +- Records = new List +- { +- new() +- { +- Kinesis = new KinesisEvent.Record +- { +- SequenceNumber = "12356", +- Data = dataStream, +- PartitionKey = "partition-14" +- } +- } +- } +- }; +- +- var handler = Substitute.For>(); +- +- // Act & Assert +- var exception = await Assert.ThrowsAsync(() => _processor.ProcessAsync(@event, handler)); +- +- // Verify the exception contains the expected details +- Assert.Contains("Failed processing record: '12356'", exception.Message); +- Assert.Single(exception.InnerExceptions); +- +- await handler.DidNotReceive().HandleAsync(Arg.Any(), Arg.Any()); +- } +- +- [Fact] +- public async Task ProcessAsync_WithNullKinesisData_HandlesGracefully() +- { +- // Arrange +- var @event = new KinesisEvent +- { +- Records = new List +- { +- new() +- { +- Kinesis = new KinesisEvent.Record +- { +- SequenceNumber = "12357", +- Data = null, +- PartitionKey = "partition-15" +- } +- } +- } +- }; +- +- var handler = Substitute.For>(); +- +- // Act & Assert +- var exception = await Assert.ThrowsAsync(() => _processor.ProcessAsync(@event, handler)); +- +- // Verify the exception contains the expected details +- Assert.Contains("Failed processing record: '12357'", exception.Message); +- Assert.Single(exception.InnerExceptions); +- +- await handler.DidNotReceive().HandleAsync(Arg.Any(), Arg.Any()); +- } +- +- // Test data classes +- public class TestKinesisMessage +- { +- public int Id { get; set; } +- public string Name { get; set; } +- } +-} +- +-// JsonSerializerContext needs to be outside the test class and partial for source generation +-[JsonSerializable(typeof(TypedKinesisEventBatchProcessorTests.TestKinesisMessage))] +-public partial class TypedKinesisTestJsonSerializerContext : JsonSerializerContext +-{ +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/TypedSqsBatchProcessorTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/TypedSqsBatchProcessorTests.cs +deleted file mode 100644 +index 4a067f37..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/TypedSqsBatchProcessorTests.cs ++++ /dev/null +@@ -1,672 +0,0 @@ +- +- +-using System; +-using System.Collections.Generic; +-using System.Linq; +-using System.Text.Json; +-using System.Text.Json.Serialization; +-using System.Threading; +-using System.Threading.Tasks; +-using Amazon.Lambda.Core; +-using Amazon.Lambda.SQSEvents; +-using AWS.Lambda.Powertools.BatchProcessing.Exceptions; +-using AWS.Lambda.Powertools.BatchProcessing.Sqs; +-using AWS.Lambda.Powertools.Common; +-using NSubstitute; +-using Xunit; +- +-namespace AWS.Lambda.Powertools.BatchProcessing.Tests; +- +-[Collection("Sequential")] +-public class TypedSqsBatchProcessorTests +-{ +- private readonly IPowertoolsConfigurations _mockConfigurations; +- private readonly TypedSqsBatchProcessor _processor; +- +- public TypedSqsBatchProcessorTests() +- { +- _mockConfigurations = Substitute.For(); +- _processor = new TypedSqsBatchProcessor(_mockConfigurations); +- } +- +- [Fact] +- public async Task ProcessAsync_WithTypedHandler_DeserializesAndProcessesSuccessfully() +- { +- // Arrange +- var testData = new TestMessage { Id = 1, Name = "Test Message" }; +- var messageBody = JsonSerializer.Serialize(testData); +- +- var @event = new SQSEvent +- { +- Records = new List +- { +- new() +- { +- MessageId = "msg-1", +- Body = messageBody, +- EventSourceArn = "arn:aws:sqs:us-east-1:123456789012:test-queue" +- } +- } +- }; +- +- var handler = Substitute.For>(); +- handler.HandleAsync(Arg.Any(), Arg.Any()) +- .Returns(RecordHandlerResult.None); +- +- // Act +- var result = await _processor.ProcessAsync(@event, handler); +- +- // Assert +- Assert.Single(result.SuccessRecords); +- Assert.Empty(result.FailureRecords); +- Assert.Empty(result.BatchItemFailuresResponse.BatchItemFailures); +- +- await handler.Received(1).HandleAsync( +- Arg.Is(m => m.Id == testData.Id && m.Name == testData.Name), +- Arg.Any()); +- } +- +- [Fact] +- public async Task ProcessAsync_WithTypedHandlerWithContext_PassesContextCorrectly() +- { +- // Arrange +- var testData = new TestMessage { Id = 2, Name = "Context Test" }; +- var messageBody = JsonSerializer.Serialize(testData); +- var context = Substitute.For(); +- context.AwsRequestId.Returns("test-request-id"); +- +- var @event = new SQSEvent +- { +- Records = new List +- { +- new() +- { +- MessageId = "msg-2", +- Body = messageBody, +- EventSourceArn = "arn:aws:sqs:us-east-1:123456789012:test-queue" +- } +- } +- }; +- +- var handler = Substitute.For>(); +- handler.HandleAsync(Arg.Any(), Arg.Any(), Arg.Any()) +- .Returns(RecordHandlerResult.None); +- +- // Act +- var result = await _processor.ProcessAsync(@event, handler, context); +- +- // Assert +- Assert.Single(result.SuccessRecords); +- Assert.Empty(result.FailureRecords); +- +- await handler.Received(1).HandleAsync( +- Arg.Is(m => m.Id == testData.Id && m.Name == testData.Name), +- Arg.Is(c => c.AwsRequestId == "test-request-id"), +- Arg.Any()); +- } +- +- [Fact] +- public async Task ProcessAsync_WithDeserializationOptions_UsesCustomOptions() +- { +- // Arrange +- var testData = new TestMessage { Id = 3, Name = "Custom Options Test" }; +- var messageBody = JsonSerializer.Serialize(testData, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); +- +- var @event = new SQSEvent +- { +- Records = new List +- { +- new() +- { +- MessageId = "msg-3", +- Body = messageBody, +- EventSourceArn = "arn:aws:sqs:us-east-1:123456789012:test-queue" +- } +- } +- }; +- +- var deserializationOptions = new DeserializationOptions +- { +- JsonSerializerOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase } +- }; +- +- var handler = Substitute.For>(); +- handler.HandleAsync(Arg.Any(), Arg.Any()) +- .Returns(RecordHandlerResult.None); +- +- // Act +- var result = await _processor.ProcessAsync(@event, handler, deserializationOptions); +- +- // Assert +- Assert.Single(result.SuccessRecords); +- Assert.Empty(result.FailureRecords); +- +- await handler.Received(1).HandleAsync( +- Arg.Is(m => m.Id == testData.Id && m.Name == testData.Name), +- Arg.Any()); +- } +- +- [Fact] +- public async Task ProcessAsync_WithJsonSerializerContext_UsesAOTCompatibleDeserialization() +- { +- // Arrange +- var testData = new TestMessage { Id = 4, Name = "AOT Test" }; +- var messageBody = JsonSerializer.Serialize(testData, TypedSqsTestJsonSerializerContext.Default.TestMessage); +- +- var @event = new SQSEvent +- { +- Records = new List +- { +- new() +- { +- MessageId = "msg-4", +- Body = messageBody, +- EventSourceArn = "arn:aws:sqs:us-east-1:123456789012:test-queue" +- } +- } +- }; +- +- var deserializationOptions = new DeserializationOptions(TypedSqsTestJsonSerializerContext.Default); +- +- var handler = Substitute.For>(); +- handler.HandleAsync(Arg.Any(), Arg.Any()) +- .Returns(RecordHandlerResult.None); +- +- // Act +- var result = await _processor.ProcessAsync(@event, handler, deserializationOptions); +- +- // Assert +- Assert.Single(result.SuccessRecords); +- Assert.Empty(result.FailureRecords); +- +- await handler.Received(1).HandleAsync( +- Arg.Is(m => m.Id == testData.Id && m.Name == testData.Name), +- Arg.Any()); +- } +- +- [Fact] +- public async Task ProcessAsync_WithInvalidJson_FailsRecordByDefault() +- { +- // Arrange +- var @event = new SQSEvent +- { +- Records = new List +- { +- new() +- { +- MessageId = "msg-5", +- Body = "invalid json", +- EventSourceArn = "arn:aws:sqs:us-east-1:123456789012:test-queue" +- } +- } +- }; +- +- var handler = Substitute.For>(); +- +- // Act & Assert +- var exception = await Assert.ThrowsAsync(() => _processor.ProcessAsync(@event, handler)); +- +- // Verify the exception contains the expected details +- Assert.Contains("Failed processing record: 'msg-5'", exception.Message); +- Assert.Single(exception.InnerExceptions); +- +- await handler.DidNotReceive().HandleAsync(Arg.Any(), Arg.Any()); +- } +- +- [Fact] +- public async Task ProcessAsync_WithInvalidJsonAndIgnorePolicy_IgnoresRecord() +- { +- // Arrange +- var @event = new SQSEvent +- { +- Records = new List +- { +- new() +- { +- MessageId = "msg-6", +- Body = "invalid json", +- EventSourceArn = "arn:aws:sqs:us-east-1:123456789012:test-queue" +- } +- } +- }; +- +- var deserializationOptions = new DeserializationOptions +- { +- ErrorPolicy = DeserializationErrorPolicy.IgnoreRecord +- }; +- +- var handler = Substitute.For>(); +- +- // Act +- var result = await _processor.ProcessAsync(@event, handler, deserializationOptions); +- +- // Assert +- Assert.Single(result.SuccessRecords); +- Assert.Empty(result.FailureRecords); +- Assert.Empty(result.BatchItemFailuresResponse.BatchItemFailures); +- +- await handler.DidNotReceive().HandleAsync(Arg.Any(), Arg.Any()); +- } +- +- [Fact] +- public async Task ProcessAsync_WithMultipleRecords_ProcessesAllSuccessfully() +- { +- // Arrange +- var testData1 = new TestMessage { Id = 7, Name = "Message 1" }; +- var testData2 = new TestMessage { Id = 8, Name = "Message 2" }; +- +- var @event = new SQSEvent +- { +- Records = new List +- { +- new() +- { +- MessageId = "msg-7", +- Body = JsonSerializer.Serialize(testData1), +- EventSourceArn = "arn:aws:sqs:us-east-1:123456789012:test-queue" +- }, +- new() +- { +- MessageId = "msg-8", +- Body = JsonSerializer.Serialize(testData2), +- EventSourceArn = "arn:aws:sqs:us-east-1:123456789012:test-queue" +- } +- } +- }; +- +- var handler = Substitute.For>(); +- handler.HandleAsync(Arg.Any(), Arg.Any()) +- .Returns(RecordHandlerResult.None); +- +- // Act +- var result = await _processor.ProcessAsync(@event, handler); +- +- // Assert +- Assert.Equal(2, result.SuccessRecords.Count); +- Assert.Empty(result.FailureRecords); +- Assert.Empty(result.BatchItemFailuresResponse.BatchItemFailures); +- +- await handler.Received(2).HandleAsync(Arg.Any(), Arg.Any()); +- } +- +- [Fact] +- public async Task ProcessAsync_WithHandlerException_FailsRecord() +- { +- // Arrange +- var testData = new TestMessage { Id = 9, Name = "Exception Test" }; +- var messageBody = JsonSerializer.Serialize(testData); +- +- var @event = new SQSEvent +- { +- Records = new List +- { +- new() +- { +- MessageId = "msg-9", +- Body = messageBody, +- EventSourceArn = "arn:aws:sqs:us-east-1:123456789012:test-queue" +- } +- } +- }; +- +- var handler = Substitute.For>(); +- handler.WhenForAnyArgs(x => x.HandleAsync(Arg.Any(), Arg.Any())) +- .Do(callInfo => throw new InvalidOperationException("Handler failed")); +- +- // Act & Assert +- var exception = await Assert.ThrowsAsync(() => _processor.ProcessAsync(@event, handler)); +- +- // Verify the exception contains the expected details +- Assert.Contains("Failed processing record: 'msg-9'", exception.Message); +- Assert.Single(exception.InnerExceptions); +- } +- +- [Fact] +- public async Task ProcessAsync_WithFifoQueue_UsesStopOnFirstFailurePolicy() +- { +- // Arrange +- var testData1 = new TestMessage { Id = 10, Name = "FIFO Message 1" }; +- var testData2 = new TestMessage { Id = 11, Name = "FIFO Message 2" }; +- +- var @event = new SQSEvent +- { +- Records = new List +- { +- new() +- { +- MessageId = "msg-10", +- Body = JsonSerializer.Serialize(testData1), +- EventSourceArn = "arn:aws:sqs:us-east-1:123456789012:test-queue.fifo" +- }, +- new() +- { +- MessageId = "msg-11", +- Body = JsonSerializer.Serialize(testData2), +- EventSourceArn = "arn:aws:sqs:us-east-1:123456789012:test-queue.fifo" +- } +- } +- }; +- +- var handler = Substitute.For>(); +- handler.WhenForAnyArgs(x => x.HandleAsync(Arg.Any(), Arg.Any())) +- .Do(callInfo => +- { +- var data = callInfo.Arg(); +- if (data.Id == 10) +- { +- throw new InvalidOperationException("First record failed"); +- } +- }); +- handler.HandleAsync(Arg.Is(m => m.Id == 11), Arg.Any()) +- .Returns(RecordHandlerResult.None); +- +- // Act & Assert +- var exception = await Assert.ThrowsAsync(() => _processor.ProcessAsync(@event, handler)); +- +- // Verify that the first record failed and the second was not processed +- Assert.Contains("Failed processing record: 'msg-10'", exception.Message); +- Assert.Contains("Record: 'msg-11' has not been processed", exception.Message); +- Assert.Equal(2, exception.InnerExceptions.Count); +- } +- +- [Fact] +- public async Task ProcessAsync_WithCancellationToken_PropagatesCancellation() +- { +- // Arrange +- var testData = new TestMessage { Id = 12, Name = "Cancellation Test" }; +- var messageBody = JsonSerializer.Serialize(testData); +- +- var @event = new SQSEvent +- { +- Records = new List +- { +- new() +- { +- MessageId = "msg-12", +- Body = messageBody, +- EventSourceArn = "arn:aws:sqs:us-east-1:123456789012:test-queue" +- } +- } +- }; +- +- var handler = Substitute.For>(); +- var cancellationTokenSource = new CancellationTokenSource(); +- cancellationTokenSource.Cancel(); +- +- // Act & Assert +- var exception = await Assert.ThrowsAsync(() => +- _processor.ProcessAsync(@event, handler, cancellationTokenSource.Token)); +- +- // Verify the cancellation was the root cause +- Assert.Contains("Failed processing record: 'msg-12'", exception.Message); +- Assert.Single(exception.InnerExceptions); +- Assert.IsType(exception.InnerExceptions.First()); +- Assert.IsType(exception.InnerExceptions.First().InnerException); +- } +- +- +- +- [Fact] +- public async Task ProcessAsync_WithNullContext_HandlesGracefully() +- { +- // Arrange +- var testData = new TestMessage { Id = 13, Name = "Null Context Test" }; +- var messageBody = JsonSerializer.Serialize(testData); +- +- var @event = new SQSEvent +- { +- Records = new List +- { +- new() +- { +- MessageId = "msg-13", +- Body = messageBody, +- EventSourceArn = "arn:aws:sqs:us-east-1:123456789012:test-queue" +- } +- } +- }; +- +- var handler = Substitute.For>(); +- handler.HandleAsync(Arg.Any(), Arg.Any(), Arg.Any()) +- .Returns(RecordHandlerResult.None); +- +- // Act +- var result = await _processor.ProcessAsync(@event, handler, context: null); +- +- // Assert +- Assert.Single(result.SuccessRecords); +- Assert.Empty(result.FailureRecords); +- +- await handler.Received(1).HandleAsync( +- Arg.Any(), +- Arg.Is(c => c == null), +- Arg.Any()); +- } +- +- // Test data classes +- public class TestMessage +- { +- public int Id { get; set; } +- public string Name { get; set; } +- } +- +- public class UnregisteredSqsMessage +- { +- public string Value { get; set; } +- } +- +- #region AOT Compatibility Tests +- +- [Fact] +- public async Task ProcessAsync_WithUnregisteredTypeInContext_ThrowsAotTypeValidationException() +- { +- // Arrange +- var @event = new SQSEvent +- { +- Records = new List +- { +- new() +- { +- MessageId = "msg-1", +- Body = """{"Value":"test"}""" +- } +- } +- }; +- +- var deserializationOptions = new DeserializationOptions(TypedSqsTestJsonSerializerContext.Default); +- var handler = Substitute.For>(); +- +- // Act & Assert +- var exception = await Assert.ThrowsAsync(() => +- _processor.ProcessAsync(@event, handler, deserializationOptions)); +- +- Assert.Equal(typeof(UnregisteredSqsMessage), exception.TargetType); +- Assert.Contains("UnregisteredSqsMessage", exception.Message); +- Assert.Contains("JsonSerializable", exception.Message); +- } +- +- [Fact] +- public async Task ProcessAsync_WithContextHandler_ValidatesAotCompatibility() +- { +- // Arrange +- var @event = new SQSEvent +- { +- Records = new List +- { +- new() +- { +- MessageId = "msg-1", +- Body = """{"Value":"test"}""" +- } +- } +- }; +- +- var context = Substitute.For(); +- var deserializationOptions = new DeserializationOptions(TypedSqsTestJsonSerializerContext.Default); +- var handler = Substitute.For>(); +- +- // Act & Assert +- var exception = await Assert.ThrowsAsync(() => +- _processor.ProcessAsync(@event, handler, context, deserializationOptions)); +- +- Assert.Equal(typeof(UnregisteredSqsMessage), exception.TargetType); +- } +- +- [Fact] +- public async Task ProcessAsync_WithValidAotContext_ProcessesSuccessfully() +- { +- // Arrange +- var testData = new TestMessage { Id = 5, Name = "AOT Valid Test" }; +- var messageBody = JsonSerializer.Serialize(testData, TypedSqsTestJsonSerializerContext.Default.TestMessage); +- +- var @event = new SQSEvent +- { +- Records = new List +- { +- new() +- { +- MessageId = "msg-1", +- Body = messageBody +- } +- } +- }; +- +- var deserializationOptions = new DeserializationOptions(TypedSqsTestJsonSerializerContext.Default); +- var handler = Substitute.For>(); +- handler.HandleAsync(Arg.Any(), Arg.Any()) +- .Returns(RecordHandlerResult.None); +- +- // Act +- var result = await _processor.ProcessAsync(@event, handler, deserializationOptions); +- +- // Assert +- Assert.NotNull(result); +- Assert.Empty(result.BatchItemFailuresResponse.BatchItemFailures); +- await handler.Received(1).HandleAsync( +- Arg.Is(m => m.Id == 5 && m.Name == "AOT Valid Test"), +- Arg.Any()); +- } +- +- #endregion +- +- #region Constructor and Instance Tests +- +- +- +- [Fact] +- public void Constructor_WithCustomServices_InitializesCorrectly() +- { +- // Arrange +- var mockDeserializationService = Substitute.For(); +- var mockRecordDataExtractor = Substitute.For>(); +- +- // Act +- var processor = new TypedSqsBatchProcessor( +- _mockConfigurations, +- mockDeserializationService, +- mockRecordDataExtractor); +- +- // Assert +- Assert.NotNull(processor); +- } +- +- [Fact] +- public void DefaultConstructor_InitializesCorrectly() +- { +- // Act & Assert - Should not throw +- var processor = new TestableTypedSqsBatchProcessor(); +- Assert.NotNull(processor); +- } +- +- // Helper class to test protected constructor +- private class TestableTypedSqsBatchProcessor : TypedSqsBatchProcessor +- { +- public TestableTypedSqsBatchProcessor() : base() +- { +- } +- } +- +- #endregion +- +- #region Wrapper Class Coverage Tests +- +- [Fact] +- public async Task ProcessAsync_WithIgnoreErrorPolicy_SkipsInvalidRecords() +- { +- // Arrange +- var @event = new SQSEvent +- { +- Records = new List +- { +- new() +- { +- MessageId = "msg-1", +- Body = "invalid-json" +- }, +- new() +- { +- MessageId = "msg-2", +- Body = JsonSerializer.Serialize(new TestMessage { Id = 1, Name = "Valid" }) +- } +- } +- }; +- +- var deserializationOptions = new DeserializationOptions +- { +- ErrorPolicy = DeserializationErrorPolicy.IgnoreRecord +- }; +- +- var handler = Substitute.For>(); +- handler.HandleAsync(Arg.Any(), Arg.Any()) +- .Returns(RecordHandlerResult.None); +- +- // Act +- var result = await _processor.ProcessAsync(@event, handler, deserializationOptions); +- +- // Assert +- Assert.NotNull(result); +- // Should only process the valid record +- await handler.Received(1).HandleAsync(Arg.Any(), Arg.Any()); +- } +- +- [Fact] +- public async Task ProcessAsync_WithContextHandlerAndIgnoreErrorPolicy_SkipsInvalidRecords() +- { +- // Arrange +- var @event = new SQSEvent +- { +- Records = new List +- { +- new() +- { +- MessageId = "msg-1", +- Body = "invalid-json" +- } +- } +- }; +- +- var context = Substitute.For(); +- var deserializationOptions = new DeserializationOptions +- { +- ErrorPolicy = DeserializationErrorPolicy.IgnoreRecord +- }; +- +- var handler = Substitute.For>(); +- +- // Act +- var result = await _processor.ProcessAsync(@event, handler, context, deserializationOptions); +- +- // Assert +- Assert.NotNull(result); +- // Should not call handler for invalid record +- await handler.DidNotReceive().HandleAsync(Arg.Any(), Arg.Any(), Arg.Any()); +- } +- +- #endregion +-} +- +-// JsonSerializerContext needs to be outside the test class and partial for source generation +-[JsonSerializable(typeof(TypedSqsBatchProcessorTests.TestMessage))] +-public partial class TypedSqsTestJsonSerializerContext : JsonSerializerContext +-{ +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Common.Tests/ConsoleWrapperTests.cs b/libraries/tests/AWS.Lambda.Powertools.Common.Tests/ConsoleWrapperTests.cs +deleted file mode 100644 +index 25cea21d..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Common.Tests/ConsoleWrapperTests.cs ++++ /dev/null +@@ -1,335 +0,0 @@ +-using System; +-using System.IO; +-using NSubstitute; +-using Xunit; +- +-namespace AWS.Lambda.Powertools.Common.Tests; +- +-public class ConsoleWrapperTests : IDisposable +-{ +- private readonly TextWriter _originalOut; +- private readonly TextWriter _originalError; +- private readonly StringWriter _testWriter; +- +- public ConsoleWrapperTests() +- { +- // Store original console outputs +- _originalOut = Console.Out; +- _originalError = Console.Error; +- +- // Setup test writer +- _testWriter = new StringWriter(); +- +- // Reset ConsoleWrapper state before each test +- ConsoleWrapper.ResetForTest(); +- +- // Clear any Lambda environment variables +- Environment.SetEnvironmentVariable("AWS_LAMBDA_FUNCTION_NAME", null); +- } +- +- public void Dispose() +- { +- // Restore original console outputs +- Console.SetOut(_originalOut); +- Console.SetError(_originalError); +- +- // Reset ConsoleWrapper state after each test +- ConsoleWrapper.ResetForTest(); +- +- // Clear any test environment variables +- Environment.SetEnvironmentVariable("AWS_LAMBDA_FUNCTION_NAME", null); +- +- _testWriter?.Dispose(); +- } +- +- [Fact] +- public void WriteLine_GivenInTestMode_WhenCalled_ThenWritesToTestOutputStream() +- { +- // Given +- ConsoleWrapper.SetOut(_testWriter); +- var wrapper = new ConsoleWrapper(); +- const string message = "test message"; +- +- // When +- wrapper.WriteLine(message); +- +- // Then +- Assert.Equal($"{message}{Environment.NewLine}", _testWriter.ToString()); +- } +- +- [Fact] +- public void WriteLine_GivenNotInLambdaEnvironment_WhenCalled_ThenWritesToConsoleDirectly() +- { +- // Given +- var wrapper = new ConsoleWrapper(); +- var consoleOutput = new StringWriter(); +- Console.SetOut(consoleOutput); +- const string message = "test message"; +- +- // When +- wrapper.WriteLine(message); +- +- // Then +- Assert.Equal($"{message}{Environment.NewLine}", consoleOutput.ToString()); +- consoleOutput.Dispose(); +- } +- +- [Fact] +- public void WriteLine_GivenInLambdaEnvironment_WhenCalled_ThenOverridesConsoleOutput() +- { +- // Given +- Environment.SetEnvironmentVariable("AWS_LAMBDA_FUNCTION_NAME", "test-function"); +- var wrapper = new ConsoleWrapper(); +- const string message = "test message"; +- +- // When +- wrapper.WriteLine(message); +- +- // Then +- // Should not throw and should have attempted to override console +- Assert.NotNull(Console.Out); +- } +- +- [Fact] +- public void WriteLine_GivenMultipleCallsInLambda_WhenConsoleIsReIntercepted_ThenReOverridesConsole() +- { +- // Given +- Environment.SetEnvironmentVariable("AWS_LAMBDA_FUNCTION_NAME", "test-function"); +- var wrapper = new ConsoleWrapper(); +- +- // When - First call should override console +- wrapper.WriteLine("First message"); +- +- // Simulate Lambda re-intercepting console by setting it to a wrapped writer +- var lambdaInterceptedWriter = new StringWriter(); +- Console.SetOut(lambdaInterceptedWriter); +- +- // Second call should detect and re-override +- wrapper.WriteLine("Second message"); +- +- // Then +- // Should not throw and console should be overridden again +- Assert.NotNull(Console.Out); +- lambdaInterceptedWriter.Dispose(); +- } +- +- [Fact] +- public void WriteLine_GivenLambdaEnvironmentWithConsoleOverrideFailing_WhenCalled_ThenDoesNotThrow() +- { +- // Given +- Environment.SetEnvironmentVariable("AWS_LAMBDA_FUNCTION_NAME", "test-function"); +- var wrapper = new ConsoleWrapper(); +- +- // When & Then - Should not throw even if console override fails +- var exception = Record.Exception(() => wrapper.WriteLine("Test message")); +- Assert.Null(exception); +- } +- +- [Fact] +- public void Debug_GivenInTestMode_WhenCalled_ThenWritesToTestOutputStream() +- { +- // Given +- ConsoleWrapper.SetOut(_testWriter); +- var wrapper = new ConsoleWrapper(); +- const string message = "debug message"; +- +- // When +- wrapper.Debug(message); +- +- // Then +- Assert.Equal($"{message}{Environment.NewLine}", _testWriter.ToString()); +- } +- +- [Fact] +- public void Debug_GivenNotInTestMode_WhenCalled_ThenDoesNotThrow() +- { +- // Given +- var wrapper = new ConsoleWrapper(); +- ConsoleWrapper.ResetForTest(); // Ensure we're not in test mode +- +- // When & Then - Just verify it doesn't throw +- var exception = Record.Exception(() => wrapper.Debug("debug message")); +- Assert.Null(exception); +- } +- +- [Fact] +- public void Error_GivenInTestMode_WhenCalled_ThenWritesToTestOutputStream() +- { +- // Given +- ConsoleWrapper.SetOut(_testWriter); +- var wrapper = new ConsoleWrapper(); +- const string message = "error message"; +- +- // When +- wrapper.Error(message); +- +- // Then +- Assert.Equal($"{message}{Environment.NewLine}", _testWriter.ToString()); +- } +- +- [Fact] +- public void Error_GivenNotInTestMode_WhenCalled_ThenDoesNotThrow() +- { +- // Given +- var wrapper = new ConsoleWrapper(); +- ConsoleWrapper.ResetForTest(); // Ensure we're not in test mode +- +- // When & Then - The Error method creates its own StreamWriter, +- // so we just verify it doesn't throw +- var exception = Record.Exception(() => wrapper.Error("error message")); +- Assert.Null(exception); +- } +- +- [Fact] +- public void Error_GivenNotOverridden_WhenCalled_ThenDoesNotThrow() +- { +- // Given +- var wrapper = new ConsoleWrapper(); +- ConsoleWrapper.ResetForTest(); // Reset to ensure _override is false +- +- // When & Then - Just verify it doesn't throw +- var exception = Record.Exception(() => wrapper.Error("error without override")); +- Assert.Null(exception); +- } +- +- [Fact] +- public void SetOut_GivenTextWriter_WhenCalled_ThenEnablesTestMode() +- { +- // Given +- var testOutput = new StringWriter(); +- +- // When +- ConsoleWrapper.SetOut(testOutput); +- +- // Then +- var wrapper = new ConsoleWrapper(); +- wrapper.WriteLine("test"); +- Assert.Equal($"test{Environment.NewLine}", testOutput.ToString()); +- testOutput.Dispose(); +- } +- +- [Fact] +- public void ResetForTest_GivenTestModeEnabled_WhenCalled_ThenResetsToNormalMode() +- { +- // Given +- var testOutput = new StringWriter(); +- ConsoleWrapper.SetOut(testOutput); +- +- // When +- ConsoleWrapper.ResetForTest(); +- +- // Then +- var wrapper = new ConsoleWrapper(); +- var consoleOutput = new StringWriter(); +- Console.SetOut(consoleOutput); +- wrapper.WriteLine("test"); +- Assert.Equal($"test{Environment.NewLine}", consoleOutput.ToString()); +- Assert.Empty(testOutput.ToString()); +- testOutput.Dispose(); +- consoleOutput.Dispose(); +- } +- +- [Fact] +- public void WriteLineStatic_GivenLogLevelAndMessage_WhenCalled_ThenFormatsWithTimestamp() +- { +- // Given +- ConsoleWrapper.SetOut(_testWriter); +- const string logLevel = "INFO"; +- const string message = "Test log message"; +- +- try +- { +- // When - Using reflection to call internal static method +- var method = typeof(ConsoleWrapper) +- .GetMethod("WriteLine", +- System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); +- +- if (method == null) +- { +- // Fall back if the method signature has changed +- Assert.True(true, "StaticWriteLine method not available or has changed signature"); +- return; +- } +- +- method.Invoke(null, new object[] { logLevel, message }); +- +- // Then +- var output = _testWriter.ToString(); +- Assert.Contains(logLevel, output); +- Assert.Contains(message, output); +- +- var lines = output.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries); +- Assert.True(lines.Length > 0, "Output should contain at least one line"); +- +- var parts = lines[0].Split('\t'); +- Assert.True(parts.Length >= 3, "Output should contain at least 3 tab-separated parts"); +- +- // Check that parts[0] contains a timestamp-like string +- Assert.Matches(@"[\d\-:TZ.]", parts[0]); +- Assert.Equal(logLevel, parts[1]); +- Assert.Equal(message, parts[2]); +- } +- catch (Exception ex) +- { +- Console.WriteLine($"Test exception: {ex}"); +- Assert.True(true, "Skipping test due to reflection error"); +- } +- } +- +- [Fact] +- public void ClearOutputResetFlag_GivenAnyState_WhenCalled_ThenDoesNotThrow() +- { +- // Given - any state +- +- // When & Then - Should not throw (kept for backward compatibility) +- var exception = Record.Exception(() => ConsoleWrapper.ClearOutputResetFlag()); +- Assert.Null(exception); +- } +- +- [Fact] +- public void ClearOutputResetFlag_GivenMultipleCalls_WhenCalled_ThenAllowsRepeatedWrites() +- { +- // Given +- var wrapper = new ConsoleWrapper(); +- ConsoleWrapper.SetOut(_testWriter); +- +- // When +- wrapper.WriteLine("First message"); +- ConsoleWrapper.ClearOutputResetFlag(); +- wrapper.WriteLine("Second message"); +- +- // Then +- Assert.Equal($"First message{Environment.NewLine}Second message{Environment.NewLine}", _testWriter.ToString()); +- } +- +- // from here +- +- [Fact] +- public void HasLambdaReInterceptedConsole_WhenConsoleOutAccessThrows_ThenReturnsTrueFromCatchBlock() +- { +- // Given - A function that throws when called (simulating Console.Out access failure) +- Func throwingAccessor = () => throw new InvalidOperationException("Console.Out access failed"); +- +- // When - Call the internal method with the throwing accessor +- var result = ConsoleWrapper.HasLambdaReInterceptedConsole(throwingAccessor); +- +- // Then - Should return true from the catch block (lines 102-105) +- Assert.True(result); +- } +- +- [Fact] +- public void OverrideLambdaLogger_WhenOpenStandardOutputThrows_ThenSetsOverrideToFalse() +- { +- // Given +- ConsoleWrapper.ResetForTest(); +- +- // A function that throws when called (simulating Console.OpenStandardOutput failure) +- Func throwingOpener = () => throw new UnauthorizedAccessException("Cannot open standard output"); +- +- // When - Call the internal method with the throwing opener +- var exception = Record.Exception(() => ConsoleWrapper.OverrideLambdaLogger(throwingOpener)); +- +- // Then - Should not throw (catch block handles it on lines 120-123) +- Assert.Null(exception); +- } +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Common.Tests/Core/LambdaLifecycleTrackerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Common.Tests/Core/LambdaLifecycleTrackerTests.cs +deleted file mode 100644 +index 5b69f457..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Common.Tests/Core/LambdaLifecycleTrackerTests.cs ++++ /dev/null +@@ -1,105 +0,0 @@ +-using System; +-using AWS.Lambda.Powertools.Common.Core; +-using Xunit; +- +-namespace AWS.Lambda.Powertools.Common.Tests; +- +-public class LambdaLifecycleTrackerTests : IDisposable +- { +- public LambdaLifecycleTrackerTests() +- { +- // Reset before each test to ensure clean state +- LambdaLifecycleTracker.Reset(); +- Environment.SetEnvironmentVariable(Constants.AWSInitializationTypeEnv, null); +- } +- +- public void Dispose() +- { +- // Reset after each test +- LambdaLifecycleTracker.Reset(); +- Environment.SetEnvironmentVariable(Constants.AWSInitializationTypeEnv, null); +- } +- +- [Fact] +- public void IsColdStart_FirstInvocation_ReturnsTrue() +- { +- // Act +- var result = LambdaLifecycleTracker.IsColdStart; +- +- // Assert +- Assert.True(result); +- } +- +- [Fact] +- public void IsColdStart_SecondInvocation_ReturnsFalse() +- { +- // Arrange - first access to trigger cold start +- _ = LambdaLifecycleTracker.IsColdStart; +- +- // Clear just the AsyncLocal value to simulate new invocation in same container +- LambdaLifecycleTracker.Reset(resetContainer: false); +- +- // Act - second invocation on same container +- var result = LambdaLifecycleTracker.IsColdStart; +- +- // Assert +- Assert.False(result); +- } +- +- [Fact] +- public void IsColdStart_WithProvisionedConcurrency_ReturnsFalse() +- { +- // Arrange +- Environment.SetEnvironmentVariable(Constants.AWSInitializationTypeEnv, "provisioned-concurrency"); +- +- // Act +- var result = LambdaLifecycleTracker.IsColdStart; +- +- // Assert +- Assert.False(result); +- } +- +- [Fact] +- public void IsColdStart_ReturnsSameValueWithinInvocation() +- { +- // Act - access multiple times in the same invocation +- var firstAccess = LambdaLifecycleTracker.IsColdStart; +- var secondAccess = LambdaLifecycleTracker.IsColdStart; +- var thirdAccess = LambdaLifecycleTracker.IsColdStart; +- +- // Assert +- Assert.True(firstAccess); +- Assert.Equal(firstAccess, secondAccess); +- Assert.Equal(firstAccess, thirdAccess); +- } +- +- [Fact] +- public void Reset_ResetsState() +- { +- // Arrange +- _ = LambdaLifecycleTracker.IsColdStart; // First invocation +- +- // Act +- LambdaLifecycleTracker.Reset(); +- var result = LambdaLifecycleTracker.IsColdStart; +- +- // Assert +- Assert.True(result); // Should be true again after reset +- } +- +- [Fact] +- public void Reset_ClearsEnvironmentSetting() +- { +- // Arrange +- Environment.SetEnvironmentVariable(Constants.AWSInitializationTypeEnv, "provisioned-concurrency"); +- _ = LambdaLifecycleTracker.IsColdStart; // Load the environment variable +- +- // Act +- LambdaLifecycleTracker.Reset(); +- Environment.SetEnvironmentVariable(Constants.AWSInitializationTypeEnv, null); // Clear the environment +- var result = LambdaLifecycleTracker.IsColdStart; +- +- // Assert +- Assert.True(result); // Should be true when env var is cleared +- } +- } +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Common.Tests/Core/PowertoolsConfigurationsTest.cs b/libraries/tests/AWS.Lambda.Powertools.Common.Tests/Core/PowertoolsConfigurationsTest.cs +index 934a162c..e154acd9 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.Common.Tests/Core/PowertoolsConfigurationsTest.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.Common.Tests/Core/PowertoolsConfigurationsTest.cs +@@ -29,17 +29,17 @@ namespace AWS.Lambda.Powertools.Common.Tests + // Arrange + var key = Guid.NewGuid().ToString(); + var defaultValue = Guid.NewGuid().ToString(); +- var environment = Substitute.For(); ++ var systemWrapper = Substitute.For(); + +- environment.GetEnvironmentVariable(key).Returns(string.Empty); ++ systemWrapper.GetEnvironmentVariable(key).Returns(string.Empty); + +- var configurations = new PowertoolsConfigurations(environment); ++ var configurations = new PowertoolsConfigurations(systemWrapper); + + // Act + var result = configurations.GetEnvironmentVariableOrDefault(key, defaultValue); + + // Assert +- environment.Received(1).GetEnvironmentVariable(key); ++ systemWrapper.Received(1).GetEnvironmentVariable(key); + + Assert.Equal(result, defaultValue); + } +@@ -49,17 +49,17 @@ namespace AWS.Lambda.Powertools.Common.Tests + { + // Arrange + var key = Guid.NewGuid().ToString(); +- var environment = Substitute.For(); ++ var systemWrapper = Substitute.For(); + +- environment.GetEnvironmentVariable(key).Returns(string.Empty); ++ systemWrapper.GetEnvironmentVariable(key).Returns(string.Empty); + +- var configurations = new PowertoolsConfigurations(environment); ++ var configurations = new PowertoolsConfigurations(systemWrapper); + + // Act + var result = configurations.GetEnvironmentVariableOrDefault(key, false); + + // Assert +- environment.Received(1).GetEnvironmentVariable(key); ++ systemWrapper.Received(1).GetEnvironmentVariable(key); + + Assert.False(result); + } +@@ -69,17 +69,17 @@ namespace AWS.Lambda.Powertools.Common.Tests + { + // Arrange + var key = Guid.NewGuid().ToString(); +- var environment = Substitute.For(); ++ var systemWrapper = Substitute.For(); + +- environment.GetEnvironmentVariable(key).Returns(string.Empty); ++ systemWrapper.GetEnvironmentVariable(key).Returns(string.Empty); + +- var configurations = new PowertoolsConfigurations(environment); ++ var configurations = new PowertoolsConfigurations(systemWrapper); + + // Act + var result = configurations.GetEnvironmentVariableOrDefault(key, true); + + // Assert +- environment.Received(1).GetEnvironmentVariable(Arg.Is(i => i == key)); ++ systemWrapper.Received(1).GetEnvironmentVariable(Arg.Is(i => i == key)); + + Assert.True(result); + } +@@ -91,17 +91,17 @@ namespace AWS.Lambda.Powertools.Common.Tests + var key = Guid.NewGuid().ToString(); + var defaultValue = Guid.NewGuid().ToString(); + var value = Guid.NewGuid().ToString(); +- var environment = Substitute.For(); ++ var systemWrapper = Substitute.For(); + +- environment.GetEnvironmentVariable(key).Returns(value); ++ systemWrapper.GetEnvironmentVariable(key).Returns(value); + +- var configurations = new PowertoolsConfigurations(environment); ++ var configurations = new PowertoolsConfigurations(systemWrapper); + + // Act + var result = configurations.GetEnvironmentVariableOrDefault(key, defaultValue); + + // Assert +- environment.Received(1).GetEnvironmentVariable(Arg.Is(i => i == key)); ++ systemWrapper.Received(1).GetEnvironmentVariable(Arg.Is(i => i == key)); + + Assert.Equal(result, value); + } +@@ -111,17 +111,17 @@ namespace AWS.Lambda.Powertools.Common.Tests + { + // Arrange + var key = Guid.NewGuid().ToString(); +- var environment = Substitute.For(); ++ var systemWrapper = Substitute.For(); + +- environment.GetEnvironmentVariable(key).Returns("true"); ++ systemWrapper.GetEnvironmentVariable(key).Returns("true"); + +- var configurations = new PowertoolsConfigurations(environment); ++ var configurations = new PowertoolsConfigurations(systemWrapper); + + // Act + var result = configurations.GetEnvironmentVariableOrDefault(key, false); + + // Assert +- environment.Received(1).GetEnvironmentVariable(Arg.Is(i => i == key)); ++ systemWrapper.Received(1).GetEnvironmentVariable(Arg.Is(i => i == key)); + + Assert.True(result); + } +@@ -131,17 +131,17 @@ namespace AWS.Lambda.Powertools.Common.Tests + { + // Arrange + var key = Guid.NewGuid().ToString(); +- var environment = Substitute.For(); ++ var systemWrapper = Substitute.For(); + +- environment.GetEnvironmentVariable(key).Returns("false"); ++ systemWrapper.GetEnvironmentVariable(key).Returns("false"); + +- var configurations = new PowertoolsConfigurations(environment); ++ var configurations = new PowertoolsConfigurations(systemWrapper); + + // Act + var result = configurations.GetEnvironmentVariableOrDefault(key, true); + + // Assert +- environment.Received(1).GetEnvironmentVariable(Arg.Is(i => i == key)); ++ systemWrapper.Received(1).GetEnvironmentVariable(Arg.Is(i => i == key)); + + Assert.False(result); + } +@@ -155,17 +155,17 @@ namespace AWS.Lambda.Powertools.Common.Tests + { + // Arrange + var defaultService = "service_undefined"; +- var environment = Substitute.For(); ++ var systemWrapper = Substitute.For(); + +- environment.GetEnvironmentVariable(Constants.ServiceNameEnv).Returns(string.Empty); ++ systemWrapper.GetEnvironmentVariable(Constants.ServiceNameEnv).Returns(string.Empty); + +- var configurations = new PowertoolsConfigurations(environment); ++ var configurations = new PowertoolsConfigurations(systemWrapper); + + // Act + var result = configurations.Service; + + // Assert +- environment.Received(1).GetEnvironmentVariable(Arg.Is(i => i == Constants.ServiceNameEnv)); ++ systemWrapper.Received(1).GetEnvironmentVariable(Arg.Is(i => i == Constants.ServiceNameEnv)); + + Assert.Equal(result, defaultService); + } +@@ -175,17 +175,17 @@ namespace AWS.Lambda.Powertools.Common.Tests + { + // Arrange + var service = Guid.NewGuid().ToString(); +- var environment = Substitute.For(); ++ var systemWrapper = Substitute.For(); + +- environment.GetEnvironmentVariable(Constants.ServiceNameEnv).Returns(service); ++ systemWrapper.GetEnvironmentVariable(Constants.ServiceNameEnv).Returns(service); + +- var configurations = new PowertoolsConfigurations(environment); ++ var configurations = new PowertoolsConfigurations(systemWrapper); + + // Act + var result = configurations.Service; + + // Assert +- environment.Received(1).GetEnvironmentVariable(Arg.Is(i => i == Constants.ServiceNameEnv)); ++ systemWrapper.Received(1).GetEnvironmentVariable(Arg.Is(i => i == Constants.ServiceNameEnv)); + + Assert.Equal(result, service); + } +@@ -199,17 +199,17 @@ namespace AWS.Lambda.Powertools.Common.Tests + { + // Arrange + var service = Guid.NewGuid().ToString(); +- var environment = Substitute.For(); ++ var systemWrapper = Substitute.For(); + +- environment.GetEnvironmentVariable(Constants.ServiceNameEnv).Returns(service); ++ systemWrapper.GetEnvironmentVariable(Constants.ServiceNameEnv).Returns(service); + +- var configurations = new PowertoolsConfigurations(environment); ++ var configurations = new PowertoolsConfigurations(systemWrapper); + + // Act + var result = configurations.IsServiceDefined; + + // Assert +- environment.Received(1).GetEnvironmentVariable(Arg.Is(i => i == Constants.ServiceNameEnv)); ++ systemWrapper.Received(1).GetEnvironmentVariable(Arg.Is(i => i == Constants.ServiceNameEnv)); + + Assert.True(result); + } +@@ -218,17 +218,17 @@ namespace AWS.Lambda.Powertools.Common.Tests + public void IsServiceDefined_WhenEnvironmentDoesNotHaveValue_ReturnsFalse() + { + // Arrange +- var environment = Substitute.For(); ++ var systemWrapper = Substitute.For(); + +- environment.GetEnvironmentVariable(Constants.ServiceNameEnv).Returns(string.Empty); ++ systemWrapper.GetEnvironmentVariable(Constants.ServiceNameEnv).Returns(string.Empty); + +- var configurations = new PowertoolsConfigurations(environment); ++ var configurations = new PowertoolsConfigurations(systemWrapper); + + // Act + var result = configurations.IsServiceDefined; + + // Assert +- environment.Received(1).GetEnvironmentVariable(Arg.Is(i => i == Constants.ServiceNameEnv)); ++ systemWrapper.Received(1).GetEnvironmentVariable(Arg.Is(i => i == Constants.ServiceNameEnv)); + + Assert.False(result); + } +@@ -241,17 +241,17 @@ namespace AWS.Lambda.Powertools.Common.Tests + public void TracerCaptureResponse_WhenEnvironmentIsNull_ReturnsDefaultValue() + { + // Arrange +- var environment = Substitute.For(); ++ var systemWrapper = Substitute.For(); + +- environment.GetEnvironmentVariable(Constants.TracerCaptureResponseEnv).Returns(string.Empty); ++ systemWrapper.GetEnvironmentVariable(Constants.TracerCaptureResponseEnv).Returns(string.Empty); + +- var configurations = new PowertoolsConfigurations(environment); ++ var configurations = new PowertoolsConfigurations(systemWrapper); + + // Act + var result = configurations.TracerCaptureResponse; + + // Assert +- environment.Received(1) ++ systemWrapper.Received(1) + .GetEnvironmentVariable(Arg.Is(i => i == Constants.TracerCaptureResponseEnv)); + + Assert.True(result); +@@ -261,17 +261,17 @@ namespace AWS.Lambda.Powertools.Common.Tests + public void TracerCaptureResponse_WhenEnvironmentHasValue_ReturnsValueFalse() + { + // Arrange +- var environment = Substitute.For(); ++ var systemWrapper = Substitute.For(); + +- environment.GetEnvironmentVariable(Constants.TracerCaptureResponseEnv).Returns("false"); ++ systemWrapper.GetEnvironmentVariable(Constants.TracerCaptureResponseEnv).Returns("false"); + +- var configurations = new PowertoolsConfigurations(environment); ++ var configurations = new PowertoolsConfigurations(systemWrapper); + + // Act + var result = configurations.TracerCaptureResponse; + + // Assert +- environment.Received(1) ++ systemWrapper.Received(1) + .GetEnvironmentVariable(Arg.Is(i => i == Constants.TracerCaptureResponseEnv)); + + Assert.False(result); +@@ -281,17 +281,17 @@ namespace AWS.Lambda.Powertools.Common.Tests + public void TracerCaptureResponse_WhenEnvironmentHasValue_ReturnsValueTrue() + { + // Arrange +- var environment = Substitute.For(); ++ var systemWrapper = Substitute.For(); + +- environment.GetEnvironmentVariable(Constants.TracerCaptureResponseEnv).Returns("true"); ++ systemWrapper.GetEnvironmentVariable(Constants.TracerCaptureResponseEnv).Returns("true"); + +- var configurations = new PowertoolsConfigurations(environment); ++ var configurations = new PowertoolsConfigurations(systemWrapper); + + // Act + var result = configurations.TracerCaptureResponse; + + // Assert +- environment.Received(1) ++ systemWrapper.Received(1) + .GetEnvironmentVariable(Arg.Is(i => i == Constants.TracerCaptureResponseEnv)); + + Assert.True(result); +@@ -305,17 +305,17 @@ namespace AWS.Lambda.Powertools.Common.Tests + public void TracerCaptureError_WhenEnvironmentIsNull_ReturnsDefaultValue() + { + // Arrange +- var environment = Substitute.For(); ++ var systemWrapper = Substitute.For(); + +- environment.GetEnvironmentVariable(Constants.TracerCaptureErrorEnv).Returns(string.Empty); ++ systemWrapper.GetEnvironmentVariable(Constants.TracerCaptureErrorEnv).Returns(string.Empty); + +- var configurations = new PowertoolsConfigurations(environment); ++ var configurations = new PowertoolsConfigurations(systemWrapper); + + // Act + var result = configurations.TracerCaptureError; + + // Assert +- environment.Received(1) ++ systemWrapper.Received(1) + .GetEnvironmentVariable(Arg.Is(i => i == Constants.TracerCaptureErrorEnv)); + + Assert.True(result); +@@ -325,17 +325,17 @@ namespace AWS.Lambda.Powertools.Common.Tests + public void TracerCaptureError_WhenEnvironmentHasValue_ReturnsValueFalse() + { + // Arrange +- var environment = Substitute.For(); ++ var systemWrapper = Substitute.For(); + +- environment.GetEnvironmentVariable(Constants.TracerCaptureErrorEnv).Returns("false"); ++ systemWrapper.GetEnvironmentVariable(Constants.TracerCaptureErrorEnv).Returns("false"); + +- var configurations = new PowertoolsConfigurations(environment); ++ var configurations = new PowertoolsConfigurations(systemWrapper); + + // Act + var result = configurations.TracerCaptureError; + + // Assert +- environment.Received(1) ++ systemWrapper.Received(1) + .GetEnvironmentVariable(Arg.Is(i => i == Constants.TracerCaptureErrorEnv)); + + Assert.False(result); +@@ -345,17 +345,17 @@ namespace AWS.Lambda.Powertools.Common.Tests + public void TracerCaptureError_WhenEnvironmentHasValue_ReturnsValueTrue() + { + // Arrange +- var environment = Substitute.For(); ++ var systemWrapper = Substitute.For(); + +- environment.GetEnvironmentVariable(Constants.TracerCaptureErrorEnv).Returns("true"); ++ systemWrapper.GetEnvironmentVariable(Constants.TracerCaptureErrorEnv).Returns("true"); + +- var configurations = new PowertoolsConfigurations(environment); ++ var configurations = new PowertoolsConfigurations(systemWrapper); + + // Act + var result = configurations.TracerCaptureError; + + // Assert +- environment.Received(1) ++ systemWrapper.Received(1) + .GetEnvironmentVariable(Arg.Is(i => i == Constants.TracerCaptureErrorEnv)); + + Assert.True(result); +@@ -369,17 +369,17 @@ namespace AWS.Lambda.Powertools.Common.Tests + public void IsSamLocal_WhenEnvironmentIsNull_ReturnsDefaultValue() + { + // Arrange +- var environment = Substitute.For(); ++ var systemWrapper = Substitute.For(); + +- environment.GetEnvironmentVariable(Constants.SamLocalEnv).Returns(string.Empty); ++ systemWrapper.GetEnvironmentVariable(Constants.SamLocalEnv).Returns(string.Empty); + +- var configurations = new PowertoolsConfigurations(environment); ++ var configurations = new PowertoolsConfigurations(systemWrapper); + + // Act + var result = configurations.IsSamLocal; + + // Assert +- environment.Received(1) ++ systemWrapper.Received(1) + .GetEnvironmentVariable(Arg.Is(i => i == Constants.SamLocalEnv)); + + Assert.False(result); +@@ -389,17 +389,17 @@ namespace AWS.Lambda.Powertools.Common.Tests + public void IsSamLocal_WhenEnvironmentHasValue_ReturnsValueFalse() + { + // Arrange +- var environment = Substitute.For(); ++ var systemWrapper = Substitute.For(); + +- environment.GetEnvironmentVariable(Constants.SamLocalEnv).Returns("false"); ++ systemWrapper.GetEnvironmentVariable(Constants.SamLocalEnv).Returns("false"); + +- var configurations = new PowertoolsConfigurations(environment); ++ var configurations = new PowertoolsConfigurations(systemWrapper); + + // Act + var result = configurations.IsSamLocal; + + // Assert +- environment.Received(1) ++ systemWrapper.Received(1) + .GetEnvironmentVariable(Arg.Is(i => i == Constants.SamLocalEnv)); + + Assert.False(result); +@@ -409,17 +409,17 @@ namespace AWS.Lambda.Powertools.Common.Tests + public void IsSamLocal_WhenEnvironmentHasValue_ReturnsValueTrue() + { + // Arrange +- var environment = Substitute.For(); ++ var systemWrapper = Substitute.For(); + +- environment.GetEnvironmentVariable(Constants.SamLocalEnv).Returns("true"); ++ systemWrapper.GetEnvironmentVariable(Constants.SamLocalEnv).Returns("true"); + +- var configurations = new PowertoolsConfigurations(environment); ++ var configurations = new PowertoolsConfigurations(systemWrapper); + + // Act + var result = configurations.IsSamLocal; + + // Assert +- environment.Received(1) ++ systemWrapper.Received(1) + .GetEnvironmentVariable(Arg.Is(i => i == Constants.SamLocalEnv)); + + Assert.True(result); +@@ -433,17 +433,17 @@ namespace AWS.Lambda.Powertools.Common.Tests + public void TracingDisabled_WhenEnvironmentIsNull_ReturnsDefaultValue() + { + // Arrange +- var environment = Substitute.For(); ++ var systemWrapper = Substitute.For(); + +- environment.GetEnvironmentVariable(Constants.TracingDisabledEnv).Returns(string.Empty); ++ systemWrapper.GetEnvironmentVariable(Constants.TracingDisabledEnv).Returns(string.Empty); + +- var configurations = new PowertoolsConfigurations(environment); ++ var configurations = new PowertoolsConfigurations(systemWrapper); + + // Act + var result = configurations.TracingDisabled; + + // Assert +- environment.Received(1) ++ systemWrapper.Received(1) + .GetEnvironmentVariable(Arg.Is(i => i == Constants.TracingDisabledEnv)); + + Assert.False(result); +@@ -453,17 +453,17 @@ namespace AWS.Lambda.Powertools.Common.Tests + public void TracingDisabled_WhenEnvironmentHasValue_ReturnsValueFalse() + { + // Arrange +- var environment = Substitute.For(); ++ var systemWrapper = Substitute.For(); + +- environment.GetEnvironmentVariable(Constants.TracingDisabledEnv).Returns("false"); ++ systemWrapper.GetEnvironmentVariable(Constants.TracingDisabledEnv).Returns("false"); + +- var configurations = new PowertoolsConfigurations(environment); ++ var configurations = new PowertoolsConfigurations(systemWrapper); + + // Act + var result = configurations.TracingDisabled; + + // Assert +- environment.Received(1) ++ systemWrapper.Received(1) + .GetEnvironmentVariable(Arg.Is(i => i == Constants.TracingDisabledEnv)); + + Assert.False(result); +@@ -473,17 +473,17 @@ namespace AWS.Lambda.Powertools.Common.Tests + public void TracingDisabled_WhenEnvironmentHasValue_ReturnsValueTrue() + { + // Arrange +- var environment = Substitute.For(); ++ var systemWrapper = Substitute.For(); + +- environment.GetEnvironmentVariable(Constants.TracingDisabledEnv).Returns("true"); ++ systemWrapper.GetEnvironmentVariable(Constants.TracingDisabledEnv).Returns("true"); + +- var configurations = new PowertoolsConfigurations(environment); ++ var configurations = new PowertoolsConfigurations(systemWrapper); + + // Act + var result = configurations.TracingDisabled; + + // Assert +- environment.Received(1) ++ systemWrapper.Received(1) + .GetEnvironmentVariable(Arg.Is(i => i == Constants.TracingDisabledEnv)); + + Assert.True(result); +@@ -497,17 +497,17 @@ namespace AWS.Lambda.Powertools.Common.Tests + public void IsLambdaEnvironment_WhenEnvironmentIsNull_ReturnsFalse() + { + // Arrange +- var environment = Substitute.For(); ++ var systemWrapper = Substitute.For(); + +- environment.GetEnvironmentVariable(Constants.LambdaTaskRoot).Returns((string)null); ++ systemWrapper.GetEnvironmentVariable(Constants.LambdaTaskRoot).Returns((string)null); + +- var configurations = new PowertoolsConfigurations(environment); ++ var configurations = new PowertoolsConfigurations(systemWrapper); + + // Act + var result = configurations.IsLambdaEnvironment; + + // Assert +- environment.Received(1) ++ systemWrapper.Received(1) + .GetEnvironmentVariable(Arg.Is(i => i == Constants.LambdaTaskRoot)); + + Assert.False(result); +@@ -517,17 +517,17 @@ namespace AWS.Lambda.Powertools.Common.Tests + public void IsLambdaEnvironment_WhenEnvironmentHasValue_ReturnsTrue() + { + // Arrange +- var environment = Substitute.For(); ++ var systemWrapper = Substitute.For(); + +- environment.GetEnvironmentVariable(Constants.TracingDisabledEnv).Returns(Guid.NewGuid().ToString()); ++ systemWrapper.GetEnvironmentVariable(Constants.TracingDisabledEnv).Returns(Guid.NewGuid().ToString()); + +- var configurations = new PowertoolsConfigurations(environment); ++ var configurations = new PowertoolsConfigurations(systemWrapper); + + // Act + var result = configurations.IsLambdaEnvironment; + + // Assert +- environment.Received(1) ++ systemWrapper.Received(1) + .GetEnvironmentVariable(Arg.Is(i => i == Constants.LambdaTaskRoot)); + + Assert.True(result); +@@ -537,20 +537,20 @@ namespace AWS.Lambda.Powertools.Common.Tests + public void Set_Lambda_Execution_Context() + { + // Arrange +- var environment = Substitute.For(); ++ var systemWrapper = Substitute.For(); + +- // environment.Setup(c => ++ // systemWrapper.Setup(c => + // c.SetExecutionEnvironment(GetType()) + // ); + +- var configurations = new PowertoolsConfigurations(environment); ++ var configurations = new PowertoolsConfigurations(systemWrapper); + + // Act + configurations.SetExecutionEnvironment(typeof(PowertoolsConfigurations)); + + // Assert + // method with correct type was called +- environment.Received(1) ++ systemWrapper.Received(1) + .SetExecutionEnvironment(Arg.Is(i => i == typeof(PowertoolsConfigurations))); + } + +diff --git a/libraries/tests/AWS.Lambda.Powertools.Common.Tests/Core/PowertoolsEnvironmentTest.cs b/libraries/tests/AWS.Lambda.Powertools.Common.Tests/Core/PowertoolsEnvironmentTest.cs +index 9f9e153c..df41e253 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.Common.Tests/Core/PowertoolsEnvironmentTest.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.Common.Tests/Core/PowertoolsEnvironmentTest.cs +@@ -1,9 +1,9 @@ + using System; ++using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Xml.Linq; + using System.Xml.XPath; +-using NSubstitute; + using Xunit; + + namespace AWS.Lambda.Powertools.Common.Tests; +@@ -14,57 +14,54 @@ public class PowertoolsEnvironmentTest : IDisposable + public void Set_Execution_Environment() + { + // Arrange +- var powertoolsEnv = new PowertoolsEnvironment(); ++ var systemWrapper = new SystemWrapper(new MockEnvironment()); + + // Act +- powertoolsEnv.SetExecutionEnvironment(this); ++ systemWrapper.SetExecutionEnvironment(this); + + // Assert +- Assert.Equal($"{Constants.FeatureContextIdentifier}/Tests/1.0.0 PTENV/AWS_LAMBDA_DOTNET{Environment.Version.Major}", powertoolsEnv.GetEnvironmentVariable("AWS_EXECUTION_ENV")); ++ Assert.Equal($"{Constants.FeatureContextIdentifier}/Fake/1.0.0", systemWrapper.GetEnvironmentVariable("AWS_EXECUTION_ENV")); + } + + [Fact] + public void Set_Execution_Environment_WhenEnvironmentHasValue() + { + // Arrange +- var powertoolsEnv = new PowertoolsEnvironment(); ++ var systemWrapper = new SystemWrapper(new MockEnvironment()); + +- powertoolsEnv.SetEnvironmentVariable("AWS_EXECUTION_ENV", "ExistingValuesInUserAgent"); ++ systemWrapper.SetEnvironmentVariable("AWS_EXECUTION_ENV", "ExistingValuesInUserAgent"); + + // Act +- powertoolsEnv.SetExecutionEnvironment(this); ++ systemWrapper.SetExecutionEnvironment(this); + + // Assert +- Assert.Equal($"ExistingValuesInUserAgent {Constants.FeatureContextIdentifier}/Tests/1.0.0 PTENV/AWS_LAMBDA_DOTNET{Environment.Version.Major}", powertoolsEnv.GetEnvironmentVariable("AWS_EXECUTION_ENV")); ++ Assert.Equal($"ExistingValuesInUserAgent {Constants.FeatureContextIdentifier}/Fake/1.0.0", systemWrapper.GetEnvironmentVariable("AWS_EXECUTION_ENV")); + } + + [Fact] +- public void Set_Same_Execution_Environment_Multiple_Times_Should_Only_Set_Once() ++ public void Set_Multiple_Execution_Environment() + { + // Arrange +- var powertoolsEnv = new PowertoolsEnvironment(); ++ var systemWrapper = new SystemWrapper(new MockEnvironment()); + + // Act +- powertoolsEnv.SetExecutionEnvironment(this); +- powertoolsEnv.SetExecutionEnvironment(this); ++ systemWrapper.SetExecutionEnvironment(this); + + // Assert +- Assert.Equal($"{Constants.FeatureContextIdentifier}/Tests/1.0.0 PTENV/AWS_LAMBDA_DOTNET{Environment.Version.Major}", powertoolsEnv.GetEnvironmentVariable("AWS_EXECUTION_ENV")); ++ Assert.Equal($"{Constants.FeatureContextIdentifier}/Fake/1.0.0", systemWrapper.GetEnvironmentVariable("AWS_EXECUTION_ENV")); + } + + [Fact] +- public void Set_Multiple_Execution_Environment() ++ public void Set_Execution_Real_Environment() + { + // Arrange +- var powertoolsEnv = new PowertoolsEnvironment(); ++ var systemWrapper = new SystemWrapper(new PowertoolsEnvironment()); + + // Act +- powertoolsEnv.SetExecutionEnvironment(this); +- powertoolsEnv.SetExecutionEnvironment(powertoolsEnv.GetType()); ++ systemWrapper.SetExecutionEnvironment(this); + + // Assert +- Assert.Equal($"{Constants.FeatureContextIdentifier}/Tests/1.0.0 PTENV/AWS_LAMBDA_DOTNET{Environment.Version.Major} {Constants.FeatureContextIdentifier}/Common/1.0.0", +- powertoolsEnv.GetEnvironmentVariable("AWS_EXECUTION_ENV")); ++ Assert.Equal($"{Constants.FeatureContextIdentifier}/Tests/1.0.0", systemWrapper.GetEnvironmentVariable("AWS_EXECUTION_ENV")); + } + + [Fact] +@@ -86,251 +83,39 @@ public class PowertoolsEnvironmentTest : IDisposable + Assert.Equal("2.8.1", packageReference.Version.ToString()); + } + +- [Fact] +- public void SetExecutionEnvironment_Should_Format_Strings_Correctly_With_Mocked_Environment() +- { +- // Arrange +- var mockEnvironment = Substitute.For(); +- +- // Mock the dependencies to return controlled values +- mockEnvironment.GetAssemblyName(Arg.Any()).Returns("AWS.Lambda.Powertools.Common.Tests"); +- mockEnvironment.GetAssemblyVersion(Arg.Any()).Returns("1.2.3"); +- mockEnvironment.GetEnvironmentVariable("AWS_EXECUTION_ENV").Returns((string)null); +- +- // Setup the actual method call to use real implementation logic +- mockEnvironment.When(x => x.SetExecutionEnvironment(Arg.Any())) +- .Do(_ => +- { +- var assemblyName = "PT/Tests"; // Parsed name +- var assemblyVersion = "1.2.3"; +- var runtimeEnv = "PTENV/AWS_LAMBDA_DOTNET8"; // Assuming .NET 8 +- var expectedValue = $"{assemblyName}/{assemblyVersion} {runtimeEnv}"; +- +- mockEnvironment.SetEnvironmentVariable("AWS_EXECUTION_ENV", expectedValue); +- }); +- +- // Act +- mockEnvironment.SetExecutionEnvironment(this); +- +- // Assert +- mockEnvironment.Received(1).SetEnvironmentVariable("AWS_EXECUTION_ENV", "PT/Tests/1.2.3 PTENV/AWS_LAMBDA_DOTNET8"); +- } +- +- [Fact] +- public void SetExecutionEnvironment_Should_Append_To_Existing_Environment_With_Mocked_Values() +- { +- // Arrange +- var mockEnvironment = Substitute.For(); +- +- // Mock existing environment value +- mockEnvironment.GetEnvironmentVariable("AWS_EXECUTION_ENV").Returns("ExistingValue"); +- mockEnvironment.GetAssemblyName(Arg.Any()).Returns("AWS.Lambda.Powertools.Logging"); +- mockEnvironment.GetAssemblyVersion(Arg.Any()).Returns("2.1.0"); +- +- // Setup the method call +- mockEnvironment.When(x => x.SetExecutionEnvironment(Arg.Any())) +- .Do(_ => +- { +- var currentEnv = "ExistingValue"; +- var assemblyName = "PT/Logging"; +- var assemblyVersion = "2.1.0"; +- var runtimeEnv = "PTENV/AWS_LAMBDA_DOTNET8"; +- var expectedValue = $"{currentEnv} {assemblyName}/{assemblyVersion} {runtimeEnv}"; +- +- mockEnvironment.SetEnvironmentVariable("AWS_EXECUTION_ENV", expectedValue); +- }); +- +- // Act +- mockEnvironment.SetExecutionEnvironment(this); +- +- // Assert +- mockEnvironment.Received(1).SetEnvironmentVariable("AWS_EXECUTION_ENV", "ExistingValue PT/Logging/2.1.0 PTENV/AWS_LAMBDA_DOTNET8"); +- } +- +- [Fact] +- public void SetExecutionEnvironment_Should_Not_Add_PTENV_Twice_With_Mocked_Values() +- { +- // Arrange +- var mockEnvironment = Substitute.For(); +- +- // Mock existing environment value that already contains PTENV +- mockEnvironment.GetEnvironmentVariable("AWS_EXECUTION_ENV").Returns("PT/Metrics/1.0.0 PTENV/AWS_LAMBDA_DOTNET8"); +- mockEnvironment.GetAssemblyName(Arg.Any()).Returns("AWS.Lambda.Powertools.Tracing"); +- mockEnvironment.GetAssemblyVersion(Arg.Any()).Returns("1.5.0"); +- +- // Setup the method call - should not add PTENV again +- mockEnvironment.When(x => x.SetExecutionEnvironment(Arg.Any())) +- .Do(_ => +- { +- var currentEnv = "PT/Metrics/1.0.0 PTENV/AWS_LAMBDA_DOTNET8"; +- var assemblyName = "PT/Tracing"; +- var assemblyVersion = "1.5.0"; +- // No PTENV added since it already exists +- var expectedValue = $"{currentEnv} {assemblyName}/{assemblyVersion}"; +- +- mockEnvironment.SetEnvironmentVariable("AWS_EXECUTION_ENV", expectedValue); +- }); +- +- // Act +- mockEnvironment.SetExecutionEnvironment(this); +- +- // Assert +- mockEnvironment.Received(1).SetEnvironmentVariable("AWS_EXECUTION_ENV", "PT/Metrics/1.0.0 PTENV/AWS_LAMBDA_DOTNET8 PT/Tracing/1.5.0"); +- } +- +- [Fact] +- public void GetAssemblyName_Should_Handle_Type_Object() +- { +- // Arrange +- var powertoolsEnv = new PowertoolsEnvironment(); +- var typeObject = typeof(PowertoolsEnvironment); +- +- // Act +- var result = powertoolsEnv.GetAssemblyName(typeObject); +- +- // Assert +- Assert.Equal("AWS.Lambda.Powertools.Common", result); +- } +- +- [Fact] +- public void GetAssemblyName_Should_Handle_Regular_Object() +- { +- // Arrange +- var powertoolsEnv = new PowertoolsEnvironment(); +- +- // Act +- var result = powertoolsEnv.GetAssemblyName(this); +- +- // Assert +- Assert.Equal("AWS.Lambda.Powertools.Common.Tests", result); +- } +- +- [Fact] +- public void GetAssemblyVersion_Should_Handle_Type_Object() +- { +- // Arrange +- var powertoolsEnv = new PowertoolsEnvironment(); +- var typeObject = typeof(PowertoolsEnvironment); +- +- // Act +- var result = powertoolsEnv.GetAssemblyVersion(typeObject); +- +- // Assert +- Assert.Matches(@"\d+\.\d+\.\d+", result); // Should match version pattern like "1.0.0" +- } +- +- [Fact] +- public void GetAssemblyVersion_Should_Handle_Regular_Object() +- { +- // Arrange +- var powertoolsEnv = new PowertoolsEnvironment(); +- +- // Act +- var result = powertoolsEnv.GetAssemblyVersion(this); +- +- // Assert +- Assert.Matches(@"\d+\.\d+\.\d+", result); // Should match version pattern like "1.0.0" +- } +- +- [Fact] +- public void ParseAssemblyName_Should_Handle_Assembly_Without_Dots() +- { +- // Act +- var result = PowertoolsEnvironment.ParseAssemblyName("SimpleAssemblyName"); +- +- // Assert +- Assert.Equal($"{Constants.FeatureContextIdentifier}/SimpleAssemblyName", result); +- } +- +- [Fact] +- public void ParseAssemblyName_Should_Handle_Assembly_With_Dots() +- { +- // Act +- var result = PowertoolsEnvironment.ParseAssemblyName("AWS.Lambda.Powertools.Common"); +- +- // Assert +- Assert.Equal($"{Constants.FeatureContextIdentifier}/Common", result); +- } +- +- [Fact] +- public void ParseAssemblyName_Should_Use_Cache_For_Same_Assembly_Name() +- { +- // Act - Call twice with same assembly name +- var result1 = PowertoolsEnvironment.ParseAssemblyName("AWS.Lambda.Powertools.Tests"); +- var result2 = PowertoolsEnvironment.ParseAssemblyName("AWS.Lambda.Powertools.Tests"); +- +- // Assert - Should return same result (cached) +- Assert.Equal(result1, result2); +- Assert.Equal($"{Constants.FeatureContextIdentifier}/Tests", result1); +- } +- +- [Fact] +- public void ParseAssemblyName_Null_Return_Empty() ++ public void Dispose() + { +- // Act - Call twice with same assembly name +- var result = PowertoolsEnvironment.ParseAssemblyName(null); ++ //Do cleanup actions here + +- // Assert - Should return null +- Assert.Empty(result); ++ Environment.SetEnvironmentVariable("AWS_EXECUTION_ENV", null); + } ++} ++ ++/// ++/// Fake Environment for testing ++/// ++class MockEnvironment : IPowertoolsEnvironment ++{ ++ private readonly Dictionary _mockEnvironment = new(); + +- [Fact] +- public void SetExecutionEnvironment_Should_Handle_Empty_Current_Environment() ++ public string GetEnvironmentVariable(string variableName) + { +- // Arrange +- var powertoolsEnv = new PowertoolsEnvironment(); +- Environment.SetEnvironmentVariable("AWS_EXECUTION_ENV", ""); +- +- // Act +- powertoolsEnv.SetExecutionEnvironment(this); +- +- // Assert +- var result = powertoolsEnv.GetEnvironmentVariable("AWS_EXECUTION_ENV"); +- Assert.Contains($"{Constants.FeatureContextIdentifier}/Tests/", result); +- Assert.Contains("PTENV/AWS_LAMBDA_DOTNET", result); ++ return _mockEnvironment.TryGetValue(variableName, out var value) ? value : null; + } +- +- [Fact] +- public void SetExecutionEnvironment_Should_Add_PTENV_When_Not_Present() ++ ++ public void SetEnvironmentVariable(string variableName, string value) + { +- // Arrange +- var powertoolsEnv = new PowertoolsEnvironment(); +- powertoolsEnv.SetEnvironmentVariable("AWS_EXECUTION_ENV", "SomeExistingValue"); +- +- // Act +- powertoolsEnv.SetExecutionEnvironment(this); +- +- // Assert +- var result = powertoolsEnv.GetEnvironmentVariable("AWS_EXECUTION_ENV"); +- Assert.StartsWith("SomeExistingValue", result); +- Assert.Contains("PTENV/AWS_LAMBDA_DOTNET", result); ++ // Check for entry not existing and add to dictionary ++ _mockEnvironment[variableName] = value; + } +- +- [Fact] +- public void SetExecutionEnvironment_Should_Not_Add_PTENV_When_Already_Present() ++ ++ public string GetAssemblyName(T type) + { +- // Arrange +- var powertoolsEnv = new PowertoolsEnvironment(); +- var existingValue = $"ExistingValue PTENV/AWS_LAMBDA_DOTNET{Environment.Version.Major}"; +- powertoolsEnv.SetEnvironmentVariable("AWS_EXECUTION_ENV", existingValue); +- +- // Act +- powertoolsEnv.SetExecutionEnvironment(this); +- +- // Assert +- var result = powertoolsEnv.GetEnvironmentVariable("AWS_EXECUTION_ENV"); +- var ptenvCount = result.Split("PTENV/").Length - 1; +- Assert.Equal(1, ptenvCount); // Should only have one PTENV entry ++ return "AWS.Lambda.Powertools.Fake"; + } + +- public void Dispose() ++ public string GetAssemblyVersion(T type) + { +- //Do cleanup actions here +- Environment.SetEnvironmentVariable("AWS_EXECUTION_ENV", null); +- +- // Clear the singleton instance to ensure fresh state for each test +- var instanceField = typeof(PowertoolsEnvironment).GetField("_instance", +- System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); +- instanceField?.SetValue(null, null); ++ return "1.0.0"; + } + } +diff --git a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/AWS.Lambda.Powertools.EventHandler.Tests.csproj b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/AWS.Lambda.Powertools.EventHandler.Tests.csproj +deleted file mode 100644 +index 2d37bab6..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/AWS.Lambda.Powertools.EventHandler.Tests.csproj ++++ /dev/null +@@ -1,52 +0,0 @@ +- +- +- +- +- +- AWS.Lambda.Powertools.EventHandler.Tests +- AWS.Lambda.Powertools.EventHandler.Tests +- net8.0 +- enable +- enable +- +- false +- true +- +- +- +- +- +- +- +- +- runtime; build; native; contentfiles; analyzers; buildtransitive +- all +- +- +- runtime; build; native; contentfiles; analyzers; buildtransitive +- all +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- PreserveNewest +- +- +- +- +- PreserveNewest +- +- +- +- +diff --git a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/BedrockAgentFunctionResolverAdditionalTests.cs b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/BedrockAgentFunctionResolverAdditionalTests.cs +deleted file mode 100644 +index 3f73c686..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/BedrockAgentFunctionResolverAdditionalTests.cs ++++ /dev/null +@@ -1,340 +0,0 @@ +-using Amazon.Lambda.Core; +-using Amazon.Lambda.TestUtilities; +-using AWS.Lambda.Powertools.EventHandler.Resolvers; +-using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; +- +-namespace AWS.Lambda.Powertools.EventHandler.BedrockAgentFunction +-{ +- public class BedrockAgentFunctionResolverAdditionalTests +- { +- [Fact] +- public async Task ResolveAsync_WithValidInput_ReturnsResult() +- { +- // Arrange +- var resolver = new BedrockAgentFunctionResolver(); +- resolver.Tool("AsyncTest", () => "Async result"); +- +- var input = new BedrockFunctionRequest { Function = "AsyncTest" }; +- var context = new TestLambdaContext(); +- +- // Act +- var result = await resolver.ResolveAsync(input, context); +- +- // Assert +- Assert.Equal("Async result", result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- [Fact] +- public void Tool_WithNullHandler_ThrowsException() +- { +- // Arrange +- var resolver = new BedrockAgentFunctionResolver(); +- Func nullHandler = null!; +- +- // Act/Assert +- Assert.Throws(() => resolver.Tool("NullTest", nullHandler)); +- } +- +- [Fact] +- public void Resolve_WithNullFunction_ReturnsErrorResponse() +- { +- // Arrange +- var resolver = new BedrockAgentFunctionResolver(); +- var input = new BedrockFunctionRequest { Function = null }; +- +- // Act +- var result = resolver.Resolve(input); +- +- // Assert +- Assert.Equal("No tool specified in the request", result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- [Fact] +- public void Resolve_WithEmptyFunction_ReturnsErrorResponse() +- { +- // Arrange +- var resolver = new BedrockAgentFunctionResolver(); +- var input = new BedrockFunctionRequest { Function = "" }; +- +- // Act +- var result = resolver.Resolve(input); +- +- // Assert +- Assert.Equal("No tool specified in the request", result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- [Fact] +- public void Tool_WithHandlerThrowingException_ReturnsErrorResponse() +- { +- // Arrange +- var resolver = new BedrockAgentFunctionResolver(); +- resolver.Tool("ExceptionTest", (BedrockFunctionRequest input, ILambdaContext ctx) => { +- throw new InvalidOperationException("Handler exception"); +- return new BedrockFunctionResponse(); +- }); +- +- var input = new BedrockFunctionRequest { Function = "ExceptionTest" }; +- +- // Act +- var result = resolver.Resolve(input); +- +- // Assert +- Assert.Equal("Error when invoking tool: Handler exception", result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- [Fact] +- public void Tool_WithDynamicInvokeException_ReturnsErrorResponse() +- { +- // Arrange +- var resolver = new BedrockAgentFunctionResolver(); +- resolver.Tool("ExceptionTest", (Func)(() => { +- throw new InvalidOperationException("Dynamic invoke exception"); +- })); +- +- var input = new BedrockFunctionRequest { Function = "ExceptionTest" }; +- +- // Act +- var result = resolver.Resolve(input); +- +- // Assert +- Assert.Contains("Error when invoking tool", result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- [Fact] +- public void Tool_ObjectFunctionRegistration_ReturnsObjectAsString() +- { +- // Arrange +- var testObject = new TestObject { Id = 123, Name = "Test" }; +- var resolver = new BedrockAgentFunctionResolver(); +- resolver.Tool("ObjectTest", () => testObject); +- +- var input = new BedrockFunctionRequest { Function = "ObjectTest" }; +- +- // Act +- var result = resolver.Resolve(input); +- +- // Assert +- Assert.Equal(testObject.ToString(), result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- [Fact] +- public async Task Resolve_WithAsyncTask_HandlesCorrectly() +- { +- // Arrange +- var resolver = new BedrockAgentFunctionResolver(); +- resolver.Tool("AsyncTaskTest", async (string message) => { +- await Task.Delay(10); // Simulate async work +- return $"Processed: {message}"; +- }); +- +- var input = new BedrockFunctionRequest { +- Function = "AsyncTaskTest", +- Parameters = new List { +- new Parameter { Name = "message", Value = "hello", Type = "String" } +- } +- }; +- +- // Act +- var result = resolver.Resolve(input); +- +- // Assert +- Assert.Equal("Processed: hello", result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- [Fact] +- public void Tool_WithBedrockFunctionResponseHandlerNoContext_MapsCorrectly() +- { +- // Arrange +- var resolver = new BedrockAgentFunctionResolver(); +- resolver.Tool("NoContextTest", (BedrockFunctionRequest request) => new BedrockFunctionResponse +- { +- Response = new Response +- { +- ActionGroup = "TestGroup", +- Function = "NoContextTest", +- FunctionResponse = new FunctionResponse +- { +- ResponseBody = new ResponseBody +- { +- Text = new TextBody { Body = "No context needed" } +- } +- } +- } +- }); +- +- var input = new BedrockFunctionRequest { Function = "NoContextTest" }; +- +- // Act +- var result = resolver.Resolve(input); +- +- // Assert +- Assert.Equal("No context needed", result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- [Fact] +- public void Tool_WithBedrockFunctionResponseHandler_MapsCorrectly() +- { +- // Arrange +- var resolver = new BedrockAgentFunctionResolver(); +- resolver.Tool("ResponseTest", () => new BedrockFunctionResponse +- { +- Response = new Response +- { +- ActionGroup = "TestGroup", +- Function = "ResponseTest", +- FunctionResponse = new FunctionResponse +- { +- ResponseBody = new ResponseBody +- { +- Text = new TextBody { Body = "Direct response" } +- } +- } +- } +- }); +- +- var input = new BedrockFunctionRequest { Function = "ResponseTest" }; +- +- // Act +- var result = resolver.Resolve(input); +- +- // Assert +- Assert.Equal("Direct response", result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- [Fact] +- public void Tool_WithCustomFailureResponse_ReturnsFailureState() +- { +- // Arrange +- var resolver = new BedrockAgentFunctionResolver(); +- resolver.Tool("CustomFailure", () => +- { +- // Return a custom FAILURE response +- return new BedrockFunctionResponse +- { +- Response = new Response +- { +- ActionGroup = "TestGroup", +- Function = "CustomFailure", +- FunctionResponse = new FunctionResponse +- { +- ResponseBody = new ResponseBody +- { +- Text = new TextBody +- { +- Body = "Critical error occurred: Database unavailable" +- } +- }, +- ResponseState = ResponseState.FAILURE // Mark as FAILURE to abort the conversation +- } +- } +- }; +- }); +- +- var input = new BedrockFunctionRequest { Function = "CustomFailure" }; +- var context = new TestLambdaContext(); +- +- // Act +- var result = resolver.Resolve(input, context); +- +- // Assert +- Assert.Equal("Critical error occurred: Database unavailable", result.Response.FunctionResponse.ResponseBody.Text.Body); +- Assert.Equal("FAILURE", result.Response.FunctionResponse.ResponseState.ToString()); +- } +- +- [Fact] +- public void Tool_WithSessionAttributesPersistence_MaintainsStateAcrossInvocations() +- { +- // Arrange +- var resolver = new BedrockAgentFunctionResolver(); +- +- // Create a counter tool that reads and updates session attributes +- resolver.Tool("CounterTool", (BedrockFunctionRequest request) => +- { +- // Read the current count from session attributes +- int currentCount = 0; +- if (request.SessionAttributes != null && +- request.SessionAttributes.TryGetValue("counter", out var countStr) && +- int.TryParse(countStr, out var count)) +- { +- currentCount = count; +- } +- +- // Increment the counter +- currentCount++; +- +- // Create a new dictionary with updated counter +- var updatedSessionAttributes = new Dictionary(request.SessionAttributes ?? new Dictionary()) +- { +- ["counter"] = currentCount.ToString(), +- ["lastAccessed"] = DateTime.UtcNow.ToString("o") +- }; +- +- // Return response with updated session attributes +- return new BedrockFunctionResponse +- { +- Response = new Response +- { +- ActionGroup = request.ActionGroup, +- Function = request.Function, +- FunctionResponse = new FunctionResponse +- { +- ResponseBody = new ResponseBody +- { +- Text = new TextBody { Body = $"Current count: {currentCount}" } +- } +- } +- }, +- SessionAttributes = updatedSessionAttributes, +- PromptSessionAttributes = request.PromptSessionAttributes +- }; +- }); +- +- // First invocation - should start with 0 and increment to 1 +- var firstInput = new BedrockFunctionRequest +- { +- Function = "CounterTool", +- SessionAttributes = new Dictionary(), +- PromptSessionAttributes = new Dictionary { ["prompt"] = "initial" } +- }; +- +- // Second invocation - should use the session attributes from first response +- var secondInput = new BedrockFunctionRequest { Function = "CounterTool" }; +- +- // Act +- var firstResult = resolver.Resolve(firstInput); +- // In a real scenario, the agent would pass the updated session attributes back to us +- secondInput.SessionAttributes = firstResult.SessionAttributes; +- secondInput.PromptSessionAttributes = firstResult.PromptSessionAttributes; +- var secondResult = resolver.Resolve(secondInput); +- +- // Now a third invocation to verify the counter keeps incrementing +- var thirdInput = new BedrockFunctionRequest { Function = "CounterTool" }; +- thirdInput.SessionAttributes = secondResult.SessionAttributes; +- thirdInput.PromptSessionAttributes = secondResult.PromptSessionAttributes; +- var thirdResult = resolver.Resolve(thirdInput); +- +- // Assert +- Assert.Equal("Current count: 1", firstResult.Response.FunctionResponse.ResponseBody.Text.Body); +- Assert.Equal("Current count: 2", secondResult.Response.FunctionResponse.ResponseBody.Text.Body); +- Assert.Equal("Current count: 3", thirdResult.Response.FunctionResponse.ResponseBody.Text.Body); +- +- // Verify session attributes are maintained +- Assert.Equal("1", firstResult.SessionAttributes["counter"]); +- Assert.Equal("2", secondResult.SessionAttributes["counter"]); +- Assert.Equal("3", thirdResult.SessionAttributes["counter"]); +- +- // Verify prompt attributes are preserved +- Assert.Equal("initial", firstResult.PromptSessionAttributes["prompt"]); +- Assert.Equal("initial", secondResult.PromptSessionAttributes["prompt"]); +- Assert.Equal("initial", thirdResult.PromptSessionAttributes["prompt"]); +- } +- +- private class TestObject +- { +- public int Id { get; set; } +- public string Name { get; set; } = ""; +- +- public override string ToString() => $"{Name} (ID: {Id})"; +- } +- } +-} +diff --git a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/BedrockAgentFunctionResolverExceptionTests.cs b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/BedrockAgentFunctionResolverExceptionTests.cs +deleted file mode 100644 +index b05c3f42..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/BedrockAgentFunctionResolverExceptionTests.cs ++++ /dev/null +@@ -1,68 +0,0 @@ +-using Amazon.Lambda.TestUtilities; +-using AWS.Lambda.Powertools.EventHandler.Resolvers; +-using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; +- +-namespace AWS.Lambda.Powertools.EventHandler.BedrockAgentFunction +-{ +- public class BedrockAgentFunctionResolverExceptionTests +- { +- [Fact] +- public void RegisterToolHandler_WithParameterMappingException_ReturnsErrorResponse() +- { +- // Arrange +- var resolver = new BedrockAgentFunctionResolver(); +- +- // Register a tool that requires a complex parameter that can't be mapped automatically +- resolver.Tool("ComplexTest", (TestComplexType complex) => $"Name: {complex.Name}"); +- +- var input = new BedrockFunctionRequest +- { +- Function = "ComplexTest", +- Parameters = new List +- { +- // This parameter can't be automatically mapped to the complex type +- new Parameter { Name = "complex", Value = "{\"name\":\"Test\"}", Type = "String" } +- } +- }; +- var context = new TestLambdaContext(); +- +- // Act +- var result = resolver.Resolve(input, context); +- +- // Assert +- // This should trigger the parameter mapping exception path +- Assert.Contains("Error when invoking tool:", result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- [Fact] +- public void RegisterToolHandler_WithNestedExceptionInDelegateInvoke_HandlesCorrectly() +- { +- // Arrange +- var resolver = new BedrockAgentFunctionResolver(); +- +- // Register a tool with a delegate that will throw an exception with inner exception +- resolver.Tool("NestedExceptionTest", () => { +- throw new AggregateException("Outer exception", +- new ApplicationException("Inner exception message")); +- return "Should not reach here"; +- }); +- +- var input = new BedrockFunctionRequest { Function = "NestedExceptionTest" }; +- var context = new TestLambdaContext(); +- +- // Act +- var result = resolver.Resolve(input, context); +- +- // Assert +- // The error should contain the inner exception message +- Assert.Contains("Inner exception message", result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- // A test complex type that can't be automatically mapped from parameters +- private class TestComplexType +- { +- public string Name { get; set; } = ""; +- public int Value { get; set; } +- } +- } +-} +diff --git a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/BedrockAgentFunctionResolverTests.cs b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/BedrockAgentFunctionResolverTests.cs +deleted file mode 100644 +index 1090a248..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/BedrockAgentFunctionResolverTests.cs ++++ /dev/null +@@ -1,943 +0,0 @@ +-using System.Globalization; +-using System.Text; +-using System.Text.Json.Serialization; +-using Amazon.Lambda.Core; +-using Amazon.Lambda.TestUtilities; +-using AWS.Lambda.Powertools.EventHandler.Resolvers; +-using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; +-using Microsoft.Extensions.DependencyInjection; +- +-#pragma warning disable CS0162 // Unreachable code detected +- +- +-namespace AWS.Lambda.Powertools.EventHandler.BedrockAgentFunction; +- +-public class BedrockAgentFunctionResolverTests +-{ +- [Fact] +- public void TestFunctionHandlerWithNoParameters() +- { +- // Arrange +- var resolver = new BedrockAgentFunctionResolver(); +- resolver.Tool("TestFunction", () => new BedrockFunctionResponse +- { +- Response = new Response +- { +- ActionGroup = "TestGroup", +- Function = "TestFunction", +- FunctionResponse = new FunctionResponse +- { +- ResponseBody = new ResponseBody +- { +- Text = new TextBody { Body = "Hello, World!" } +- } +- } +- } +- }); +- +- var input = new BedrockFunctionRequest { Function = "TestFunction" }; +- var context = new TestLambdaContext(); +- +- // Act +- var result = resolver.Resolve(input, context); +- +- // Assert +- Assert.Equal("Hello, World!", result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- [Fact] +- public void TestFunctionHandlerWithDescription() +- { +- // Arrange +- var resolver = new BedrockAgentFunctionResolver(); +- resolver.Tool("TestFunction", () => new BedrockFunctionResponse +- { +- Response = new Response +- { +- ActionGroup = "TestGroup", +- Function = "TestFunction", +- FunctionResponse = new FunctionResponse +- { +- ResponseBody = new ResponseBody +- { +- Text = new TextBody { Body = "Hello, World!" } +- } +- } +- } +- }, +- "This is a test function"); +- +- var input = new BedrockFunctionRequest { Function = "TestFunction" }; +- var context = new TestLambdaContext(); +- +- // Act +- var result = resolver.Resolve(input, context); +- +- // Assert +- Assert.Equal("Hello, World!", result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- [Fact] +- public void TestFunctionHandlerWithMultiplTools() +- { +- // Arrange +- var resolver = new BedrockAgentFunctionResolver(); +- +- resolver.Tool("TestFunction1", () => new BedrockFunctionResponse +- { +- Response = new Response +- { +- ActionGroup = "TestGroup", +- Function = "TestFunction", +- FunctionResponse = new FunctionResponse +- { +- ResponseBody = new ResponseBody +- { +- Text = new TextBody { Body = "Hello from Function 1!" } +- } +- } +- } +- }); +- resolver.Tool("TestFunction2", () => new BedrockFunctionResponse +- { +- Response = new Response +- { +- ActionGroup = "TestGroup", +- Function = "TestFunction", +- FunctionResponse = new FunctionResponse +- { +- ResponseBody = new ResponseBody +- { +- Text = new TextBody { Body = "Hello from Function 2!" } +- } +- } +- } +- }); +- +- var input1 = new BedrockFunctionRequest { Function = "TestFunction1" }; +- var input2 = new BedrockFunctionRequest { Function = "TestFunction2" }; +- var context = new TestLambdaContext(); +- +- // Act +- var result1 = resolver.Resolve(input1, context); +- var result2 = resolver.Resolve(input2, context); +- +- // Assert +- Assert.Equal("Hello from Function 1!", result1.Response.FunctionResponse.ResponseBody.Text.Body); +- Assert.Equal("Hello from Function 2!", result2.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- [Fact] +- public void TestFunctionHandlerWithMultiplToolsDuplicate() +- { +- // Arrange +- var resolver = new BedrockAgentFunctionResolver(); +- resolver.Tool("TestFunction1", () => new BedrockFunctionResponse +- { +- Response = new Response +- { +- ActionGroup = "TestGroup", +- Function = "TestFunction", +- FunctionResponse = new FunctionResponse +- { +- ResponseBody = new ResponseBody +- { +- Text = new TextBody { Body = "Hello from Function 1!" } +- } +- } +- } +- }); +- resolver.Tool("TestFunction1", () => new BedrockFunctionResponse +- { +- Response = new Response +- { +- ActionGroup = "TestGroup", +- Function = "TestFunction", +- FunctionResponse = new FunctionResponse +- { +- ResponseBody = new ResponseBody +- { +- Text = new TextBody { Body = "Hello from Function 2!" } +- } +- } +- } +- }); +- +- var input1 = new BedrockFunctionRequest { Function = "TestFunction1" }; +- var input2 = new BedrockFunctionRequest { Function = "TestFunction1" }; +- var context = new TestLambdaContext(); +- +- // Act +- var result1 = resolver.Resolve(input1, context); +- var result2 = resolver.Resolve(input2, context); +- +- // Assert +- Assert.Equal("Hello from Function 2!", result1.Response.FunctionResponse.ResponseBody.Text.Body); +- Assert.Equal("Hello from Function 2!", result2.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- +- [Fact] +- public void TestFunctionHandlerWithInput() +- { +- // Arrange +- var resolver = new BedrockAgentFunctionResolver(); +- resolver.Tool("TestFunction", +- (input, context) => new BedrockFunctionResponse +- { +- Response = new Response +- { +- ActionGroup = "TestGroup", +- Function = "TestFunction", +- FunctionResponse = new FunctionResponse +- { +- ResponseBody = new ResponseBody +- { +- Text = new TextBody { Body = $"Hello, {input.Function}!" } +- } +- } +- } +- }); +- +- var input = new BedrockFunctionRequest { Function = "TestFunction" }; +- var context = new TestLambdaContext(); +- +- // Act +- var result = resolver.Resolve(input, context); +- +- // Assert +- Assert.Equal("Hello, TestFunction!", result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- [Fact] +- public void TestFunctionHandlerNoToolMatch() +- { +- // Arrange +- var resolver = new BedrockAgentFunctionResolver(); +- resolver.Tool("TestFunction", () => new BedrockFunctionResponse +- { +- Response = new Response +- { +- ActionGroup = "TestGroup", +- Function = "TestFunction", +- FunctionResponse = new FunctionResponse +- { +- ResponseBody = new ResponseBody +- { +- Text = new TextBody { Body = "Hello, World!" } +- } +- } +- } +- }); +- +- var input = new BedrockFunctionRequest { Function = "NonExistentFunction" }; +- var context = new TestLambdaContext(); +- +- // Act +- var result = resolver.Resolve(input, context); +- +- // Assert +- Assert.Equal($"Error: Tool {input.Function} has not been registered in handler", +- result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- [Fact] +- public void TestFunctionHandlerWithEvent() +- { +- // Arrange +- var resolver = new BedrockAgentFunctionResolver(); +- resolver.Tool( +- name: "GetCustomForecast", +- description: "Get detailed forecast for a location", +- handler: (string location, int days, ILambdaContext ctx) => +- { +- ctx.Logger.LogLine($"Getting forecast for {location}"); +- return $"{days}-day forecast for {location}"; +- } +- ); +- +- resolver.Tool( +- name: "Greet", +- description: "Greet a user", +- handler: (string name) => { return $"Hello {name}"; } +- ); +- +- resolver.Tool( +- name: "Simple", +- description: "Greet a user", +- handler: () => { return "Hello"; } +- ); +- +- var input = new BedrockFunctionRequest +- { +- Function = "GetCustomForecast", +- Parameters = new List +- { +- new Parameter +- { +- Name = "location", +- Value = "Lisbon", +- Type = "String" +- }, +- new Parameter +- { +- Name = "days", +- Value = "1", +- Type = "Number" +- } +- } +- }; +- +- var context = new TestLambdaContext(); +- +- // Act +- var result = resolver.Resolve(input, context); +- +- // Assert +- Assert.Equal("1-day forecast for Lisbon", result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- [Fact] +- public void TestFunctionHandlerWithEventAndServices() +- { +- // Setup DI +- var services = new ServiceCollection(); +- services.AddSingleton(new MyImplementation()); +- services.AddBedrockResolver(); +- +- var serviceProvider = services.BuildServiceProvider(); +- var resolver = serviceProvider.GetRequiredService(); +- +- resolver.Tool( +- name: "GetCustomForecast", +- description: "Get detailed forecast for a location", +- handler: async (string location, int days, IMyInterface client, ILambdaContext ctx) => +- { +- var resp = await client.DoSomething(location, days); +- return resp; +- } +- ); +- +- var input = new BedrockFunctionRequest +- { +- Function = "GetCustomForecast", +- Parameters = new List +- { +- new Parameter +- { +- Name = "location", +- Value = "Lisbon", +- Type = "String" +- }, +- new Parameter +- { +- Name = "days", +- Value = "1", +- Type = "Number" +- } +- } +- }; +- +- var context = new TestLambdaContext(); +- +- // Act +- var result = resolver.Resolve(input, context); +- +- // Assert +- Assert.Equal("Forecast for Lisbon for 1 days", result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- [Fact] +- public void TestFunctionHandlerWithBooleanParameter() +- { +- // Arrange +- var resolver = new BedrockAgentFunctionResolver(); +- resolver.Tool( +- name: "TestBool", +- description: "Test boolean parameter", +- handler: (bool isEnabled) => { return $"Feature is {(isEnabled ? "enabled" : "disabled")}"; } +- ); +- +- var input = new BedrockFunctionRequest +- { +- Function = "TestBool", +- Parameters = new List +- { +- new Parameter +- { +- Name = "isEnabled", +- Value = "true", +- Type = "Boolean" +- } +- } +- }; +- +- // Act +- var result = resolver.Resolve(input); +- +- // Assert +- Assert.Equal("Feature is enabled", result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- [Fact] +- public void TestFunctionHandlerWithMissingRequiredParameter() +- { +- // Arrange +- var resolver = new BedrockAgentFunctionResolver(); +- resolver.Tool( +- name: "RequiredParam", +- description: "Function with required parameter", +- handler: (string name) => $"Hello, {name}!" +- ); +- +- var input = new BedrockFunctionRequest +- { +- Function = "RequiredParam", +- Parameters = new List() // Empty parameters +- }; +- +- // Act +- var result = resolver.Resolve(input); +- +- // Assert +- Assert.Contains("Hello, !", result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- [Fact] +- public void TestFunctionHandlerWithMultipleParameterTypes() +- { +- // Arrange +- var resolver = new BedrockAgentFunctionResolver(); +- resolver.Tool( +- name: "ComplexFunction", +- description: "Test multiple parameter types", +- handler: (string name, int count, bool isActive) => +- { +- return $"Name: {name}, Count: {count}, Active: {isActive}"; +- } +- ); +- +- var input = new BedrockFunctionRequest +- { +- Function = "ComplexFunction", +- Parameters = new List +- { +- new Parameter { Name = "name", Value = "Test", Type = "String" }, +- new Parameter { Name = "count", Value = "5", Type = "Integer" }, +- new Parameter { Name = "isActive", Value = "true", Type = "Boolean" } +- } +- }; +- +- // Act +- var result = resolver.Resolve(input); +- +- // Assert +- Assert.Equal("Name: Test, Count: 5, Active: True", result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- public enum TestEnum +- { +- Option1, +- Option2, +- Option3 +- } +- +- [Fact] +- public void TestFunctionHandlerWithEnumParameter() +- { +- // Arrange +- var resolver = new BedrockAgentFunctionResolver(); +- resolver.Tool( +- name: "EnumTest", +- description: "Test enum parameter", +- handler: (TestEnum option) => { return $"Selected option: {option}"; } +- ); +- +- var input = new BedrockFunctionRequest +- { +- Function = "EnumTest", +- Parameters = new List +- { +- new Parameter +- { +- Name = "option", +- Value = "Option2", +- Type = "String" // Enums come as strings +- } +- } +- }; +- +- // Act +- var result = resolver.Resolve(input); +- +- // Assert +- Assert.Equal("Selected option: Option2", result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- [Fact] +- public void TestParameterNameCaseSensitivity() +- { +- // Arrange +- var resolver = new BedrockAgentFunctionResolver(); +- resolver.Tool( +- name: "CaseTest", +- description: "Test case sensitivity", +- handler: (string userName) => $"Hello, {userName}!" +- ); +- +- var input = new BedrockFunctionRequest +- { +- Function = "CaseTest", +- Parameters = new List +- { +- new Parameter +- { +- Name = "UserName", // Different case than parameter +- Value = "John", +- Type = "String" +- } +- } +- }; +- +- // Act +- var result = resolver.Resolve(input); +- +- // Assert +- Assert.Equal("Hello, John!", result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- [Fact] +- public void TestParameterOrderIndependence() +- { +- // Arrange +- var resolver = new BedrockAgentFunctionResolver(); +- resolver.Tool( +- name: "OrderTest", +- description: "Test parameter order independence", +- handler: (string firstName, string lastName) => { return $"Name: {firstName} {lastName}"; } +- ); +- +- var input = new BedrockFunctionRequest +- { +- Function = "OrderTest", +- Parameters = new List +- { +- // Parameters in reverse order of handler parameters +- new Parameter { Name = "lastName", Value = "Smith", Type = "String" }, +- new Parameter { Name = "firstName", Value = "John", Type = "String" } +- } +- }; +- +- // Act +- var result = resolver.Resolve(input); +- +- // Assert +- Assert.Equal("Name: John Smith", result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- [Fact] +- public void TestFunctionHandlerWithDecimalParameter() +- { +- // Arrange +- var resolver = new BedrockAgentFunctionResolver(); +- resolver.Tool( +- name: "PriceCalculator", +- description: "Calculate total price with tax", +- handler: (decimal price) => +- { +- var withTax = price * 1.2m; +- return $"Total price with tax: {withTax.ToString("F2", CultureInfo.InvariantCulture)}"; +- } +- ); +- +- var input = new BedrockFunctionRequest +- { +- Function = "PriceCalculator", +- Parameters = new List +- { +- new Parameter +- { +- Name = "price", +- Value = "29.99", +- Type = "Number" +- } +- } +- }; +- +- // Act +- var result = resolver.Resolve(input); +- +- // Assert +- Assert.Contains("35.99", result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- [Fact] +- public void TestFunctionHandlerWithStringArrayParameter() +- { +- // Arrange +- var resolver = new BedrockAgentFunctionResolver(); +- resolver.Tool( +- name: "ProcessWorkout", +- description: "Process workout exercises", +- handler: (string[] exercises) => +- { +- var result = new StringBuilder(); +- result.AppendLine("Your workout plan:"); +- +- for (int i = 0; i < exercises.Length; i++) +- { +- result.AppendLine($" {i + 1}. {exercises[i]}"); +- } +- +- return result.ToString(); +- } +- ); +- +- var input = new BedrockFunctionRequest +- { +- Function = "ProcessWorkout", +- Parameters = new List +- { +- new Parameter +- { +- Name = "exercises", +- Value = +- "[\"Squats, 3 sets of 10 reps\",\"Push-ups, 3 sets of 10 reps\",\"Plank, 3 sets of 30 seconds\"]", +- Type = "String" // The type is String since it contains JSON +- } +- } +- }; +- +- // Act +- var result = resolver.Resolve(input); +- +- // Assert +- Assert.Contains("Your workout plan:", result.Response.FunctionResponse.ResponseBody.Text.Body); +- Assert.Contains("1. Squats, 3 sets of 10 reps", result.Response.FunctionResponse.ResponseBody.Text.Body); +- Assert.Contains("2. Push-ups, 3 sets of 10 reps", result.Response.FunctionResponse.ResponseBody.Text.Body); +- Assert.Contains("3. Plank, 3 sets of 30 seconds", result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- [Fact] +- public void TestFunctionHandlerWithExceptionInHandler() +- { +- // Arrange +- var resolver = new BedrockAgentFunctionResolver(); +- resolver.Tool( +- name: "ThrowingFunction", +- description: "Function that throws exception", +- handler: () => +- { +- throw new InvalidOperationException("Test error"); +- return "This will not run:"; +- } +- ); +- +- var input = new BedrockFunctionRequest { Function = "ThrowingFunction" }; +- +- // Act +- var result = resolver.Resolve(input); +- +- // Assert +- Assert.Contains("Error when invoking tool: Test error", result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- [Fact] +- public void TestSessionAttributesPreservation() +- { +- // Arrange +- var resolver = new BedrockAgentFunctionResolver(); +- resolver.Tool( +- name: "SessionTest", +- description: "Test session attributes preservation", +- handler: (string message) => message +- ); +- +- var input = new BedrockFunctionRequest +- { +- Function = "SessionTest", +- ActionGroup = "TestGroup", +- Parameters = new List +- { +- new Parameter { Name = "message", Value = "Hello", Type = "String" } +- }, +- SessionAttributes = new Dictionary +- { +- { "userId", "12345" }, +- { "preferredLanguage", "en-US" } +- }, +- PromptSessionAttributes = new Dictionary +- { +- { "context", "customer_support" }, +- { "previousQuestion", "How do I reset my password?" } +- } +- }; +- +- // Act +- var result = resolver.Resolve(input); +- +- // Assert +- Assert.Equal("Hello", result.Response.FunctionResponse.ResponseBody.Text.Body); +- Assert.Equal(2, result.SessionAttributes.Count); +- Assert.Equal("12345", result.SessionAttributes["userId"]); +- Assert.Equal("en-US", result.SessionAttributes["preferredLanguage"]); +- Assert.Equal(2, result.PromptSessionAttributes.Count); +- Assert.Equal("customer_support", result.PromptSessionAttributes["context"]); +- Assert.Equal("How do I reset my password?", result.PromptSessionAttributes["previousQuestion"]); +- } +- +- [Fact] +- public void TestSessionAttributesPreservationWithErrorHandling() +- { +- // Arrange +- var resolver = new BedrockAgentFunctionResolver(); +- resolver.Tool( +- name: "ErrorTest", +- description: "Test session attributes preservation with error", +- handler: () => { throw new Exception("Test error"); return "This will not run"; } +- ); +- +- var input = new BedrockFunctionRequest +- { +- Function = "ErrorTest", +- ActionGroup = "TestGroup", +- SessionAttributes = new Dictionary +- { +- { "userId", "12345" }, +- { "session", "active" } +- }, +- PromptSessionAttributes = new Dictionary +- { +- { "lastAction", "login" } +- } +- }; +- +- // Act +- var result = resolver.Resolve(input); +- +- // Assert +- Assert.Contains("Error when invoking tool: Test error", result.Response.FunctionResponse.ResponseBody.Text.Body); +- Assert.Equal(2, result.SessionAttributes.Count); +- Assert.Equal("12345", result.SessionAttributes["userId"]); +- Assert.Equal("active", result.SessionAttributes["session"]); +- Assert.Equal(1, result.PromptSessionAttributes?.Count); +- Assert.Equal("login", result.PromptSessionAttributes?["lastAction"]); +- } +- +- [Fact] +- public void TestSessionAttributesPreservationWithNoToolMatch() +- { +- // Arrange +- var resolver = new BedrockAgentFunctionResolver(); +- +- var input = new BedrockFunctionRequest +- { +- Function = "NonExistentTool", +- SessionAttributes = new Dictionary +- { +- { "preferredTheme", "dark" } +- }, +- PromptSessionAttributes = new Dictionary +- { +- { "lastVisited", "homepage" } +- } +- }; +- +- // Act +- var result = resolver.Resolve(input); +- +- // Assert +- Assert.Contains($"Error: Tool {input.Function} has not been registered in handler", result.Response.FunctionResponse.ResponseBody.Text.Body); +- Assert.Equal(1, result.SessionAttributes?.Count); +- Assert.Equal("dark", result.SessionAttributes?["preferredTheme"]); +- Assert.Equal(1, result.PromptSessionAttributes?.Count); +- Assert.Equal("homepage", result.PromptSessionAttributes?["lastVisited"]); +- } +- +- [Fact] +- public void TestSReturningNull() +- { +- // Arrange +- var resolver = new BedrockAgentFunctionResolver(); +- resolver.Tool( +- name: "NullTest", +- description: "Test session attributes preservation with error", +- handler: () => +- { +- string test = null!; +- return test; +- } +- ); +- +- var input = new BedrockFunctionRequest +- { +- Function = "NullTest", +- }; +- +- // Act +- var result = resolver.Resolve(input); +- +- // Assert +- Assert.Equal("", result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- [Fact] +- public void TestToolOverrideWithWarning() +- { +- // Arrange +- var resolver = new BedrockAgentFunctionResolver(); +- +- // Register a tool +- resolver.Tool("Calculator", () => "Original Calculator"); +- +- // Register same tool again with different implementation +- resolver.Tool("Calculator", () => "New Calculator"); +- +- // Verify the tool was overridden +- var input = new BedrockFunctionRequest { Function = "Calculator" }; +- var result = resolver.Resolve(input); +- +- // The second registration should have overwritten the first +- Assert.Equal("New Calculator", result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- [Fact] +- public void TestFunctionHandlerWithCustomType() +- { +- // Arrange +- var resolver = new BedrockAgentFunctionResolver(); +- resolver.Tool( +- name: "PriceCalculator", +- description: "Calculate total price with tax", +- handler: (MyCustomType myCustomType) => +- { +- var withTax = myCustomType.Price * 1.2m; +- return $"Total price with tax: {withTax.ToString("F2", CultureInfo.InvariantCulture)}"; +- } +- ); +- +- var input = new BedrockFunctionRequest +- { +- Function = "PriceCalculator", +- InputText = "{\"Price\": 29.99}", // JSON representation of MyCustomType +- }; +- +- // Act +- var result = resolver.Resolve(input); +- +- // Assert +- Assert.Contains("35.99", result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- [Fact] +- public void TestFunctionHandlerWithCustomTypeWithTypeInfoResolver() +- { +- // Arrange +- var resolver = new BedrockAgentFunctionResolver(MycustomSerializationContext.Default); +- resolver.Tool( +- name: "PriceCalculator", +- description: "Calculate total price with tax", +- handler: (MyCustomType myCustomType) => +- { +- var withTax = myCustomType.Price * 1.2m; +- return $"Total price with tax: {withTax.ToString("F2", CultureInfo.InvariantCulture)}"; +- } +- ); +- +- var input = new BedrockFunctionRequest +- { +- Function = "PriceCalculator", +- InputText = "{\"Price\": 29.99}", // JSON representation of MyCustomType +- }; +- +- // Act +- var result = resolver.Resolve(input); +- +- // Assert +- Assert.Contains("35.99", result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- [Fact] +- public void TestAttributeBasedToolRegistration() +- { +- // Arrange +- +- var services = new ServiceCollection(); +- services.AddSingleton(new MyImplementation()); +- services.AddBedrockResolver(); +- +- var serviceProvider = services.BuildServiceProvider(); +- var resolver = serviceProvider.GetRequiredService() +- .RegisterTool(); +- +- // Create test input for echo function +- var echoInput = new BedrockFunctionRequest +- { +- Function = "Echo", +- Parameters = new List +- { +- new Parameter { Name = "message", Value = "Hello world", Type = "String" } +- } +- }; +- +- // Create test input for calculate function +- var calcInput = new BedrockFunctionRequest +- { +- Function = "Calculate", +- Parameters = new List +- { +- new Parameter { Name = "x", Value = "5", Type = "Number" }, +- new Parameter { Name = "y", Value = "3", Type = "Number" } +- } +- }; +- +- // Act +- var echoResult = resolver.Resolve(echoInput); +- var calcResult = resolver.Resolve(calcInput); +- +- // Assert +- Assert.Equal("You asked: Forecast for Lisbon for 1 days", echoResult.Response.FunctionResponse.ResponseBody.Text.Body); +- Assert.Equal("Result: 8", calcResult.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- // Example tool class using attributes +- [BedrockFunctionType] +- public class AttributeBasedTool +- { +- [BedrockFunctionTool(Name = "Echo", Description = "Echoes back the input message")] +- public static string EchoMessage(string message, IMyInterface myInterface, ILambdaContext context) +- { +- return $"You asked: {myInterface.DoSomething("Lisbon", 1).Result}"; +- } +- +- [BedrockFunctionTool(Name = "Calculate", Description = "Adds two numbers together")] +- public static string Calculate(int x, int y) +- { +- return $"Result: {x + y}"; +- } +- } +-} +- +-public interface IMyInterface +-{ +- Task DoSomething(string location, int days); +-} +- +-public class MyImplementation : IMyInterface +-{ +- public async Task DoSomething(string location, int days) +- { +- return await Task.FromResult($"Forecast for {location} for {days} days"); +- } +-} +- +-public class MyCustomType +-{ +- public decimal Price { get; set; } +-} +- +- +-[JsonSerializable(typeof(MyCustomType))] +-public partial class MycustomSerializationContext : JsonSerializerContext +-{ +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/Helpers/ParameterAccessorTests.cs b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/Helpers/ParameterAccessorTests.cs +deleted file mode 100644 +index b5d54046..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/Helpers/ParameterAccessorTests.cs ++++ /dev/null +@@ -1,338 +0,0 @@ +-using AWS.Lambda.Powertools.EventHandler.Resolvers; +-using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Helpers; +- +-namespace AWS.Lambda.Powertools.EventHandler.BedrockAgentFunction.Helpers +-{ +- public class ParameterAccessorTests +- { +- [Fact] +- public void Get_WithStringParameter_ReturnsValue() +- { +- // Arrange +- var parameters = new List +- { +- new Parameter { Name = "name", Value = "TestValue", Type = "String" } +- }; +- var accessor = new ParameterAccessor(parameters); +- +- // Act +- var result = accessor.Get("name"); +- +- // Assert +- Assert.Equal("TestValue", result); +- } +- +- [Fact] +- public void Get_WithIntParameter_ReturnsValue() +- { +- // Arrange +- var parameters = new List +- { +- new Parameter { Name = "age", Value = "30", Type = "Number" } +- }; +- var accessor = new ParameterAccessor(parameters); +- +- // Act +- var result = accessor.Get("age"); +- +- // Assert +- Assert.Equal(30, result); +- } +- +- [Fact] +- public void Get_WithBoolParameter_ReturnsValue() +- { +- // Arrange +- var parameters = new List +- { +- new Parameter { Name = "active", Value = "true", Type = "Boolean" } +- }; +- var accessor = new ParameterAccessor(parameters); +- +- // Act +- var result = accessor.Get("active"); +- +- // Assert +- Assert.True(result); +- } +- +- [Fact] +- public void Get_WithLongParameter_ReturnsValue() +- { +- // Arrange +- var parameters = new List +- { +- new Parameter { Name = "bigNumber", Value = "9223372036854775807", Type = "Number" } +- }; +- var accessor = new ParameterAccessor(parameters); +- +- // Act +- var result = accessor.Get("bigNumber"); +- +- // Assert +- Assert.Equal(9223372036854775807, result); +- } +- +- [Fact] +- public void Get_WithDoubleParameter_ReturnsValue() +- { +- // Arrange +- var parameters = new List +- { +- new Parameter { Name = "price", Value = "99.99", Type = "Number" } +- }; +- var accessor = new ParameterAccessor(parameters); +- +- // Act +- var result = accessor.Get("price"); +- +- // Assert +- Assert.Equal(99.99, result); +- } +- +- [Fact] +- public void Get_WithDecimalParameter_ReturnsValue() +- { +- // Arrange +- var parameters = new List +- { +- new Parameter { Name = "amount", Value = "123.456", Type = "Number" } +- }; +- var accessor = new ParameterAccessor(parameters); +- +- // Act +- var result = accessor.Get("amount"); +- +- // Assert +- Assert.Equal(123.456m, result); +- } +- +- [Fact] +- public void Get_WithNonExistentParameter_ReturnsDefault() +- { +- // Arrange +- var parameters = new List +- { +- new Parameter { Name = "existing", Value = "value", Type = "String" } +- }; +- var accessor = new ParameterAccessor(parameters); +- +- // Act +- var stringResult = accessor.Get("nonExistent"); +- var intResult = accessor.Get("nonExistent"); +- var boolResult = accessor.Get("nonExistent"); +- +- // Assert +- Assert.Null(stringResult); +- Assert.Equal(0, intResult); +- Assert.False(boolResult); +- } +- +- [Fact] +- public void Get_WithCaseSensitivity_WorksCaseInsensitively() +- { +- // Arrange +- var parameters = new List +- { +- new Parameter { Name = "userName", Value = "John", Type = "String" } +- }; +- var accessor = new ParameterAccessor(parameters); +- +- // Act +- var result1 = accessor.Get("userName"); +- var result2 = accessor.Get("UserName"); +- var result3 = accessor.Get("USERNAME"); +- +- // Assert +- Assert.Equal("John", result1); +- Assert.Equal("John", result2); +- Assert.Equal("John", result3); +- } +- +- [Fact] +- public void Get_WithNullParameters_ReturnsDefault() +- { +- // Arrange +- var accessor = new ParameterAccessor(null); +- +- // Act +- var stringResult = accessor.Get("any"); +- var intResult = accessor.Get("any"); +- +- // Assert +- Assert.Null(stringResult); +- Assert.Equal(0, intResult); +- } +- +- [Fact] +- public void Get_WithInvalidType_ReturnsDefault() +- { +- // Arrange +- var parameters = new List +- { +- new Parameter { Name = "number", Value = "not-a-number", Type = "Number" } +- }; +- var accessor = new ParameterAccessor(parameters); +- +- // Act +- var result = accessor.Get("number"); +- +- // Assert +- Assert.Equal(0, result); +- } +- +- [Fact] +- public void Get_WithEmptyParameters_ReturnsDefault() +- { +- // Arrange +- var parameters = new List(); +- var accessor = new ParameterAccessor(parameters); +- +- // Act +- var result = accessor.Get("anything"); +- +- // Assert +- Assert.Null(result); +- } +- +- [Fact] +- public void GetAt_WithValidIndex_ReturnsValue() +- { +- // Arrange +- var parameters = new List +- { +- new Parameter { Name = "first", Value = "Value1", Type = "String" }, +- new Parameter { Name = "second", Value = "42", Type = "Number" }, +- new Parameter { Name = "third", Value = "true", Type = "Boolean" } +- }; +- var accessor = new ParameterAccessor(parameters); +- +- // Act +- var stringResult = accessor.GetAt(0); +- var intResult = accessor.GetAt(1); +- var boolResult = accessor.GetAt(2); +- +- // Assert +- Assert.Equal("Value1", stringResult); +- Assert.Equal(42, intResult); +- Assert.True(boolResult); +- } +- +- [Fact] +- public void GetAt_WithInvalidIndex_ReturnsDefaultValue() +- { +- // Arrange +- var parameters = new List +- { +- new Parameter { Name = "param", Value = "Value", Type = "String" } +- }; +- var accessor = new ParameterAccessor(parameters); +- +- // Act +- var negativeIndexResult = accessor.GetAt(-1); +- var tooLargeIndexResult = accessor.GetAt(1); +- +- // Assert +- Assert.Null(negativeIndexResult); +- Assert.Null(tooLargeIndexResult); +- } +- +- [Fact] +- public void GetAt_WithNullParameters_ReturnsDefaultValue() +- { +- // Arrange +- var accessor = new ParameterAccessor(null); +- +- // Act +- var result = accessor.GetAt(0); +- +- // Assert +- Assert.Null(result); +- } +- +- [Fact] +- public void GetAt_WithNullValue_ReturnsDefaultValue() +- { +- // Arrange +- var parameters = new List +- { +- new Parameter { Name = "param", Value = null, Type = "String" } +- }; +- var accessor = new ParameterAccessor(parameters); +- +- // Act +- var result = accessor.GetAt(0); +- +- // Assert +- Assert.Null(result); +- } +- +- [Fact] +- public void GetOrDefault_WithExistingParameter_ReturnsValue() +- { +- // Arrange +- var parameters = new List +- { +- new Parameter { Name = "name", Value = "TestValue", Type = "String" } +- }; +- var accessor = new ParameterAccessor(parameters); +- +- // Act +- var result = accessor.GetOrDefault("name", "DefaultValue"); +- +- // Assert +- Assert.Equal("TestValue", result); +- } +- +- [Fact] +- public void GetOrDefault_WithNonExistentParameter_ReturnsDefaultValue() +- { +- // Arrange +- var parameters = new List +- { +- new Parameter { Name = "existing", Value = "value", Type = "String" } +- }; +- var accessor = new ParameterAccessor(parameters); +- +- // Act +- var result = accessor.GetOrDefault("nonExistent", "DefaultValue"); +- +- // Assert +- Assert.Equal("DefaultValue", result); +- } +- +- [Fact] +- public void GetOrDefault_WithNullValue_ReturnsDefaultValue() +- { +- // Arrange +- var parameters = new List +- { +- new Parameter { Name = "param", Value = null, Type = "String" } +- }; +- var accessor = new ParameterAccessor(parameters); +- +- // Act +- var result = accessor.GetOrDefault("param", "DefaultValue"); +- +- // Assert +- Assert.Equal("DefaultValue", result); +- } +- +- [Fact] +- public void GetOrDefault_WithInvalidConversion_ReturnsDefaultValue() +- { +- // Arrange +- var parameters = new List +- { +- new Parameter { Name = "invalidNumber", Value = "not-a-number", Type = "Number" } +- }; +- var accessor = new ParameterAccessor(parameters); +- +- // Act +- var result = accessor.GetOrDefault("invalidNumber", 999); +- +- // Assert +- Assert.Equal(999, result); +- } +- } +-} +diff --git a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/Helpers/ParameterMapperTests.cs b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/Helpers/ParameterMapperTests.cs +deleted file mode 100644 +index b4cd5705..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/Helpers/ParameterMapperTests.cs ++++ /dev/null +@@ -1,311 +0,0 @@ +-using Amazon.Lambda.Core; +-using Amazon.Lambda.TestUtilities; +-using AWS.Lambda.Powertools.EventHandler.Resolvers; +-using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Helpers; +-using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; +-using NSubstitute; +- +-namespace AWS.Lambda.Powertools.EventHandler.BedrockAgentFunction.Helpers +-{ +- public class ParameterMapperTests +- { +- private readonly ParameterMapper _mapper = new(); +- +- [Fact] +- public void MapParameters_WithNoParameters_ReturnsEmptyArray() +- { +- // Arrange +- var methodInfo = typeof(TestMethodsClass).GetMethod(nameof(TestMethodsClass.NoParameters))!; +- var input = new BedrockFunctionRequest(); +- var context = new TestLambdaContext(); +- +- // Act +- var result = _mapper.MapParameters(methodInfo, input, context, null); +- +- // Assert +- Assert.Empty(result); +- } +- +- [Fact] +- public void MapParameters_WithLambdaContext_MapsCorrectly() +- { +- // Arrange +- var methodInfo = typeof(TestMethodsClass).GetMethod(nameof(TestMethodsClass.WithLambdaContext))!; +- var input = new BedrockFunctionRequest(); +- var context = new TestLambdaContext(); +- +- // Act +- var result = _mapper.MapParameters(methodInfo, input, context, null); +- +- // Assert +- Assert.Single(result); +- Assert.Same(context, result[0]); +- } +- +- [Fact] +- public void MapParameters_WithBedrockFunctionRequest_MapsCorrectly() +- { +- // Arrange +- var methodInfo = typeof(TestMethodsClass).GetMethod(nameof(TestMethodsClass.WithBedrockFunctionRequest))!; +- var input = new BedrockFunctionRequest(); +- var context = new TestLambdaContext(); +- +- // Act +- var result = _mapper.MapParameters(methodInfo, input, context, null); +- +- // Assert +- Assert.Single(result); +- Assert.Same(input, result[0]); +- } +- +- [Fact] +- public void MapParameters_WithStringParameter_MapsCorrectly() +- { +- // Arrange +- var methodInfo = typeof(TestMethodsClass).GetMethod(nameof(TestMethodsClass.WithStringParameter))!; +- var input = new BedrockFunctionRequest +- { +- Parameters = new List +- { +- new() { Name = "name", Value = "TestValue", Type = "String" } +- } +- }; +- var context = new TestLambdaContext(); +- +- // Act +- var result = _mapper.MapParameters(methodInfo, input, context, null); +- +- // Assert +- Assert.Single(result); +- Assert.Equal("TestValue", result[0]); +- } +- +- [Fact] +- public void MapParameters_WithIntParameter_MapsCorrectly() +- { +- // Arrange +- var methodInfo = typeof(TestMethodsClass).GetMethod(nameof(TestMethodsClass.WithIntParameter))!; +- var input = new BedrockFunctionRequest +- { +- Parameters = new List +- { +- new() { Name = "value", Value = "42", Type = "Number" } +- } +- }; +- var context = new TestLambdaContext(); +- +- // Act +- var result = _mapper.MapParameters(methodInfo, input, context, null); +- +- // Assert +- Assert.Single(result); +- Assert.Equal(42, result[0]); +- } +- +- [Fact] +- public void MapParameters_WithBoolParameter_MapsCorrectly() +- { +- // Arrange +- var methodInfo = typeof(TestMethodsClass).GetMethod(nameof(TestMethodsClass.WithBoolParameter))!; +- var input = new BedrockFunctionRequest +- { +- Parameters = new List +- { +- new() { Name = "flag", Value = "true", Type = "Boolean" } +- } +- }; +- var context = new TestLambdaContext(); +- +- // Act +- var result = _mapper.MapParameters(methodInfo, input, context, null); +- +- // Assert +- Assert.Single(result); +- Assert.True((bool)result[0]!); +- } +- +- [Fact] +- public void MapParameters_WithEnumParameter_MapsCorrectly() +- { +- // Arrange +- var methodInfo = typeof(TestMethodsClass).GetMethod(nameof(TestMethodsClass.WithEnumParameter))!; +- var input = new BedrockFunctionRequest +- { +- Parameters = new List +- { +- new() { Name = "testEnum", Value = "Option2", Type = "String" } +- } +- }; +- var context = new TestLambdaContext(); +- +- // Act +- var result = _mapper.MapParameters(methodInfo, input, context, null); +- +- // Assert +- Assert.Single(result); +- Assert.Equal(TestEnum.Option2, result[0]); +- } +- +- [Fact] +- public void MapParameters_WithStringArrayParameter_MapsCorrectly() +- { +- // Arrange +- var methodInfo = typeof(TestMethodsClass).GetMethod(nameof(TestMethodsClass.WithStringArrayParameter))!; +- var input = new BedrockFunctionRequest +- { +- Parameters = new List +- { +- new() { Name = "values", Value = "[\"one\",\"two\",\"three\"]", Type = "String" } +- } +- }; +- var context = new TestLambdaContext(); +- +- // Act +- var result = _mapper.MapParameters(methodInfo, input, context, null); +- +- // Assert +- Assert.Single(result); +- var array = (string[])result[0]!; +- Assert.Equal(3, array.Length); +- Assert.Equal("one", array[0]); +- Assert.Equal("two", array[1]); +- Assert.Equal("three", array[2]); +- } +- +- [Fact] +- public void MapParameters_WithIntArrayParameter_MapsCorrectly() +- { +- // Arrange +- var methodInfo = typeof(TestMethodsClass).GetMethod(nameof(TestMethodsClass.WithIntArrayParameter))!; +- var input = new BedrockFunctionRequest +- { +- Parameters = new List +- { +- new() { Name = "values", Value = "[1,2,3]", Type = "String" } +- } +- }; +- var context = new TestLambdaContext(); +- +- // Act +- var result = _mapper.MapParameters(methodInfo, input, context, null); +- +- // Assert +- Assert.Single(result); +- var array = (int[])result[0]!; +- Assert.Equal(3, array.Length); +- Assert.Equal(1, array[0]); +- Assert.Equal(2, array[1]); +- Assert.Equal(3, array[2]); +- } +- +- [Fact] +- public void MapParameters_WithInvalidJsonArray_ReturnsNull() +- { +- // Arrange +- var methodInfo = typeof(TestMethodsClass).GetMethod(nameof(TestMethodsClass.WithStringArrayParameter))!; +- var input = new BedrockFunctionRequest +- { +- Parameters = new List +- { +- new() { Name = "values", Value = "[invalid json]", Type = "String" } +- } +- }; +- var context = new TestLambdaContext(); +- +- // Act +- var result = _mapper.MapParameters(methodInfo, input, context, null); +- +- // Assert +- Assert.Single(result); +- Assert.Null(result[0]); +- } +- +- [Fact] +- public void MapParameters_WithServiceProvider_ResolvesService() +- { +- // Arrange +- var methodInfo = typeof(TestMethodsClass).GetMethod(nameof(TestMethodsClass.WithDependencyInjection))!; +- var input = new BedrockFunctionRequest(); +- var context = new TestLambdaContext(); +- +- // Create a test service +- var testService = new TestService(); +- +- // Setup service provider +- var serviceProvider = Substitute.For(); +- serviceProvider.GetService(typeof(ITestService)).Returns(testService); +- +- // Act +- var result = _mapper.MapParameters(methodInfo, input, context, serviceProvider); +- +- // Assert +- Assert.Equal(3, result.Length); +- Assert.Same(context, result[0]); +- Assert.Same(input, result[1]); +- Assert.Same(testService, result[2]); +- } +- +- [Fact] +- public void MapParameters_WithMultipleParameterTypes_MapsAllCorrectly() +- { +- // Arrange +- var methodInfo = typeof(TestMethodsClass).GetMethod(nameof(TestMethodsClass.WithMultipleParameterTypes))!; +- var input = new BedrockFunctionRequest +- { +- Parameters = new List +- { +- new() { Name = "name", Value = "TestUser", Type = "String" }, +- new() { Name = "age", Value = "30", Type = "Number" }, +- new() { Name = "isActive", Value = "true", Type = "Boolean" } +- } +- }; +- var context = new TestLambdaContext(); +- +- // Act +- var result = _mapper.MapParameters(methodInfo, input, context, null); +- +- // Assert +- Assert.Equal(4, result.Length); +- Assert.Equal("TestUser", result[0]); +- Assert.Equal(30, result[1]); +- Assert.True((bool)result[2]!); +- Assert.Same(context, result[3]); +- } +- +- public class TestMethodsClass +- { +- public void NoParameters() { } +- +- public void WithLambdaContext(ILambdaContext context) { } +- +- public void WithBedrockFunctionRequest(BedrockFunctionRequest request) { } +- +- public void WithStringParameter(string name) { } +- +- public void WithIntParameter(int value) { } +- +- public void WithBoolParameter(bool flag) { } +- +- public void WithEnumParameter(TestEnum testEnum) { } +- +- public void WithStringArrayParameter(string[] values) { } +- +- public void WithIntArrayParameter(int[] values) { } +- +- public void WithDependencyInjection(ILambdaContext context, BedrockFunctionRequest request, ITestService service) { } +- +- public void WithMultipleParameterTypes(string name, int age, bool isActive, ILambdaContext context) { } +- } +- +- public interface ITestService { } +- +- public class TestService : ITestService { } +- +- public enum TestEnum +- { +- Option1, +- Option2, +- Option3 +- } +- } +-} +diff --git a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/Helpers/ParameterTypeValidatorTests.cs b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/Helpers/ParameterTypeValidatorTests.cs +deleted file mode 100644 +index b8ec3353..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/Helpers/ParameterTypeValidatorTests.cs ++++ /dev/null +@@ -1,49 +0,0 @@ +-using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Helpers; +- +-namespace AWS.Lambda.Powertools.EventHandler.BedrockAgentFunction.Helpers +-{ +- public class ParameterTypeValidatorTests +- { +- private readonly ParameterTypeValidator _validator = new(); +- +- [Theory] +- [InlineData(typeof(string), true)] +- [InlineData(typeof(int), true)] +- [InlineData(typeof(long), true)] +- [InlineData(typeof(double), true)] +- [InlineData(typeof(bool), true)] +- [InlineData(typeof(decimal), true)] +- [InlineData(typeof(DateTime), true)] +- [InlineData(typeof(Guid), true)] +- [InlineData(typeof(string[]), true)] +- [InlineData(typeof(int[]), true)] +- [InlineData(typeof(long[]), true)] +- [InlineData(typeof(double[]), true)] +- [InlineData(typeof(bool[]), true)] +- [InlineData(typeof(decimal[]), true)] +- [InlineData(typeof(TestEnum), true)] // Enum should be valid +- [InlineData(typeof(object), false)] +- [InlineData(typeof(Dictionary), false)] +- [InlineData(typeof(List), false)] +- [InlineData(typeof(float), false)] +- [InlineData(typeof(char), false)] +- [InlineData(typeof(byte), false)] +- [InlineData(typeof(float[]), false)] +- [InlineData(typeof(object[]), false)] +- public void IsBedrockParameter_WithVariousTypes_ReturnsExpectedResult(Type type, bool expected) +- { +- // Act +- var result = _validator.IsBedrockParameter(type); +- +- // Assert +- Assert.Equal(expected, result); +- } +- +- private enum TestEnum +- { +- One, +- Two, +- Three +- } +- } +-} +diff --git a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/Helpers/ResultConverterTests.cs b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/Helpers/ResultConverterTests.cs +deleted file mode 100644 +index 437118e5..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/Helpers/ResultConverterTests.cs ++++ /dev/null +@@ -1,276 +0,0 @@ +-using Amazon.Lambda.Core; +-using Amazon.Lambda.TestUtilities; +-using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Helpers; +-using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; +- +-namespace AWS.Lambda.Powertools.EventHandler.BedrockAgentFunction.Helpers +-{ +- public class ResultConverterTests +- { +- private readonly ResultConverter _converter = new(); +- private readonly BedrockFunctionRequest _defaultInput = new() +- { +- Function = "TestFunction", +- ActionGroup = "TestGroup", +- SessionAttributes = new Dictionary { { "testKey", "testValue" } }, +- PromptSessionAttributes = new Dictionary { { "promptKey", "promptValue" } } +- }; +- private readonly string _functionName = "TestFunction"; +- private readonly ILambdaContext _context = new TestLambdaContext(); +- +- [Fact] +- public void ProcessResult_WithBedrockFunctionResponse_ReturnsUnchanged() +- { +- // Arrange +- var response = BedrockFunctionResponse.WithText( +- "Test response", +- "TestGroup", +- "TestFunction", +- new Dictionary(), +- new Dictionary(), +- new Dictionary()); +- +- // Act +- var result = _converter.ProcessResult(response, _defaultInput, _functionName, _context); +- +- // Assert +- Assert.Same(response, result); +- } +- +- [Fact] +- public void ProcessResult_WithNullValue_ReturnsEmptyResponse() +- { +- // Arrange +- object? nullValue = null; +- +- // Act +- var result = _converter.ProcessResult(nullValue, _defaultInput, _functionName, _context); +- +- // Assert +- Assert.Equal(string.Empty, result.Response.FunctionResponse.ResponseBody.Text.Body); +- Assert.Equal(_defaultInput.ActionGroup, result.Response.ActionGroup); +- Assert.Equal(_defaultInput.Function, result.Response.Function); +- } +- +- [Fact] +- public void ProcessResult_WithStringValue_ReturnsTextResponse() +- { +- // Arrange +- var stringValue = "Hello, world!"; +- +- // Act +- var result = _converter.ProcessResult(stringValue, _defaultInput, _functionName, _context); +- +- // Assert +- Assert.Equal(stringValue, result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- [Fact] +- public void ProcessResult_WithIntValue_ReturnsTextResponse() +- { +- // Arrange +- var intValue = 42; +- +- // Act +- var result = _converter.ProcessResult(intValue, _defaultInput, _functionName, _context); +- +- // Assert +- Assert.Equal("42", result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- [Fact] +- public void ProcessResult_WithDecimalValue_ReturnsTextResponse() +- { +- // Arrange +- var decimalValue = 42.5m; +- +- // Act +- var result = _converter.ProcessResult(decimalValue, _defaultInput, _functionName, _context); +- +- // Assert +- Assert.Equal("42.5", result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- [Fact] +- public void ProcessResult_WithBoolValue_ReturnsTextResponse() +- { +- // Arrange +- var boolValue = true; +- +- // Act +- var result = _converter.ProcessResult(boolValue, _defaultInput, _functionName, _context); +- +- // Assert +- Assert.Equal("True", result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- [Fact] +- public void ProcessResult_WithObjectValue_ReturnsToString() +- { +- // Arrange +- var testObject = new TestObject { Name = "Test", Value = 42 }; +- +- // Act +- var result = _converter.ProcessResult(testObject, _defaultInput, _functionName, _context); +- +- // Assert +- Assert.Equal(testObject.ToString(), result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- [Fact] +- public async Task ProcessResult_WithTaskStringResult_ReturnsTextResponse() +- { +- // Arrange +- Task task = Task.FromResult("Async result"); +- +- // Act +- var result = _converter.ProcessResult(task, _defaultInput, _functionName, _context); +- +- // Assert +- Assert.Equal("Async result", result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- [Fact] +- public async Task ProcessResult_WithTaskIntResult_ReturnsTextResponse() +- { +- // Arrange +- Task task = Task.FromResult(42); +- +- // Act +- var result = _converter.ProcessResult(task, _defaultInput, _functionName, _context); +- +- // Assert +- Assert.Equal("42", result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- [Fact] +- public async Task ProcessResult_WithTaskBoolResult_ReturnsTextResponse() +- { +- // Arrange +- Task task = Task.FromResult(true); +- +- // Act +- var result = _converter.ProcessResult(task, _defaultInput, _functionName, _context); +- +- // Assert +- Assert.Equal("True", result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- [Fact] +- public async Task ProcessResult_WithVoidTask_ReturnsEmptyResponse() +- { +- // Arrange +- Task task = Task.CompletedTask; +- +- // Act +- var result = _converter.ProcessResult(task, _defaultInput, _functionName, _context); +- +- // Assert +- Assert.Equal(string.Empty, result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- [Fact] +- public async Task ProcessResult_WithTaskBedrockResponse_ReturnsResponse() +- { +- // Arrange +- var response = BedrockFunctionResponse.WithText( +- "Async response", +- "AsyncGroup", +- "AsyncFunction", +- new Dictionary(), +- new Dictionary(), +- new Dictionary()); +- +- Task task = Task.FromResult(response); +- +- // Act +- var result = _converter.ProcessResult(task, _defaultInput, _functionName, _context); +- +- // Assert +- Assert.Equal("Async response", result.Response.FunctionResponse.ResponseBody.Text.Body); +- Assert.Equal("AsyncGroup", result.Response.ActionGroup); +- Assert.Equal("AsyncFunction", result.Response.Function); +- } +- +- [Fact] +- public void EnsureResponseMetadata_WithEmptyMetadata_FillsFromInput() +- { +- // Arrange +- var response = BedrockFunctionResponse.WithText( +- "Test response", +- "", // Empty action group +- "", // Empty function name +- _defaultInput.SessionAttributes, +- _defaultInput.PromptSessionAttributes, +- new Dictionary()); +- +- // Act +- var result = _converter.ConvertToOutput(response, _defaultInput); +- +- // Assert +- Assert.Equal("Test response", result.Response.FunctionResponse.ResponseBody.Text.Body); +- Assert.Equal(_defaultInput.ActionGroup, result.Response.ActionGroup); // Filled from input +- Assert.Equal(_defaultInput.Function, result.Response.Function); // Filled from input +- } +- +- [Fact] +- public void ConvertToOutput_PreservesSessionAttributes() +- { +- // Arrange +- var sessionAttributes = new Dictionary { { "userID", "test123" } }; +- var promptAttributes = new Dictionary { { "context", "testing" } }; +- +- var input = new BedrockFunctionRequest +- { +- Function = "TestFunction", +- ActionGroup = "TestGroup", +- SessionAttributes = sessionAttributes, +- PromptSessionAttributes = promptAttributes +- }; +- +- // Act +- var result = _converter.ConvertToOutput("Test response", input); +- +- // Assert +- Assert.Equal(sessionAttributes, result.SessionAttributes); +- Assert.Equal(promptAttributes, result.PromptSessionAttributes); +- } +- +- [Fact] +- public void ProcessResult_WithLongValue_ReturnsTextResponse() +- { +- // Arrange +- long longValue = 9223372036854775807; +- +- // Act +- var result = _converter.ProcessResult(longValue, _defaultInput, _functionName, _context); +- +- // Assert +- Assert.Equal("9223372036854775807", result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- [Fact] +- public void ProcessResult_WithDoubleValue_ReturnsTextResponse() +- { +- // Arrange +- double doubleValue = 123.456; +- +- // Act +- var result = _converter.ProcessResult(doubleValue, _defaultInput, _functionName, _context); +- +- // Assert +- Assert.Equal("123.456", result.Response.FunctionResponse.ResponseBody.Text.Body); +- } +- +- private class TestObject +- { +- public string Name { get; set; } = ""; +- public int Value { get; set; } +- +- public override string ToString() +- { +- return $"{Name}:{Value}"; +- } +- } +- } +-} +diff --git a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/bedrockFunctionEvent.json b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/bedrockFunctionEvent.json +deleted file mode 100644 +index f2cedeb1..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/bedrockFunctionEvent.json ++++ /dev/null +@@ -1,27 +0,0 @@ +-{ +- "messageVersion": "1.0", +- "function": "sum_numbers", +- "sessionId": "455081292773641", +- "agent": { +- "name": "powertools-test", +- "version": "DRAFT", +- "id": "WPMRGAPAPJ", +- "alias": "TSTALIASID" +- }, +- "parameters": [ +- { +- "name": "a", +- "type": "number", +- "value": "1" +- }, +- { +- "name": "b", +- "type": "number", +- "value": "1" +- } +- ], +- "actionGroup": "utility-tasks", +- "sessionAttributes": {}, +- "promptSessionAttributes": {}, +- "inputText": "Sum 1 and 1" +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/EventHandler/AppSyncEventsTests.cs b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/EventHandler/AppSyncEventsTests.cs +deleted file mode 100644 +index 07c0e9fa..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/EventHandler/AppSyncEventsTests.cs ++++ /dev/null +@@ -1,881 +0,0 @@ +-using System.Text.Json; +-using System.Text.Json.Serialization; +-using Amazon.Lambda.Core; +-using Amazon.Lambda.TestUtilities; +-using AWS.Lambda.Powertools.EventHandler.AppSyncEvents; +-#pragma warning disable CS8604 // Possible null reference argument. +-#pragma warning disable CS8602 // Dereference of a possibly null reference. +- +-namespace AWS.Lambda.Powertools.EventHandler; +- +-public class AppSyncEventsTests +-{ +- private readonly AppSyncEventsRequest _appSyncEvent; +- +- public AppSyncEventsTests() +- { +- _appSyncEvent = JsonSerializer.Deserialize( +- File.ReadAllText("appSyncEventsEvent.json"), +- new JsonSerializerOptions +- { +- PropertyNameCaseInsensitive = true, +- Converters = { new JsonStringEnumConverter() } +- })!; +- } +- +- [Fact] +- public void Should_Return_Unchanged_Payload_No_Handlers() +- { +- // Arrange +- var lambdaContext = new TestLambdaContext(); +- var app = new AppSyncEventsResolver(); +- +- // Act +- var result = app.Resolve(_appSyncEvent, lambdaContext); +- +- // Assert +- Assert.Equal(3, result.Events.Count); +- Assert.Equal("1", result.Events[0].Id); +- Assert.Equal("data_1", result.Events[0].Payload?["event_1"].ToString()); +- Assert.Equal("2", result.Events[1].Id); +- Assert.Equal("data_2", result.Events[1].Payload?["event_2"].ToString()); +- Assert.Equal("3", result.Events[2].Id); +- Assert.Equal("data_3", result.Events[2].Payload?["event_3"].ToString()); +- } +- +- [Fact] +- public void Should_Return_Unchanged_Payload() +- { +- // Arrange +- var lambdaContext = new TestLambdaContext(); +- var app = new AppSyncEventsResolver(); +- +- app.OnPublish("/default/channel", payload => +- { +- // Handle channel1 events +- return payload; +- }); +- +- // Act +- var result = app.Resolve(_appSyncEvent, lambdaContext); +- +- // Assert +- Assert.Equal(3, result.Events.Count); +- Assert.Equal("1", result.Events[0].Id); +- Assert.Equal("data_1", result.Events[0].Payload?["event_1"].ToString()); +- Assert.Equal("2", result.Events[1].Id); +- Assert.Equal("data_2", result.Events[1].Payload?["event_2"].ToString()); +- Assert.Equal("3", result.Events[2].Id); +- Assert.Equal("data_3", result.Events[2].Payload?["event_3"].ToString()); +- } +- +- [Fact] +- public async Task Should_Return_Unchanged_Payload_Async() +- { +- // Arrange +- var lambdaContext = new TestLambdaContext(); +- var app = new AppSyncEventsResolver(); +- +- app.OnPublishAsync("/default/channel", payload => +- { +- // Handle channel1 events +- return Task.FromResult(payload); +- }); +- +- // Act +- var result = +- await app.ResolveAsync(_appSyncEvent, lambdaContext); +- +- // Assert +- Assert.Equal(3, result.Events.Count); +- Assert.Equal("1", result.Events[0].Id); +- Assert.Equal("data_1", result.Events[0].Payload?["event_1"].ToString()); +- Assert.Equal("2", result.Events[1].Id); +- Assert.Equal("data_2", result.Events[1].Payload?["event_2"].ToString()); +- Assert.Equal("3", result.Events[2].Id); +- Assert.Equal("data_3", result.Events[2].Payload?["event_3"].ToString()); +- } +- +- [Fact] +- public async Task Should_Handle_Error_In_Event_Processing() +- { +- // Arrange +- var lambdaContext = new TestLambdaContext(); +- var app = new AppSyncEventsResolver(); +- +- app.OnPublishAsync("/default/channel", (payload) => +- { +- // Throw exception for second event +- if (payload.ContainsKey("event_2")) +- { +- throw new InvalidOperationException("Test error"); +- } +- +- return Task.FromResult(payload); +- }); +- +- // Act +- var result = await app.ResolveAsync(_appSyncEvent, lambdaContext); +- +- // Assert +- if (result.Events != null) +- { +- Assert.Equal(3, result.Events.Count); +- Assert.Equal("1", result.Events[0].Id); +- Assert.Equal("data_1", result.Events[0].Payload?["event_1"].ToString()); +- Assert.Equal("2", result.Events[1].Id); +- Assert.NotNull(result.Events[1].Error); +- Assert.Contains("Test error", result.Events[1].Error); +- Assert.Equal("3", result.Events[2].Id); +- Assert.Equal("data_3", result.Events[2].Payload?["event_3"].ToString()); +- } +- } +- +- [Fact] +- public async Task Should_Match_Path_With_Wildcard() +- { +- // Arrange +- var lambdaContext = new TestLambdaContext(); +- var app = new AppSyncEventsResolver(); +- +- int callCount = 0; +- app.OnPublishAsync("/default/*", (payload) => +- { +- callCount++; +- return Task.FromResult(new Dictionary { ["wildcard_matched"] = true }); +- }); +- +- // Act +- var result = await app.ResolveAsync(_appSyncEvent, lambdaContext); +- +- // Assert +- if (result.Events != null) +- { +- Assert.Equal(3, result.Events.Count); +- Assert.Equal(3, callCount); +- Assert.True((bool)(result.Events[0].Payload?["wildcard_matched"] ?? false)); +- } +- } +- +- [Fact] +- public async Task Should_Authorize_Subscription() +- { +- // Arrange +- var lambdaContext = new TestLambdaContext(); +- var app = new AppSyncEventsResolver(); +- +- app.OnPublishAsync("/default/channel", (payload) => Task.FromResult(payload)); +- +- app.OnSubscribeAsync("/default/*", (info) => Task.FromResult(true)); +- var subscribeEvent = new AppSyncEventsRequest +- { +- Info = new Information +- { +- Channel = new Channel +- { +- Path = "/default/channel", +- Segments = ["default", "channel"] +- }, +- Operation = AppSyncEventsOperation.Subscribe, +- ChannelNamespace = new ChannelNamespace { Name = "default" } +- } +- }; +- // Act +- var result = await app.ResolveAsync(subscribeEvent, lambdaContext); +- +- // Assert +- Assert.Null(result); +- } +- +- [Fact] +- public void Should_Deny_Subscription() +- { +- // Arrange +- var lambdaContext = new TestLambdaContext(); +- var app = new AppSyncEventsResolver(); +- +- app.OnPublish("/default/channel", (payload) => payload); +- +- app.OnSubscribe("/default/*", (info) => false); +- var subscribeEvent = new AppSyncEventsRequest +- { +- Info = new Information +- { +- Channel = new Channel { Path = "/default/channel", Segments = ["default", "channel"] }, +- Operation = AppSyncEventsOperation.Subscribe, +- ChannelNamespace = new ChannelNamespace { Name = "default" } +- } +- }; +- // Act +- var result = app.Resolve(subscribeEvent, lambdaContext); +- +- // Assert +- Assert.NotNull(result.Error); +- } +- +- [Fact] +- public void Should_Deny_Subscription_On_Exception() +- { +- // Arrange +- var lambdaContext = new TestLambdaContext(); +- var app = new AppSyncEventsResolver(); +- +- app.OnPublish("/default/channel", (payload) => payload); +- +- app.OnSubscribe("/default/*", (info) => { throw new Exception("Authorization error"); }); +- +- var subscribeEvent = new AppSyncEventsRequest +- { +- Info = new Information +- { +- Channel = new Channel { Path = "/default/channel", Segments = ["default", "channel"] }, +- Operation = AppSyncEventsOperation.Subscribe, +- ChannelNamespace = new ChannelNamespace { Name = "default" } +- } +- }; +- +- // Act +- var result = app.Resolve(subscribeEvent, lambdaContext); +- +- // Assert +- Assert.Equal("Authorization error", result.Error); +- } +- +- [Fact] +- public void Should_Handle_Error_In_Aggregate_Mode() +- { +- // Arrange +- var lambdaContext = new TestLambdaContext(); +- var app = new AppSyncEventsResolver(); +- +- app.OnPublishAggregate("/default/channel", +- (evt, ctx) => { throw new InvalidOperationException("Aggregate error"); }); +- +- // Act +- var result = app.Resolve(_appSyncEvent, lambdaContext); +- +- // Assert +- Assert.Contains("Aggregate error", result.Error); +- } +- +- [Fact] +- public async Task Should_Handle_Error_In_Aggregate_Mode_Async() +- { +- // Arrange +- var lambdaContext = new TestLambdaContext(); +- var app = new AppSyncEventsResolver(); +- +- app.OnPublishAggregateAsync("/default/channel", (evt, ctx) => { throw new InvalidOperationException("Aggregate error"); }); +- +- // Act +- var result = await app.ResolveAsync(_appSyncEvent, lambdaContext); +- +- // Assert +- Assert.Contains("Aggregate error", result.Error); +- } +- +- [Fact] +- public void Should_Handle_TransformingPayload() +- { +- // Arrange +- var lambdaContext = new TestLambdaContext(); +- var app = new AppSyncEventsResolver(); +- +- app.OnPublish("/default/channel", (payload) => +- { +- // Transform each event payload +- var transformedPayload = new Dictionary(); +- foreach (var key in payload.Keys) +- { +- transformedPayload[$"transformed_{key}"] = $"transformed_{payload[key]}"; +- } +- +- return transformedPayload; +- }); +- +- // Act +- var result = app.Resolve(_appSyncEvent, lambdaContext); +- +- // Assert +- if (result.Events != null) +- { +- Assert.Equal(3, result.Events.Count); +- Assert.Equal("transformed_event_1", result.Events[0].Payload?.Keys.First()); +- Assert.Equal("transformed_data_1", result.Events[0].Payload?["transformed_event_1"].ToString()); +- } +- } +- +- [Fact] +- public async Task Should_Handle_TransformingPayload_Async() +- { +- // Arrange +- var lambdaContext = new TestLambdaContext(); +- var app = new AppSyncEventsResolver(); +- +- app.OnPublishAsync("/default/channel", (payload) => +- { +- // Transform each event payload +- var transformedPayload = new Dictionary(); +- foreach (var key in payload.Keys) +- { +- transformedPayload[$"transformed_{key}"] = $"transformed_{payload[key]}"; +- } +- +- return Task.FromResult(transformedPayload); +- }); +- +- // Act +- var result = await app.ResolveAsync(_appSyncEvent, lambdaContext); +- +- // Assert +- if (result.Events != null) +- { +- Assert.Equal(3, result.Events.Count); +- Assert.Equal("transformed_event_1", result.Events[0].Payload?.Keys.First()); +- Assert.Equal("transformed_data_1", result.Events[0].Payload?["transformed_event_1"].ToString()); +- } +- } +- +- [Fact] +- public async Task Should_Throw_For_Unknown_EventType_Async() +- { +- // Arrange +- var lambdaContext = new TestLambdaContext(); +- var app = new AppSyncEventsResolver(); +- +- var unknownEvent = new AppSyncEventsRequest +- { +- Info = new Information +- { +- Channel = new Channel { Path = "/default/channel", Segments = ["default", "channel"] }, +- Operation = (AppSyncEventsOperation)999, // Unknown operation +- ChannelNamespace = new ChannelNamespace { Name = "default" } +- } +- }; +- +- // Act & Assert +- await Assert.ThrowsAsync(() => +- app.ResolveAsync(unknownEvent, lambdaContext)); +- } +- +- [Fact] +- public void Should_Throw_For_Unknown_EventType() +- { +- // Arrange +- var lambdaContext = new TestLambdaContext(); +- var app = new AppSyncEventsResolver(); +- +- var unknownEvent = new AppSyncEventsRequest +- { +- Info = new Information +- { +- Channel = new Channel { Path = "/default/channel", Segments = ["default", "channel"] }, +- Operation = (AppSyncEventsOperation)999, // Unknown operation +- ChannelNamespace = new ChannelNamespace { Name = "default" } +- } +- }; +- +- // Act & Assert +- Assert.Throws(() => +- app.Resolve(unknownEvent, lambdaContext)); +- } +- +- [Fact] +- public void Should_Return_NonDictionary_Values_Wrapped_In_Data() +- { +- // Arrange +- var lambdaContext = new TestLambdaContext(); +- var app = new AppSyncEventsResolver(); +- +- app.OnPublish("/default/channel", (payload) => +- { +- // Return a non-dictionary value +- return "string value"; +- }); +- +- // Act +- var result = app.Resolve(_appSyncEvent, lambdaContext); +- +- // Assert +- if (result.Events != null) +- { +- Assert.Equal(3, result.Events.Count); +- Assert.Equal("string value", result.Events[0].Payload?["data"].ToString()); +- } +- } +- +- [Fact] +- public void Should_Skip_Invalid_Path_Registration() +- { +- // Arrange +- var lambdaContext = new TestLambdaContext(); +- var app = new AppSyncEventsResolver(); +- var handlerCalled = false; +- +- // Register with invalid path +- app.OnPublish("/invalid/*/path", (payload) => +- { +- handlerCalled = true; +- return payload; +- }); +- +- // Act +- var result = app.Resolve(_appSyncEvent, lambdaContext); +- +- // Assert - Should return original payload, handler not called +- if (result.Events != null) +- { +- Assert.Equal(3, result.Events.Count); +- Assert.Equal("data_1", result.Events[0].Payload?["event_1"].ToString()); +- } +- +- Assert.False(handlerCalled); +- } +- +- [Fact] +- public void Should_Replace_Handler_When_RegisteringTwice() +- { +- // Arrange +- var lambdaContext = new TestLambdaContext(); +- var app = new AppSyncEventsResolver(); +- +- app.OnPublish("/default/channel", +- (payload) => { return new Dictionary { ["handler"] = "first" }; }); +- +- app.OnPublish("/default/channel", +- (payload) => { return new Dictionary { ["handler"] = "second" }; }); +- +- // Act +- var result = app.Resolve(_appSyncEvent, lambdaContext); +- +- // Assert - Only second handler should be used +- if (result.Events != null) +- { +- Assert.Equal(3, result.Events.Count); +- Assert.Equal("second", result.Events[0].Payload?["handler"].ToString()); +- } +- } +- +- [Fact] +- public async Task Should_Replace_Handler_When_RegisteringTwice_Async() +- { +- // Arrange +- var lambdaContext = new TestLambdaContext(); +- var app = new AppSyncEventsResolver(); +- +- app.OnPublishAsync("/default/channel", (payload) => { return Task.FromResult(new Dictionary { ["handler"] = "first" }); }); +- +- app.OnPublishAsync("/default/channel", (payload) => { return Task.FromResult(new Dictionary { ["handler"] = "second" }); }); +- +- // Act +- var result = await app.ResolveAsync(_appSyncEvent, lambdaContext); +- +- // Assert - Only second handler should be used +- if (result.Events != null) +- { +- Assert.Equal(3, result.Events.Count); +- Assert.Equal("second", result.Events[0].Payload?["handler"].ToString()); +- } +- } +- +- [Fact] +- public void Should_Maintain_EventIds_When_Processing() +- { +- // Arrange +- var lambdaContext = new TestLambdaContext(); +- var app = new AppSyncEventsResolver(); +- +- app.OnPublish("/default/channel", +- (payload) => { return new Dictionary { ["processed"] = true }; }); +- +- // Act +- var result = app.Resolve(_appSyncEvent, lambdaContext); +- +- // Assert +- if (result.Events != null) +- { +- Assert.Equal(3, result.Events.Count); +- Assert.Equal("1", result.Events[0].Id); +- Assert.Equal("2", result.Events[1].Id); +- Assert.Equal("3", result.Events[2].Id); +- } +- } +- +- [Fact] +- public async Task Aggregate_Handler_Can_Return_Individual_Results_With_Ids() +- { +- // Arrange +- var lambdaContext = new TestLambdaContext(); +- var app = new AppSyncEventsResolver(); +- +- app.OnPublishAggregateAsync("/default/channel13", (payload) => { throw new Exception("My custom exception"); }); +- +- app.OnPublishAsync("/default/channel12", (payload) => { throw new Exception("My custom exception"); }); +- +- app.OnPublishAggregateAsync("/default/channel", (evt) => +- { +- // Iterate through events and return individual results with IDs +- var results = new List(); +- +- foreach (var eventItem in evt.Events) +- { +- try +- { +- if (eventItem.Payload.ContainsKey("event_2")) +- { +- // Create an error for the second event +- results.Add(new AppSyncEvent +- { +- Id = eventItem.Id, +- Error = "Intentional error for event 2" +- }); +- } +- else +- { +- // Process normally +- results.Add(new AppSyncEvent +- { +- Id = eventItem.Id, +- Payload = new Dictionary +- { +- ["processed"] = true, +- ["originalData"] = eventItem.Payload +- } +- }); +- } +- } +- catch (Exception ex) +- { +- results.Add(new AppSyncEvent +- { +- Id = eventItem.Id, +- Error = $"{ex.GetType().Name} - {ex.Message}" +- }); +- } +- } +- +- return Task.FromResult(new AppSyncEventsResponse { Events = results }); +- }); +- +- // Act +- var result = await app.ResolveAsync(_appSyncEvent, lambdaContext); +- +- // Assert +- if (result.Events != null) +- { +- Assert.Equal(3, result.Events.Count); +- Assert.Equal("1", result.Events[0].Id); +- Assert.True((bool)(result.Events[0].Payload?["processed"] ?? false)); +- Assert.Equal("2", result.Events[1].Id); +- Assert.NotNull(result.Events[1].Error); +- Assert.Contains("Intentional error for event 2", result.Events[1].Error); +- Assert.Equal("3", result.Events[2].Id); +- Assert.True((bool)(result.Events[2].Payload?["processed"] ?? false)); +- } +- } +- +- [Fact] +- public async Task Should_Verify_Ids_Are_Preserved_In_Error_Case() +- { +- // Arrange +- var lambdaContext = new TestLambdaContext(); +- var app = new AppSyncEventsResolver(); +- +- // Create handlers that throw exceptions for specific events +- app.OnPublishAsync("/default/channel", (payload) => +- { +- if (payload.ContainsKey("event_1")) +- throw new InvalidOperationException("Error for event 1"); +- if (payload.ContainsKey("event_3")) +- throw new ArgumentException("Error for event 3"); +- return Task.FromResult(payload); +- }); +- +- // Act +- var result = await app.ResolveAsync(_appSyncEvent, lambdaContext); +- +- // Assert +- Assert.Equal(3, result.Events.Count); +- Assert.Equal("1", result.Events[0].Id); +- Assert.Contains("Error for event 1", result.Events[0].Error); +- Assert.Equal("2", result.Events[1].Id); +- Assert.Null(result.Events[1].Error); +- Assert.Equal("3", result.Events[2].Id); +- Assert.Contains("Error for event 3", result.Events[2].Error); +- } +- +- [Fact] +- public async Task Should_Match_Most_Specific_Handler_Only() +- { +- // Arrange +- var lambdaContext = new TestLambdaContext(); +- var app = new AppSyncEventsResolver(); +- +- int firstHandlerCalls = 0; +- int secondHandlerCalls = 0; +- +- app.OnPublishAsync("/default/channel", (payload) => +- { +- firstHandlerCalls++; +- return Task.FromResult(new Dictionary { ["handler"] = "first" }); +- }); +- +- app.OnPublishAsync("/default/*", (payload) => +- { +- secondHandlerCalls++; +- return Task.FromResult(new Dictionary { ["handler"] = "second" }); +- }); +- +- // Act +- var result = await app.ResolveAsync(_appSyncEvent, lambdaContext); +- +- // Assert - Only the first (most specific) handler should be called +- Assert.Equal(3, result.Events.Count); +- Assert.Equal("first", result.Events[0].Payload["handler"].ToString()); +- Assert.Equal(3, firstHandlerCalls); +- Assert.Equal(0, secondHandlerCalls); +- } +- +- [Fact] +- public async Task Should_Handle_Multiple_Keys_In_Payload() +- { +- // Arrange +- var lambdaContext = new TestLambdaContext(); +- var app = new AppSyncEventsResolver(); +- +- // Create an event with multiple keys in the payload +- var multiKeyEvent = new AppSyncEventsRequest +- { +- Info = new Information +- { +- Channel = new Channel { Path = "/default/channel", Segments = ["default", "channel"] }, +- Operation = AppSyncEventsOperation.Publish, +- ChannelNamespace = new ChannelNamespace { Name = "default" } +- }, +- Events = +- [ +- new AppSyncEvent +- { +- Id = "1", +- Payload = new Dictionary +- { +- ["event_1"] = "data_1", +- ["event_1a"] = "data_1a" +- } +- } +- ] +- }; +- +- app.OnPublishAsync("/default/channel", (payload) => +- { +- // Check that both keys are present +- Assert.Equal("data_1", payload["event_1"]); +- Assert.Equal("data_1a", payload["event_1a"]); +- +- // Return a processed result with both keys +- return Task.FromResult(new Dictionary +- { +- ["processed_1"] = payload["event_1"], +- ["processed_1a"] = payload["event_1a"] +- }); +- }); +- +- // Act +- var result = await app.ResolveAsync(multiKeyEvent, lambdaContext); +- +- // Assert +- Assert.Single(result.Events); +- Assert.Equal("1", result.Events[0].Id); +- Assert.Equal("data_1", result.Events[0].Payload["processed_1"]); +- Assert.Equal("data_1a", result.Events[0].Payload["processed_1a"]); +- } +- +- [Fact] +- public async Task Should_Only_Use_First_Matching_Handler_By_Specificity() +- { +- // Arrange +- var lambdaContext = new TestLambdaContext(); +- var app = new AppSyncEventsResolver(); +- +- // Register handlers with different specificity +- app.OnPublishAsync("/*", (payload) => Task.FromResult(new Dictionary { ["handler"] = "least-specific" })); +- +- app.OnPublishAsync("/default/*", (payload) => Task.FromResult(new Dictionary { ["handler"] = "more-specific" })); +- +- app.OnPublishAsync("/default/channel", (payload) => Task.FromResult(new Dictionary { ["handler"] = "most-specific" })); +- +- // Act +- var result = await app.ResolveAsync(_appSyncEvent, lambdaContext); +- +- // Assert - Only the most specific handler should be called +- Assert.Equal(3, result.Events.Count); +- Assert.Equal("most-specific", result.Events[0].Payload["handler"].ToString()); +- Assert.Equal("most-specific", result.Events[1].Payload["handler"].ToString()); +- Assert.Equal("most-specific", result.Events[2].Payload["handler"].ToString()); +- } +- +- [Fact] +- public async Task Should_Fallback_To_Less_Specific_Handler_If_No_Exact_Match() +- { +- // Arrange +- var lambdaContext = new TestLambdaContext(); +- var app = new AppSyncEventsResolver(); +- +- // Create an event with a path that has no exact match +- var fallbackEvent = new AppSyncEventsRequest +- { +- Info = new Information +- { +- Channel = new Channel { Path = "/default/specific/path", Segments = ["default", "specific", "path"] }, +- Operation = AppSyncEventsOperation.Publish, +- ChannelNamespace = new ChannelNamespace { Name = "default" } +- }, +- Events = +- [ +- new AppSyncEvent +- { +- Id = "1", +- Payload = new Dictionary { ["key"] = "value" } +- } +- ] +- }; +- +- app.OnPublishAsync("/default/*", (payload) => Task.FromResult(new Dictionary { ["handler"] = "wildcard-handler" })); +- +- // Act +- var result = await app.ResolveAsync(fallbackEvent, lambdaContext); +- +- // Assert +- Assert.Single(result.Events); +- Assert.Equal("wildcard-handler", result.Events[0].Payload["handler"].ToString()); +- } +- +- [Fact] +- public async Task Should_Return_Null_When_Subscribing_To_Path_Without_Publish_Handler() +- { +- // Arrange +- var lambdaContext = new TestLambdaContext(); +- var app = new AppSyncEventsResolver(); +- +- // Only set up a subscribe handler without corresponding publish handler +- app.OnSubscribeAsync("/subscribe-only", (info) => Task.FromResult(true)); +- +- var subscribeEvent = new AppSyncEventsRequest +- { +- Info = new Information +- { +- Channel = new Channel { Path = "/subscribe-only", Segments = ["subscribe-only"] }, +- Operation = AppSyncEventsOperation.Subscribe, +- ChannelNamespace = new ChannelNamespace { Name = "default" } +- } +- }; +- +- // Act +- var result = await app.ResolveAsync(subscribeEvent, lambdaContext); +- +- // Assert +- Assert.Null(result); +- } +- +- [Theory] +- [InlineData("/default/channel", "/default/channel1")] +- [InlineData("/default/channel3", "/default/channel")] +- public void Should_Return_Null_When_Subscribing_To_Path_With_No_Match_Publish_Handler(string publishPath, +- string subscribePath) +- { +- // Arrange +- var lambdaContext = new TestLambdaContext(); +- var app = new AppSyncEventsResolver(); +- +- app.OnPublish(publishPath, (payload) => payload); +- app.OnSubscribe(subscribePath, (info) => true); +- +- var subscribeEvent = new AppSyncEventsRequest +- { +- Info = new Information +- { +- Channel = new Channel { Path = subscribePath, Segments = ["default", "channel"] }, +- Operation = AppSyncEventsOperation.Subscribe, +- ChannelNamespace = new ChannelNamespace { Name = "default" } +- } +- }; +- +- // Act +- var result = app.Resolve(subscribeEvent, lambdaContext); +- +- // Assert +- Assert.Null(result); +- } +- +- [Theory] +- [InlineData("/default/channel", "/default/channel")] +- [InlineData("/default/channel", "/default/*")] +- [InlineData("/default/test", "/default/*")] +- [InlineData("/default/*", "/default/*")] +- public async Task Should_Return_UnauthorizedException_When_Throwing_UnauthorizedException(string publishPath, +- string subscribePath) +- { +- // Arrange +- var lambdaContext = new TestLambdaContext(); +- var app = new AppSyncEventsResolver(); +- +- app.OnPublishAsync(publishPath, (payload) => Task.FromResult(payload)); +- app.OnSubscribeAsync(subscribePath, +- (info, lambdaContext) => { throw new UnauthorizedException("OOPS"); }); +- +- var subscribeEvent = new AppSyncEventsRequest +- { +- Info = new Information +- { +- Channel = new Channel { Path = subscribePath, Segments = ["default", "channel"] }, +- Operation = AppSyncEventsOperation.Subscribe, +- ChannelNamespace = new ChannelNamespace { Name = "default" } +- } +- }; +- +- // Act && Assert +- await Assert.ThrowsAsync(() => +- app.ResolveAsync(subscribeEvent, lambdaContext)); +- } +- +- [Theory] +- [InlineData(true)] +- [InlineData(false)] +- public async Task Should_Return_UnauthorizedException_When_Throwing_UnauthorizedException_Publish(bool aggreate) +- { +- // Arrange +- var lambdaContext = new TestLambdaContext(); +- var app = new AppSyncEventsResolver(); +- +- if (aggreate) +- { +- app.OnPublishAggregateAsync("/default/channel", (payload) => throw new UnauthorizedException("OOPS")); +- } +- else +- { +- app.OnPublishAsync("/default/channel", (payload) => throw new UnauthorizedException("OOPS")); +- } +- +- var subscribeEvent = new AppSyncEventsRequest +- { +- Info = new Information +- { +- Channel = new Channel { Path = "/default/channel", Segments = ["default", "channel"] }, +- Operation = AppSyncEventsOperation.Publish, +- ChannelNamespace = new ChannelNamespace { Name = "default" } +- }, +- Events = +- [ +- new AppSyncEvent +- { +- Id = "1", +- Payload = new Dictionary { ["key"] = "value" } +- } +- ] +- }; +- +- // Act && Assert +- await Assert.ThrowsAsync(() => +- app.ResolveAsync(subscribeEvent, lambdaContext)); +- } +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/EventHandler/RouteHandlerRegistryTests.cs b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/EventHandler/RouteHandlerRegistryTests.cs +deleted file mode 100644 +index ac712da6..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/EventHandler/RouteHandlerRegistryTests.cs ++++ /dev/null +@@ -1,232 +0,0 @@ +-using System.Diagnostics.CodeAnalysis; +-using AWS.Lambda.Powertools.EventHandler.Internal; +-#pragma warning disable CS8605 // Unboxing a possibly null value. +-#pragma warning disable CS8601 // Possible null reference assignment. +-#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. +-#pragma warning disable CS8602 // Dereference of a possibly null reference. +- +-namespace AWS.Lambda.Powertools.EventHandler; +- +-[SuppressMessage("Usage", "xUnit1031:Do not use blocking task operations in test method")] +-public class RouteHandlerRegistryTests +-{ +- [Theory] +- [InlineData("/default/channel", true)] +- [InlineData("/default/*", true)] +- [InlineData("/*", true)] +- [InlineData("/a/b/c", true)] +- [InlineData("/a/*/c", false)] // Wildcard in the middle is invalid +- [InlineData("*/default", false)] // Wildcard at the beginning is invalid +- [InlineData("default/*", false)] // Not starting with slash +- [InlineData("", false)] // Empty path +- [InlineData(null, false)] // Null path +- public void IsValidPath_ShouldValidateCorrectly(string? path, bool expected) +- { +- // Create a private method accessor to test private IsValidPath method +- var registry = new RouteHandlerRegistry(); +- var isValidPathMethod = typeof(RouteHandlerRegistry) +- .GetMethod("IsValidPath", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); +- +- // Act +- var result = (bool)isValidPathMethod.Invoke(null, new object[] { path }); +- +- // Assert +- Assert.Equal(expected, result); +- } +- +- [Fact] +- public void Register_ShouldNotAddInvalidPath() +- { +- // Arrange +- var registry = new RouteHandlerRegistry(); +- +- // Act +- registry.Register(new RouteHandlerOptions +- { +- Path = "/invalid/*/path", // Invalid path with wildcard in the middle +- Handler = (_, _) => Task.FromResult(null) +- }); +- +- // Assert - Try to resolve an invalid path +- var result = registry.ResolveFirst("/invalid/test/path"); +- Assert.Null(result); // Should not find any handler +- } +- +- [Fact] +- public void Register_ShouldReplaceExistingHandler() +- { +- // Arrange +- var registry = new RouteHandlerRegistry(); +- int firstHandlerCalled = 0; +- int secondHandlerCalled = 0; +- +- // Act +- registry.Register(new RouteHandlerOptions +- { +- Path = "/test/path", +- Handler = (_, _) => { +- firstHandlerCalled++; +- return Task.FromResult("first"); +- } +- }); +- +- registry.Register(new RouteHandlerOptions +- { +- Path = "/test/path", // Same path, should replace first handler +- Handler = (_, _) => { +- secondHandlerCalled++; +- return Task.FromResult("second"); +- } +- }); +- +- // Assert +- var handler = registry.ResolveFirst("/test/path"); +- Assert.NotNull(handler); +- var result = handler.Handler(null, null).Result; +- Assert.Equal("second", result); +- Assert.Equal(0, firstHandlerCalled); +- Assert.Equal(1, secondHandlerCalled); +- } +- +- [Fact] +- public async Task ResolveFirst_ShouldReturnMostSpecificHandler() +- { +- // Arrange +- var registry = new RouteHandlerRegistry(); +- +- registry.Register(new RouteHandlerOptions +- { +- Path = "/*", +- Handler = (_, _) => Task.FromResult("least-specific") +- }); +- +- registry.Register(new RouteHandlerOptions +- { +- Path = "/default/*", +- Handler = (_, _) => Task.FromResult("more-specific") +- }); +- +- registry.Register(new RouteHandlerOptions +- { +- Path = "/default/channel", +- Handler = (_, _) => Task.FromResult("most-specific") +- }); +- +- // Act - Test various paths +- var exactMatch = registry.ResolveFirst("/default/channel"); +- var wildcardMatch = registry.ResolveFirst("/default/something"); +- var rootMatch = registry.ResolveFirst("/something"); +- +- // Assert +- Assert.NotNull(exactMatch); +- Assert.Equal("most-specific", await exactMatch.Handler(null, null)); +- +- Assert.NotNull(wildcardMatch); +- Assert.Equal("more-specific", await wildcardMatch.Handler(null, null)); +- +- Assert.NotNull(rootMatch); +- Assert.Equal("least-specific", await rootMatch.Handler(null, null)); +- } +- +- [Fact] +- public void ResolveFirst_ShouldReturnNullWhenNoMatch() +- { +- // Arrange +- var registry = new RouteHandlerRegistry(); +- +- registry.Register(new RouteHandlerOptions +- { +- Path = "/default/*", +- Handler = (_, _) => Task.FromResult("test") +- }); +- +- // Act +- var result = registry.ResolveFirst("/other/path"); +- +- // Assert +- Assert.Null(result); +- } +- +- [Fact] +- public void ResolveFirst_ShouldUseCacheForRepeatedPaths() +- { +- // Arrange +- var registry = new RouteHandlerRegistry(); +- int handlerCallCount = 0; +- +- registry.Register(new RouteHandlerOptions +- { +- Path = "/test/*", +- Handler = (_, _) => { +- handlerCallCount++; +- return Task.FromResult("cached"); +- } +- }); +- +- // Act - Resolve the same path multiple times +- var first = registry.ResolveFirst("/test/path"); +- var firstResult = first.Handler(null, null).Result; +- +- // Should use cached result +- var second = registry.ResolveFirst("/test/path"); +- var secondResult = second.Handler(null, null).Result; +- +- // Assert +- Assert.Equal("cached", firstResult); +- Assert.Equal("cached", secondResult); +- Assert.Equal(2, handlerCallCount); // Handler should be called twice because handlers are executed +- // even though the path resolution is cached +- +- // The objects should be the same instance +- Assert.Same(first, second); +- } +- +- [Fact] +- public void LRUCache_ShouldEvictOldestItemsWhenFull() +- { +- // Arrange - Create a cache with size 2 +- var cache = new LruCache(2); +- +- // Act +- cache.Set("key1", "value1"); +- cache.Set("key2", "value2"); +- cache.Set("key3", "value3"); // Should evict key1 +- +- // Assert +- Assert.False(cache.TryGet("key1", out _)); // Should be evicted +- Assert.True(cache.TryGet("key2", out var value2)); +- Assert.Equal("value2", value2); +- Assert.True(cache.TryGet("key3", out var value3)); +- Assert.Equal("value3", value3); +- } +- +- [Fact] +- public void IsWildcardMatch_ShouldMatchPathsCorrectly() +- { +- // Arrange +- var registry = new RouteHandlerRegistry(); +- var isWildcardMatchMethod = typeof(RouteHandlerRegistry) +- .GetMethod("IsWildcardMatch", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); +- +- // Test cases +- var testCases = new[] +- { +- (pattern: "/default/*", path: "/default/channel", expected: true), +- (pattern: "/default/*", path: "/default/other", expected: true), +- (pattern: "/default/*", path: "/default/nested/path", expected: true), +- (pattern: "/default/channel", path: "/default/channel", expected: true), +- (pattern: "/default/channel", path: "/default/other", expected: false), +- (pattern: "/*", path: "/anything", expected: true), +- (pattern: "/*", path: "/default/nested/deep", expected: true) +- }; +- +- foreach (var (pattern, path, expected) in testCases) +- { +- // Act +- var result = (bool)isWildcardMatchMethod.Invoke(registry, new object[] { pattern, path }); +- +- // Assert +- Assert.Equal(expected, result); +- } +- } +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/appSyncEventsEvent.json b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/appSyncEventsEvent.json +deleted file mode 100644 +index 1334b5ac..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/appSyncEventsEvent.json ++++ /dev/null +@@ -1,76 +0,0 @@ +-{ +- "identity":"None", +- "result":"None", +- "request":{ +- "headers": { +- "x-forwarded-for": "1.1.1.1, 2.2.2.2", +- "cloudfront-viewer-country": "US", +- "cloudfront-is-tablet-viewer": "false", +- "via": "2.0 xxxxxxxxxxxxxxxx.cloudfront.net (CloudFront)", +- "cloudfront-forwarded-proto": "https", +- "origin": "https://us-west-1.console.aws.amazon.com", +- "content-length": "217", +- "accept-language": "en-US,en;q=0.9", +- "host": "xxxxxxxxxxxxxxxx.appsync-api.us-west-1.amazonaws.com", +- "x-forwarded-proto": "https", +- "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36", +- "accept": "*/*", +- "cloudfront-is-mobile-viewer": "false", +- "cloudfront-is-smarttv-viewer": "false", +- "accept-encoding": "gzip, deflate, br", +- "referer": "https://us-west-1.console.aws.amazon.com/appsync/home?region=us-west-1", +- "content-type": "application/json", +- "sec-fetch-mode": "cors", +- "x-amz-cf-id": "3aykhqlUwQeANU-HGY7E_guV5EkNeMMtwyOgiA==", +- "x-amzn-trace-id": "Root=1-5f512f51-fac632066c5e848ae714", +- "authorization": "eyJraWQiOiJScWFCSlJqYVJlM0hrSnBTUFpIcVRXazNOW...", +- "sec-fetch-dest": "empty", +- "x-amz-user-agent": "AWS-Console-AppSync/", +- "cloudfront-is-desktop-viewer": "true", +- "sec-fetch-site": "cross-site", +- "x-forwarded-port": "443" +- }, +- "domainName":"None" +- }, +- "info":{ +- "channel":{ +- "path":"/default/channel", +- "segments":[ +- "default", +- "channel" +- ] +- }, +- "channelNamespace":{ +- "name":"default" +- }, +- "operation":"PUBLISH" +- }, +- "error":"None", +- "prev":"None", +- "stash":{ +- +- }, +- "outErrors":[ +- +- ], +- "events":[ +- { +- "payload":{ +- "event_1":"data_1" +- }, +- "id":"1" +- }, +- { +- "payload":{ +- "event_2":"data_2" +- }, +- "id":"2" +- }, +- { +- "payload":{ +- "event_3":"data_3" +- }, +- "id":"3" +- } +- ] +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/IdempotencyTest.cs b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/IdempotencyTest.cs +index 13dd5a7a..8e85d616 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/IdempotencyTest.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/IdempotencyTest.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.Collections.Generic; + using System.IO; +diff --git a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotencySerializerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotencySerializerTests.cs +index 065c985c..819b0130 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotencySerializerTests.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotencySerializerTests.cs +@@ -14,9 +14,7 @@ public class IdempotencySerializerTests + { + public IdempotencySerializerTests() + { +-#if NET8_0_OR_GREATER + IdempotencySerializer.AddTypeInfoResolver(TestJsonSerializerContext.Default); +-#endif + } + + [Fact] +@@ -59,8 +57,6 @@ public class IdempotencySerializerTests + Assert.True(options.PropertyNameCaseInsensitive); + } + +-#if NET8_0_OR_GREATER +- + [Fact] + public void GetTypeInfo_UnknownType_ThrowsException() + { +@@ -155,5 +151,4 @@ public class IdempotencySerializerTests + Assert.Same(newOptions, options); + } + +-#endif + } +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotentAspectTests.cs b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotentAspectTests.cs +index 324ccd5c..f91ee7b8 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotentAspectTests.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotentAspectTests.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.Linq; + using System.Text.Json; +@@ -26,9 +41,7 @@ public class IdempotentAspectTests : IDisposable + var store = Substitute.For(); + Idempotency.Configure(builder => + builder +-#if NET8_0_OR_GREATER + .WithJsonSerializationContext(TestJsonSerializerContext.Default) +-#endif + .WithPersistenceStore(store) + .WithOptions(optionsBuilder => optionsBuilder.WithEventKeyJmesPath("Id")) + ); +@@ -74,9 +87,7 @@ public class IdempotentAspectTests : IDisposable + // GIVEN + Idempotency.Configure(builder => + builder +-#if NET8_0_OR_GREATER + .WithJsonSerializationContext(TestJsonSerializerContext.Default) +-#endif + .WithPersistenceStore(store) + .WithOptions(optionsBuilder => optionsBuilder.WithEventKeyJmesPath("Id")) + ); +@@ -113,9 +124,7 @@ public class IdempotentAspectTests : IDisposable + Idempotency.Configure(builder => + builder + .WithPersistenceStore(store) +-#if NET8_0_OR_GREATER + .WithJsonSerializationContext(TestJsonSerializerContext.Default) +-#endif + .WithOptions(optionsBuilder => optionsBuilder.WithEventKeyJmesPath("Id")) + ); + +@@ -154,9 +163,7 @@ public class IdempotentAspectTests : IDisposable + Idempotency.Configure(builder => + builder + .WithPersistenceStore(store) +-#if NET8_0_OR_GREATER + .WithJsonSerializationContext(TestJsonSerializerContext.Default) +-#endif + .WithOptions(optionsBuilder => optionsBuilder.WithEventKeyJmesPath("Id")) + ); + +@@ -197,9 +204,7 @@ public class IdempotentAspectTests : IDisposable + Idempotency.Configure(builder => + builder + .WithPersistenceStore(store) +-#if NET8_0_OR_GREATER + .WithJsonSerializationContext(TestJsonSerializerContext.Default) +-#endif + .WithOptions(optionsBuilder => optionsBuilder.WithEventKeyJmesPath("Id")) + ); + +@@ -227,9 +232,7 @@ public class IdempotentAspectTests : IDisposable + Idempotency.Configure(builder => + builder + .WithPersistenceStore(store) +-#if NET8_0_OR_GREATER + .WithJsonSerializationContext(TestJsonSerializerContext.Default) +-#endif + .WithOptions(optionsBuilder => optionsBuilder.WithEventKeyJmesPath("Id")) + ); + +@@ -249,16 +252,25 @@ public class IdempotentAspectTests : IDisposable + public void Idempotency_Set_Execution_Environment_Context() + { + // Arrange ++ var assemblyName = "AWS.Lambda.Powertools.Idempotency"; ++ var assemblyVersion = "1.0.0"; ++ ++ var env = Substitute.For(); ++ env.GetAssemblyName(Arg.Any()).Returns(assemblyName); ++ env.GetAssemblyVersion(Arg.Any()).Returns(assemblyVersion); + +- var env = new PowertoolsEnvironment(); +- var conf = new PowertoolsConfigurations(env); ++ var conf = new PowertoolsConfigurations(new SystemWrapper(env)); + + // Act + var xRayRecorder = new Idempotency(conf); + + // Assert +- Assert.Contains($"{Constants.FeatureContextIdentifier}/Idempotency/", +- env.GetEnvironmentVariable("AWS_EXECUTION_ENV")); ++ env.Received(1).SetEnvironmentVariable( ++ "AWS_EXECUTION_ENV", ++ $"{Constants.FeatureContextIdentifier}/Idempotency/{assemblyVersion}" ++ ); ++ ++ env.Received(1).GetEnvironmentVariable("AWS_EXECUTION_ENV"); + + Assert.NotNull(xRayRecorder); + } +@@ -336,9 +348,7 @@ public class IdempotentAspectTests : IDisposable + Idempotency.Configure(builder => + builder + .WithPersistenceStore(store) +-#if NET8_0_OR_GREATER + .WithJsonSerializationContext(TestJsonSerializerContext.Default) +-#endif + .WithOptions(optionsBuilder => optionsBuilder.WithEventKeyJmesPath("Id")) + ); + +diff --git a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Persistence/BasePersistenceStoreTests.cs b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Persistence/BasePersistenceStoreTests.cs +index 104beb68..ad93313b 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Persistence/BasePersistenceStoreTests.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Persistence/BasePersistenceStoreTests.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.IO; + using System.Text.Json; +@@ -524,7 +539,7 @@ public class BasePersistenceStoreTests + // Assert + generatedHash.Should().Be(expectedHash); + } +- ++ + [Fact] + public async Task When_Key_Prefix_Set_Should_Create_With_Prefix() + { +@@ -551,9 +566,7 @@ public class BasePersistenceStoreTests + var eventJson = File.ReadAllText("./resources/apigw_event.json"); + try + { +-#if NET8_0_OR_GREATER + IdempotencySerializer.AddTypeInfoResolver(TestJsonSerializerContext.Default); +-#endif + var request = IdempotencySerializer.Deserialize(eventJson); + return request!; + } +@@ -563,35 +576,4 @@ public class BasePersistenceStoreTests + throw; + } + } +- +- [Fact] +- public async Task ProcessExistingRecord_WhenValidRecord_ShouldReturnRecordAndSaveToCache() +- { +- // Arrange +- var persistenceStore = new InMemoryPersistenceStore(); +- var request = LoadApiGatewayProxyRequest(); +- LRUCache cache = new(2); +- +- persistenceStore.Configure(new IdempotencyOptionsBuilder() +- .WithUseLocalCache(true) +- .Build(), null, null, cache); +- +- var now = DateTimeOffset.UtcNow; +- var existingRecord = new DataRecord( +- "testFunction#5eff007a9ed2789a9f9f6bc182fc6ae6", +- DataRecord.DataRecordStatus.COMPLETED, +- now.AddSeconds(3600).ToUnixTimeSeconds(), +- "existing response", +- null); +- +- // Act +- var result = +- persistenceStore.ProcessExistingRecord(existingRecord, JsonSerializer.SerializeToDocument(request)!); +- +- // Assert +- result.Should().Be(existingRecord); +- cache.Count.Should().Be(1); +- cache.TryGet("testFunction#5eff007a9ed2789a9f9f6bc182fc6ae6", out var cachedRecord).Should().BeTrue(); +- cachedRecord.Should().Be(existingRecord); +- } + } +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Persistence/DynamoDBPersistenceStoreTests.cs b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Persistence/DynamoDBPersistenceStoreTests.cs +index 09b4c781..6dc2fb84 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Persistence/DynamoDBPersistenceStoreTests.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Persistence/DynamoDBPersistenceStoreTests.cs +@@ -1,12 +1,12 @@ + /* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +- * ++ * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at +- * ++ * + * http://aws.amazon.com/apache2.0 +- * ++ * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing +@@ -33,7 +33,7 @@ public class DynamoDbPersistenceStoreTests : IClassFixture + private readonly DynamoDBPersistenceStore _dynamoDbPersistenceStore; + private readonly AmazonDynamoDBClient _client; + private readonly string _tableName; +- ++ + public DynamoDbPersistenceStoreTests(DynamoDbFixture fixture) + { + _client = fixture.Client; +@@ -42,23 +42,21 @@ public class DynamoDbPersistenceStoreTests : IClassFixture + .WithTableName(_tableName) + .WithDynamoDBClient(_client) + .Build(); +- _dynamoDbPersistenceStore.Configure(new IdempotencyOptionsBuilder().Build(), functionName: null, +- keyPrefix: null); ++ _dynamoDbPersistenceStore.Configure(new IdempotencyOptionsBuilder().Build(),functionName: null, keyPrefix: null); + } +- ++ + //putRecord + [Fact] + public async Task PutRecord_WhenRecordDoesNotExist_ShouldCreateRecordInDynamoDB() + { + // Arrange + var now = DateTimeOffset.UtcNow; +- var uniqueKey = $"key_{Guid.NewGuid()}"; + var expiry = now.AddSeconds(3600).ToUnixTimeSeconds(); +- var key = CreateKey(uniqueKey); +- ++ var key = CreateKey("key"); ++ + // Act + await _dynamoDbPersistenceStore +- .PutRecord(new DataRecord(uniqueKey, DataRecord.DataRecordStatus.COMPLETED, expiry, null, null), now); ++ .PutRecord(new DataRecord("key", DataRecord.DataRecordStatus.COMPLETED, expiry, null, null), now); + + // Assert + var getItemResponse = +@@ -75,7 +73,7 @@ public class DynamoDbPersistenceStoreTests : IClassFixture + } + + [Fact] +- public async Task PutRecord_WhenRecordAlreadyExist_ShouldThrowIdempotencyItemAlreadyExistsException() ++ public async Task PutRecord_WhenRecordAlreadyExist_ShouldThrowIdempotencyItemAlreadyExistsException() + { + // Arrange + var key = CreateKey("key"); +@@ -84,7 +82,7 @@ public class DynamoDbPersistenceStoreTests : IClassFixture + Dictionary item = new(key); + var now = DateTimeOffset.UtcNow; + var expiry = now.AddSeconds(30).ToUnixTimeSeconds(); +- item.Add("expiration", new AttributeValue { N = expiry.ToString() }); ++ item.Add("expiration", new AttributeValue {N = expiry.ToString()}); + item.Add("status", new AttributeValue(DataRecord.DataRecordStatus.COMPLETED.ToString())); + item.Add("data", new AttributeValue("Fake Data")); + await _client.PutItemAsync(new PutItemRequest +@@ -102,24 +100,24 @@ public class DynamoDbPersistenceStoreTests : IClassFixture + null, + null + ), now); +- ++ + // Assert + await act.Should().ThrowAsync(); +- ++ + // item was not updated, retrieve the initial one + var itemInDb = (await _client.GetItemAsync(new GetItemRequest +- { +- TableName = _tableName, +- Key = key +- })).Item; ++ { ++ TableName = _tableName, ++ Key = key ++ })).Item; + itemInDb.Should().NotBeNull(); + itemInDb["status"].S.Should().Be("COMPLETED"); + itemInDb["expiration"].N.Should().Be(expiry.ToString()); + itemInDb["data"].S.Should().Be("Fake Data"); + } +- ++ + [Fact] +- public async Task PutRecord_ShouldBlockUpdate_IfRecordAlreadyExistAndProgressNotExpiredAfterLambdaTimedOut() ++ public async Task PutRecord_ShouldBlockUpdate_IfRecordAlreadyExistAndProgressNotExpiredAfterLambdaTimedOut() + { + // Arrange + var key = CreateKey("key"); +@@ -129,18 +127,18 @@ public class DynamoDbPersistenceStoreTests : IClassFixture + var now = DateTimeOffset.UtcNow; + var expiry = now.AddSeconds(30).ToUnixTimeSeconds(); + var progressExpiry = now.AddSeconds(30).ToUnixTimeMilliseconds(); +- +- item.Add("expiration", new AttributeValue { N = expiry.ToString() }); ++ ++ item.Add("expiration", new AttributeValue {N = expiry.ToString()}); + item.Add("status", new AttributeValue(DataRecord.DataRecordStatus.INPROGRESS.ToString())); + item.Add("data", new AttributeValue("Fake Data")); +- item.Add("in_progress_expiration", new AttributeValue { N = progressExpiry.ToString() }); +- ++ item.Add("in_progress_expiration", new AttributeValue {N = progressExpiry.ToString()}); ++ + await _client.PutItemAsync(new PutItemRequest + { + TableName = _tableName, + Item = item + }); +- ++ + var expiry2 = now.AddSeconds(3600).ToUnixTimeSeconds(); + // Act + var act = () => _dynamoDbPersistenceStore.PutRecord( +@@ -150,10 +148,10 @@ public class DynamoDbPersistenceStoreTests : IClassFixture + "Fake Data 2", + null + ), now); +- ++ + // Assert + await act.Should().ThrowAsync(); +- ++ + // item was not updated, retrieve the initial one + var itemInDb = (await _client.GetItemAsync(new GetItemRequest + { +@@ -165,9 +163,9 @@ public class DynamoDbPersistenceStoreTests : IClassFixture + itemInDb["expiration"].N.Should().Be(expiry.ToString()); + itemInDb["data"].S.Should().Be("Fake Data"); + } +- ++ + [Fact] +- public async Task PutRecord_ShouldCreateRecordInDynamoDB_IfLambdaWasInProgressAndTimedOut() ++ public async Task PutRecord_ShouldCreateRecordInDynamoDB_IfLambdaWasInProgressAndTimedOut() + { + // Arrange + var key = CreateKey("key"); +@@ -177,20 +175,20 @@ public class DynamoDbPersistenceStoreTests : IClassFixture + var now = DateTimeOffset.UtcNow; + var expiry = now.AddSeconds(30).ToUnixTimeSeconds(); + var progressExpiry = now.AddSeconds(-30).ToUnixTimeMilliseconds(); +- +- item.Add("expiration", new AttributeValue { N = expiry.ToString() }); ++ ++ item.Add("expiration", new AttributeValue {N = expiry.ToString()}); + item.Add("status", new AttributeValue(DataRecord.DataRecordStatus.INPROGRESS.ToString())); + item.Add("data", new AttributeValue("Fake Data")); +- item.Add("in_progress_expiration", new AttributeValue { N = progressExpiry.ToString() }); +- ++ item.Add("in_progress_expiration", new AttributeValue {N = progressExpiry.ToString()}); ++ + await _client.PutItemAsync(new PutItemRequest + { + TableName = _tableName, + Item = item + }); +- ++ + var expiry2 = now.AddSeconds(3600).ToUnixTimeSeconds(); +- ++ + // Act + await _dynamoDbPersistenceStore.PutRecord( + new DataRecord("key", +@@ -199,7 +197,7 @@ public class DynamoDbPersistenceStoreTests : IClassFixture + null, + null + ), now); +- ++ + // Assert + // an item is inserted + var itemInDb = (await _client.GetItemAsync(new GetItemRequest +@@ -207,23 +205,23 @@ public class DynamoDbPersistenceStoreTests : IClassFixture + TableName = _tableName, + Key = key + })).Item; +- ++ + itemInDb.Should().NotBeNull(); + itemInDb["status"].S.Should().Be("INPROGRESS"); + itemInDb["expiration"].N.Should().Be(expiry2.ToString()); + } +- ++ + //getRecord + [Fact] + public async Task GetRecord_WhenRecordExistsInDynamoDb_ShouldReturnExistingRecord() + { + // Arrange + //await InitializeAsync(); +- ++ + // Insert a fake item with same id + Dictionary item = new() + { +- { "id", new AttributeValue("key") } //key ++ {"id", new AttributeValue("key")} //key + }; + var now = DateTimeOffset.UtcNow; + var expiry = now.AddSeconds(30).ToUnixTimeSeconds(); +@@ -254,10 +252,10 @@ public class DynamoDbPersistenceStoreTests : IClassFixture + { + //Arrange + await _dynamoDbPersistenceStore.DeleteRecord("key"); +- ++ + // Act + Func act = () => _dynamoDbPersistenceStore.GetRecord("key"); +- ++ + // Assert + await act.Should().ThrowAsync(); + } +@@ -282,8 +280,7 @@ public class DynamoDbPersistenceStoreTests : IClassFixture + Item = item + }); + // enable payload validation +- _dynamoDbPersistenceStore.Configure( +- new IdempotencyOptionsBuilder().WithPayloadValidationJmesPath("path").Build(), ++ _dynamoDbPersistenceStore.Configure(new IdempotencyOptionsBuilder().WithPayloadValidationJmesPath("path").Build(), + null, null); + + // Act +@@ -306,14 +303,14 @@ public class DynamoDbPersistenceStoreTests : IClassFixture + + //deleteRecord + [Fact] +- public async Task DeleteRecord_WhenRecordExistsInDynamoDb_ShouldDeleteRecord() ++ public async Task DeleteRecord_WhenRecordExistsInDynamoDb_ShouldDeleteRecord() + { + // Arrange: Insert a fake item with same id + var key = CreateKey("key"); + Dictionary item = new(key); + var now = DateTimeOffset.UtcNow; + var expiry = now.AddSeconds(360).ToUnixTimeSeconds(); +- item.Add("expiration", new AttributeValue { N = expiry.ToString() }); ++ item.Add("expiration", new AttributeValue {N=expiry.ToString()}); + item.Add("status", new AttributeValue(DataRecord.DataRecordStatus.INPROGRESS.ToString())); + await _client.PutItemAsync(new PutItemRequest + { +@@ -370,7 +367,7 @@ public class DynamoDbPersistenceStoreTests : IClassFixture + .WithStatusAttr("state") + .WithValidationAttr("valid") + .Build(); +- persistenceStore.Configure(new IdempotencyOptionsBuilder().Build(), functionName: null, keyPrefix: null); ++ persistenceStore.Configure(new IdempotencyOptionsBuilder().Build(),functionName: null, keyPrefix: null); + + var now = DateTimeOffset.UtcNow; + var record = new DataRecord( +@@ -422,6 +419,7 @@ public class DynamoDbPersistenceStoreTests : IClassFixture + { + TableName = tableNameCustom + })).Count.Should().Be(0); ++ + } + finally + { +@@ -440,18 +438,18 @@ public class DynamoDbPersistenceStoreTests : IClassFixture + } + + [Fact] +- public async Task GetRecord_WhenIdempotencyDisabled_ShouldNotCreateClients() ++ public async Task GetRecord_WhenIdempotencyDisabled_ShouldNotCreateClients() + { + try + { + // Arrange + Environment.SetEnvironmentVariable(Constants.IdempotencyDisabledEnv, "true"); +- ++ + var store = new DynamoDBPersistenceStoreBuilder().WithTableName(_tableName).Build(); +- ++ + // Act + Func act = () => store.GetRecord("fake"); +- ++ + // Assert + await act.Should().ThrowAsync(); + } +@@ -460,136 +458,12 @@ public class DynamoDbPersistenceStoreTests : IClassFixture + Environment.SetEnvironmentVariable(Constants.IdempotencyDisabledEnv, "false"); + } + } +- + private static Dictionary CreateKey(string keyValue) + { + var key = new Dictionary + { +- { "id", new AttributeValue(keyValue) } ++ {"id", new AttributeValue(keyValue)} + }; + return key; + } +- +- [Fact] +- public async Task PutRecord_WhenRecordAlreadyExists_ShouldReturnExistingRecordInException() +- { +- // Arrange +- var key = CreateKey("key"); +- var now = DateTimeOffset.UtcNow; +- var expiry = now.AddSeconds(30).ToUnixTimeSeconds(); +- +- // Insert a fake item with same id +- Dictionary item = new(key); +- item.Add("expiration", new AttributeValue { N = expiry.ToString() }); +- item.Add("status", new AttributeValue(DataRecord.DataRecordStatus.COMPLETED.ToString())); +- item.Add("data", new AttributeValue("Existing Data")); +- item.Add("validation", new AttributeValue("existing-hash")); +- +- await _client.PutItemAsync(new PutItemRequest +- { +- TableName = _tableName, +- Item = item +- }); +- +- var newRecord = new DataRecord("key", +- DataRecord.DataRecordStatus.INPROGRESS, +- now.AddSeconds(3600).ToUnixTimeSeconds(), +- null, +- null); +- +- // Act +- var exception = +- await Assert.ThrowsAsync(() => +- _dynamoDbPersistenceStore.PutRecord(newRecord, now)); +- +- // Assert +- exception.Record.Should().NotBeNull(); +- exception.Record.IdempotencyKey.Should().Be("key"); +- exception.Record.Status.Should().Be(DataRecord.DataRecordStatus.COMPLETED); +- exception.Record.ResponseData.Should().Be("Existing Data"); +- exception.Record.PayloadHash.Should().Be("existing-hash"); +- exception.Record.ExpiryTimestamp.Should().Be(expiry); +- } +- +- [Fact] +- public async Task PutRecord_WhenRecordWithInProgressExpiryExists_ShouldReturnExistingRecordInException() +- { +- // Arrange +- var key = CreateKey("key"); +- var now = DateTimeOffset.UtcNow; +- var expiry = now.AddSeconds(30).ToUnixTimeSeconds(); +- var inProgressExpiry = now.AddSeconds(30).ToUnixTimeMilliseconds(); +- +- // Insert a fake item with same id including in_progress_expiration +- Dictionary item = new(key); +- item.Add("expiration", new AttributeValue { N = expiry.ToString() }); +- item.Add("status", new AttributeValue(DataRecord.DataRecordStatus.INPROGRESS.ToString())); +- item.Add("data", new AttributeValue("In Progress Data")); +- item.Add("in_progress_expiration", new AttributeValue { N = inProgressExpiry.ToString() }); +- +- await _client.PutItemAsync(new PutItemRequest +- { +- TableName = _tableName, +- Item = item +- }); +- +- var newRecord = new DataRecord("key", +- DataRecord.DataRecordStatus.INPROGRESS, +- now.AddSeconds(3600).ToUnixTimeSeconds(), +- null, +- null); +- +- // Act +- var exception = +- await Assert.ThrowsAsync(() => +- _dynamoDbPersistenceStore.PutRecord(newRecord, now)); +- +- // Assert +- exception.Record.Should().NotBeNull(); +- exception.Record.IdempotencyKey.Should().Be("key"); +- exception.Record.Status.Should().Be(DataRecord.DataRecordStatus.INPROGRESS); +- exception.Record.ResponseData.Should().Be("In Progress Data"); +- exception.Record.InProgressExpiryTimestamp.Should().Be(inProgressExpiry); +- exception.Record.ExpiryTimestamp.Should().Be(expiry); +- } +- +- [Fact] +- public async Task PutRecord_WhenRecordExistsWithMissingOptionalFields_ShouldHandleNullValues() +- { +- // Arrange +- var key = CreateKey("key"); +- var now = DateTimeOffset.UtcNow; +- var expiry = now.AddSeconds(30).ToUnixTimeSeconds(); +- +- // Insert a minimal record without optional fields (data, validation, in_progress_expiration) +- Dictionary item = new(key); +- item.Add("expiration", new AttributeValue { N = expiry.ToString() }); +- item.Add("status", new AttributeValue(DataRecord.DataRecordStatus.INPROGRESS.ToString())); +- +- await _client.PutItemAsync(new PutItemRequest +- { +- TableName = _tableName, +- Item = item +- }); +- +- var newRecord = new DataRecord("key", +- DataRecord.DataRecordStatus.INPROGRESS, +- now.AddSeconds(3600).ToUnixTimeSeconds(), +- null, +- null); +- +- // Act +- var exception = +- await Assert.ThrowsAsync(() => +- _dynamoDbPersistenceStore.PutRecord(newRecord, now)); +- +- // Assert +- exception.Record.Should().NotBeNull(); +- exception.Record.IdempotencyKey.Should().Be("key"); +- exception.Record.Status.Should().Be(DataRecord.DataRecordStatus.INPROGRESS); +- exception.Record.ResponseData.Should().BeNull(); +- exception.Record.PayloadHash.Should().BeNull(); +- exception.Record.InProgressExpiryTimestamp.Should().BeNull(); +- exception.Record.ExpiryTimestamp.Should().Be(expiry); +- } + } +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/ResponseHookTest.cs b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/ResponseHookTest.cs +deleted file mode 100644 +index 9136c4a8..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/ResponseHookTest.cs ++++ /dev/null +@@ -1,168 +0,0 @@ +-using System; +-using System.Collections.Generic; +-using System.IO; +-using System.Threading.Tasks; +-using Amazon.DynamoDBv2; +-using Amazon.Lambda.APIGatewayEvents; +-using AWS.Lambda.Powertools.Idempotency.Internal.Serializers; +-using AWS.Lambda.Powertools.Idempotency.Persistence; +-using AWS.Lambda.Powertools.Idempotency.Tests.Persistence; +-using FluentAssertions; +-using Xunit; +- +-namespace AWS.Lambda.Powertools.Idempotency.Tests; +- +-public class ResponseHookTest : IClassFixture +-{ +- private readonly AmazonDynamoDBClient _client; +- private readonly string _tableName; +- +- public ResponseHookTest(DynamoDbFixture fixture) +- { +- _client = fixture.Client; +- _tableName = fixture.TableName; +- } +- +- [Fact] +- [Trait("Category", "Integration")] +- public async Task ResponseHook_ShouldNotExecuteOnFirstCall() +- { +- // Arrange +- var hookExecuted = false; +- +- Idempotency.Configure(builder => builder +- .WithOptions(options => options +- .WithEventKeyJmesPath("powertools_json(body).address") +- .WithResponseHook((responseData, dataRecord) => { +- hookExecuted = true; +- if (responseData is APIGatewayProxyResponse proxyResponse) +- { +- var headers = new Dictionary(proxyResponse.Headers ?? new Dictionary()); +- headers["x-idempotency-response"] = "true"; +- headers["x-idempotency-expiration"] = dataRecord.ExpiryTimestamp.ToString(); +- proxyResponse.Headers = headers; +- return proxyResponse; +- } +- return responseData; +- })) +- .WithPersistenceStore(new DynamoDBPersistenceStoreBuilder() +- .WithTableName(_tableName) +- .WithDynamoDBClient(_client) +- .Build())); +- +- var function = new ResponseHookTestFunction(); +- var request = IdempotencySerializer.Deserialize( +- await File.ReadAllTextAsync("./resources/apigw_event2.json")); +- +- // Act - First call +- var response = await function.Handle(request); +- +- // Assert - Hook should not execute on first call +- hookExecuted.Should().BeFalse(); +- response.Headers.Should().NotContainKey("x-idempotency-response"); +- function.HandlerExecuted.Should().BeTrue(); +- } +- +- [Fact] +- [Trait("Category", "Integration")] +- public async Task ResponseHook_ShouldExecuteOnIdempotentCall() +- { +- // Arrange +- var hookExecuted = false; +- +- Idempotency.Configure(builder => builder +- .WithOptions(options => options +- .WithEventKeyJmesPath("powertools_json(body).address") +- .WithResponseHook((responseData, dataRecord) => { +- hookExecuted = true; +- if (responseData is APIGatewayProxyResponse proxyResponse) +- { +- var headers = new Dictionary(proxyResponse.Headers ?? new Dictionary()); +- headers["x-idempotency-response"] = "true"; +- headers["x-idempotency-expiration"] = dataRecord.ExpiryTimestamp.ToString(); +- proxyResponse.Headers = headers; +- return proxyResponse; +- } +- return responseData; +- })) +- .WithPersistenceStore(new DynamoDBPersistenceStoreBuilder() +- .WithTableName(_tableName) +- .WithDynamoDBClient(_client) +- .Build())); +- +- var function = new ResponseHookTestFunction(); +- var request = IdempotencySerializer.Deserialize( +- await File.ReadAllTextAsync("./resources/apigw_event2.json")); +- +- // Act - First call to populate cache +- await function.Handle(request); +- function.HandlerExecuted = false; +- hookExecuted = false; +- +- // Act - Second call (idempotent) +- var response = await function.Handle(request); +- +- // Assert - Hook should execute on idempotent call +- hookExecuted.Should().BeTrue(); +- response.Headers.Should().ContainKey("x-idempotency-response"); +- response.Headers["x-idempotency-response"].Should().Be("true"); +- response.Headers.Should().ContainKey("x-idempotency-expiration"); +- function.HandlerExecuted.Should().BeFalse(); +- } +- +- [Fact] +- [Trait("Category", "Integration")] +- public async Task ResponseHook_ShouldHandleExceptionsGracefully() +- { +- // Arrange +- Idempotency.Configure(builder => builder +- .WithOptions(options => options +- .WithEventKeyJmesPath("powertools_json(body).address") +- .WithResponseHook((responseData, dataRecord) => { +- throw new InvalidOperationException("Hook failed"); +- })) +- .WithPersistenceStore(new DynamoDBPersistenceStoreBuilder() +- .WithTableName(_tableName) +- .WithDynamoDBClient(_client) +- .Build())); +- +- var function = new ResponseHookTestFunction(); +- var request = IdempotencySerializer.Deserialize( +- await File.ReadAllTextAsync("./resources/apigw_event2.json")); +- +- // Act - First call to populate cache +- var firstResponse = await function.Handle(request); +- function.HandlerExecuted = false; +- +- // Act - Second call (idempotent) - should not throw despite hook exception +- var response = await function.Handle(request); +- +- // Assert - Should return original response despite hook exception +- response.Should().NotBeNull(); +- response.Body.Should().Be(firstResponse.Body); +- function.HandlerExecuted.Should().BeFalse(); +- } +-} +- +-public class ResponseHookTestFunction +-{ +- public bool HandlerExecuted { get; set; } +- +- [Idempotent] +- public async Task Handle(APIGatewayProxyRequest request) +- { +- HandlerExecuted = true; +- +- await Task.Delay(100); // Simulate some work +- +- return new APIGatewayProxyResponse +- { +- StatusCode = 200, +- Body = "Hello World", +- Headers = new Dictionary +- { +- ["Content-Type"] = "application/json" +- } +- }; +- } +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/TestSetup.cs b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/TestSetup.cs +index 41a898e3..26f0e213 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/TestSetup.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/TestSetup.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using Xunit; + + [assembly: CollectionBehavior(DisableTestParallelization = true)] +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/GlobalUsings.cs b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/GlobalUsings.cs +index 8c927eb7..2cdb71da 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/GlobalUsings.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/GlobalUsings.cs +@@ -1 +1,16 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + global using Xunit; +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/JmesPathExamples.cs b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/JmesPathExamples.cs +index 59542d06..a1386ea6 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/JmesPathExamples.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/JmesPathExamples.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System.Text.Json; + using Xunit.Abstractions; + +diff --git a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/JmesPathTests.cs b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/JmesPathTests.cs +index 8e2131e7..7a82c697 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/JmesPathTests.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/JmesPathTests.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System.Text.Json; + using AWS.Lambda.Powertools.JMESPath.Utilities; + using Xunit.Abstractions; +diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/AWS.Lambda.Powertools.Kafka.Tests.csproj b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/AWS.Lambda.Powertools.Kafka.Tests.csproj +deleted file mode 100644 +index e0f501b4..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/AWS.Lambda.Powertools.Kafka.Tests.csproj ++++ /dev/null +@@ -1,97 +0,0 @@ +- +- +- +- +- +- AWS.Lambda.Powertools.Kafka.Tests +- AWS.Lambda.Powertools.Kafka.Tests +- net8.0 +- enable +- enable +- +- false +- true +- +- +- +- +- +- all +- runtime; build; native; contentfiles; analyzers; buildtransitive +- +- +- +- +- runtime; build; native; contentfiles; analyzers; buildtransitive +- all +- +- +- runtime; build; native; contentfiles; analyzers; buildtransitive +- all +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- PreserveNewest +- +- +- +- PreserveNewest +- +- +- +- PreserveNewest +- +- +- +- PreserveNewest +- +- +- +- PreserveNewest +- +- +- +- Client +- PreserveNewest +- MSBuild:Compile +- +- +- +- PreserveNewest +- +- +- +- PreserveNewest +- +- +- +- Client +- PreserveNewest +- MSBuild:Compile +- +- +- +- PreserveNewest +- +- +- +- +- +- PreserveNewest +- +- +- +- +diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/AWS/Lambda/Powertools/Kafka/Tests/AvroKey.cs b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/AWS/Lambda/Powertools/Kafka/Tests/AvroKey.cs +deleted file mode 100644 +index 96d09316..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/AWS/Lambda/Powertools/Kafka/Tests/AvroKey.cs ++++ /dev/null +@@ -1,70 +0,0 @@ +-// ------------------------------------------------------------------------------ +-// +-// Generated by avrogen, version 1.12.0+8c27801dc8d42ccc00997f25c0b8f45f8d4a233e +-// Changes to this file may cause incorrect behavior and will be lost if code +-// is regenerated +-// +-// ------------------------------------------------------------------------------ +-namespace AWS.Lambda.Powertools.Kafka.Tests +-{ +- using System; +- using System.Collections.Generic; +- using System.Text; +- using global::Avro; +- using global::Avro.Specific; +- +- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("avrogen", "1.12.0+8c27801dc8d42ccc00997f25c0b8f45f8d4a233e")] +- public partial class AvroKey : global::Avro.Specific.ISpecificRecord +- { +- public static global::Avro.Schema _SCHEMA = global::Avro.Schema.Parse(@"{""type"":""record"",""name"":""AvroKey"",""namespace"":""AWS.Lambda.Powertools.Kafka.Tests"",""fields"":[{""name"":""id"",""type"":""int""},{""name"":""color"",""type"":{""type"":""enum"",""name"":""Color"",""namespace"":""AWS.Lambda.Powertools.Kafka.Tests"",""symbols"":[""UNKNOWN"",""GREEN"",""RED""],""default"":""UNKNOWN""}}]}"); +- private int _id; +- private AWS.Lambda.Powertools.Kafka.Tests.Color _color = AWS.Lambda.Powertools.Kafka.Tests.Color.UNKNOWN; +- public virtual global::Avro.Schema Schema +- { +- get +- { +- return AvroKey._SCHEMA; +- } +- } +- public int id +- { +- get +- { +- return this._id; +- } +- set +- { +- this._id = value; +- } +- } +- public AWS.Lambda.Powertools.Kafka.Tests.Color color +- { +- get +- { +- return this._color; +- } +- set +- { +- this._color = value; +- } +- } +- public virtual object Get(int fieldPos) +- { +- switch (fieldPos) +- { +- case 0: return this.id; +- case 1: return this.color; +- default: throw new global::Avro.AvroRuntimeException("Bad index " + fieldPos + " in Get()"); +- }; +- } +- public virtual void Put(int fieldPos, object fieldValue) +- { +- switch (fieldPos) +- { +- case 0: this.id = (System.Int32)fieldValue; break; +- case 1: this.color = (AWS.Lambda.Powertools.Kafka.Tests.Color)fieldValue; break; +- default: throw new global::Avro.AvroRuntimeException("Bad index " + fieldPos + " in Put()"); +- }; +- } +- } +-} +diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/AWS/Lambda/Powertools/Kafka/Tests/AvroProduct.cs b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/AWS/Lambda/Powertools/Kafka/Tests/AvroProduct.cs +deleted file mode 100644 +index f1c6aa8d..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/AWS/Lambda/Powertools/Kafka/Tests/AvroProduct.cs ++++ /dev/null +@@ -1,86 +0,0 @@ +-// ------------------------------------------------------------------------------ +-// +-// Generated by avrogen, version 1.12.0+8c27801dc8d42ccc00997f25c0b8f45f8d4a233e +-// Changes to this file may cause incorrect behavior and will be lost if code +-// is regenerated +-// +-// ------------------------------------------------------------------------------ +-namespace AWS.Lambda.Powertools.Kafka.Tests +-{ +- using System; +- using System.Collections.Generic; +- using System.Text; +- using global::Avro; +- using global::Avro.Specific; +- +- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("avrogen", "1.12.0+8c27801dc8d42ccc00997f25c0b8f45f8d4a233e")] +- public partial class AvroProduct : global::Avro.Specific.ISpecificRecord +- { +- public static global::Avro.Schema _SCHEMA = global::Avro.Schema.Parse("{\"type\":\"record\",\"name\":\"AvroProduct\",\"namespace\":\"AWS.Lambda.Powertools.Kafka.Te" + +- "sts\",\"fields\":[{\"name\":\"id\",\"type\":\"int\"},{\"name\":\"name\",\"type\":\"string\"},{\"name" + +- "\":\"price\",\"type\":\"double\"}]}"); +- private int _id; +- private string _name; +- private double _price; +- public virtual global::Avro.Schema Schema +- { +- get +- { +- return AvroProduct._SCHEMA; +- } +- } +- public int id +- { +- get +- { +- return this._id; +- } +- set +- { +- this._id = value; +- } +- } +- public string name +- { +- get +- { +- return this._name; +- } +- set +- { +- this._name = value; +- } +- } +- public double price +- { +- get +- { +- return this._price; +- } +- set +- { +- this._price = value; +- } +- } +- public virtual object Get(int fieldPos) +- { +- switch (fieldPos) +- { +- case 0: return this.id; +- case 1: return this.name; +- case 2: return this.price; +- default: throw new global::Avro.AvroRuntimeException("Bad index " + fieldPos + " in Get()"); +- }; +- } +- public virtual void Put(int fieldPos, object fieldValue) +- { +- switch (fieldPos) +- { +- case 0: this.id = (System.Int32)fieldValue; break; +- case 1: this.name = (System.String)fieldValue; break; +- case 2: this.price = (System.Double)fieldValue; break; +- default: throw new global::Avro.AvroRuntimeException("Bad index " + fieldPos + " in Put()"); +- }; +- } +- } +-} +diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/AWS/Lambda/Powertools/Kafka/Tests/Color.cs b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/AWS/Lambda/Powertools/Kafka/Tests/Color.cs +deleted file mode 100644 +index 96323367..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/AWS/Lambda/Powertools/Kafka/Tests/Color.cs ++++ /dev/null +@@ -1,23 +0,0 @@ +-// ------------------------------------------------------------------------------ +-// +-// Generated by avrogen, version 1.12.0+8c27801dc8d42ccc00997f25c0b8f45f8d4a233e +-// Changes to this file may cause incorrect behavior and will be lost if code +-// is regenerated +-// +-// ------------------------------------------------------------------------------ +-namespace AWS.Lambda.Powertools.Kafka.Tests +-{ +- using System; +- using System.Collections.Generic; +- using System.Text; +- using global::Avro; +- using global::Avro.Specific; +- +- [global::System.CodeDom.Compiler.GeneratedCodeAttribute("avrogen", "1.12.0+8c27801dc8d42ccc00997f25c0b8f45f8d4a233e")] +- public enum Color +- { +- UNKNOWN, +- GREEN, +- RED, +- } +-} +diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/AvroKey.avsc b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/AvroKey.avsc +deleted file mode 100644 +index cc15c9e7..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/AvroKey.avsc ++++ /dev/null +@@ -1,24 +0,0 @@ +-{ +- "namespace": "AWS.Lambda.Powertools.Kafka.Tests", +- "type": "record", +- "name": "AvroKey", +- "fields": [ +- { +- "name": "id", +- "type": "int" +- }, +- { +- "name": "color", +- "type": { +- "type": "enum", +- "name": "Color", +- "symbols": [ +- "UNKNOWN", +- "GREEN", +- "RED" +- ], +- "default": "UNKNOWN" +- } +- } +- ] +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/AvroProduct.avsc b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/AvroProduct.avsc +deleted file mode 100644 +index 60b8ed00..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/AvroProduct.avsc ++++ /dev/null +@@ -1,10 +0,0 @@ +-{ +- "namespace": "AWS.Lambda.Powertools.Kafka.Tests", +- "type": "record", +- "name": "AvroProduct", +- "fields": [ +- {"name": "id", "type": "int"}, +- {"name": "name", "type": "string"}, +- {"name": "price", "type": "double"} +- ] +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/HandlerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/HandlerTests.cs +deleted file mode 100644 +index aa2f8307..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/HandlerTests.cs ++++ /dev/null +@@ -1,414 +0,0 @@ +-using System.Text; +-using Amazon.Lambda.Core; +-using Amazon.Lambda.TestUtilities; +-using Avro.IO; +-using Avro.Specific; +-using AWS.Lambda.Powertools.Kafka.Avro; +- +-#if DEBUG +-using KafkaAlias = AWS.Lambda.Powertools.Kafka; +-#else +-using KafkaAlias = AWS.Lambda.Powertools.Kafka.Avro; +-#endif +- +-namespace AWS.Lambda.Powertools.Kafka.Tests.Avro; +- +-public class KafkaHandlerTests +-{ +- [Fact] +- public async Task Handler_ProcessesKafkaEvent_Successfully() +- { +- // Arrange +- var kafkaJson = GetMockKafkaEvent(); +- var mockContext = new TestLambdaContext(); +- var serializer = new PowertoolsKafkaAvroSerializer(); +- +- // Convert JSON string to stream for deserialization +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaJson)); +- +- // Act - Deserialize and process +- var kafkaEvent = serializer.Deserialize>(stream); +- var response = await Handler(kafkaEvent, mockContext); +- +- // Assert +- Assert.Equal("Successfully processed Kafka events", response); +- +- // Verify event structure +- Assert.Equal("aws:kafka", kafkaEvent.EventSource); +- Assert.Single(kafkaEvent.Records); +- +- // Verify record content +- var records = kafkaEvent.Records["mytopic-0"]; +- Assert.Equal(3, records.Count); +- +- // Verify first record +- var firstRecord = records[0]; +- Assert.Equal("mytopic", firstRecord.Topic); +- Assert.Equal(0, firstRecord.Partition); +- Assert.Equal(15, firstRecord.Offset); +- +- // Verify deserialized value +- var product = firstRecord.Value; +- Assert.Equal("Laptop", product.name); +- Assert.Equal(999.99, product.price); +- +- // Verify decoded key and headers +- Assert.Equal(42, firstRecord.Key); +- Assert.Equal("headerValue", firstRecord.Headers["headerKey"].DecodedValue()); +- +- var secondRecord = records[1]; +- Assert.Equal(43, secondRecord.Key); +- +- var thirdRecord = records[2]; +- Assert.Equal(0, thirdRecord.Key); +- } +- +- [Fact] +- public async Task Handler_ProcessesKafkaEvent_Primitive_Successfully() +- { +- // Arrange +- var kafkaJson = GetSimpleMockKafkaEvent(); +- var mockContext = new TestLambdaContext(); +- var serializer = new PowertoolsKafkaAvroSerializer(); +- +- // Convert JSON string to stream for deserialization +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaJson)); +- +- // Act - Deserialize and process +- var kafkaEvent = serializer.Deserialize>(stream); +- var response = await HandlerSimple(kafkaEvent, mockContext); +- +- // Assert +- Assert.Equal("Successfully processed Kafka events", response); +- +- // Verify event structure +- Assert.Equal("aws:kafka", kafkaEvent.EventSource); +- Assert.Single(kafkaEvent.Records); +- +- // Verify record content +- var records = kafkaEvent.Records["mytopic-0"]; +- Assert.Equal(3, records.Count); +- +- // Verify first record +- var firstRecord = records[0]; +- Assert.Equal("mytopic", firstRecord.Topic); +- Assert.Equal(0, firstRecord.Partition); +- Assert.Equal(15, firstRecord.Offset); +- +- // Verify deserialized value +- Assert.Equal("Laptop", firstRecord.Value); +- +- // Verify decoded key and headers +- Assert.Equal(42, firstRecord.Key); +- Assert.Equal("headerValue", firstRecord.Headers["headerKey"].DecodedValue()); +- +- var secondRecord = records[1]; +- Assert.Equal(43, secondRecord.Key); +- Assert.Equal("Smartphone", secondRecord.Value); +- +- var thirdRecord = records[2]; +- Assert.Equal(0, thirdRecord.Key); +- Assert.Null(thirdRecord.Value); +- } +- +- private string GetMockKafkaEvent() +- { +- // For testing, we'll create base64-encoded Avro data for our test products +- var laptop = new AvroProduct { name = "Laptop", price = 999.99 }; +- var smartphone = new AvroProduct { name = "Smartphone", price = 499.99 }; +- var headphones = new AvroProduct { name = "Headphones", price = 99.99 }; +- +- // Convert to base64-encoded Avro +- string laptopBase64 = ConvertToAvroBase64(laptop); +- string smartphoneBase64 = ConvertToAvroBase64(smartphone); +- string headphonesBase64 = ConvertToAvroBase64(headphones); +- +- string firstRecordKey = Convert.ToBase64String(Encoding.UTF8.GetBytes("42")); // Example key +- string secondRecordKey = Convert.ToBase64String(Encoding.UTF8.GetBytes("43")); // Example key for second record +- +- // Create mock Kafka event JSON +- return @$"{{ +- ""eventSource"": ""aws:kafka"", +- ""eventSourceArn"": ""arn:aws:kafka:us-east-1:0123456789019:cluster/SalesCluster/abcd1234-abcd-cafe-abab-9876543210ab-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"": {{ +- ""mytopic-0"": [ +- {{ +- ""topic"": ""mytopic"", +- ""partition"": 0, +- ""offset"": 15, +- ""timestamp"": 1545084650987, +- ""timestampType"": ""CREATE_TIME"", +- ""key"": ""{firstRecordKey}"", +- ""value"": ""{laptopBase64}"", +- ""headers"": [ +- {{ ""headerKey"": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] }} +- ] +- }}, +- {{ +- ""topic"": ""mytopic"", +- ""partition"": 0, +- ""offset"": 16, +- ""timestamp"": 1545084650988, +- ""timestampType"": ""CREATE_TIME"", +- ""key"": ""{secondRecordKey}"", +- ""value"": ""{smartphoneBase64}"", +- ""headers"": [ +- {{ ""headerKey"": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] }} +- ] +- }}, +- {{ +- ""topic"": ""mytopic"", +- ""partition"": 0, +- ""offset"": 17, +- ""timestamp"": 1545084650989, +- ""timestampType"": ""CREATE_TIME"", +- ""key"": null, +- ""value"": ""{headphonesBase64}"", +- ""headers"": [ +- {{ ""headerKey"": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] }} +- ] +- }} +- ] +- }} +- }}"; +- } +- +- private string GetSimpleMockKafkaEvent() +- { +- // For testing, we'll create base64-encoded Avro data for our test products +- +- // Convert to base64-encoded Avro +- string laptopBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes("Laptop")); +- string smartphoneBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes("Smartphone")); +- +- string firstRecordKey = Convert.ToBase64String(Encoding.UTF8.GetBytes("42")); // Example key +- string secondRecordKey = Convert.ToBase64String(Encoding.UTF8.GetBytes("43")); // Example key for second record +- +- // Create mock Kafka event JSON +- return @$"{{ +- ""eventSource"": ""aws:kafka"", +- ""eventSourceArn"": ""arn:aws:kafka:us-east-1:0123456789019:cluster/SalesCluster/abcd1234-abcd-cafe-abab-9876543210ab-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"": {{ +- ""mytopic-0"": [ +- {{ +- ""topic"": ""mytopic"", +- ""partition"": 0, +- ""offset"": 15, +- ""timestamp"": 1545084650987, +- ""timestampType"": ""CREATE_TIME"", +- ""key"": ""{firstRecordKey}"", +- ""value"": ""{laptopBase64}"", +- ""headers"": [ +- {{ ""headerKey"": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] }} +- ] +- }}, +- {{ +- ""topic"": ""mytopic"", +- ""partition"": 0, +- ""offset"": 16, +- ""timestamp"": 1545084650988, +- ""timestampType"": ""CREATE_TIME"", +- ""key"": ""{secondRecordKey}"", +- ""value"": ""{smartphoneBase64}"", +- ""headers"": [ +- {{ ""headerKey"": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] }} +- ] +- }}, +- {{ +- ""topic"": ""mytopic"", +- ""partition"": 0, +- ""offset"": 17, +- ""timestamp"": 1545084650989, +- ""timestampType"": ""CREATE_TIME"", +- ""key"": null, +- ""value"": null, +- ""headers"": [ +- {{ ""headerKey"": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] }} +- ] +- }} +- ] +- }} +- }}"; +- } +- +- private string ConvertToAvroBase64(AvroProduct product) +- { +- using var stream = new MemoryStream(); +- var encoder = new BinaryEncoder(stream); +- var writer = new SpecificDatumWriter(AvroProduct._SCHEMA); +- +- writer.Write(product, encoder); +- encoder.Flush(); +- +- return Convert.ToBase64String(stream.ToArray()); +- } +- +- // Define the test handler method +- private async Task Handler(KafkaAlias.ConsumerRecords records, ILambdaContext context) +- { +- foreach (var record in records) +- { +- var product = record.Value; +- context.Logger.LogInformation($"Processing {product.name} at ${product.price}"); +- } +- +- return "Successfully processed Kafka events"; +- } +- +- private async Task HandlerSimple(KafkaAlias.ConsumerRecords records, ILambdaContext context) +- { +- foreach (var record in records) +- { +- var product = record.Value; +- context.Logger.LogInformation($"Processing {product}"); +- } +- +- return "Successfully processed Kafka events"; +- } +- +- [Fact] +- public async Task Handler_ProcessesKafkaEvent_WithAvroKey_Successfully() +- { +- // Arrange +- var kafkaJson = GetMockKafkaEventWithAvroKeys(); +- var mockContext = new TestLambdaContext(); +- var serializer = new PowertoolsKafkaAvroSerializer(); +- +- // Convert JSON string to stream for deserialization +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaJson)); +- +- // Act - Deserialize and process +- var kafkaEvent = serializer.Deserialize>(stream); +- var response = await HandlerWithAvroKeys(kafkaEvent, mockContext); +- +- // Assert +- Assert.Equal("Successfully processed Kafka events", response); +- +- // Verify event structure +- Assert.Equal("aws:kafka", kafkaEvent.EventSource); +- Assert.Single(kafkaEvent.Records); +- +- // Verify record content +- var records = kafkaEvent.Records["mytopic-0"]; +- Assert.Equal(3, records.Count); +- +- // Verify first record +- var firstRecord = records[0]; +- Assert.Equal("mytopic", firstRecord.Topic); +- Assert.Equal(0, firstRecord.Partition); +- Assert.Equal(15, firstRecord.Offset); +- +- // Verify deserialized Avro key and value +- Assert.Equal("Laptop", firstRecord.Value.name); +- Assert.Equal(999.99, firstRecord.Value.price); +- Assert.Equal(1, firstRecord.Key.id); +- Assert.Equal(Color.GREEN, firstRecord.Key.color); +- +- // Verify headers +- Assert.Equal("headerValue", firstRecord.Headers["headerKey"].DecodedValue()); +- +- var secondRecord = records[1]; +- Assert.Equal(2, secondRecord.Key.id); +- Assert.Equal(Color.UNKNOWN, secondRecord.Key.color); +- +- var thirdRecord = records[2]; +- Assert.Equal(3, thirdRecord.Key.id); +- Assert.Equal(Color.RED, thirdRecord.Key.color); +- } +- +- private string GetMockKafkaEventWithAvroKeys() +- { +- // Create test products +- var laptop = new AvroProduct { name = "Laptop", price = 999.99 }; +- var smartphone = new AvroProduct { name = "Smartphone", price = 499.99 }; +- var headphones = new AvroProduct { name = "Headphones", price = 99.99 }; +- +- // Create test keys +- var key1 = new AvroKey { id = 1, color = Color.GREEN }; +- var key2 = new AvroKey { id = 2 }; +- var key3 = new AvroKey { id = 3, color = Color.RED }; +- +- // Convert values to base64-encoded Avro +- string laptopBase64 = ConvertToAvroBase64(laptop); +- string smartphoneBase64 = ConvertToAvroBase64(smartphone); +- string headphonesBase64 = ConvertToAvroBase64(headphones); +- +- // Convert keys to base64-encoded Avro +- string key1Base64 = ConvertKeyToAvroBase64(key1); +- string key2Base64 = ConvertKeyToAvroBase64(key2); +- string key3Base64 = ConvertKeyToAvroBase64(key3); +- +- // Create mock Kafka event JSON +- return @$"{{ +- ""eventSource"": ""aws:kafka"", +- ""eventSourceArn"": ""arn:aws:kafka:us-east-1:0123456789019:cluster/SalesCluster/abcd1234-abcd-cafe-abab-9876543210ab-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"": {{ +- ""mytopic-0"": [ +- {{ +- ""topic"": ""mytopic"", +- ""partition"": 0, +- ""offset"": 15, +- ""timestamp"": 1545084650987, +- ""timestampType"": ""CREATE_TIME"", +- ""key"": ""{key1Base64}"", +- ""value"": ""{laptopBase64}"", +- ""headers"": [ +- {{ ""headerKey"": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] }} +- ] +- }}, +- {{ +- ""topic"": ""mytopic"", +- ""partition"": 0, +- ""offset"": 16, +- ""timestamp"": 1545084650988, +- ""timestampType"": ""CREATE_TIME"", +- ""key"": ""{key2Base64}"", +- ""value"": ""{smartphoneBase64}"", +- ""headers"": [ +- {{ ""headerKey"": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] }} +- ] +- }}, +- {{ +- ""topic"": ""mytopic"", +- ""partition"": 0, +- ""offset"": 17, +- ""timestamp"": 1545084650989, +- ""timestampType"": ""CREATE_TIME"", +- ""key"": ""{key3Base64}"", +- ""value"": ""{headphonesBase64}"", +- ""headers"": [ +- {{ ""headerKey"": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] }} +- ] +- }} +- ] +- }} +- }}"; +- } +- +- private string ConvertKeyToAvroBase64(AvroKey key) +- { +- using var stream = new MemoryStream(); +- var encoder = new BinaryEncoder(stream); +- var writer = new SpecificDatumWriter(AvroKey._SCHEMA); +- +- writer.Write(key, encoder); +- encoder.Flush(); +- +- return Convert.ToBase64String(stream.ToArray()); +- } +- +- private async Task HandlerWithAvroKeys(KafkaAlias.ConsumerRecords records, +- ILambdaContext context) +- { +- foreach (var record in records) +- { +- var key = record.Key.id; +- var product = record.Value; +- } +- +- return "Successfully processed Kafka events"; +- } +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/PowertoolsKafkaAvroSerializerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/PowertoolsKafkaAvroSerializerTests.cs +deleted file mode 100644 +index 4dc2c7cc..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/PowertoolsKafkaAvroSerializerTests.cs ++++ /dev/null +@@ -1,149 +0,0 @@ +-using System.Runtime.Serialization; +-using System.Text; +-using AWS.Lambda.Powertools.Kafka.Avro; +- +-#if DEBUG +-using KafkaAlias = AWS.Lambda.Powertools.Kafka; +-#else +-using KafkaAlias = AWS.Lambda.Powertools.Kafka.Avro; +-#endif +- +-namespace AWS.Lambda.Powertools.Kafka.Tests.Avro; +- +-public class PowertoolsKafkaAvroSerializerTests +-{ +- [Fact] +- public void Deserialize_KafkaEventWithAvroPayload_DeserializesToCorrectType() +- { +- // Arrange +- var serializer = new PowertoolsKafkaAvroSerializer(); +- string kafkaEventJson = File.ReadAllText("Avro/kafka-avro-event.json"); +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); +- +- // Act +- var result = serializer.Deserialize>(stream); +- +- // Assert +- Assert.NotNull(result); +- Assert.Equal("aws:kafka", result.EventSource); +- +- // Verify records were deserialized +- Assert.True(result.Records.ContainsKey("mytopic-0")); +- var records = result.Records["mytopic-0"]; +- Assert.Equal(3, records.Count); +- +- // Verify first record's content +- var firstRecord = records[0]; +- Assert.Equal("mytopic", firstRecord.Topic); +- Assert.Equal(0, firstRecord.Partition); +- Assert.Equal(15, firstRecord.Offset); +- Assert.Equal(42, firstRecord.Key); +- +- // Verify deserialized Avro value +- var product = firstRecord.Value; +- Assert.Equal("Laptop", product.name); +- Assert.Equal(1001, product.id); +- Assert.Equal(999.99000000000001, product.price); +- +- // Verify second record +- var secondRecord = records[1]; +- var smartphone = secondRecord.Value; +- Assert.Equal("Smartphone", smartphone.name); +- } +- +- [Fact] +- public void KafkaEvent_ImplementsIEnumerable_ForDirectIteration() +- { +- // Arrange +- var serializer = new PowertoolsKafkaAvroSerializer(); +- string kafkaEventJson = File.ReadAllText("Avro/kafka-avro-event.json"); +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); +- +- // Act +- var result = serializer.Deserialize>(stream); +- +- // Assert - Test enumeration +- int count = 0; +- var products = new List(); +- +- // Directly iterate over ConsumerRecords +- foreach (var record in result) +- { +- count++; +- products.Add(record.Value.name); +- } +- +- // Verify correct count and values +- Assert.Equal(3, count); +- Assert.Contains("Laptop", products); +- Assert.Contains("Smartphone", products); +- Assert.Equal(3, products.Count); +- +- // Get first record directly through Linq extension +- var firstRecord = result.First(); +- Assert.Equal("Laptop", firstRecord.Value.name); +- Assert.Equal(1001, firstRecord.Value.id); +- } +- +- [Fact] +- public void Primitive_Deserialization() +- { +- // Arrange +- var serializer = new PowertoolsKafkaAvroSerializer(); +- string kafkaEventJson = +- CreateKafkaEvent(Convert.ToBase64String("MyKey"u8.ToArray()), +- Convert.ToBase64String("Myvalue"u8.ToArray())); +- +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); +- +- // Act +- var result = serializer.Deserialize>(stream); +- var firstRecord = result.First(); +- Assert.Equal("Myvalue", firstRecord.Value); +- Assert.Equal("MyKey", firstRecord.Key); +- } +- +- [Fact] +- public void DeserializeComplexKey_WhenAllDeserializationMethodsFail_ReturnsException() +- { +- // Arrange +- var serializer = new PowertoolsKafkaAvroSerializer(); +- // Invalid JSON and not Avro binary +- byte[] invalidBytes = { 0xDE, 0xAD, 0xBE, 0xEF }; +- +- string kafkaEventJson = CreateKafkaEvent( +- keyValue: Convert.ToBase64String(invalidBytes), +- valueValue: Convert.ToBase64String(Encoding.UTF8.GetBytes("test")) +- ); +- +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); +- +- Assert.Throws(() => +- serializer.Deserialize>(stream)); +- } +- +- private string CreateKafkaEvent(string keyValue, string valueValue) +- { +- return @$"{{ +- ""eventSource"": ""aws:kafka"", +- ""eventSourceArn"": ""arn:aws:kafka:us-east-1:0123456789019:cluster/TestCluster/abcd1234"", +- ""bootstrapServers"": ""b-1.test-cluster.kafka.us-east-1.amazonaws.com:9092"", +- ""records"": {{ +- ""mytopic-0"": [ +- {{ +- ""topic"": ""mytopic"", +- ""partition"": 0, +- ""offset"": 15, +- ""timestamp"": 1645084650987, +- ""timestampType"": ""CREATE_TIME"", +- ""key"": ""{keyValue}"", +- ""value"": ""{valueValue}"", +- ""headers"": [ +- {{ ""headerKey"": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] }} +- ] +- }} +- ] +- }} +- }}"; +- } +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/kafka-avro-event.json b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/kafka-avro-event.json +deleted file mode 100644 +index 8d6ef221..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/kafka-avro-event.json ++++ /dev/null +@@ -1,51 +0,0 @@ +-{ +- "eventSource": "aws:kafka", +- "eventSourceArn": "arn:aws:kafka:us-east-1:0123456789019:cluster/SalesCluster/abcd1234-abcd-cafe-abab-9876543210ab-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": { +- "mytopic-0": [ +- { +- "topic": "mytopic", +- "partition": 0, +- "offset": 15, +- "timestamp": 1545084650987, +- "timestampType": "CREATE_TIME", +- "key": "NDI=", +- "value": "0g8MTGFwdG9wUrgehes/j0A=", +- "headers": [ +- { +- "headerKey": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] +- } +- ] +- }, +- { +- "topic": "mytopic", +- "partition": 0, +- "offset": 16, +- "timestamp": 1545084650988, +- "timestampType": "CREATE_TIME", +- "key": "NDI=", +- "value": "1A8UU21hcnRwaG9uZVK4HoXrv4JA", +- "headers": [ +- { +- "headerKey": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] +- } +- ] +- }, +- { +- "topic": "mytopic", +- "partition": 0, +- "offset": 17, +- "timestamp": 1545084650989, +- "timestampType": "CREATE_TIME", +- "key": null, +- "value": "1g8USGVhZHBob25lc0jhehSuv2JA", +- "headers": [ +- { +- "headerKey": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] +- } +- ] +- } +- ] +- } +-} +diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/AvroErrorHandlingTests.cs b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/AvroErrorHandlingTests.cs +deleted file mode 100644 +index 3ada7575..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/AvroErrorHandlingTests.cs ++++ /dev/null +@@ -1,74 +0,0 @@ +-using System.Runtime.Serialization; +-using System.Text; +-using AWS.Lambda.Powertools.Kafka.Avro; +- +-#if DEBUG +-using KafkaAlias = AWS.Lambda.Powertools.Kafka; +-#else +-using KafkaAlias = AWS.Lambda.Powertools.Kafka.Avro; +-#endif +- +-namespace AWS.Lambda.Powertools.Kafka.Tests; +- +-public class AvroErrorHandlingTests +-{ +- [Fact] +- public void AvroSerializer_WithCorruptedKeyData_ThrowSerializationException() +- { +- // Arrange +- var serializer = new PowertoolsKafkaAvroSerializer(); +- var corruptedData = new byte[] { 0xDE, 0xAD, 0xBE, 0xEF }; +- +- string kafkaEventJson = CreateKafkaEvent( +- Convert.ToBase64String(corruptedData), +- Convert.ToBase64String(Encoding.UTF8.GetBytes("valid-value")) +- ); +- +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); +- +- // Act & Assert +- var ex = Assert.Throws(() => +- serializer.Deserialize>(stream)); +- +- Assert.Contains("Failed to deserialize key data", ex.Message); +- } +- +- [Fact] +- public void AvroSerializer_WithCorruptedValueData_ThrowSerializationException() +- { +- // Arrange +- var serializer = new PowertoolsKafkaAvroSerializer(); +- var corruptedData = new byte[] { 0xDE, 0xAD, 0xBE, 0xEF }; +- +- string kafkaEventJson = CreateKafkaEvent( +- Convert.ToBase64String(Encoding.UTF8.GetBytes("valid-key")), +- Convert.ToBase64String(corruptedData) +- ); +- +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); +- +- // Act & Assert +- var ex = Assert.Throws(() => +- serializer.Deserialize>(stream)); +- +- Assert.Contains("Failed to deserialize value data", ex.Message); +- } +- +- private string CreateKafkaEvent(string keyValue, string valueValue) +- { +- return @$"{{ +- ""eventSource"": ""aws:kafka"", +- ""records"": {{ +- ""mytopic-0"": [ +- {{ +- ""topic"": ""mytopic"", +- ""partition"": 0, +- ""offset"": 15, +- ""key"": ""{keyValue}"", +- ""value"": ""{valueValue}"" +- }} +- ] +- }} +- }}"; +- } +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/HeaderExtensionsTests.cs b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/HeaderExtensionsTests.cs +deleted file mode 100644 +index 7114a698..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/HeaderExtensionsTests.cs ++++ /dev/null +@@ -1,89 +0,0 @@ +-using System.Text; +-using AWS.Lambda.Powertools.Kafka.Avro; +- +-namespace AWS.Lambda.Powertools.Kafka.Tests +-{ +- public class HeaderExtensionsTests +- { +- [Fact] +- public void DecodedValues_WithValidHeaders_DecodesCorrectly() +- { +- // Arrange +- var headers = new Dictionary +- { +- { "header1", Encoding.UTF8.GetBytes("value1") }, +- { "header2", Encoding.UTF8.GetBytes("value2") } +- }; +- +- // Act +- var decoded = headers.DecodedValues(); +- +- // Assert +- Assert.Equal(2, decoded.Count); +- Assert.Equal("value1", decoded["header1"]); +- Assert.Equal("value2", decoded["header2"]); +- } +- +- [Fact] +- public void DecodedValues_WithEmptyDictionary_ReturnsEmptyDictionary() +- { +- // Arrange +- var headers = new Dictionary(); +- +- // Act +- var decoded = headers.DecodedValues(); +- +- // Assert +- Assert.Empty(decoded); +- } +- +- [Fact] +- public void DecodedValues_WithNullDictionary_ReturnsEmptyDictionary() +- { +- // Arrange +- Dictionary headers = null; +- +- // Act +- var decoded = headers.DecodedValues(); +- +- // Assert +- Assert.Empty(decoded); +- } +- +- [Fact] +- public void DecodedValue_WithValidBytes_DecodesCorrectly() +- { +- // Arrange +- var bytes = Encoding.UTF8.GetBytes("test-value"); +- +- // Act +- var decoded = bytes.DecodedValue(); +- +- // Assert +- Assert.Equal("test-value", decoded); +- } +- +- [Fact] +- public void DecodedValue_WithEmptyBytes_ReturnsEmptyString() +- { +- // Arrange +- var bytes = Array.Empty(); +- +- // Act +- var decoded = bytes.DecodedValue(); +- +- // Assert +- Assert.Equal("", decoded); +- } +- +- [Fact] +- public void DecodedValue_WithNullBytes_ReturnsEmptyString() +- { +- // Act +- var decoded = ((byte[])null).DecodedValue(); +- +- // Assert +- Assert.Equal("", decoded); +- } +- } +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Json/PowertoolsKafkaJsonSerializerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Json/PowertoolsKafkaJsonSerializerTests.cs +deleted file mode 100644 +index dfe21542..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Json/PowertoolsKafkaJsonSerializerTests.cs ++++ /dev/null +@@ -1,416 +0,0 @@ +-using System.Text; +-using System.Text.Json; +-using System.Text.Json.Serialization; +-using AWS.Lambda.Powertools.Kafka.Json; +- +-#if DEBUG +-using KafkaAlias = AWS.Lambda.Powertools.Kafka; +-#else +-using KafkaAlias = AWS.Lambda.Powertools.Kafka.Json; +-#endif +- +-namespace AWS.Lambda.Powertools.Kafka.Tests.Json; +- +-public class PowertoolsKafkaJsonSerializerTests +-{ +- [Fact] +- public void Deserialize_KafkaEventWithJsonPayload_DeserializesToCorrectType() +- { +- // Arrange +- var serializer = new PowertoolsKafkaJsonSerializer(); +- var testModel = new TestModel { Name = "Test Product", Value = 123 }; +- var jsonValue = JsonSerializer.Serialize(testModel); +- var base64Value = Convert.ToBase64String(Encoding.UTF8.GetBytes(jsonValue)); +- +- string kafkaEventJson = CreateKafkaEvent("NDI=", base64Value); // Key is 42 in base64 +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); +- +- // Act +- var result = serializer.Deserialize>(stream); +- +- // Assert +- Assert.NotNull(result); +- var record = result.First(); +- Assert.Equal(42, record.Key); +- Assert.Equal("Test Product", record.Value.Name); +- Assert.Equal(123, record.Value.Value); +- } +- +- [Fact] +- public void KafkaEvent_ImplementsIEnumerable_ForDirectIteration() +- { +- // Arrange +- var serializer = new PowertoolsKafkaJsonSerializer(); +- string kafkaEventJson = File.ReadAllText("Json/kafka-json-event.json"); +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); +- +- // Act +- var result = serializer.Deserialize>(stream); +- +- // Assert - Test enumeration +- int count = 0; +- var products = new List(); +- +- // Directly iterate over ConsumerRecords +- foreach (var record in result) +- { +- count++; +- products.Add(record.Value.Name); +- } +- +- // Verify correct count and values +- Assert.Equal(3, count); +- Assert.Contains("product5", products); +- +- // Get first record directly through Linq extension +- var firstRecord = result.First(); +- Assert.Equal("product5", firstRecord.Value.Name); +- Assert.Equal(12345, firstRecord.Value.Id); +- } +- +- [Fact] +- public void Primitive_Deserialization() +- { +- // Arrange +- var serializer = new PowertoolsKafkaJsonSerializer(); +- string kafkaEventJson = +- CreateKafkaEvent(Convert.ToBase64String("MyKey"u8.ToArray()), +- Convert.ToBase64String("Myvalue"u8.ToArray())); +- +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); +- +- // Act +- var result = serializer.Deserialize>(stream); +- var firstRecord = result.First(); +- Assert.Equal("Myvalue", firstRecord.Value); +- Assert.Equal("MyKey", firstRecord.Key); +- } +- +- [Fact] +- public void DeserializeComplexKey_StandardJsonDeserialization_Works() +- { +- // Arrange +- var serializer = new PowertoolsKafkaJsonSerializer(); +- var complexObject = new { Name = "Test", Id = 123 }; +- var jsonBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(complexObject)); +- +- string kafkaEventJson = CreateKafkaEvent( +- keyValue: Convert.ToBase64String(jsonBytes), +- valueValue: Convert.ToBase64String(Encoding.UTF8.GetBytes("test")) +- ); +- +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); +- +- // Act +- var result = serializer.Deserialize, string>>(stream); +- +- // Assert +- var record = result.First(); +- Assert.NotNull(record.Key); +- Assert.Equal("Test", record.Key["Name"].ToString()); +- Assert.Equal(123, int.Parse(record.Key["Id"].ToString())); +- } +- +- [Fact] +- public void DeserializeComplexKey_WithSerializerContext_UsesContext() +- { +- // Arrange +- // Create custom context +- var options = new JsonSerializerOptions(); +- var context = new TestJsonSerializerContext(options); +- var serializer = new PowertoolsKafkaJsonSerializer(context); +- +- // Create test data with the registered type +- var testModel = new TestModel { Name = "TestFromContext", Value = 456 }; +- var jsonBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(testModel)); +- +- string kafkaEventJson = CreateKafkaEvent( +- keyValue: Convert.ToBase64String(jsonBytes), +- valueValue: Convert.ToBase64String(Encoding.UTF8.GetBytes("test")) +- ); +- +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); +- +- // Act +- var result = serializer.Deserialize>(stream); +- +- // Assert +- var record = result.First(); +- Assert.NotNull(record.Key); +- Assert.Equal("TestFromContext", record.Key.Name); +- Assert.Equal(456, record.Key.Value); +- } +- +- [Fact] +- public void DeserializeComplexValue_WithSerializerContext_UsesContext() +- { +- // Arrange +- var options = new JsonSerializerOptions(); +- var context = new TestJsonSerializerContext(options); +- var serializer = new PowertoolsKafkaJsonSerializer(context); +- +- // Create test data with the registered type +- var testModel = new TestModel { Name = "ValueFromContext", Value = 789 }; +- var jsonBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(testModel)); +- +- string kafkaEventJson = CreateKafkaEvent( +- keyValue: Convert.ToBase64String(Encoding.UTF8.GetBytes("testKey")), +- valueValue: Convert.ToBase64String(jsonBytes) +- ); +- +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); +- +- // Act +- var result = serializer.Deserialize>(stream); +- +- // Assert +- var record = result.First(); +- Assert.Equal("testKey", record.Key); +- Assert.NotNull(record.Value); +- Assert.Equal("ValueFromContext", record.Value.Name); +- Assert.Equal(789, record.Value.Value); +- } +- +- [Fact] +- public void DeserializeComplexValue_WithCustomJsonOptions_RespectsOptions() +- { +- // Arrange - create custom options with different naming policy +- var options = new JsonSerializerOptions +- { +- PropertyNamingPolicy = JsonNamingPolicy.CamelCase, +- PropertyNameCaseInsensitive = false // Force exact case match +- }; +- var serializer = new PowertoolsKafkaJsonSerializer(options); +- +- // Create test data with camelCase property names +- var jsonBytes = Encoding.UTF8.GetBytes(@"{""id"":999,""name"":""camelCase"",""price"":29.99}"); +- +- string kafkaEventJson = CreateKafkaEvent( +- keyValue: Convert.ToBase64String(Encoding.UTF8.GetBytes("testKey")), +- valueValue: Convert.ToBase64String(jsonBytes) +- ); +- +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); +- +- // Act +- var result = serializer.Deserialize>(stream); +- +- // Assert +- var record = result.First(); +- Assert.Equal(999, record.Value.Id); +- Assert.Equal("camelCase", record.Value.Name); +- Assert.Equal(29.99m, record.Value.Price); +- } +- +- [Fact] +- public void DeserializeComplexValue_WithEmptyData_ReturnsNullOrDefault() +- { +- // Arrange +- var serializer = new PowertoolsKafkaJsonSerializer(); +- // Empty JSON data +- byte[] emptyBytes = Array.Empty(); +- +- string kafkaEventJson = CreateKafkaEvent( +- keyValue: Convert.ToBase64String(Encoding.UTF8.GetBytes("testKey")), +- valueValue: Convert.ToBase64String(emptyBytes) +- ); +- +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); +- +- // Act +- var result = serializer.Deserialize>(stream); +- +- // Assert +- var record = result.First(); +- Assert.Equal("testKey", record.Key); +- Assert.Null(record.Value); // Should be null for empty input +- } +- +- [Fact] +- public void DeserializeComplexValue_WithContextAndNullResult_ReturnsNull() +- { +- // Arrange - create a context with JsonNullHandling.Include +- var options = new JsonSerializerOptions +- { +- DefaultIgnoreCondition = JsonIgnoreCondition.Never, +- IgnoreNullValues = false +- }; +- var context = new TestJsonSerializerContext(options); +- var serializer = new PowertoolsKafkaJsonSerializer(context); +- +- // JSON that explicitly sets the value to null +- var jsonBytes = Encoding.UTF8.GetBytes("null"); +- +- string kafkaEventJson = CreateKafkaEvent( +- keyValue: Convert.ToBase64String(Encoding.UTF8.GetBytes("testKey")), +- valueValue: Convert.ToBase64String(jsonBytes) +- ); +- +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); +- +- // Act +- var result = serializer.Deserialize>(stream); +- +- // Assert +- var record = result.First(); +- Assert.Equal("testKey", record.Key); +- Assert.Null(record.Value); +- } +- +- +- /// +- /// Helper method to create Kafka event JSON with specified key and value in base64 format +- /// +- private string CreateKafkaEvent(string keyValue, string valueValue) +- { +- return @$"{{ +- ""eventSource"": ""aws:kafka"", +- ""eventSourceArn"": ""arn:aws:kafka:us-east-1:0123456789019:cluster/TestCluster/abcd1234"", +- ""bootstrapServers"": ""b-1.test-cluster.kafka.us-east-1.amazonaws.com:9092"", +- ""records"": {{ +- ""mytopic-0"": [ +- {{ +- ""topic"": ""mytopic"", +- ""partition"": 0, +- ""offset"": 15, +- ""timestamp"": 1645084650987, +- ""timestampType"": ""CREATE_TIME"", +- ""key"": ""{keyValue}"", +- ""value"": ""{valueValue}"", +- ""headers"": [ +- {{ ""headerKey"": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] }} +- ] +- }} +- ] +- }} +- }}"; +- } +- +- [Fact] +- public void DirectJsonSerializerTest_InvokesFormatSpecificMethod() +- { +- // This test directly tests the JSON serializer methods +- var serializer = new TestJsonDeserializer(); +- +- // Create test data with valid JSON +- var testModel = new TestModel { Name = "DirectTest", Value = 555 }; +- var jsonBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(testModel)); +- +- // Act +- var result = serializer.TestDeserializeFormatSpecific(jsonBytes, typeof(TestModel), false); +- +- // Assert +- Assert.NotNull(result); +- var model = result as TestModel; +- Assert.NotNull(model); +- Assert.Equal("DirectTest", model!.Name); +- Assert.Equal(555, model.Value); +- } +- +- [Fact] +- public void DirectJsonSerializerTest_WithContext_UsesContext() +- { +- // Create a context that includes TestModel +- var options = new JsonSerializerOptions(); +- var context = new TestJsonSerializerContext(options); +- +- // Create the serializer with context +- var serializer = new TestJsonDeserializer(context); +- +- // Create test data with valid JSON +- var testModel = new TestModel { Name = "ContextTest", Value = 999 }; +- var jsonBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(testModel)); +- +- // Act - directly test the protected method +- var result = serializer.TestDeserializeFormatSpecific(jsonBytes, typeof(TestModel), false); +- +- // Assert +- Assert.NotNull(result); +- var model = result as TestModel; +- Assert.NotNull(model); +- Assert.Equal("ContextTest", model!.Name); +- Assert.Equal(999, model.Value); +- } +- +- [Fact] +- public void DirectJsonSerializerTest_WithEmptyJson_ReturnsNullOrDefault() +- { +- // Create the serializer +- var serializer = new TestJsonDeserializer(); +- +- // Create empty JSON data +- var emptyJsonBytes = Array.Empty(); +- +- // Act - test with reference type +- var resultRef = serializer.TestDeserializeFormatSpecific(emptyJsonBytes, typeof(TestModel), false); +- // Act - test with value type +- var resultVal = serializer.TestDeserializeFormatSpecific(emptyJsonBytes, typeof(int), false); +- +- // Assert +- Assert.Null(resultRef); // Reference type should get null +- Assert.Equal(0, resultVal); // Value type should get default +- } +- +- [Fact] +- public void DirectJsonSerializerTest_WithContextResultingInNull_ReturnsNull() +- { +- // Create context +- var options = new JsonSerializerOptions(); +- var context = new TestJsonSerializerContext(options); +- +- // Create serializer with context +- var serializer = new TestJsonDeserializer(context); +- +- // Create JSON that is "null" +- var jsonBytes = Encoding.UTF8.GetBytes("null"); +- +- // Act - even with context, null JSON should return null +- var result = serializer.TestDeserializeFormatSpecific(jsonBytes, typeof(TestModel), false); +- +- // Assert +- Assert.Null(result); +- } +- +- /// +- /// Test helper to directly access protected methods +- /// +- private class TestJsonDeserializer : PowertoolsKafkaJsonSerializer +- { +- public TestJsonDeserializer() : base() { } +- +- public TestJsonDeserializer(JsonSerializerOptions options) : base(options) { } +- +- public TestJsonDeserializer(JsonSerializerContext context) : base(context) { } +- +- public object? TestDeserializeFormatSpecific(byte[] data, Type targetType, bool isKey) +- { +- // Call the protected method directly +- return base.DeserializeComplexTypeFormat(data, targetType, isKey); +- } +- } +-} +- +-[JsonSerializable(typeof(TestModel))] +-public partial class TestJsonSerializerContext : JsonSerializerContext +-{ +-} +- +-public class TestModel +-{ +- public string Name { get; set; } = string.Empty; +- public int Value { get; set; } +-} +- +-public record JsonProduct +-{ +- public int Id { get; set; } +- public string Name { get; set; } = string.Empty; +- public decimal Price { get; set; } +-} +- +-public struct ValueTypeProduct +-{ +- public int Id { get; set; } +- public string Name { get; set; } +- public decimal Price { get; set; } +-} +diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Json/kafka-json-event.json b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Json/kafka-json-event.json +deleted file mode 100644 +index d85c4065..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Json/kafka-json-event.json ++++ /dev/null +@@ -1,50 +0,0 @@ +-{ +- "eventSource": "aws:kafka", +- "eventSourceArn": "arn:aws:kafka:us-east-1:0123456789019:cluster/SalesCluster/abcd1234-abcd-cafe-abab-9876543210ab-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": { +- "mytopic-0": [ +- { +- "topic": "mytopic", +- "partition": 0, +- "offset": 15, +- "timestamp": 1545084650987, +- "timestampType": "CREATE_TIME", +- "key": "cmVjb3JkS2V5", +- "value": "ewogICJpZCI6IDEyMzQ1LAogICJuYW1lIjogInByb2R1Y3Q1IiwKICAicHJpY2UiOiA0NQp9", +- "headers": [ +- { +- "headerKey": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] +- } +- ] +- }, +- { +- "topic": "mytopic", +- "partition": 0, +- "offset": 15, +- "timestamp": 1545084650987, +- "timestampType": "CREATE_TIME", +- "value": "ewogICJpZCI6IDEyMzQ1LAogICJuYW1lIjogInByb2R1Y3Q1IiwKICAicHJpY2UiOiA0NQp9", +- "headers": [ +- { +- "headerKey": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] +- } +- ] +- }, +- { +- "topic": "mytopic", +- "partition": 0, +- "offset": 15, +- "timestamp": 1545084650987, +- "timestampType": "CREATE_TIME", +- "key": null, +- "value": "ewogICJpZCI6IDEyMzQ1LAogICJuYW1lIjogInByb2R1Y3Q1IiwKICAicHJpY2UiOiA0NQp9", +- "headers": [ +- { +- "headerKey": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] +- } +- ] +- } +- ] +- } +-} +diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/JsonErrorHandlingTests.cs b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/JsonErrorHandlingTests.cs +deleted file mode 100644 +index 5ce8987b..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/JsonErrorHandlingTests.cs ++++ /dev/null +@@ -1,74 +0,0 @@ +-using System.Runtime.Serialization; +-using System.Text; +-using AWS.Lambda.Powertools.Kafka.Json; +- +-#if DEBUG +-using KafkaAlias = AWS.Lambda.Powertools.Kafka; +-#else +-using KafkaAlias = AWS.Lambda.Powertools.Kafka.Json; +-#endif +- +-namespace AWS.Lambda.Powertools.Kafka.Tests; +- +-public class JsonErrorHandlingTests +-{ +- [Fact] +- public void JsonSerializer_WithCorruptedKeyData_ThrowSerializationException() +- { +- // Arrange +- var serializer = new PowertoolsKafkaJsonSerializer(); +- var corruptedData = new byte[] { 0xDE, 0xAD, 0xBE, 0xEF }; +- +- string kafkaEventJson = CreateKafkaEvent( +- Convert.ToBase64String(corruptedData), +- Convert.ToBase64String(Encoding.UTF8.GetBytes("valid-value")) +- ); +- +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); +- +- // Act & Assert +- var ex = Assert.Throws(() => +- serializer.Deserialize>(stream)); +- +- Assert.Contains("Failed to deserialize key data", ex.Message); +- } +- +- [Fact] +- public void JsonSerializer_WithCorruptedValueData_ThrowSerializationException() +- { +- // Arrange +- var serializer = new PowertoolsKafkaJsonSerializer(); +- var corruptedData = new byte[] { 0xDE, 0xAD, 0xBE, 0xEF }; +- +- string kafkaEventJson = CreateKafkaEvent( +- Convert.ToBase64String(Encoding.UTF8.GetBytes("valid-key")), +- Convert.ToBase64String(corruptedData) +- ); +- +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); +- +- // Act & Assert +- var ex = Assert.Throws(() => +- serializer.Deserialize>(stream)); +- +- Assert.Contains("Failed to deserialize value data", ex.Message); +- } +- +- private string CreateKafkaEvent(string keyValue, string valueValue) +- { +- return @$"{{ +- ""eventSource"": ""aws:kafka"", +- ""records"": {{ +- ""mytopic-0"": [ +- {{ +- ""topic"": ""mytopic"", +- ""partition"": 0, +- ""offset"": 15, +- ""key"": ""{keyValue}"", +- ""value"": ""{valueValue}"" +- }} +- ] +- }} +- }}"; +- } +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/JsonTests.cs b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/JsonTests.cs +deleted file mode 100644 +index 4c719100..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/JsonTests.cs ++++ /dev/null +@@ -1,100 +0,0 @@ +-using System.Text; +-using Amazon.Lambda.Core; +-using Amazon.Lambda.TestUtilities; +-using AWS.Lambda.Powertools.Kafka.Json; +- +-#if DEBUG +-using KafkaAlias = AWS.Lambda.Powertools.Kafka; +-#else +-using KafkaAlias = AWS.Lambda.Powertools.Kafka.Json; +-#endif +- +-namespace AWS.Lambda.Powertools.Kafka.Tests; +- +-public class JsonTests +-{ +- [Fact] +- public void Given_JsonStreamInput_When_DeserializedWithJsonSerializer_Then_CorrectlyDeserializes() +- { +- // Given +- var serializer = new PowertoolsKafkaJsonSerializer(); +- string json = @"{ +- ""eventSource"": ""aws:kafka"", +- ""records"": { +- ""mytopic-0"": [ +- { +- ""topic"": ""mytopic"", +- ""partition"": 0, +- ""offset"": 15, +- ""timestamp"": 1645084650987, +- ""key"": """ + Convert.ToBase64String(Encoding.UTF8.GetBytes("key1")) + @""", +- ""value"": """ + Convert.ToBase64String(Encoding.UTF8.GetBytes("{\"Name\":\"JSON Test\",\"Price\":199.99,\"Id\":456}")) + @""" +- } +- ] +- } +- }"; +- +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); +- +- // When +- var result = serializer.Deserialize>(stream); +- +- // Then +- Assert.Equal("aws:kafka", result.EventSource); +- Assert.Single(result.Records); +- var record = result.First(); +- Assert.Equal("key1", record.Key); +- Assert.Equal("JSON Test", record.Value.Name); +- Assert.Equal(199.99m, record.Value.Price); +- Assert.Equal(456, record.Value.Id); +- } +- +- [Fact] +- public void Given_RawUtf8Data_When_ProcessedWithDefaultHandler_Then_DeserializesToStrings() +- { +- // Given +- string Handler(KafkaAlias.ConsumerRecords records, ILambdaContext context) +- { +- foreach (var record in records) +- { +- context.Logger.LogInformation($"Key: {record.Key}, Value: {record.Value}"); +- } +- return "Processed raw data"; +- } +- +- var mockLogger = new TestLambdaLogger(); +- var mockContext = new TestLambdaContext { Logger = mockLogger }; +- +- // Create Kafka event with raw base64-encoded strings +- string kafkaEventJson = @$"{{ +- ""eventSource"": ""aws:kafka"", +- ""records"": {{ +- ""mytopic-0"": [ +- {{ +- ""topic"": ""mytopic"", +- ""partition"": 0, +- ""offset"": 15, +- ""key"": ""{Convert.ToBase64String(Encoding.UTF8.GetBytes("simple-key"))}"", +- ""value"": ""{Convert.ToBase64String(Encoding.UTF8.GetBytes("Simple UTF-8 text value"))}"", +- ""headers"": [ +- {{ ""content-type"": [{(int)'t'}, {(int)'e'}, {(int)'x'}, {(int)'t'}] }} +- ] +- }} +- ] +- }} +- }}"; +- +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); +- +- // Use the default serializer which handles base64 → UTF-8 conversion +- var serializer = new PowertoolsKafkaJsonSerializer(); +- var records = serializer.Deserialize>(stream); +- +- // When +- var result = Handler(records, mockContext); +- +- // Then +- Assert.Equal("Processed raw data", result); +- Assert.Contains("Key: simple-key, Value: Simple UTF-8 text value", mockLogger.Buffer.ToString()); +- } +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/KafkaHandlerFunctionalTests.cs b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/KafkaHandlerFunctionalTests.cs +deleted file mode 100644 +index 9d360284..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/KafkaHandlerFunctionalTests.cs ++++ /dev/null +@@ -1,418 +0,0 @@ +-using System.Runtime.Serialization; +-using System.Text; +-using Amazon.Lambda.Core; +-using Amazon.Lambda.TestUtilities; +-using AWS.Lambda.Powertools.Kafka.Json; +-using TestKafka; +- +-#if DEBUG +-using KafkaAvro = AWS.Lambda.Powertools.Kafka; +-using KafkaProto = AWS.Lambda.Powertools.Kafka; +-using KafkaJson = AWS.Lambda.Powertools.Kafka; +-#else +-using KafkaAvro = AWS.Lambda.Powertools.Kafka.Avro; +-using KafkaProto = AWS.Lambda.Powertools.Kafka.Protobuf; +-using KafkaJson = AWS.Lambda.Powertools.Kafka.Json; +-#endif +- +-namespace AWS.Lambda.Powertools.Kafka.Tests; +- +-public class KafkaHandlerFunctionalTests +-{ +- #region JSON Serializer Tests +- +- [Fact] +- public void Given_SingleJsonRecord_When_ProcessedWithHandler_Then_SuccessfullyDeserializedAndProcessed() +- { +- // Given +- string Handler(KafkaJson.ConsumerRecords records, ILambdaContext context) +- { +- foreach (var record in records) +- { +- context.Logger.LogInformation($"Processing {record.Value.Name} at ${record.Value.Price}"); +- } +- return "Successfully processed JSON Kafka events"; +- } +- +- var mockLogger = new TestLambdaLogger(); +- var mockContext = new TestLambdaContext { Logger = mockLogger }; +- +- // Create a single record +- var records = new KafkaJson.ConsumerRecords +- { +- Records = new Dictionary>> +- { +- { "mytopic-0", new List> +- { +- new() +- { +- Topic = "mytopic", +- Partition = 0, +- Offset = 15, +- Timestamp = 1645084650987, +- TimestampType = "CREATE_TIME", +- Key = "product-123", +- Value = new JsonProduct { Name = "Laptop", Price = 999.99m, Id = 123 }, +- Headers = new Dictionary +- { +- { "source", Encoding.UTF8.GetBytes("online-store") } +- } +- } +- } +- } +- } +- }; +- +- // When +- var result = Handler(records, mockContext); +- +- // Then +- Assert.Equal("Successfully processed JSON Kafka events", result); +- Assert.Contains("Processing Laptop at $999.99", mockLogger.Buffer.ToString()); +- } +- +- [Fact] +- public void Given_MultipleJsonRecords_When_ProcessedWithHandler_Then_AllRecordsProcessed() +- { +- // Given +- int processedCount = 0; +- string Handler(KafkaJson.ConsumerRecords records, ILambdaContext context) +- { +- foreach (var record in records) +- { +- context.Logger.LogInformation($"Processing {record.Value.Name}"); +- processedCount++; +- } +- return $"Processed {processedCount} records"; +- } +- +- var mockLogger = new TestLambdaLogger(); +- var mockContext = new TestLambdaContext { Logger = mockLogger }; +- +- // Create multiple records +- var records = new KafkaJson.ConsumerRecords +- { +- Records = new Dictionary>> +- { +- { "mytopic-0", new List> +- { +- new() { Topic = "mytopic", Value = new JsonProduct { Name = "Laptop" } }, +- new() { Topic = "mytopic", Value = new JsonProduct { Name = "Phone" } }, +- new() { Topic = "mytopic", Value = new JsonProduct { Name = "Tablet" } } +- } +- } +- } +- }; +- +- // When +- var result = Handler(records, mockContext); +- +- // Then +- Assert.Equal("Processed 3 records", result); +- Assert.Contains("Processing Laptop", mockLogger.Buffer.ToString()); +- Assert.Contains("Processing Phone", mockLogger.Buffer.ToString()); +- Assert.Contains("Processing Tablet", mockLogger.Buffer.ToString()); +- } +- +- [Fact] +- public void Given_JsonRecordWithMetadata_When_ProcessedWithHandler_Then_MetadataIsAccessible() +- { +- // Given +- string Handler(KafkaJson.ConsumerRecords records, ILambdaContext context) +- { +- var record = records.First(); +- context.Logger.LogInformation($"Topic: {record.Topic}, Partition: {record.Partition}, Offset: {record.Offset}, Time: {record.Timestamp}"); +- return "Metadata accessed"; +- } +- +- var mockLogger = new TestLambdaLogger(); +- var mockContext = new TestLambdaContext { Logger = mockLogger }; +- +- var records = new KafkaJson.ConsumerRecords +- { +- Records = new Dictionary>> +- { +- { "mytopic-0", new List> +- { +- new() +- { +- Topic = "sales-data", +- Partition = 3, +- Offset = 42, +- Timestamp = 1645084650987, +- TimestampType = "CREATE_TIME", +- Value = new JsonProduct { Name = "Metadata Test" } +- } +- } +- } +- } +- }; +- +- // When +- var result = Handler(records, mockContext); +- +- // Then +- Assert.Equal("Metadata accessed", result); +- Assert.Contains("Topic: sales-data, Partition: 3, Offset: 42", mockLogger.Buffer.ToString()); +- } +- +- [Fact] +- public void Given_JsonRecordWithHeaders_When_ProcessedWithHandler_Then_HeadersAreAccessible() +- { +- // Given +- string Handler(KafkaJson.ConsumerRecords records, ILambdaContext context) +- { +- var record = records.First(); +- var source = record.Headers["source"].DecodedValue(); +- var contentType = record.Headers["content-type"].DecodedValue(); +- context.Logger.LogInformation($"Headers: source={source}, content-type={contentType}"); +- return "Headers processed"; +- } +- +- var mockLogger = new TestLambdaLogger(); +- var mockContext = new TestLambdaContext { Logger = mockLogger }; +- +- var records = new KafkaJson.ConsumerRecords +- { +- Records = new Dictionary>> +- { +- { "mytopic-0", new List> +- { +- new() +- { +- Value = new JsonProduct { Name = "Header Test" }, +- Headers = new Dictionary +- { +- { "source", Encoding.UTF8.GetBytes("web-app") }, +- { "content-type", Encoding.UTF8.GetBytes("application/json") } +- } +- } +- } +- } +- } +- }; +- +- // When +- var result = Handler(records, mockContext); +- +- // Then +- Assert.Equal("Headers processed", result); +- Assert.Contains("Headers: source=web-app, content-type=application/json", mockLogger.Buffer.ToString()); +- } +- +- #endregion +- +- #region Avro Serializer Tests +- +- [Fact] +- public void Given_SingleAvroRecord_When_ProcessedWithHandler_Then_SuccessfullyDeserializedAndProcessed() +- { +- // Given +- string Handler(KafkaAvro.ConsumerRecords records, ILambdaContext context) +- { +- foreach (var record in records) +- { +- context.Logger.LogInformation($"Processing {record.Value.name} at ${record.Value.price}"); +- } +- return "Successfully processed Avro Kafka events"; +- } +- +- var mockLogger = new TestLambdaLogger(); +- var mockContext = new TestLambdaContext { Logger = mockLogger }; +- +- // Create a single record +- var records = new KafkaAvro.ConsumerRecords +- { +- Records = new Dictionary>> +- { +- { "mytopic-0", new List> +- { +- new() +- { +- Topic = "mytopic", +- Partition = 0, +- Offset = 15, +- Key = "avro-key", +- Value = new AvroProduct { name = "Camera", price = 349.95 } +- } +- } +- } +- } +- }; +- +- // When +- var result = Handler(records, mockContext); +- +- // Then +- Assert.Equal("Successfully processed Avro Kafka events", result); +- Assert.Contains("Processing Camera at $349.95", mockLogger.Buffer.ToString()); +- } +- +- [Fact] +- public void Given_ComplexAvroKey_When_ProcessedWithHandler_Then_KeyIsCorrectlyDeserialized() +- { +- // Given +- string Handler(KafkaAvro.ConsumerRecords records, ILambdaContext context) +- { +- var record = records.First(); +- context.Logger.LogInformation($"Processing product with key ID: {record.Key.id}, color: {record.Key.color}"); +- return "Successfully processed complex keys"; +- } +- +- var mockLogger = new TestLambdaLogger(); +- var mockContext = new TestLambdaContext { Logger = mockLogger }; +- +- var records = new KafkaAvro.ConsumerRecords +- { +- Records = new Dictionary>> +- { +- { "mytopic-0", new List> +- { +- new() +- { +- Key = new AvroKey { id = 42, color = Color.GREEN }, +- Value = new AvroProduct { name = "Green Item", price = 49.99 } +- } +- } +- } +- } +- }; +- +- // When +- var result = Handler(records, mockContext); +- +- // Then +- Assert.Equal("Successfully processed complex keys", result); +- Assert.Contains("Processing product with key ID: 42, color: GREEN", mockLogger.Buffer.ToString()); +- } +- +- [Fact] +- public void Given_MissingAvroSchema_When_DeserializedWithAvroSerializer_Then_ReturnsException() +- { +- // Arrange +- var serializer = new AWS.Lambda.Powertools.Kafka.Avro.PowertoolsKafkaAvroSerializer(); +- +- // Create data that looks like Avro but without schema +- byte[] invalidAvroData = { 0x01, 0x02, 0x03, 0x04 }; // Just some random bytes +- string base64Data = Convert.ToBase64String(invalidAvroData); +- +- string kafkaEventJson = @$"{{ +- ""eventSource"": ""aws:kafka"", +- ""records"": {{ +- ""mytopic-0"": [ +- {{ +- ""topic"": ""mytopic"", +- ""partition"": 0, +- ""offset"": 15, +- ""key"": ""{Convert.ToBase64String(Encoding.UTF8.GetBytes("test-key"))}"", +- ""value"": ""{base64Data}"" +- }} +- ] +- }} +- }}"; +- +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); +- Assert.Throws(() => +- serializer.Deserialize>(stream)); +- } +- +- #endregion +- +- #region Protobuf Serializer Tests +- +- [Fact] +- public void Given_SingleProtobufRecord_When_ProcessedWithHandler_Then_SuccessfullyDeserializedAndProcessed() +- { +- // Given +- string Handler(KafkaProto.ConsumerRecords records, ILambdaContext context) +- { +- foreach (var record in records) +- { +- context.Logger.LogInformation($"Processing {record.Value.Name} at ${record.Value.Price}"); +- } +- return "Successfully processed Protobuf Kafka events"; +- } +- +- var mockLogger = new TestLambdaLogger(); +- var mockContext = new TestLambdaContext { Logger = mockLogger }; +- +- // Create a single record +- var records = new KafkaProto.ConsumerRecords +- { +- Records = new Dictionary>> +- { +- { "mytopic-0", new List> +- { +- new() +- { +- Topic = "mytopic", +- Partition = 0, +- Offset = 15, +- Key = 42, +- Value = new ProtobufProduct { Name = "Smart Watch", Id = 789, Price = 249.99 } +- } +- } +- } +- } +- }; +- +- // When +- var result = Handler(records, mockContext); +- +- // Then +- Assert.Equal("Successfully processed Protobuf Kafka events", result); +- Assert.Contains("Processing Smart Watch at $249.99", mockLogger.Buffer.ToString()); +- } +- +- [Fact] +- public void Given_NullKeyOrValue_When_ProcessedWithHandler_Then_HandlesNullsCorrectly() +- { +- // Given +- string Handler(KafkaProto.ConsumerRecords records, ILambdaContext context) +- { +- foreach (var record in records) +- { +- string keyInfo = record.Key.HasValue ? record.Key.Value.ToString() : "null"; +- string valueInfo = record.Value != null ? record.Value.Name : "null"; +- context.Logger.LogInformation($"Key: {keyInfo}, Value: {valueInfo}"); +- } +- return "Processed records with nulls"; +- } +- +- var mockLogger = new TestLambdaLogger(); +- var mockContext = new TestLambdaContext { Logger = mockLogger }; +- +- var records = new KafkaProto.ConsumerRecords +- { +- Records = new Dictionary>> +- { +- { "mytopic-0", new List> +- { +- new() { Key = 1, Value = new ProtobufProduct { Name = "Valid Product" } }, +- new() { Key = null, Value = new ProtobufProduct { Name = "No Key" } }, +- new() { Key = 3, Value = null } +- } +- } +- } +- }; +- +- // When +- var result = Handler(records, mockContext); +- +- // Then +- Assert.Equal("Processed records with nulls", result); +- Assert.Contains("Key: 1, Value: Valid Product", mockLogger.Buffer.ToString()); +- Assert.Contains("Key: null, Value: No Key", mockLogger.Buffer.ToString()); +- Assert.Contains("Key: 3, Value: null", mockLogger.Buffer.ToString()); +- } +- +- #endregion +-} +- +-// Model classes for testing +-public class JsonProduct +-{ +- public string Name { get; set; } +- public int Id { get; set; } +- public decimal Price { get; set; } +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/PowertoolsKafkaSerializerBaseTests.cs b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/PowertoolsKafkaSerializerBaseTests.cs +deleted file mode 100644 +index b8832b0d..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/PowertoolsKafkaSerializerBaseTests.cs ++++ /dev/null +@@ -1,751 +0,0 @@ +-using System.Runtime.Serialization; +-using System.Text; +-using System.Text.Json; +-using System.Text.Json.Serialization; +-using AWS.Lambda.Powertools.Kafka.Avro; +- +-namespace AWS.Lambda.Powertools.Kafka.Tests +-{ +- /// +- /// Additional tests for PowertoolsKafkaSerializerBase +- /// +- public class PowertoolsKafkaSerializerBaseTests +- { +- /// +- /// Simple serializer implementation for testing base class +- /// +- private class TestKafkaSerializer : PowertoolsKafkaSerializerBase +- { +- public TestKafkaSerializer() : base() +- { +- } +- +- public TestKafkaSerializer(JsonSerializerOptions options) : base(options) +- { +- } +- +- public TestKafkaSerializer(JsonSerializerContext context) : base(context) +- { +- } +- +- public TestKafkaSerializer(JsonSerializerOptions options, JsonSerializerContext context) +- : base(options, context) +- { +- } +- +- // Implementation of the abstract method for test purposes +- protected override object? DeserializeComplexTypeFormat(byte[] data, +- Type targetType, bool isKey, SchemaMetadata? schemaMetadata = null) +- { +- // Test implementation using JSON for all complex types +- var jsonStr = Encoding.UTF8.GetString(data); +- +- if (SerializerContext != null) +- { +- var typeInfo = SerializerContext.GetTypeInfo(targetType); +- if (typeInfo != null) +- { +- return JsonSerializer.Deserialize(jsonStr, typeInfo); +- } +- } +- +- return JsonSerializer.Deserialize(jsonStr, targetType, JsonOptions); +- } +- +- // Expose protected methods for direct testing +- public object? TestDeserializeFormatSpecific(byte[] data, Type targetType, bool isKey, +- SchemaMetadata? schemaMetadata = null) +- { +- return DeserializeFormatSpecific(data, targetType, isKey, schemaMetadata); +- } +- +- public object? TestDeserializeComplexTypeFormat(byte[] data, Type targetType, bool isKey, +- SchemaMetadata? schemaMetadata = null) +- { +- return DeserializeComplexTypeFormat(data, targetType, isKey, schemaMetadata); +- } +- +- public object? TestDeserializePrimitiveValue(byte[] data, Type targetType) +- { +- return DeserializePrimitiveValue(data, targetType); +- } +- +- public bool TestIsPrimitiveOrSimpleType(Type type) +- { +- return IsPrimitiveOrSimpleType(type); +- } +- +- public object TestDeserializeValue(string base64Value, Type valueType, +- SchemaMetadata? schemaMetadata = null) +- { +- return DeserializeValue(base64Value, valueType, schemaMetadata); +- } +- } +- +- [Fact] +- public void Deserialize_BooleanValues_HandlesCorrectly() +- { +- // Arrange +- var serializer = new TestKafkaSerializer(); +- string kafkaEventJson = CreateKafkaEvent( +- keyValue: "dHJ1ZQ==", // "true" in base64 +- valueValue: "AQ==" // byte[1] = {1} in base64 +- ); +- +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); +- +- // Act +- var result = serializer.Deserialize>(stream); +- +- // Assert +- Assert.NotNull(result); +- var firstRecord = result.First(); +- Assert.Equal("true", firstRecord.Key); +- Assert.True(firstRecord.Value); +- } +- +- [Fact] +- public void Deserialize_NumericValues_HandlesCorrectly() +- { +- // Arrange +- var serializer = new TestKafkaSerializer(); +- string kafkaEventJson = CreateKafkaEvent( +- keyValue: "NDI=", // "42" in base64 +- valueValue: "MTIzNA==" // "1234" in base64 +- ); +- +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); +- +- // Act +- var result = serializer.Deserialize>(stream); +- +- // Assert +- Assert.NotNull(result); +- var firstRecord = result.First(); +- Assert.Equal(42, firstRecord.Key); +- Assert.Equal(1234, firstRecord.Value); +- } +- +- [Fact] +- public void Deserialize_GuidValues_HandlesCorrectly() +- { +- // Arrange +- var guid = Guid.NewGuid(); +- var serializer = new TestKafkaSerializer(); +- string kafkaEventJson = CreateKafkaEvent( +- keyValue: Convert.ToBase64String(guid.ToByteArray()), +- valueValue: Convert.ToBase64String(Encoding.UTF8.GetBytes(guid.ToString())) +- ); +- +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); +- +- // Act +- var result = serializer.Deserialize>(stream); +- +- // Assert +- Assert.NotNull(result); +- var firstRecord = result.First(); +- Assert.Equal(guid, firstRecord.Key); +- Assert.Equal(guid.ToString(), firstRecord.Value); +- } +- +- [Fact] +- public void Deserialize_InvalidJson_ThrowsException() +- { +- // Arrange +- var serializer = new TestKafkaSerializer(); +- string invalidJson = "{ this is not valid json }"; +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(invalidJson)); +- +- // Act & Assert +- Assert.ThrowsAny(() => +- serializer.Deserialize>(stream)); +- } +- +- [Fact] +- public void Deserialize_MalformedBase64_ThrowsException() +- { +- // Arrange +- var serializer = new TestKafkaSerializer(); +- string kafkaEventJson = CreateKafkaEvent( +- keyValue: "not-base64!", +- valueValue: "valid-base64==" +- ); +- +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); +- +- // Act & Assert +- var ex = Assert.Throws(() => +- serializer.Deserialize>(stream)); +- +- Assert.Contains("Failed to deserialize key data", ex.Message); +- } +- +- [Fact] +- public void Serialize_ValidObject_WritesToStream() +- { +- // Arrange +- var serializer = new TestKafkaSerializer(); +- var testObject = new { Name = "Test", Value = 42 }; +- using var responseStream = new MemoryStream(); +- +- // Act +- serializer.Serialize(testObject, responseStream); +- responseStream.Position = 0; +- string result = Encoding.UTF8.GetString(responseStream.ToArray()); +- +- // Assert +- Assert.Contains("\"Name\":\"Test\"", result); +- Assert.Contains("\"Value\":42", result); +- } +- +- [Fact] +- public void Serialize_NullObject_WritesNullToStream() +- { +- // Arrange +- var serializer = new TestKafkaSerializer(); +- using var responseStream = new MemoryStream(); +- +- // Act +- serializer.Serialize(null, responseStream); +- responseStream.Position = 0; +- string result = Encoding.UTF8.GetString(responseStream.ToArray()); +- +- // Assert +- Assert.Equal("null", result); +- } +- +- [Fact] +- public void DeserializePrimitiveValue_EmptyBytes_ReturnsNull() +- { +- // Arrange +- var serializer = new TestKafkaSerializer(); +- +- // Act +- var result = serializer.TestDeserializePrimitiveValue(Array.Empty(), typeof(string)); +- +- // Assert +- Assert.Null(result); +- } +- +- [Fact] +- public void DeserializePrimitiveValue_LongValue_DeserializesCorrectly() +- { +- // Arrange +- var serializer = new TestKafkaSerializer(); +- var longBytes = BitConverter.GetBytes(long.MaxValue); +- +- // Act +- var result = serializer.TestDeserializePrimitiveValue(longBytes, typeof(long)); +- +- // Assert +- Assert.Equal(long.MaxValue, result); +- } +- +- [Fact] +- public void DeserializePrimitiveValue_DoubleValue_DeserializesCorrectly() +- { +- // Arrange +- var serializer = new TestKafkaSerializer(); +- var doubleBytes = BitConverter.GetBytes(3.14159); +- +- // Act +- var result = serializer.TestDeserializePrimitiveValue(doubleBytes, typeof(double)); +- +- // Assert +- Assert.Equal(3.14159, result); +- } +- +- [Fact] +- public void ProcessHeaders_MultipleHeaders_DeserializesCorrectly() +- { +- // Arrange +- var serializer = new TestKafkaSerializer(); +- string kafkaEventJson = @$"{{ +- ""eventSource"": ""aws:kafka"", +- ""records"": {{ +- ""mytopic-0"": [ +- {{ +- ""topic"": ""mytopic"", +- ""partition"": 0, +- ""offset"": 15, +- ""key"": ""{Convert.ToBase64String(Encoding.UTF8.GetBytes("key"))}"", +- ""value"": ""{Convert.ToBase64String(Encoding.UTF8.GetBytes("value"))}"", +- ""headers"": [ +- {{ ""header1"": [104, 101, 108, 108, 111] }}, +- {{ ""header2"": [119, 111, 114, 108, 100] }} +- ] +- }} +- ] +- }} +- }}"; +- +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); +- +- // Act +- var result = serializer.Deserialize>(stream); +- +- // Assert +- var record = result.First(); +- Assert.Equal(2, record.Headers.Count); +- Assert.Equal("hello", Encoding.ASCII.GetString(record.Headers["header1"])); +- Assert.Equal("world", Encoding.ASCII.GetString(record.Headers["header2"])); +- } +- +- [Fact] +- public void Deserialize_WithSerializerContext_UsesContextForRegisteredTypes() +- { +- // Arrange +- var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; +- var context = new TestSerializerContext(options); +- // Use only options for constructor, but we'll make the context available for the model deserialization +- var serializer = new TestKafkaSerializer(options); +- +- var testModel = new TestModel { Name = "Test", Value = 123 }; +- var modelJson = JsonSerializer.Serialize(testModel, context.TestModel); +- var base64Value = Convert.ToBase64String(Encoding.UTF8.GetBytes(modelJson)); +- +- string kafkaEventJson = CreateKafkaEvent( +- keyValue: Convert.ToBase64String(Encoding.UTF8.GetBytes("testKey")), +- valueValue: base64Value +- ); +- +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); +- +- // Act +- var result = serializer.Deserialize>(stream); +- +- // Assert +- Assert.NotNull(result); +- var record = result.First(); +- Assert.Equal("testKey", record.Key); +- Assert.Equal("Test", record.Value.Name); +- Assert.Equal(123, record.Value.Value); +- } +- +- [Fact] +- public void Serialize_WithSerializerContext_UsesContextForRegisteredTypes() +- { +- // Arrange +- var options = new JsonSerializerOptions(); +- var context = new TestSerializerContext(options); +- var serializer = new TestKafkaSerializer(options, context); +- +- var testModel = new TestModel { Name = "Test", Value = 123 }; +- using var responseStream = new MemoryStream(); +- +- // Act +- serializer.Serialize(testModel, responseStream); +- responseStream.Position = 0; +- string result = Encoding.UTF8.GetString(responseStream.ToArray()); +- +- // Assert +- Assert.Contains("\"Name\":\"Test\"", result); +- Assert.Contains("\"Value\":123", result); +- } +- +- [Fact] +- public void Deserialize_WithSerializerContext_FallsBackWhenTypeNotRegistered() +- { +- // Arrange +- var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; +- var context = new TestSerializerContext(options); +- var serializer = new TestKafkaSerializer(options, context); +- +- // Using a non-registered type (Dictionary instead of TestModel) +- var dictionary = new Dictionary { ["Key"] = 42 }; +- var dictJson = JsonSerializer.Serialize(dictionary); +- var base64Value = Convert.ToBase64String(Encoding.UTF8.GetBytes(dictJson)); +- +- string kafkaEventJson = CreateKafkaEvent( +- keyValue: Convert.ToBase64String(Encoding.UTF8.GetBytes("testKey")), +- valueValue: base64Value +- ); +- +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); +- +- // Act +- var result = serializer.Deserialize>>(stream); +- +- // Assert +- Assert.NotNull(result); +- var record = result.First(); +- Assert.Equal("testKey", record.Key); +- Assert.Single(record.Value); +- Assert.Equal(42, record.Value["Key"]); +- } +- +- [Fact] +- public void Serialize_NonRegisteredType_FallsBackToRegularSerialization() +- { +- // Arrange +- var options = new JsonSerializerOptions(); +- // Use serializer WITHOUT context to test the fallback path +- var serializer = new TestKafkaSerializer(options); +- +- // Using a non-registered type +- var nonRegisteredType = new { Id = Guid.NewGuid(), Message = "Not in context" }; +- using var responseStream = new MemoryStream(); +- +- // Act +- serializer.Serialize(nonRegisteredType, responseStream); +- responseStream.Position = 0; +- string result = Encoding.UTF8.GetString(responseStream.ToArray()); +- +- // Assert +- Assert.Contains("\"Id\":", result); +- Assert.Contains("\"Message\":\"Not in context\"", result); +- } +- +- [Fact] +- public void Deserialize_NonConsumerRecordWithSerializerContext_UsesTypeInfo() +- { +- // Arrange +- var options = new JsonSerializerOptions(); +- var context = new TestSerializerContext(options); +- var serializer = new TestKafkaSerializer(options, context); +- +- var testModel = new TestModel { Name = "DirectDeserialization", Value = 42 }; +- var json = JsonSerializer.Serialize(testModel); +- +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); +- +- // Act +- var result = serializer.Deserialize(stream); +- +- // Assert +- Assert.NotNull(result); +- Assert.Equal("DirectDeserialization", result.Name); +- Assert.Equal(42, result.Value); +- } +- +- [Fact] +- public void Deserialize_NonConsumerRecordWithoutTypeInfo_UsesRegularDeserialize() +- { +- // Arrange +- var options = new JsonSerializerOptions(); +- var context = new TestSerializerContext(options); +- var serializer = new TestKafkaSerializer(options, context); +- +- // Dictionary is not registered in TestSerializerContext +- var dict = new Dictionary { ["test"] = 123 }; +- var json = JsonSerializer.Serialize(dict); +- +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); +- +- // Act +- var result = serializer.Deserialize>(stream); +- +- // Assert +- Assert.NotNull(result); +- Assert.Equal(123, result["test"]); +- } +- +- [Fact] +- public void Deserialize_NonConsumerRecordFailed_ThrowsException() +- { +- // Arrange +- var serializer = new TestKafkaSerializer(); +- var invalidJson = "{ invalid json"; +- +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(invalidJson)); +- +- // Act & Assert +- // With invalid JSON input, JsonSerializer throws JsonException directly +- var ex = Assert.Throws(() => +- serializer.Deserialize(stream)); +- +- // Check that we're getting a JSON parsing error +- Assert.Contains("invalid", ex.Message.ToLower()); +- } +- +- [Theory] +- [InlineData(new byte[] { 42 }, 42)] // Single byte +- [InlineData(new byte[] { 0x2A, 0x00, 0x00, 0x00 }, 42)] // Four bytes +- public void DeserializePrimitiveValue_IntWithDifferentByteFormats_DeserializesCorrectly(byte[] bytes, +- int expected) +- { +- // Arrange +- var serializer = new TestKafkaSerializer(); +- +- // Act +- var result = serializer.TestDeserializePrimitiveValue(bytes, typeof(int)); +- +- // Assert +- Assert.Equal(expected, result); +- } +- +- [Theory] +- [InlineData(new byte[] { 0x2A, 0x00, 0x00, 0x00 }, 42L)] // Four bytes as int +- [InlineData(new byte[] { 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 42L)] // Eight bytes as long +- public void DeserializePrimitiveValue_LongWithDifferentByteFormats_DeserializesCorrectly(byte[] bytes, +- long expected) +- { +- // Arrange +- var serializer = new TestKafkaSerializer(); +- +- // Act +- var result = serializer.TestDeserializePrimitiveValue(bytes, typeof(long)); +- +- // Assert +- Assert.Equal(expected, result); +- } +- +- [Fact] +- public void DeserializePrimitiveValue_DoubleWithShortBytes_ReturnsZero() +- { +- // Arrange +- var serializer = new TestKafkaSerializer(); +- var shortBytes = new byte[] { 0x00, 0x00, 0x00, 0x00 }; // Less than 8 bytes +- +- // Act +- var result = serializer.TestDeserializePrimitiveValue(shortBytes, typeof(double)); +- +- // Assert +- Assert.Equal(0.0, result); +- } +- +- [Fact] +- public void Serialize_WithTypeInfoFromContext_WritesToStream() +- { +- // Arrange +- var options = new JsonSerializerOptions(); +- var context = new TestSerializerContext(options); +- var serializer = new TestKafkaSerializer(options, context); +- +- var testModel = new TestModel { Name = "ContextSerialization", Value = 555 }; +- using var responseStream = new MemoryStream(); +- +- // Act +- serializer.Serialize(testModel, responseStream); +- responseStream.Position = 0; +- string result = Encoding.UTF8.GetString(responseStream.ToArray()); +- +- // Assert +- Assert.Contains("\"Name\":\"ContextSerialization\"", result); +- Assert.Contains("\"Value\":555", result); +- } +- +- [Fact] +- public void Deserialize_WithSchemaMetadata_PopulatesSchemaMetadataProperties() +- { +- // Arrange +- var serializer = new TestKafkaSerializer(); +- +- string kafkaEventJson = @$"{{ +- ""eventSource"": ""aws:kafka"", +- ""eventSourceArn"": ""arn:aws:kafka:us-east-1:0123456789019:cluster/TestCluster/abcd1234"", +- ""bootstrapServers"": ""b-1.test-cluster.kafka.us-east-1.amazonaws.com:9092"", +- ""records"": {{ +- ""mytopic-0"": [ +- {{ +- ""topic"": ""mytopic"", +- ""partition"": 0, +- ""offset"": 15, +- ""timestamp"": 1645084650987, +- ""timestampType"": ""CREATE_TIME"", +- ""key"": ""{Convert.ToBase64String(Encoding.UTF8.GetBytes("testKey"))}"", +- ""value"": ""{Convert.ToBase64String(Encoding.UTF8.GetBytes("testValue"))}"", +- ""headers"": [ +- {{ ""headerKey"": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] }} +- ], +- ""keySchemaMetadata"": {{ +- ""dataFormat"": ""JSON"", +- ""schemaId"": ""key-schema-001"" +- }}, +- ""valueSchemaMetadata"": {{ +- ""dataFormat"": ""AVRO"", +- ""schemaId"": ""value-schema-002"" +- }} +- }} +- ] +- }} +- }}"; +- +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); +- +- // Act +- var result = serializer.Deserialize>(stream); +- +- // Assert +- Assert.NotNull(result); +- var record = result.First(); +- +- // Assert key schema metadata +- Assert.NotNull(record.KeySchemaMetadata); +- Assert.Equal("JSON", record.KeySchemaMetadata.DataFormat); +- Assert.Equal("key-schema-001", record.KeySchemaMetadata.SchemaId); +- +- // Assert value schema metadata +- Assert.NotNull(record.ValueSchemaMetadata); +- Assert.Equal("AVRO", record.ValueSchemaMetadata.DataFormat); +- Assert.Equal("value-schema-002", record.ValueSchemaMetadata.SchemaId); +- } +- +- // NEW TESTS FOR LATEST CHANGES +- +- [Fact] +- public void DeserializeFormatSpecific_PrimitiveType_UsesDeserializePrimitiveValue() +- { +- // Arrange +- var serializer = new TestKafkaSerializer(); +- var stringBytes = Encoding.UTF8.GetBytes("primitive-test"); +- +- // Act +- var result = +- serializer.TestDeserializeFormatSpecific(stringBytes, typeof(string), isKey: false, +- schemaMetadata: null); +- +- // Assert +- Assert.Equal("primitive-test", result); +- } +- +- [Fact] +- public void DeserializeFormatSpecific_ComplexType_UsesDeserializeComplexTypeFormat() +- { +- // Arrange +- var serializer = new TestKafkaSerializer(); +- var complexObject = new TestModel { Name = "complex-test", Value = 42 }; +- var jsonBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(complexObject)); +- +- // Act +- var result = +- serializer.TestDeserializeFormatSpecific(jsonBytes, typeof(TestModel), isKey: false, +- schemaMetadata: null); +- +- // Assert +- Assert.NotNull(result); +- var testModel = (TestModel)result!; +- Assert.Equal("complex-test", testModel.Name); +- Assert.Equal(42, testModel.Value); +- } +- +- [Fact] +- public void DeserializeComplexTypeFormat_ValidJson_DeserializesCorrectly() +- { +- // Arrange +- var serializer = new TestKafkaSerializer(); +- var complexObject = new TestModel { Name = "direct-test", Value = 123 }; +- var jsonBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(complexObject)); +- +- // Act +- var result = +- serializer.TestDeserializeComplexTypeFormat(jsonBytes, typeof(TestModel), isKey: true, +- schemaMetadata: null); +- +- // Assert +- Assert.NotNull(result); +- var testModel = (TestModel)result!; +- Assert.Equal("direct-test", testModel.Name); +- Assert.Equal(123, testModel.Value); +- } +- +- [Fact] +- public void DeserializeComplexTypeFormat_InvalidJson_ThrowsException() +- { +- // Arrange +- var serializer = new TestKafkaSerializer(); +- var invalidBytes = new byte[] { 0xDE, 0xAD, 0xBE, 0xEF }; // Invalid JSON data +- +- // Act & Assert +- // The TestKafkaSerializer throws JsonException directly for invalid JSON +- var ex = Assert.Throws(() => +- serializer.TestDeserializeComplexTypeFormat(invalidBytes, typeof(TestModel), isKey: true, +- schemaMetadata: null)); +- +- Assert.Contains("invalid", ex.Message.ToLower()); +- } +- +- [Fact] +- public void DeserializeValue_Base64String_DeserializesCorrectly() +- { +- // Arrange +- var serializer = new TestKafkaSerializer(); +- var testValue = "test-value-123"; +- var base64Value = Convert.ToBase64String(Encoding.UTF8.GetBytes(testValue)); +- +- // Act +- var result = serializer.TestDeserializeValue(base64Value, typeof(string), schemaMetadata: null); +- +- // Assert +- Assert.Equal(testValue, result); +- } +- +- [Fact] +- public void DeserializeValue_WithSchemaMetadata_PassesMetadataToFormatSpecific() +- { +- // Arrange +- var serializer = new TestKafkaSerializer(); +- var testValue = "test-value-with-metadata"; +- var base64Value = Convert.ToBase64String(Encoding.UTF8.GetBytes(testValue)); +- var schemaMetadata = new SchemaMetadata { DataFormat = "JSON", SchemaId = "test-schema-001" }; +- +- // Act +- var result = serializer.TestDeserializeValue(base64Value, typeof(string), schemaMetadata); +- +- // Assert +- Assert.Equal(testValue, result); +- } +- +- [Fact] +- public void IsPrimitiveOrSimpleType_ChecksVariousTypes() +- { +- // Arrange +- var serializer = new TestKafkaSerializer(); +- +- // Act & Assert +- // Primitive types +- Assert.True(serializer.TestIsPrimitiveOrSimpleType(typeof(int))); +- Assert.True(serializer.TestIsPrimitiveOrSimpleType(typeof(long))); +- Assert.True(serializer.TestIsPrimitiveOrSimpleType(typeof(bool))); +- +- // Simple types +- Assert.True(serializer.TestIsPrimitiveOrSimpleType(typeof(string))); +- Assert.True(serializer.TestIsPrimitiveOrSimpleType(typeof(Guid))); +- Assert.True(serializer.TestIsPrimitiveOrSimpleType(typeof(DateTime))); +- +- // Complex types +- Assert.False(serializer.TestIsPrimitiveOrSimpleType(typeof(TestModel))); +- Assert.False(serializer.TestIsPrimitiveOrSimpleType(typeof(Dictionary))); +- } +- +- // Helper method to create Kafka event JSON with specified key and value +- private string CreateKafkaEvent(string keyValue, string valueValue) +- { +- return @$"{{ +- ""eventSource"": ""aws:kafka"", +- ""eventSourceArn"": ""arn:aws:kafka:us-east-1:0123456789019:cluster/TestCluster/abcd1234"", +- ""bootstrapServers"": ""b-1.test-cluster.kafka.us-east-1.amazonaws.com:9092"", +- ""records"": {{ +- ""mytopic-0"": [ +- {{ +- ""topic"": ""mytopic"", +- ""partition"": 0, +- ""offset"": 15, +- ""timestamp"": 1645084650987, +- ""timestampType"": ""CREATE_TIME"", +- ""key"": ""{keyValue}"", +- ""value"": ""{valueValue}"", +- ""headers"": [ +- {{ ""headerKey"": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] }} +- ] +- }} +- ] +- }} +- }}"; +- } +- } +- +- [JsonSerializable(typeof(TestModel))] +- [JsonSerializable(typeof(ConsumerRecords))] +- [JsonSerializable(typeof(Dictionary))] +- public partial class TestSerializerContext : JsonSerializerContext +- { +- } +- +- public class TestModel +- { +- public string Name { get; set; } +- public int Value { get; set; } +- } +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/HandlerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/HandlerTests.cs +deleted file mode 100644 +index ac2de2ca..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/HandlerTests.cs ++++ /dev/null +@@ -1,368 +0,0 @@ +-using System.Text; +-using Amazon.Lambda.Core; +-using Amazon.Lambda.TestUtilities; +-using AWS.Lambda.Powertools.Kafka.Protobuf; +-using Google.Protobuf; +-using TestKafka; +- +-#if DEBUG +-using KafkaAlias = AWS.Lambda.Powertools.Kafka; +-#else +-using KafkaAlias = AWS.Lambda.Powertools.Kafka.Protobuf; +-#endif +- +-namespace AWS.Lambda.Powertools.Kafka.Tests.Protobuf; +- +-public class ProtobufHandlerTests +-{ +- [Fact] +- public async Task Handler_ProcessesKafkaEvent_Successfully() +- { +- // Arrange +- var kafkaJson = GetMockKafkaEvent(); +- var mockContext = new TestLambdaContext(); +- var serializer = new PowertoolsKafkaProtobufSerializer(); +- +- // Convert JSON string to stream for deserialization +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaJson)); +- +- // Act - Deserialize and process +- var kafkaEvent = serializer.Deserialize>(stream); +- var response = await Handler(kafkaEvent, mockContext); +- +- // Assert +- Assert.Equal("Successfully processed Protobuf Kafka events", response); +- +- // Verify event structure +- Assert.Equal("aws:kafka", kafkaEvent.EventSource); +- Assert.Single(kafkaEvent.Records); +- +- // Verify record content +- var records = kafkaEvent.Records["mytopic-0"]; +- Assert.Equal(3, records.Count); +- +- // Verify first record +- var firstRecord = records[0]; +- Assert.Equal("mytopic", firstRecord.Topic); +- Assert.Equal(0, firstRecord.Partition); +- Assert.Equal(15, firstRecord.Offset); +- +- // Verify deserialized value +- var product = firstRecord.Value; +- Assert.Equal("Laptop", product.Name); +- Assert.Equal(999.99, product.Price); +- +- // Verify decoded key and headers +- Assert.Equal(42, firstRecord.Key); +- Assert.Equal("headerValue", firstRecord.Headers["headerKey"].DecodedValue()); +- +- var secondRecord = records[1]; +- Assert.Equal(43, secondRecord.Key); +- +- var thirdRecord = records[2]; +- Assert.Equal(0, thirdRecord.Key); +- } +- +- [Fact] +- public async Task Handler_ProcessesKafkaEvent_WithProtobufKey_Successfully() +- { +- // Arrange +- var kafkaJson = GetMockKafkaEventWithProtobufKeys(); +- var mockContext = new TestLambdaContext(); +- var serializer = new PowertoolsKafkaProtobufSerializer(); +- +- // Convert JSON string to stream for deserialization +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaJson)); +- +- // Act - Deserialize and process +- var kafkaEvent = serializer.Deserialize>(stream); +- var response = await HandlerWithProtobufKeys(kafkaEvent, mockContext); +- +- // Assert +- Assert.Equal("Successfully processed Protobuf Kafka events with complex keys", response); +- +- // Verify event structure +- Assert.Equal("aws:kafka", kafkaEvent.EventSource); +- Assert.Single(kafkaEvent.Records); +- +- // Verify record content +- var records = kafkaEvent.Records["mytopic-0"]; +- Assert.Equal(3, records.Count); +- +- // Verify first record +- var firstRecord = records[0]; +- Assert.Equal("mytopic", firstRecord.Topic); +- Assert.Equal(0, firstRecord.Partition); +- Assert.Equal(15, firstRecord.Offset); +- +- // Verify deserialized Protobuf key and value +- Assert.Equal("Laptop", firstRecord.Value.Name); +- Assert.Equal(999.99, firstRecord.Value.Price); +- Assert.Equal(1, firstRecord.Key.Id); +- Assert.Equal(TestKafka.Color.Green, firstRecord.Key.Color); +- +- // Verify headers +- Assert.Equal("headerValue", firstRecord.Headers["headerKey"].DecodedValue()); +- +- var secondRecord = records[1]; +- Assert.Equal(2, secondRecord.Key.Id); +- Assert.Equal(TestKafka.Color.Unknown, secondRecord.Key.Color); +- +- var thirdRecord = records[2]; +- Assert.Equal(3, thirdRecord.Key.Id); +- Assert.Equal(TestKafka.Color.Red, thirdRecord.Key.Color); +- } +- +- private string GetMockKafkaEvent() +- { +- // For testing, we'll create base64-encoded Protobuf data for our test products +- var laptop = new ProtobufProduct +- { +- Name = "Laptop", +- Id = 1001, +- Price = 999.99 +- }; +- +- var smartphone = new ProtobufProduct +- { +- Name = "Smartphone", +- Id = 1002, +- Price = 499.99 +- }; +- +- var headphones = new ProtobufProduct +- { +- Name = "Headphones", +- Id = 1003, +- Price = 99.99 +- }; +- +- // Convert to base64-encoded Protobuf +- string laptopBase64 = Convert.ToBase64String(laptop.ToByteArray()); +- string smartphoneBase64 = Convert.ToBase64String(smartphone.ToByteArray()); +- string headphonesBase64 = Convert.ToBase64String(headphones.ToByteArray()); +- +- string firstRecordKey = Convert.ToBase64String(Encoding.UTF8.GetBytes("42")); // Example key +- string secondRecordKey = Convert.ToBase64String(Encoding.UTF8.GetBytes("43")); // Example key for second record +- +- // Create mock Kafka event JSON +- return @$"{{ +- ""eventSource"": ""aws:kafka"", +- ""eventSourceArn"": ""arn:aws:kafka:us-east-1:0123456789019:cluster/SalesCluster/abcd1234-abcd-cafe-abab-9876543210ab-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"": {{ +- ""mytopic-0"": [ +- {{ +- ""topic"": ""mytopic"", +- ""partition"": 0, +- ""offset"": 15, +- ""timestamp"": 1545084650987, +- ""timestampType"": ""CREATE_TIME"", +- ""key"": ""{firstRecordKey}"", +- ""value"": ""{laptopBase64}"", +- ""headers"": [ +- {{ ""headerKey"": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] }} +- ] +- }}, +- {{ +- ""topic"": ""mytopic"", +- ""partition"": 0, +- ""offset"": 16, +- ""timestamp"": 1545084650988, +- ""timestampType"": ""CREATE_TIME"", +- ""key"": ""{secondRecordKey}"", +- ""value"": ""{smartphoneBase64}"", +- ""headers"": [ +- {{ ""headerKey"": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] }} +- ] +- }}, +- {{ +- ""topic"": ""mytopic"", +- ""partition"": 0, +- ""offset"": 17, +- ""timestamp"": 1545084650989, +- ""timestampType"": ""CREATE_TIME"", +- ""key"": null, +- ""value"": ""{headphonesBase64}"", +- ""headers"": [ +- {{ ""headerKey"": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] }} +- ] +- }} +- ] +- }} +- }}"; +- } +- +- private string GetMockKafkaEventWithProtobufKeys() +- { +- // Create test products +- var laptop = new ProtobufProduct +- { +- Name = "Laptop", +- Id = 1001, +- Price = 999.99 +- }; +- +- var smartphone = new ProtobufProduct +- { +- Name = "Smartphone", +- Id = 1002, +- Price = 499.99 +- }; +- +- var headphones = new ProtobufProduct +- { +- Name = "Headphones", +- Id = 1003, +- Price = 99.99 +- }; +- +- // Create test keys +- var key1 = new ProtobufKey { Id = 1, Color = TestKafka.Color.Green }; +- var key2 = new ProtobufKey { Id = 2 }; +- var key3 = new ProtobufKey { Id = 3, Color = TestKafka.Color.Red }; +- +- // Convert values to base64-encoded Protobuf +- string laptopBase64 = Convert.ToBase64String(laptop.ToByteArray()); +- string smartphoneBase64 = Convert.ToBase64String(smartphone.ToByteArray()); +- string headphonesBase64 = Convert.ToBase64String(headphones.ToByteArray()); +- +- // Convert keys to base64-encoded Protobuf +- string key1Base64 = Convert.ToBase64String(key1.ToByteArray()); +- string key2Base64 = Convert.ToBase64String(key2.ToByteArray()); +- string key3Base64 = Convert.ToBase64String(key3.ToByteArray()); +- +- // Create mock Kafka event JSON +- return @$"{{ +- ""eventSource"": ""aws:kafka"", +- ""eventSourceArn"": ""arn:aws:kafka:us-east-1:0123456789019:cluster/SalesCluster/abcd1234-abcd-cafe-abab-9876543210ab-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"": {{ +- ""mytopic-0"": [ +- {{ +- ""topic"": ""mytopic"", +- ""partition"": 0, +- ""offset"": 15, +- ""timestamp"": 1545084650987, +- ""timestampType"": ""CREATE_TIME"", +- ""key"": ""{key1Base64}"", +- ""value"": ""{laptopBase64}"", +- ""headers"": [ +- {{ ""headerKey"": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] }} +- ] +- }}, +- {{ +- ""topic"": ""mytopic"", +- ""partition"": 0, +- ""offset"": 16, +- ""timestamp"": 1545084650988, +- ""timestampType"": ""CREATE_TIME"", +- ""key"": ""{key2Base64}"", +- ""value"": ""{smartphoneBase64}"", +- ""headers"": [ +- {{ ""headerKey"": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] }} +- ] +- }}, +- {{ +- ""topic"": ""mytopic"", +- ""partition"": 0, +- ""offset"": 17, +- ""timestamp"": 1545084650989, +- ""timestampType"": ""CREATE_TIME"", +- ""key"": ""{key3Base64}"", +- ""value"": ""{headphonesBase64}"", +- ""headers"": [ +- {{ ""headerKey"": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] }} +- ] +- }} +- ] +- }} +- }}"; +- } +- +- // Define the test handler method +- private async Task Handler(KafkaAlias.ConsumerRecords records, ILambdaContext context) +- { +- foreach (var record in records) +- { +- var product = record.Value; +- context.Logger.LogInformation($"Processing {product.Name} at ${product.Price}"); +- } +- +- return "Successfully processed Protobuf Kafka events"; +- } +- +- private async Task HandlerWithProtobufKeys(KafkaAlias.ConsumerRecords records, +- ILambdaContext context) +- { +- foreach (var record in records) +- { +- var key = record.Key; +- var product = record.Value; +- context.Logger.LogInformation($"Processing key {key.Id} - {product.Name} at ${product.Price}"); +- } +- +- return "Successfully processed Protobuf Kafka events with complex keys"; +- } +- +- [Fact] +- public void SimpleHandlerTest() +- { +- string Handler(KafkaAlias.ConsumerRecords records, ILambdaContext context) +- { +- foreach (var record in records) +- { +- var product = record.Value; +- context.Logger.LogInformation($"Processing {product.Name} at ${product.Price}"); +- } +- +- return "Successfully processed Protobuf Kafka events"; +- } +- // Simulate the handler execution +- var mockLogger = new TestLambdaLogger(); +- var mockContext = new TestLambdaContext +- { +- Logger = mockLogger +- }; +- +- var records = new KafkaAlias.ConsumerRecords +- { +- Records = new Dictionary>> +- { +- { "mytopic-0", new List> +- { +- new() +- { +- Topic = "mytopic", +- Partition = 0, +- Offset = 15, +- Key = 42, +- Value = new ProtobufProduct { Name = "Test Product", Id = 1, Price = 99.99 } +- } +- } +- } +- } +- }; +- +- // Call the handler +- var result = Handler(records, mockContext); +- +- // Assert the result +- Assert.Equal("Successfully processed Protobuf Kafka events", result); +- +- // Verify the context logger output +- Assert.Contains("Processing Test Product at $99.99", mockLogger.Buffer.ToString()); +- +- // Verify the records were processed +- Assert.Single(records.Records); +- Assert.Contains("mytopic-0", records.Records.Keys); +- Assert.Single(records.Records["mytopic-0"]); +- Assert.Equal("mytopic", records.Records["mytopic-0"][0].Topic); +- Assert.Equal(0, records.Records["mytopic-0"][0].Partition); +- Assert.Equal(15, records.Records["mytopic-0"][0].Offset); +- Assert.Equal(42, records.Records["mytopic-0"][0].Key); +- Assert.Equal("Test Product", records.Records["mytopic-0"][0].Value.Name); +- Assert.Equal(1, records.Records["mytopic-0"][0].Value.Id); +- Assert.Equal(99.99, records.Records["mytopic-0"][0].Value.Price); +- } +-} +diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/Key.proto b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/Key.proto +deleted file mode 100644 +index deedcf5d..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/Key.proto ++++ /dev/null +@@ -1,14 +0,0 @@ +-syntax = "proto3"; +- +-option csharp_namespace = "TestKafka"; +- +-message ProtobufKey { +- int32 id = 1; +- Color color = 2; +-} +- +-enum Color { +- UNKNOWN = 0; +- GREEN = 1; +- RED = 2; +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/PowertoolsKafkaProtobufSerializerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/PowertoolsKafkaProtobufSerializerTests.cs +deleted file mode 100644 +index fc9074db..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/PowertoolsKafkaProtobufSerializerTests.cs ++++ /dev/null +@@ -1,305 +0,0 @@ +-using System.Runtime.Serialization; +-using System.Text; +-using AWS.Lambda.Powertools.Kafka.Protobuf; +-using Com.Example.Protobuf; +-using TestKafka; +- +-#if DEBUG +-using KafkaAlias = AWS.Lambda.Powertools.Kafka; +-#else +-using KafkaAlias = AWS.Lambda.Powertools.Kafka.Protobuf; +-#endif +- +-namespace AWS.Lambda.Powertools.Kafka.Tests.Protobuf; +- +-public class PowertoolsKafkaProtobufSerializerTests +-{ +- [Fact] +- public void Deserialize_KafkaEventWithProtobufPayload_DeserializesToCorrectType() +- { +- // Arrange +- var serializer = new PowertoolsKafkaProtobufSerializer(); +- string kafkaEventJson = File.ReadAllText("Protobuf/kafka-protobuf-event.json"); +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); +- +- // Act +- var result = serializer.Deserialize>(stream); +- +- // Assert +- Assert.NotNull(result); +- Assert.Equal("aws:kafka", result.EventSource); +- +- // Verify records were deserialized +- Assert.True(result.Records.ContainsKey("mytopic-0")); +- var records = result.Records["mytopic-0"]; +- Assert.Equal(3, records.Count); // Fixed to expect 3 records instead of 1 +- +- // Verify first record's content +- var firstRecord = records[0]; +- Assert.Equal("mytopic", firstRecord.Topic); +- Assert.Equal(0, firstRecord.Partition); +- Assert.Equal(15, firstRecord.Offset); +- Assert.Equal(42, firstRecord.Key); +- +- // Verify deserialized Protobuf value +- var product = firstRecord.Value; +- Assert.Equal("Laptop", product.Name); +- Assert.Equal(1001, product.Id); +- Assert.Equal(999.99, product.Price); +- +- // Verify second record +- var secondRecord = records[1]; +- var smartphone = secondRecord.Value; +- Assert.Equal("Smartphone", smartphone.Name); +- Assert.Equal(1002, smartphone.Id); +- Assert.Equal(599.99, smartphone.Price); +- +- // Verify third record +- var thirdRecord = records[2]; +- var headphones = thirdRecord.Value; +- Assert.Equal("Headphones", headphones.Name); +- Assert.Equal(1003, headphones.Id); +- Assert.Equal(149.99, headphones.Price); +- } +- +- [Fact] +- public void KafkaEvent_ImplementsIEnumerable_ForDirectIteration() +- { +- // Arrange +- var serializer = new PowertoolsKafkaProtobufSerializer(); +- string kafkaEventJson = File.ReadAllText("Protobuf/kafka-protobuf-event.json"); +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); +- +- // Act +- var result = serializer.Deserialize>(stream); +- +- // Assert - Test enumeration +- int count = 0; +- var products = new List(); +- +- // Directly iterate over ConsumerRecords +- foreach (var record in result) +- { +- count++; +- products.Add(record.Value.Name); +- } +- +- // Verify correct count and values +- Assert.Equal(3, count); +- Assert.Contains("Laptop", products); +- Assert.Contains("Smartphone", products); +- Assert.Contains("Headphones", products); +- +- // Get first record directly through Linq extension +- var firstRecord = result.First(); +- Assert.Equal("Laptop", firstRecord.Value.Name); +- Assert.Equal(1001, firstRecord.Value.Id); +- } +- +- [Fact] +- public void Primitive_Deserialization() +- { +- // Arrange +- var serializer = new PowertoolsKafkaProtobufSerializer(); +- string kafkaEventJson = +- CreateKafkaEvent(Convert.ToBase64String("MyKey"u8.ToArray()), +- Convert.ToBase64String("Myvalue"u8.ToArray())); +- +- +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); +- +- // Act +- var result = serializer.Deserialize>(stream); +- var firstRecord = result.First(); +- Assert.Equal("Myvalue", firstRecord.Value); +- Assert.Equal("MyKey", firstRecord.Key); +- } +- +- [Fact] +- public void DeserializeComplexKey_WhenAllDeserializationMethodsFail_ReturnsException() +- { +- // Arrange +- var serializer = new PowertoolsKafkaProtobufSerializer(); +- // Invalid JSON and not Protobuf binary +- byte[] invalidBytes = { 0xDE, 0xAD, 0xBE, 0xEF }; +- +- string kafkaEventJson = CreateKafkaEvent( +- keyValue: Convert.ToBase64String(invalidBytes), +- valueValue: Convert.ToBase64String(Encoding.UTF8.GetBytes("test")) +- ); +- +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); +- +- // Act +- var message = +- Assert.Throws(() => +- serializer.Deserialize>(stream)); +- Assert.Contains("Failed to deserialize key data: Unsupported", message.Message); +- } +- +- [Fact] +- public void Deserialize_Confluent_DeserializeCorrectly() +- { +- // Arrange +- var serializer = new PowertoolsKafkaProtobufSerializer(); +- string kafkaEventJson = File.ReadAllText("Protobuf/kafka-protobuf-confluent-event.json"); +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); +- +- // Act +- var result = serializer.Deserialize>(stream); +- +- // Assert +- Assert.NotNull(result); +- Assert.Equal("aws:kafka", result.EventSource); +- +- // Verify records +- Assert.True(result.Records.ContainsKey("confluent_proto-0")); +- var records = result.Records["confluent_proto-0"]; +- Assert.Equal(4, records.Count); +- +- // Verify all records have been deserialized correctly (all should have the same content) +- Assert.Equal("a8e40971-1552-420d-a7c9-b8982325702d", records[0].Value.UserId); +- Assert.Equal("Bob", records[0].Value.Name); +- Assert.Equal("bob@example.com", records[0].Value.Email); +- Assert.Equal("Seattle", records[0].Value.Address.City); +- Assert.Equal(28, records[0].Value.Age); +- +- Assert.Equal("4dcfc61b-3993-49c3-a04f-8a6c7aaf7881", records[1].Value.UserId); +- Assert.Equal("Bob", records[1].Value.Name); +- Assert.Equal("bob@example.com", records[1].Value.Email); +- Assert.Equal("Seattle", records[1].Value.Address.City); +- Assert.Equal(28, records[1].Value.Age); +- +- Assert.Equal("2a861628-0800-4b76-bd3f-6ecba7cd286c", records[2].Value.UserId); +- Assert.Equal("Bob", records[2].Value.Name); +- Assert.Equal("Seattle", records[2].Value.Address.City); +- Assert.Equal(28, records[2].Value.Age); +- } +- +- [Fact] +- public void Deserialize_Glue_DeserializeCorrectly() +- { +- // Arrange +- var serializer = new PowertoolsKafkaProtobufSerializer(); +- string kafkaEventJson = File.ReadAllText("Protobuf/kafka-protobuf-glue-event.json"); +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); +- +- // Act +- var result = serializer.Deserialize>(stream); +- +- // Assert +- Assert.NotNull(result); +- Assert.Equal("aws:kafka", result.EventSource); +- +- // Verify records +- Assert.True(result.Records.ContainsKey("gsr_proto-0")); +- var records = result.Records["gsr_proto-0"]; +- Assert.Equal(4, records.Count); +- +- // Verify all records have been deserialized correctly (all should have the same content) +- Assert.Equal("u859", records[0].Value.UserId); +- Assert.Equal("Alice", records[0].Value.Name); +- Assert.Equal("alice@example.com", records[0].Value.Email); +- Assert.Equal("dark", records[0].Value.Address.City); +- Assert.Equal(54, records[0].Value.Age); +- +- Assert.Equal("u809", records[1].Value.UserId); +- Assert.Equal("Alice", records[1].Value.Name); +- Assert.Equal("alice@example.com", records[1].Value.Email); +- Assert.Equal("dark", records[1].Value.Address.City); +- Assert.Equal(40, records[1].Value.Age); +- +- Assert.Equal("u453", records[2].Value.UserId); +- Assert.Equal("Alice", records[2].Value.Name); +- Assert.Equal("dark", records[2].Value.Address.City); +- Assert.Equal(74, records[2].Value.Age); +- } +- +- [Fact] +- public void Deserialize_MessageIndexWithCorruptData_HandlesError() +- { +- // Arrange - Create invalid message index data (starts with 5 but doesn't have 5 entries) +- byte[] invalidData = [5, 1, 2]; // Claims to have 5 entries but only has 2 +- string kafkaEventJson = CreateKafkaEvent("NDI=", Convert.ToBase64String(invalidData)); +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); +- var serializer = new PowertoolsKafkaProtobufSerializer(); +- +- // Act & Assert +- var ex = Assert.Throws(() => +- serializer.Deserialize>(stream)); +- +- // Verify the exception message contains useful information +- Assert.Contains("Failed to deserialize value data:", ex.Message); +- } +- +- /* +- 1/ If this field is None = We go in the easy way that is decode pure protobuf +- 2/ If the schemaId in this field is a uuid (16+ chars), its Glue and then you need to strip only the first byte and the deserialize +- 3/ If the len(schemaId) is 4, it means it is Confluent and then you need to strip the message fields numbers +- */ +- [Theory] +- [InlineData( +- "CgMxMjMSBFRlc3QaDHRlc3RAZ214LmNvbSAKMgoyMDI1LTA2LTIwOgR0YWcxOgR0YWcySg4KBXRoZW1lEgVsaWdodFIaCgpNeXRoZW5xdWFpEgZadXJpY2gaBDgwMDI=", +- null)] +- [InlineData( +- "AAoDMTIzEgRUZXN0Ggx0ZXN0QGdteC5jb20gCjIKMjAyNS0wNi0yMDoEdGFnMToEdGFnMkoOCgV0aGVtZRIFbGlnaHRSGgoKTXl0aGVucXVhaRIGWnVyaWNoGgQ4MDAy", +- "123")] +- [InlineData( +- "BAIACgMxMjMSBFRlc3QaDHRlc3RAZ214LmNvbSAKMgoyMDI1LTA2LTIwOgR0YWcxOgR0YWcyQQAAAAAAAChASg4KBXRoZW1lEgVsaWdodFIaCgpNeXRoZW5xdWFpEgZadXJpY2gaBDgwMDI=", +- "456")] +- [InlineData( +- "AQoDMTIzEgRUZXN0Ggx0ZXN0QGdteC5jb20gCjIKMjAyNS0wNi0yMDoEdGFnMToEdGFnMkoOCgV0aGVtZRIFbGlnaHRSGgoKTXl0aGVucXVhaRIGWnVyaWNoGgQ4MDAy", +- "12345678-1234-1234-1234-123456789012")] +- public void Deserialize_MultipleFormats_EachFormatDeserializesCorrectly(string base64Value, +- string? schemaId) +- { +- // Arrange +- var serializer = new PowertoolsKafkaProtobufSerializer(); +- string kafkaEventJson = CreateKafkaEvent("NDI=", base64Value, schemaId); // Key is 42 in base64 +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); +- +- // Act +- var result = serializer.Deserialize>(stream); +- +- // Assert +- var record = result.First(); +- Assert.NotNull(record); +- Assert.Equal(42, record.Key); // Key should be 42 +- +- // Value should be the same regardless of message index format +- Assert.Equal("Test", record.Value.Name); +- Assert.Equal("Zurich", record.Value.Address.City); +- Assert.Equal(10, record.Value.Age); +- Assert.Single(record.Value.Preferences); +- Assert.Equal("light",record.Value.Preferences.First().Value); +- } +- +- private string CreateKafkaEvent(string keyValue, string valueValue, string? schemaId = null) +- { +- return @$"{{ +- ""eventSource"": ""aws:kafka"", +- ""eventSourceArn"": ""arn:aws:kafka:us-east-1:0123456789019:cluster/TestCluster/abcd1234"", +- ""bootstrapServers"": ""b-1.test-cluster.kafka.us-east-1.amazonaws.com:9092"", +- ""records"": {{ +- ""mytopic-0"": [ +- {{ +- ""topic"": ""mytopic"", +- ""partition"": 0, +- ""offset"": 15, +- ""timestamp"": 1645084650987, +- ""timestampType"": ""CREATE_TIME"", +- ""key"": ""{keyValue}"", +- ""value"": ""{valueValue}"", +- ""headers"": [ +- {{ ""headerKey"": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] }} +- ], +- ""valueSchemaMetadata"": {{ +- ""dataFormat"": ""PROTOBUF"", +- ""schemaId"": ""{schemaId}"" +- }} +- }} +- ] +- }} +- }}"; +- } +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/Product.proto b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/Product.proto +deleted file mode 100644 +index 1d4c64e9..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/Product.proto ++++ /dev/null +@@ -1,9 +0,0 @@ +-syntax = "proto3"; +- +-option csharp_namespace = "TestKafka"; +- +-message ProtobufProduct { +- int32 id = 1; +- string name = 2; +- double price = 3; +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/UserProfile.proto b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/UserProfile.proto +deleted file mode 100644 +index 9ebc26ed..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/UserProfile.proto ++++ /dev/null +@@ -1,21 +0,0 @@ +-syntax = "proto3"; +-package com.example.protobuf; +- +-message Address { +- string street = 1; +- string city = 2; +- string zip = 3; +-} +- +-message UserProfile { +- string userId = 1; +- string name = 2; +- string email = 3; +- int32 age = 4; +- bool isActive = 5; +- string signupDate = 6; +- repeated string tags = 7; +- double score = 8; +- map preferences = 9; +- Address address = 10; +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/kafka-protobuf-confluent-event.json b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/kafka-protobuf-confluent-event.json +deleted file mode 100644 +index 6e7acf97..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/kafka-protobuf-confluent-event.json ++++ /dev/null +@@ -1,66 +0,0 @@ +-{ +- "bootstrapServers": "boot-u18.warpnest.kdhspn.c25.kafka.us-east-1.amazonaws.com:9098,boot-3xz.warpnest.kdhspn.c25.kafka.us-east-1.amazonaws.com:9098,boot-vvi.warpnest.kdhspn.c25.kafka.us-east-1.amazonaws.com:9098", +- "eventSource": "aws:kafka", +- "eventSourceArn": "arn:aws:kafka:us-east-1:408865831329:cluster/WarpNest/717bd2d1-e34b-4a86-9ae8-f7a16158c0f6-25", +- "records": { +- "confluent_proto-0": [ +- { +- "headers": [], +- "key": "YThlNDA5NzEtMTU1Mi00MjBkLWE3YzktYjg5ODIzMjU3MDJk", +- "offset": 4209910, +- "partition": 0, +- "timestamp": 1750358101849, +- "timestampType": "CREATE_TIME", +- "topic": "confluent_proto", +- "value": "AgIKJGE4ZTQwOTcxLTE1NTItNDIwZC1hN2M5LWI4OTgyMzI1NzAyZBIDQm9iGg9ib2JAZXhhbXBsZS5jb20gHCgBMgoyMDI0LTAyLTAyOgR0YWcxOgR0YWcyQQAAAAAAAFZASg4KBXRoZW1lEgVsaWdodFIZCgcxMjMgQXZlEgdTZWF0dGxlGgU5ODEwMQ==", +- "valueSchemaMetadata": { +- "dataFormat": "PROTOBUF", +- "schemaId": "1" +- } +- }, +- { +- "headers": [], +- "key": "NGRjZmM2MWItMzk5My00OWMzLWEwNGYtOGE2YzdhYWY3ODgx", +- "offset": 4209911, +- "partition": 0, +- "timestamp": 1750358102849, +- "timestampType": "CREATE_TIME", +- "topic": "confluent_proto", +- "value": "AgIKJDRkY2ZjNjFiLTM5OTMtNDljMy1hMDRmLThhNmM3YWFmNzg4MRIDQm9iGg9ib2JAZXhhbXBsZS5jb20gHCgBMgoyMDI0LTAyLTAyOgR0YWcxOgR0YWcyQQAAAAAAAFZASg4KBXRoZW1lEgVsaWdodFIZCgcxMjMgQXZlEgdTZWF0dGxlGgU5ODEwMQ==", +- "valueSchemaMetadata": { +- "dataFormat": "PROTOBUF", +- "schemaId": "1" +- } +- }, +- { +- "headers": [], +- "key": "MmE4NjE2MjgtMDgwMC00Yjc2LWJkM2YtNmVjYmE3Y2QyODZj", +- "offset": 4209912, +- "partition": 0, +- "timestamp": 1750358103849, +- "timestampType": "CREATE_TIME", +- "topic": "confluent_proto", +- "value": "AgIKJDJhODYxNjI4LTA4MDAtNGI3Ni1iZDNmLTZlY2JhN2NkMjg2YxIDQm9iGg9ib2JAZXhhbXBsZS5jb20gHCgBMgoyMDI0LTAyLTAyOgR0YWcxOgR0YWcyQQAAAAAAAFZASg4KBXRoZW1lEgVsaWdodFIZCgcxMjMgQXZlEgdTZWF0dGxlGgU5ODEwMQ==", +- "valueSchemaMetadata": { +- "dataFormat": "PROTOBUF", +- "schemaId": "1" +- } +- }, +- { +- "headers": [], +- "key": "NzEzMjBjNzMtZWM1Ny00NDZlLWJkNWItOTI1MmQ2OTQzMTgy", +- "offset": 4209913, +- "partition": 0, +- "timestamp": 1750358104849, +- "timestampType": "CREATE_TIME", +- "topic": "confluent_proto", +- "value": "AgIKJDcxMzIwYzczLWVjNTctNDQ2ZS1iZDViLTkyNTJkNjk0MzE4MhIDQm9iGg9ib2JAZXhhbXBsZS5jb20gHCgBMgoyMDI0LTAyLTAyOgR0YWcxOgR0YWcyQQAAAAAAAFZASg4KBXRoZW1lEgVsaWdodFIZCgcxMjMgQXZlEgdTZWF0dGxlGgU5ODEwMQ==", +- "valueSchemaMetadata": { +- "dataFormat": "PROTOBUF", +- "schemaId": "1" +- } +- } +- ] +- } +-} +- +diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/kafka-protobuf-event.json b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/kafka-protobuf-event.json +deleted file mode 100644 +index b3e0139e..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/kafka-protobuf-event.json ++++ /dev/null +@@ -1,51 +0,0 @@ +-{ +- "eventSource": "aws:kafka", +- "eventSourceArn": "arn:aws:kafka:us-east-1:0123456789019:cluster/SalesCluster/abcd1234-abcd-cafe-abab-9876543210ab-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": { +- "mytopic-0": [ +- { +- "topic": "mytopic", +- "partition": 0, +- "offset": 15, +- "timestamp": 1545084650987, +- "timestampType": "CREATE_TIME", +- "key": "NDI=", +- "value": "COkHEgZMYXB0b3AZUrgehes/j0A=", +- "headers": [ +- { +- "headerKey": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] +- } +- ] +- }, +- { +- "topic": "mytopic", +- "partition": 0, +- "offset": 16, +- "timestamp": 1545084650988, +- "timestampType": "CREATE_TIME", +- "key": "NDI=", +- "value": "COoHEgpTbWFydHBob25lGVK4HoXrv4JA", +- "headers": [ +- { +- "headerKey": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] +- } +- ] +- }, +- { +- "topic": "mytopic", +- "partition": 0, +- "offset": 17, +- "timestamp": 1545084650989, +- "timestampType": "CREATE_TIME", +- "key": null, +- "value": "COsHEgpIZWFkcGhvbmVzGUjhehSuv2JA", +- "headers": [ +- { +- "headerKey": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] +- } +- ] +- } +- ] +- } +-} +diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/kafka-protobuf-glue-event.json b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/kafka-protobuf-glue-event.json +deleted file mode 100644 +index 29241344..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/kafka-protobuf-glue-event.json ++++ /dev/null +@@ -1,66 +0,0 @@ +-{ +- "bootstrapServers": "boot-u18.warpnest.kdhspn.c25.kafka.us-east-1.amazonaws.com:9098,boot-3xz.warpnest.kdhspn.c25.kafka.us-east-1.amazonaws.com:9098,boot-vvi.warpnest.kdhspn.c25.kafka.us-east-1.amazonaws.com:9098", +- "eventSource": "aws:kafka", +- "eventSourceArn": "arn:aws:kafka:us-east-1:408865831329:cluster/WarpNest/717bd2d1-e34b-4a86-9ae8-f7a16158c0f6-25", +- "records": { +- "gsr_proto-0": [ +- { +- "headers": [], +- "key": "dTg1OQ==", +- "offset": 4130352, +- "partition": 0, +- "timestamp": 1750284651283, +- "timestampType": "CREATE_TIME", +- "topic": "gsr_proto", +- "value": "AQoEdTg1ORIFQWxpY2UaEWFsaWNlQGV4YW1wbGUuY29tIDYyCjIwMjQtMDEtMDE6GgoIMTIzIE1haW4SB1NlYXR0bGUaBTk4MTAxQgR0YWcxQgR0YWcySZZFopoJWkdAUg0KBXRoZW1lEgRkYXJr", +- "valueSchemaMetadata": { +- "dataFormat": "PROTOBUF", +- "schemaId": "7d55d475-2244-4485-8341-f74468c1e058" +- } +- }, +- { +- "headers": [], +- "key": "dTgwOQ==", +- "offset": 4130353, +- "partition": 0, +- "timestamp": 1750284652283, +- "timestampType": "CREATE_TIME", +- "topic": "gsr_proto", +- "value": "AQoEdTgwORIFQWxpY2UaEWFsaWNlQGV4YW1wbGUuY29tICgyCjIwMjQtMDEtMDE6GgoIMTIzIE1haW4SB1NlYXR0bGUaBTk4MTAxQgR0YWcxQgR0YWcySTnSqQSHn0FAUg0KBXRoZW1lEgRkYXJr", +- "valueSchemaMetadata": { +- "dataFormat": "PROTOBUF", +- "schemaId": "7d55d475-2244-4485-8341-f74468c1e058" +- } +- }, +- { +- "headers": [], +- "key": "dTQ1Mw==", +- "offset": 4130354, +- "partition": 0, +- "timestamp": 1750284653283, +- "timestampType": "CREATE_TIME", +- "topic": "gsr_proto", +- "value": "AQoEdTQ1MxIFQWxpY2UaEWFsaWNlQGV4YW1wbGUuY29tIEooATIKMjAyNC0wMS0wMToaCggxMjMgTWFpbhIHU2VhdHRsZRoFOTgxMDFCBHRhZzFCBHRhZzJJRJi47bmvV0BSDQoFdGhlbWUSBGRhcms=", +- "valueSchemaMetadata": { +- "dataFormat": "PROTOBUF", +- "schemaId": "7d55d475-2244-4485-8341-f74468c1e058" +- } +- }, +- { +- "headers": [], +- "key": "dTcwNQ==", +- "offset": 4130355, +- "partition": 0, +- "timestamp": 1750284654283, +- "timestampType": "CREATE_TIME", +- "topic": "gsr_proto", +- "value": "AQoEdTcwNRIFQWxpY2UaEWFsaWNlQGV4YW1wbGUuY29tIBMyCjIwMjQtMDEtMDE6GgoIMTIzIE1haW4SB1NlYXR0bGUaBTk4MTAxQgR0YWcxQgR0YWcySUSydyF28ldAUg0KBXRoZW1lEgRkYXJr", +- "valueSchemaMetadata": { +- "dataFormat": "PROTOBUF", +- "schemaId": "7d55d475-2244-4485-8341-f74468c1e058" +- } +- } +- ] +- } +-} +- +diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/ProtobufErrorHandlingTests.cs b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/ProtobufErrorHandlingTests.cs +deleted file mode 100644 +index 0813d426..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/ProtobufErrorHandlingTests.cs ++++ /dev/null +@@ -1,74 +0,0 @@ +-using System.Runtime.Serialization; +-using System.Text; +-using AWS.Lambda.Powertools.Kafka.Protobuf; +- +-#if DEBUG +-using KafkaAlias = AWS.Lambda.Powertools.Kafka; +-#else +-using KafkaAlias = AWS.Lambda.Powertools.Kafka.Protobuf; +-#endif +- +-namespace AWS.Lambda.Powertools.Kafka.Tests; +- +-public class ProtobufErrorHandlingTests +-{ +- [Fact] +- public void ProtobufSerializer_WithCorruptedKeyData_ThrowSerializationException() +- { +- // Arrange +- var serializer = new PowertoolsKafkaProtobufSerializer(); +- var corruptedData = new byte[] { 0xDE, 0xAD, 0xBE, 0xEF }; +- +- string kafkaEventJson = CreateKafkaEvent( +- Convert.ToBase64String(corruptedData), +- Convert.ToBase64String(Encoding.UTF8.GetBytes("valid-value")) +- ); +- +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); +- +- // Act & Assert +- var ex = Assert.Throws(() => +- serializer.Deserialize>(stream)); +- +- Assert.Contains("Failed to deserialize key data", ex.Message); +- } +- +- [Fact] +- public void ProtobufSerializer_WithCorruptedValueData_ThrowSerializationException() +- { +- // Arrange +- var serializer = new PowertoolsKafkaProtobufSerializer(); +- var corruptedData = new byte[] { 0xDE, 0xAD, 0xBE, 0xEF }; +- +- string kafkaEventJson = CreateKafkaEvent( +- Convert.ToBase64String(Encoding.UTF8.GetBytes("valid-key")), +- Convert.ToBase64String(corruptedData) +- ); +- +- using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); +- +- // Act & Assert +- var ex = Assert.Throws(() => +- serializer.Deserialize>(stream)); +- +- Assert.Contains("Failed to deserialize value data", ex.Message); +- } +- +- private string CreateKafkaEvent(string keyValue, string valueValue) +- { +- return @$"{{ +- ""eventSource"": ""aws:kafka"", +- ""records"": {{ +- ""mytopic-0"": [ +- {{ +- ""topic"": ""mytopic"", +- ""partition"": 0, +- ""offset"": 15, +- ""key"": ""{keyValue}"", +- ""value"": ""{valueValue}"" +- }} +- ] +- }} +- }}"; +- } +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Attributes/LoggerAspectTests.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Attributes/LoggerAspectTests.cs +index 946af011..f3a8335e 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Attributes/LoggerAspectTests.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Attributes/LoggerAspectTests.cs +@@ -1,7 +1,22 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; +-using System.IO; + using AWS.Lambda.Powertools.Common; + using AWS.Lambda.Powertools.Logging.Internal; ++using AWS.Lambda.Powertools.Logging.Serializers; + using AWS.Lambda.Powertools.Logging.Tests.Handlers; + using AWS.Lambda.Powertools.Logging.Tests.Serializers; + using Microsoft.Extensions.Logging; +@@ -13,31 +28,21 @@ namespace AWS.Lambda.Powertools.Logging.Tests.Attributes; + [Collection("Sequential")] + public class LoggerAspectTests : IDisposable + { +- static LoggerAspectTests() +- { +- ResetAllState(); +- } +- ++ private ISystemWrapper _mockSystemWrapper; ++ private readonly IPowertoolsConfigurations _mockPowertoolsConfigurations; ++ + public LoggerAspectTests() + { +- // Start each test with clean state +- ResetAllState(); ++ _mockSystemWrapper = Substitute.For(); ++ _mockPowertoolsConfigurations = Substitute.For(); + } +- ++ + [Fact] + public void OnEntry_ShouldInitializeLogger_WhenCalledWithValidArguments() + { + // Arrange +- var consoleOut = Substitute.For(); +- +- var config = new PowertoolsLoggerConfiguration +- { +- Service = "TestService", +- MinimumLogLevel = LogLevel.Information, +- LogOutput = consoleOut +- }; +- +- var logger = PowertoolsLoggerFactory.Create(config).CreatePowertoolsLogger(); ++ // Add seriolization context for AOT ++ PowertoolsLoggingSerializer.AddSerializerContext(TestJsonContext.Default); + + var instance = new object(); + var name = "TestMethod"; +@@ -59,29 +64,17 @@ public class LoggerAspectTests : IDisposable + } + }; + +- var aspectArgs = new AspectEventArgs +- { +- Instance = instance, +- Name = name, +- Args = args, +- Type = hostType, +- Method = method, +- ReturnType = returnType, +- Triggers = triggers +- }; ++ _mockSystemWrapper.GetRandom().Returns(0.7); + + // Act +- var loggingAspect = new LoggingAspect(logger); +- loggingAspect.OnEntry(aspectArgs); ++ var loggingAspect = new LoggingAspect(_mockPowertoolsConfigurations, _mockSystemWrapper); ++ loggingAspect.OnEntry(instance, name, args, hostType, method, returnType, triggers); + + // Assert +- consoleOut.Received(1).WriteLine(Arg.Is(s => +- s.Contains("\"Level\":\"Information\"") && +- s.Contains("\"Service\":\"TestService\"") && +- s.Contains("\"Name\":\"AWS.Lambda.Powertools.Logging.Logger\"") && +- s.Contains("\"Message\":{\"FullName\":\"Powertools\",\"Age\":20,\"Headers\":null}") && +- s.Contains("\"CorrelationId\":\"20\"") && +- s.Contains("\"SamplingRate\":0.5") ++ _mockSystemWrapper.Received().LogLine(Arg.Is(s => ++ s.Contains( ++ "\"Level\":\"Information\",\"Service\":\"TestService\",\"Name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"Message\":{\"FullName\":\"Powertools\",\"Age\":20,\"Headers\":null},\"SamplingRate\":0.5}") ++ && s.Contains("\"CorrelationId\":\"20\"") + )); + } + +@@ -89,86 +82,9 @@ public class LoggerAspectTests : IDisposable + public void OnEntry_ShouldLog_Event_When_EnvironmentVariable_Set() + { + // Arrange +- Environment.SetEnvironmentVariable(Constants.LoggerLogEventNameEnv, "true"); +- var consoleOut = Substitute.For(); +- +- var config = new PowertoolsLoggerConfiguration +- { +- Service = "TestService", +- MinimumLogLevel = LogLevel.Information, +- LogEvent = true, +- LogOutput = consoleOut +- }; +- +- var logger = PowertoolsLoggerFactory.Create(config).CreatePowertoolsLogger(); +- +- var instance = new object(); +- var name = "TestMethod"; +- var args = new object[] { new TestObject { FullName = "Powertools", Age = 20 } }; +- var hostType = typeof(string); +- var method = typeof(TestHandlers).GetMethod("TestMethod"); +- var returnType = typeof(string); +- var triggers = new Attribute[] +- { +- new LoggingAttribute +- { +- Service = "TestService", +- LoggerOutputCase = LoggerOutputCase.PascalCase, +- LogLevel = LogLevel.Information, +- CorrelationIdPath = "/Age", +- ClearState = true +- } +- }; +- +- var aspectArgs = new AspectEventArgs +- { +- Instance = instance, +- Name = name, +- Args = args, +- Type = hostType, +- Method = method, +- ReturnType = returnType, +- Triggers = triggers +- }; ++ // Add seriolization context for AOT ++ PowertoolsLoggingSerializer.AddSerializerContext(TestJsonContext.Default); + +- // Act +- var loggingAspect = new LoggingAspect(logger); +- loggingAspect.OnEntry(aspectArgs); +- +- var updatedConfig = PowertoolsLoggingBuilderExtensions.GetCurrentConfiguration(); +- +- // Assert +- Assert.Equal("TestService", updatedConfig.Service); +- Assert.Equal(LoggerOutputCase.PascalCase, updatedConfig.LoggerOutputCase); +- Assert.Equal(0, updatedConfig.SamplingRate); +- Assert.True(updatedConfig.LogEvent); +- +- consoleOut.Received(1).WriteLine(Arg.Is(s => +- s.Contains("\"Level\":\"Information\"") && +- s.Contains("\"Service\":\"TestService\"") && +- s.Contains("\"Name\":\"AWS.Lambda.Powertools.Logging.Logger\"") && +- s.Contains("\"Message\":{\"FullName\":\"Powertools\",\"Age\":20,\"Headers\":null}") && +- s.Contains("\"CorrelationId\":\"20\"") +- )); +- } +- +- [Fact] +- public void OnEntry_Should_NOT_Log_Event_When_EnvironmentVariable_Set_But_Attribute_False() +- { +- // Arrange +- Environment.SetEnvironmentVariable(Constants.LoggerLogEventNameEnv, "true"); +- var consoleOut = Substitute.For(); +- +- var config = new PowertoolsLoggerConfiguration +- { +- Service = "TestService", +- MinimumLogLevel = LogLevel.Information, +- LogEvent = true, +- LogOutput = consoleOut +- }; +- +- var logger = PowertoolsLoggerFactory.Create(config).CreatePowertoolsLogger(); +- + var instance = new object(); + var name = "TestMethod"; + var args = new object[] { new TestObject { FullName = "Powertools", Age = 20 } }; +@@ -187,50 +103,35 @@ public class LoggerAspectTests : IDisposable + ClearState = true + } + }; +- + +- var aspectArgs = new AspectEventArgs +- { +- Instance = instance, +- Name = name, +- Args = args, +- Type = hostType, +- Method = method, +- ReturnType = returnType, +- Triggers = triggers +- }; ++ // Env returns true ++ _mockPowertoolsConfigurations.LoggerLogEvent.Returns(true); ++ ++ // Act ++ var loggingAspect = new LoggingAspect(_mockPowertoolsConfigurations, _mockSystemWrapper); ++ loggingAspect.OnEntry(instance, name, args, hostType, method, returnType, triggers); + +- // Act +- var loggingAspect = new LoggingAspect(logger); +- loggingAspect.OnEntry(aspectArgs); +- +- var updatedConfig = PowertoolsLoggingBuilderExtensions.GetCurrentConfiguration(); +- + // Assert +- Assert.Equal("TestService", updatedConfig.Service); +- Assert.Equal(LoggerOutputCase.PascalCase, updatedConfig.LoggerOutputCase); +- Assert.Equal(0, updatedConfig.SamplingRate); +- Assert.True(updatedConfig.LogEvent); ++ var config = _mockPowertoolsConfigurations.CurrentConfig(); ++ Assert.NotNull(Logger.LoggerProvider); ++ Assert.Equal("TestService", config.Service); ++ Assert.Equal(LoggerOutputCase.PascalCase, config.LoggerOutputCase); ++ Assert.Equal(0, config.SamplingRate); + +- consoleOut.DidNotReceive().WriteLine(Arg.Any()); ++ _mockSystemWrapper.Received().LogLine(Arg.Is(s => ++ s.Contains( ++ "\"Level\":\"Information\",\"Service\":\"TestService\",\"Name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"Message\":{\"FullName\":\"Powertools\",\"Age\":20,\"Headers\":null}}") ++ && s.Contains("\"CorrelationId\":\"20\"") ++ )); + } +- ++ + [Fact] + public void OnEntry_ShouldLog_SamplingRate_When_EnvironmentVariable_Set() + { + // Arrange +- var consoleOut = Substitute.For(); +- +- var config = new PowertoolsLoggerConfiguration +- { +- Service = "TestService", +- MinimumLogLevel = LogLevel.Information, +- SamplingRate = 0.5, +- LogOutput = consoleOut +- }; +- +- var logger = PowertoolsLoggerFactory.Create(config).CreatePowertoolsLogger(); +- ++ // Add seriolization context for AOT ++ PowertoolsLoggingSerializer.AddSerializerContext(TestJsonContext.Default); ++ + var instance = new object(); + var name = "TestMethod"; + var args = new object[] { new TestObject { FullName = "Powertools", Age = 20 } }; +@@ -250,54 +151,31 @@ public class LoggerAspectTests : IDisposable + } + }; + ++ // Env returns true ++ _mockPowertoolsConfigurations.LoggerSampleRate.Returns(0.5); + +- var aspectArgs = new AspectEventArgs +- { +- Instance = instance, +- Name = name, +- Args = args, +- Type = hostType, +- Method = method, +- ReturnType = returnType, +- Triggers = triggers +- }; ++ // Act ++ var loggingAspect = new LoggingAspect(_mockPowertoolsConfigurations, _mockSystemWrapper); ++ loggingAspect.OnEntry(instance, name, args, hostType, method, returnType, triggers); + +- // Act +- var loggingAspect = new LoggingAspect(logger); +- loggingAspect.OnEntry(aspectArgs); +- + // Assert +- var updatedConfig = PowertoolsLoggingBuilderExtensions.GetCurrentConfiguration(); +- +- Assert.Equal("TestService", updatedConfig.Service); +- Assert.Equal(LoggerOutputCase.PascalCase, updatedConfig.LoggerOutputCase); +- Assert.Equal(0.5, updatedConfig.SamplingRate); +- +- consoleOut.Received(1).WriteLine(Arg.Is(s => +- s.Contains("\"Level\":\"Information\"") && +- s.Contains("\"Service\":\"TestService\"") && +- s.Contains("\"Name\":\"AWS.Lambda.Powertools.Logging.Logger\"") && +- s.Contains("\"Message\":{\"FullName\":\"Powertools\",\"Age\":20,\"Headers\":null}") && +- s.Contains("\"CorrelationId\":\"20\"") && +- s.Contains("\"SamplingRate\":0.5") ++ var config = _mockPowertoolsConfigurations.CurrentConfig(); ++ Assert.NotNull(Logger.LoggerProvider); ++ Assert.Equal("TestService", config.Service); ++ Assert.Equal(LoggerOutputCase.PascalCase, config.LoggerOutputCase); ++ Assert.Equal(0.5, config.SamplingRate); ++ ++ _mockSystemWrapper.Received().LogLine(Arg.Is(s => ++ s.Contains( ++ "\"Level\":\"Information\",\"Service\":\"TestService\",\"Name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"Message\":{\"FullName\":\"Powertools\",\"Age\":20,\"Headers\":null},\"SamplingRate\":0.5}") ++ && s.Contains("\"CorrelationId\":\"20\"") + )); + } +- ++ + [Fact] + public void OnEntry_ShouldLogEvent_WhenLogEventIsTrue() + { + // Arrange +- var consoleOut = Substitute.For(); +- +- var config = new PowertoolsLoggerConfiguration +- { +- Service = "TestService", +- MinimumLogLevel = LogLevel.Information, +- LogOutput = consoleOut, +- }; +- +- var logger = PowertoolsLoggerFactory.Create(config).CreatePowertoolsLogger(); +- + var eventObject = new { testData = "test-data" }; + var triggers = new Attribute[] + { +@@ -306,43 +184,26 @@ public class LoggerAspectTests : IDisposable + LogEvent = true + } + }; +- ++ + // Act +- +- var aspectArgs = new AspectEventArgs +- { +- Args = new object[] { eventObject }, +- Triggers = triggers +- }; + +- // Act +- var loggingAspect = new LoggingAspect(logger); +- loggingAspect.OnEntry(aspectArgs); +- ++ var loggingAspect = new LoggingAspect(_mockPowertoolsConfigurations, _mockSystemWrapper); ++ loggingAspect.OnEntry(null, null, new object[] { eventObject }, null, null, null, triggers); ++ + // Assert +- consoleOut.Received(1).WriteLine(Arg.Is(s => +- s.Contains("\"level\":\"Information\"") && +- s.Contains("\"service\":\"TestService\"") && +- s.Contains("\"name\":\"AWS.Lambda.Powertools.Logging.Logger\"") && +- s.Contains("\"message\":{\"test_data\":\"test-data\"}") ++ _mockSystemWrapper.Received().LogLine(Arg.Is(s => ++ s.Contains( ++ "\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"message\":{\"test_data\":\"test-data\"}}") + )); + } +- ++ + [Fact] + public void OnEntry_ShouldNot_Log_Info_When_LogLevel_Higher_EnvironmentVariable() + { + // Arrange +- var consoleOut = Substitute.For(); +- +- var config = new PowertoolsLoggerConfiguration +- { +- Service = "TestService", +- MinimumLogLevel = LogLevel.Error, +- LogOutput = consoleOut +- }; +- +- var logger = PowertoolsLoggerFactory.Create(config).CreatePowertoolsLogger(); +- ++ // Add seriolization context for AOT ++ PowertoolsLoggingSerializer.AddSerializerContext(TestJsonContext.Default); ++ + var instance = new object(); + var name = "TestMethod"; + var args = new object[] { new TestObject { FullName = "Powertools", Age = 20 } }; +@@ -355,49 +216,35 @@ public class LoggerAspectTests : IDisposable + { + Service = "TestService", + LoggerOutputCase = LoggerOutputCase.PascalCase, +- ++ + LogEvent = true, + CorrelationIdPath = "/age" + } + }; +- + +- var aspectArgs = new AspectEventArgs +- { +- Instance = instance, +- Name = name, +- Args = args, +- Type = hostType, +- Method = method, +- ReturnType = returnType, +- Triggers = triggers +- }; ++ // Env returns true ++ _mockPowertoolsConfigurations.LogLevel.Returns(LogLevel.Error.ToString()); ++ ++ // Act ++ var loggingAspect = new LoggingAspect(_mockPowertoolsConfigurations, _mockSystemWrapper); ++ loggingAspect.OnEntry(instance, name, args, hostType, method, returnType, triggers); + +- // Act +- var loggingAspect = new LoggingAspect(logger); +- loggingAspect.OnEntry(aspectArgs); +- +- var updatedConfig = PowertoolsLoggingBuilderExtensions.GetCurrentConfiguration(); +- + // Assert +- Assert.Equal("TestService", updatedConfig.Service); +- Assert.Equal(LoggerOutputCase.PascalCase, updatedConfig.LoggerOutputCase); +- +- consoleOut.DidNotReceive().WriteLine(Arg.Any()); ++ var config = _mockPowertoolsConfigurations.CurrentConfig(); ++ Assert.NotNull(Logger.LoggerProvider); ++ Assert.Equal("TestService", config.Service); ++ Assert.Equal(LoggerOutputCase.PascalCase, config.LoggerOutputCase); ++ ++ _mockSystemWrapper.DidNotReceive().LogLine(Arg.Any()); + } +- ++ + [Fact] + public void OnEntry_Should_LogDebug_WhenSet_EnvironmentVariable() + { + // Arrange +- Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", "Debug"); +- +- var consoleOut = Substitute.For(); +- var config = new PowertoolsLoggerConfiguration +- { +- LogOutput = consoleOut +- }; +- ++ // Add seriolization context for AOT ++ PowertoolsLoggingSerializer.AddSerializerContext(TestJsonContext.Default); ++ + var instance = new object(); + var name = "TestMethod"; + var args = new object[] +@@ -417,38 +264,25 @@ public class LoggerAspectTests : IDisposable + CorrelationIdPath = "/Headers/MyRequestIdHeader" + } + }; +- +- var logger = PowertoolsLoggerFactory.Create(config).CreatePowertoolsLogger(); + ++ // Env returns true ++ _mockPowertoolsConfigurations.LogLevel.Returns(LogLevel.Debug.ToString()); + +- var aspectArgs = new AspectEventArgs +- { +- Instance = instance, +- Name = name, +- Args = args, +- Type = hostType, +- Method = method, +- ReturnType = returnType, +- Triggers = triggers +- }; ++ // Act ++ var loggingAspect = new LoggingAspect(_mockPowertoolsConfigurations, _mockSystemWrapper); ++ loggingAspect.OnEntry(instance, name, args, hostType, method, returnType, triggers); + +- // Act +- var stringWriter = new StringWriter(); +- Console.SetOut(stringWriter); +- var loggingAspect = new LoggingAspect(logger); +- loggingAspect.OnEntry(aspectArgs); +- + // Assert +- var updatedConfig = PowertoolsLoggingBuilderExtensions.GetCurrentConfiguration(); +- +- Assert.Equal("TestService", updatedConfig.Service); +- Assert.Equal(LoggerOutputCase.PascalCase, updatedConfig.LoggerOutputCase); +- Assert.Equal(LogLevel.Debug, updatedConfig.MinimumLogLevel); +- +- string consoleOutput = stringWriter.ToString(); +- Assert.Contains("Skipping Lambda Context injection because ILambdaContext context parameter not found.", consoleOutput); +- +- consoleOut.Received(1).WriteLine(Arg.Is(s => ++ var config = _mockPowertoolsConfigurations.CurrentConfig(); ++ Assert.NotNull(Logger.LoggerProvider); ++ Assert.Equal("TestService", config.Service); ++ Assert.Equal(LoggerOutputCase.PascalCase, config.LoggerOutputCase); ++ Assert.Equal(LogLevel.Debug, config.MinimumLevel); ++ ++ _mockSystemWrapper.Received(1).LogLine(Arg.Is(s => ++ s == "Skipping Lambda Context injection because ILambdaContext context parameter not found.")); ++ ++ _mockSystemWrapper.Received(1).LogLine(Arg.Is(s => + s.Contains("\"CorrelationId\":\"test\"") && + s.Contains( + "\"Message\":{\"FullName\":\"Powertools\",\"Age\":20,\"Headers\":{\"MyRequestIdHeader\":\"test\"}") +@@ -457,28 +291,7 @@ public class LoggerAspectTests : IDisposable + + public void Dispose() + { +- ResetAllState(); +- } +- +- private static void ResetAllState() +- { +- // Clear environment variables +- Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_CASE", null); +- Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", null); +- Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", null); +- +- // Reset all logging components + LoggingAspect.ResetForTest(); +- Logger.Reset(); +- PowertoolsLoggingBuilderExtensions.ResetAllProviders(); +- LoggerFactoryHolder.Reset(); +- +- // Force default configuration +- var config = new PowertoolsLoggerConfiguration +- { +- MinimumLogLevel = LogLevel.Information, +- LoggerOutputCase = LoggerOutputCase.SnakeCase +- }; +- PowertoolsLoggingBuilderExtensions.UpdateConfiguration(config); ++ PowertoolsLoggingSerializer.ClearOptions(); + } + } +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Attributes/LoggingAttributeTest.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Attributes/LoggingAttributeTest.cs +index 5fc3e717..9843c71e 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Attributes/LoggingAttributeTest.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Attributes/LoggingAttributeTest.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.Collections.Generic; + using System.IO; +@@ -7,12 +22,10 @@ using Amazon.Lambda.ApplicationLoadBalancerEvents; + using Amazon.Lambda.CloudWatchEvents.S3Events; + using Amazon.Lambda.TestUtilities; + using AWS.Lambda.Powertools.Common; +-using AWS.Lambda.Powertools.Common.Core; +-using AWS.Lambda.Powertools.Common.Tests; + using AWS.Lambda.Powertools.Logging.Internal; ++using AWS.Lambda.Powertools.Logging.Serializers; + using AWS.Lambda.Powertools.Logging.Tests.Handlers; + using AWS.Lambda.Powertools.Logging.Tests.Serializers; +-using Microsoft.Extensions.Logging; + using NSubstitute; + using Xunit; + +@@ -22,65 +35,77 @@ namespace AWS.Lambda.Powertools.Logging.Tests.Attributes + public class LoggingAttributeTests : IDisposable + { + private TestHandlers _testHandlers; +- ++ + public LoggingAttributeTests() + { + _testHandlers = new TestHandlers(); + } +- ++ + [Fact] +- public void OnEntry_WhenLambdaContextDoesNotExist_IgnoresLambdaContext_No_Debug() ++ public void OnEntry_WhenLambdaContextDoesNotExist_IgnoresLambdaContext() + { + // Arrange +- var stringWriter = new StringWriter(); +- Console.SetOut(stringWriter); ++ var consoleOut = Substitute.For(); ++ SystemWrapper.Instance.SetOut(consoleOut); ++ + // Act + _testHandlers.TestMethod(); +- ++ + // Assert + var allKeys = Logger.GetAllKeys() + .ToDictionary(keyValuePair => keyValuePair.Key, keyValuePair => keyValuePair.Value); +- +- Assert.Empty(allKeys); +- +- var st = stringWriter.ToString(); +- Assert.Empty(st); ++ ++ Assert.NotNull(Logger.LoggerProvider); ++ Assert.True(allKeys.ContainsKey(LoggingConstants.KeyColdStart)); ++ //Assert.True((bool)allKeys[LoggingConstants.KeyColdStart]); ++ Assert.False(allKeys.ContainsKey(LoggingConstants.KeyFunctionName)); ++ Assert.False(allKeys.ContainsKey(LoggingConstants.KeyFunctionVersion)); ++ Assert.False(allKeys.ContainsKey(LoggingConstants.KeyFunctionMemorySize)); ++ Assert.False(allKeys.ContainsKey(LoggingConstants.KeyFunctionArn)); ++ Assert.False(allKeys.ContainsKey(LoggingConstants.KeyFunctionRequestId)); ++ ++ consoleOut.DidNotReceive().WriteLine(Arg.Any()); + } +- ++ + [Fact] + public void OnEntry_WhenLambdaContextDoesNotExist_IgnoresLambdaContextAndLogDebug() + { + // Arrange +- var consoleOut = GetConsoleOutput(); +- var stringWriter = new StringWriter(); +- Console.SetOut(stringWriter); +- Logger.Configure(options => +- { +- options.LogOutput = consoleOut; +- }); +- ++ var consoleOut = Substitute.For(); ++ SystemWrapper.Instance.SetOut(consoleOut); ++ + // Act + _testHandlers.TestMethodDebug(); +- ++ + // Assert + var allKeys = Logger.GetAllKeys() + .ToDictionary(keyValuePair => keyValuePair.Key, keyValuePair => keyValuePair.Value); +- +- Assert.Empty(allKeys); +- +- var st = stringWriter.ToString(); +- Assert.Contains("Skipping Lambda Context injection because ILambdaContext context parameter not found", st); ++ ++ Assert.NotNull(Logger.LoggerProvider); ++ Assert.True(allKeys.ContainsKey(LoggingConstants.KeyColdStart)); ++ //Assert.True((bool)allKeys[LoggingConstants.KeyColdStart]); ++ Assert.False(allKeys.ContainsKey(LoggingConstants.KeyFunctionName)); ++ Assert.False(allKeys.ContainsKey(LoggingConstants.KeyFunctionVersion)); ++ Assert.False(allKeys.ContainsKey(LoggingConstants.KeyFunctionMemorySize)); ++ Assert.False(allKeys.ContainsKey(LoggingConstants.KeyFunctionArn)); ++ Assert.False(allKeys.ContainsKey(LoggingConstants.KeyFunctionRequestId)); ++ ++ consoleOut.Received(1).WriteLine( ++ Arg.Is(i => ++ i == $"Skipping Lambda Context injection because ILambdaContext context parameter not found.") ++ ); + } +- ++ + [Fact] + public void OnEntry_WhenEventArgDoesNotExist_DoesNotLogEventArg() + { + // Arrange +- var consoleOut = GetConsoleOutput(); +- ++ var consoleOut = Substitute.For(); ++ SystemWrapper.Instance.SetOut(consoleOut); ++ + // Act + _testHandlers.LogEventNoArgs(); +- ++ + consoleOut.DidNotReceive().WriteLine( + Arg.Any() + ); +@@ -90,18 +115,17 @@ namespace AWS.Lambda.Powertools.Logging.Tests.Attributes + public void OnEntry_WhenEventArgExist_LogEvent() + { + // Arrange +- var consoleOut = GetConsoleOutput(); ++ var consoleOut = Substitute.For(); ++ SystemWrapper.Instance.SetOut(consoleOut); + var correlationId = Guid.NewGuid().ToString(); +- Logger.Configure(options => +- { +- options.LogOutput = consoleOut; +- }); +- ++ ++ // Add seriolization context for AOT ++ PowertoolsLoggingSerializer.AddSerializerContext(TestJsonContext.Default); + var context = new TestLambdaContext() + { + FunctionName = "PowertoolsLoggingSample-HelloWorldFunction-Gg8rhPwO7Wa1" + }; +- ++ + var testObj = new TestObject + { + Headers = new Header +@@ -112,7 +136,7 @@ namespace AWS.Lambda.Powertools.Logging.Tests.Attributes + + // Act + _testHandlers.LogEvent(testObj, context); +- ++ + consoleOut.Received(1).WriteLine( + Arg.Is(i => i.Contains("FunctionName\":\"PowertoolsLoggingSample-HelloWorldFunction-Gg8rhPwO7Wa1")) + ); +@@ -122,8 +146,11 @@ namespace AWS.Lambda.Powertools.Logging.Tests.Attributes + public void OnEntry_WhenEventArgExist_LogEvent_False_Should_Not_Log() + { + // Arrange +- var consoleOut = GetConsoleOutput(); +- ++ var consoleOut = Substitute.For(); ++ SystemWrapper.Instance.SetOut(consoleOut); ++ ++ // Add seriolization context for AOT ++ PowertoolsLoggingSerializer.AddSerializerContext(TestJsonContext.Default); + var context = new TestLambdaContext() + { + FunctionName = "PowertoolsLoggingSample-HelloWorldFunction-Gg8rhPwO7Wa1" +@@ -131,42 +158,41 @@ namespace AWS.Lambda.Powertools.Logging.Tests.Attributes + + // Act + _testHandlers.LogEventFalse(context); +- ++ + consoleOut.DidNotReceive().WriteLine( + Arg.Any() + ); + } +- ++ + [Fact] + public void OnEntry_WhenEventArgDoesNotExist_DoesNotLogEventArgAndLogDebug() + { + // Arrange +- var consoleOut = GetConsoleOutput(); +- var stringWriter = new StringWriter(); +- Console.SetOut(stringWriter); +- Logger.Configure(options => +- { +- options.LogOutput = consoleOut; +- }); +- ++ var consoleOut = Substitute.For(); ++ SystemWrapper.Instance.SetOut(consoleOut); ++ + // Act + _testHandlers.LogEventDebug(); +- +- // Assert +- var st = stringWriter.ToString(); +- Assert.Contains("Skipping Event Log because event parameter not found.", st); +- Assert.Contains("Skipping Lambda Context injection because ILambdaContext context parameter not found", st); ++ ++ consoleOut.Received(1).WriteLine( ++ Arg.Is(i => i == "Skipping Event Log because event parameter not found.") ++ ); + } +- ++ + [Fact] + public void OnExit_WhenHandler_ClearState_Enabled_ClearKeys() + { ++ // Arrange ++ var consoleOut = Substitute.For(); ++ SystemWrapper.Instance.SetOut(consoleOut); ++ + // Act + _testHandlers.ClearState(); +- ++ ++ Assert.NotNull(Logger.LoggerProvider); + Assert.False(Logger.GetAllKeys().Any()); + } +- ++ + [Theory] + [InlineData(CorrelationIdPaths.ApiGatewayRest)] + [InlineData(CorrelationIdPaths.ApplicationLoadBalancer)] +@@ -176,7 +202,10 @@ namespace AWS.Lambda.Powertools.Logging.Tests.Attributes + { + // Arrange + var correlationId = Guid.NewGuid().ToString(); +- ++ ++ // Add seriolization context for AOT ++ PowertoolsLoggingSerializer.AddSerializerContext(TestJsonContext.Default); ++ + // Act + switch (correlationIdPath) + { +@@ -214,15 +243,15 @@ namespace AWS.Lambda.Powertools.Logging.Tests.Attributes + }); + break; + } +- ++ + // Assert + var allKeys = Logger.GetAllKeys() + .ToDictionary(keyValuePair => keyValuePair.Key, keyValuePair => keyValuePair.Value); +- ++ + Assert.True(allKeys.ContainsKey(LoggingConstants.KeyCorrelationId)); + Assert.Equal((string)allKeys[LoggingConstants.KeyCorrelationId], correlationId); + } +- ++ + [Theory] + [InlineData(LoggerOutputCase.SnakeCase)] + [InlineData(LoggerOutputCase.PascalCase)] +@@ -231,7 +260,10 @@ namespace AWS.Lambda.Powertools.Logging.Tests.Attributes + { + // Arrange + var correlationId = Guid.NewGuid().ToString(); +- ++ ++ // Add seriolization context for AOT ++ PowertoolsLoggingSerializer.AddSerializerContext(TestJsonContext.Default); ++ + // Act + switch (outputCase) + { +@@ -263,11 +295,11 @@ namespace AWS.Lambda.Powertools.Logging.Tests.Attributes + }); + break; + } +- ++ + // Assert + var allKeys = Logger.GetAllKeys() + .ToDictionary(keyValuePair => keyValuePair.Key, keyValuePair => keyValuePair.Value); +- ++ + Assert.True(allKeys.ContainsKey(LoggingConstants.KeyCorrelationId)); + Assert.Equal((string)allKeys[LoggingConstants.KeyCorrelationId], correlationId); + } +@@ -280,7 +312,10 @@ namespace AWS.Lambda.Powertools.Logging.Tests.Attributes + { + // Arrange + var correlationId = Guid.NewGuid().ToString(); +- ++ ++ // Add seriolization context for AOT ++ PowertoolsLoggingSerializer.AddSerializerContext(TestJsonContext.Default); ++ + // Act + switch (outputCase) + { +@@ -314,11 +349,11 @@ namespace AWS.Lambda.Powertools.Logging.Tests.Attributes + }); + break; + } +- ++ + // Assert + var allKeys = Logger.GetAllKeys() + .ToDictionary(keyValuePair => keyValuePair.Key, keyValuePair => keyValuePair.Value); +- ++ + Assert.True(allKeys.ContainsKey(LoggingConstants.KeyCorrelationId)); + Assert.Equal((string)allKeys[LoggingConstants.KeyCorrelationId], correlationId); + } +@@ -327,58 +362,42 @@ namespace AWS.Lambda.Powertools.Logging.Tests.Attributes + public void When_Setting_SamplingRate_Should_Add_Key() + { + // Arrange +- var consoleOut = GetConsoleOutput(); +- Logger.Configure(options => +- { +- options.LogOutput = consoleOut; +- }); +- ++ var consoleOut = Substitute.For(); ++ SystemWrapper.Instance.SetOut(consoleOut); ++ + // Act + _testHandlers.HandlerSamplingRate(); + + // Assert +- consoleOut.Received(1).WriteLine(Arg.Is(s => +- s.Contains("\"level\":\"Information\"") && +- s.Contains("\"service\":\"service_undefined\"") && +- s.Contains("\"name\":\"AWS.Lambda.Powertools.Logging.Logger\"") && +- s.Contains("\"message\":\"test\"") && +- s.Contains("\"samplingRate\":0.5") +- )); ++ ++ consoleOut.Received().WriteLine( ++ Arg.Is(i => i.Contains("\"message\":\"test\",\"samplingRate\":0.5")) ++ ); + } + + [Fact] + public void When_Setting_Service_Should_Update_Key() + { + // Arrange +- var consoleOut = new TestLoggerOutput(); +- Logger.Configure(options => +- { +- options.LogOutput = consoleOut; +- }); +- ++ var consoleOut = new StringWriter(); ++ SystemWrapper.Instance.SetOut(consoleOut); ++ + // Act + _testHandlers.HandlerService(); + + // Assert + + var st = consoleOut.ToString(); +- +- Assert.Contains("\"level\":\"Information\"", st); +- Assert.Contains("\"service\":\"test\"", st); +- Assert.Contains("\"name\":\"AWS.Lambda.Powertools.Logging.Logger\"", st); +- Assert.Contains("\"message\":\"test\"", st); ++ Assert.Contains("\"level\":\"Information\",\"service\":\"test\",\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"message\":\"test\"", st); + } + + [Fact] + public void When_Setting_LogLevel_Should_Update_LogLevel() + { + // Arrange +- var consoleOut = new TestLoggerOutput();; +- Logger.Configure(options => +- { +- options.LogOutput = consoleOut; +- }); +- ++ var consoleOut = new StringWriter(); ++ SystemWrapper.Instance.SetOut(consoleOut); ++ + // Act + _testHandlers.TestLogLevelCritical(); + +@@ -392,12 +411,8 @@ namespace AWS.Lambda.Powertools.Logging.Tests.Attributes + public void When_Setting_LogLevel_HigherThanInformation_Should_Not_LogEvent() + { + // Arrange +- var consoleOut = GetConsoleOutput(); +- Logger.Configure(options => +- { +- options.LogOutput = consoleOut; +- }); +- ++ var consoleOut = Substitute.For(); ++ SystemWrapper.Instance.SetOut(consoleOut); + var context = new TestLambdaContext() + { + FunctionName = "PowertoolsLoggingSample-HelloWorldFunction-Gg8rhPwO7Wa1" +@@ -414,175 +429,101 @@ namespace AWS.Lambda.Powertools.Logging.Tests.Attributes + public void When_LogLevel_Debug_Should_Log_Message_When_No_Context_And_LogEvent_True() + { + // Arrange +- var consoleOut = GetConsoleOutput(); +- var stringWriter = new StringWriter(); +- Console.SetOut(stringWriter); +- +- Logger.Configure(options => +- { +- options.LogOutput = consoleOut; +- }); ++ var consoleOut = Substitute.For(); ++ SystemWrapper.Instance.SetOut(consoleOut); + + // Act + _testHandlers.TestLogEventWithoutContext(); +- ++ + // Assert +- var st = stringWriter.ToString(); +- Assert.Contains("Skipping Event Log because event parameter not found.", st); +- Assert.Contains("Skipping Lambda Context injection because ILambdaContext context parameter not found", st); +- ++ consoleOut.Received(1).WriteLine(Arg.Is(s => s == "Skipping Event Log because event parameter not found.")); + } + + [Fact] + public void Should_Log_When_Not_Using_Decorator() + { + // Arrange +- var consoleOut = GetConsoleOutput(); +- Logger.Configure(options => +- { +- options.LogOutput = consoleOut; +- }); ++ var consoleOut = Substitute.For(); ++ SystemWrapper.Instance.SetOut(consoleOut); + + var test = new TestHandlers(); + + // Act + test.TestLogNoDecorator(); +- ++ + // Assert +- consoleOut.Received(1).WriteLine(Arg.Is(s => +- s.Contains("\"level\":\"Information\"") && +- s.Contains("\"service\":\"service_undefined\"") && +- s.Contains("\"name\":\"AWS.Lambda.Powertools.Logging.Logger\"") && +- s.Contains("\"message\":\"test\"") +- )); ++ consoleOut.Received().WriteLine( ++ Arg.Is(i => i.Contains("\"level\":\"Information\",\"service\":\"service_undefined\",\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"message\":\"test\"}")) ++ ); + } + +- [Fact] +- public void LoggingAspect_ShouldRespectDynamicLogLevelChanges() ++ public void Dispose() + { +- // Arrange +- var consoleOut = GetConsoleOutput(); +- var stringWriter = new StringWriter(); +- Console.SetOut(stringWriter); +- +- Logger.Configure(options => +- { +- options.LogOutput = consoleOut; +- options.MinimumLogLevel = LogLevel.Warning; +- }); +- +- // Act +- _testHandlers.TestMethodDebug(); // Uses LogLevel.Debug attribute +- +- // Assert +- var st = stringWriter.ToString(); +- Assert.Contains("Skipping Lambda Context injection because ILambdaContext context parameter not found", st); ++ Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_CASE", ""); ++ Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", ""); ++ LoggingAspect.ResetForTest(); ++ PowertoolsLoggingSerializer.ClearOptions(); + } ++ } ++ ++ [Collection("A Sequential")] ++ public class ServiceTests : IDisposable ++ { ++ private readonly TestServiceHandler _testHandler; + +- [Fact] +- public void LoggingAspect_ShouldCorrectlyResetLogLevelAfterExecution() ++ public ServiceTests() + { +- // Arrange +- var consoleOut = GetConsoleOutput(); +- Logger.Configure(options => +- { +- options.LogOutput = consoleOut; +- options.MinimumLogLevel = LogLevel.Warning; +- }); +- +- // Act - First call with Debug level attribute +- _testHandlers.TestMethodDebug(); +- consoleOut.ClearReceivedCalls(); +- +- // Act - Then log directly at Debug level (should still work) +- Logger.LogDebug("This should be logged"); +- +- // Assert +- consoleOut.Received(1).WriteLine(Arg.Is(s => +- s.Contains("\"level\":\"Debug\"") && +- s.Contains("\"message\":\"This should be logged\""))); ++ _testHandler = new TestServiceHandler(); + } + + [Fact] +- public void LoggingAspect_ShouldRespectAttributePrecedenceOverEnvironment() ++ public void When_Setting_Service_Should_Override_Env() + { + // Arrange +- Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", "Error"); +- var consoleOut = GetConsoleOutput(); +- var stringWriter = new StringWriter(); +- Console.SetOut(stringWriter); +- +- Logger.Configure(options => +- { +- options.LogOutput = consoleOut; +- }); +- ++ var consoleOut = Substitute.For(); ++ SystemWrapper.Instance.SetOut(consoleOut); ++ + // Act +- _testHandlers.TestMethodDebug(); // Uses LogLevel.Debug attribute +- ++ _testHandler.LogWithEnv(); ++ _testHandler.Handler(); ++ + // Assert +- var st = stringWriter.ToString(); +- Assert.Contains("Skipping Lambda Context injection because ILambdaContext context parameter not found", st); ++ ++ consoleOut.Received(1).WriteLine( ++ Arg.Is(i => i.Contains("\"level\":\"Information\",\"service\":\"Environment Service\",\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"message\":\"Service: Environment Service\"")) ++ ); ++ consoleOut.Received(1).WriteLine( ++ Arg.Is(i => i.Contains("\"level\":\"Information\",\"service\":\"Attribute Service\",\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"message\":\"Service: Attribute Service\"")) ++ ); + } + + [Fact] +- public void LoggingAspect_ShouldImmediatelyApplyFilterLevelChanges() ++ public void When_Setting_Service_Should_Override_Env_And_Empty() + { + // Arrange +- var consoleOut = GetConsoleOutput(); +- +- Logger.Configure(options => +- { +- options.LogOutput = consoleOut; +- options.MinimumLogLevel = LogLevel.Error; +- }); +- ++ var consoleOut = Substitute.For(); ++ SystemWrapper.Instance.SetOut(consoleOut); ++ + // Act +- Logger.LogInformation("This should NOT be logged"); +- _testHandlers.TestMethodDebug(); // Should change level to Debug +- Logger.LogInformation("This should be logged"); +- ++ _testHandler.LogWithAndWithoutEnv(); ++ _testHandler.Handler(); ++ + // Assert + +- consoleOut.Received(1).WriteLine(Arg.Is(s => +- s.Contains("\"message\":\"This should be logged\""))); +- consoleOut.DidNotReceive().WriteLine(Arg.Is(s => +- s.Contains("\"message\":\"This should NOT be logged\""))); ++ consoleOut.Received(2).WriteLine( ++ Arg.Is(i => i.Contains("\"level\":\"Information\",\"service\":\"service_undefined\",\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"message\":\"Service: service_undefined\"")) ++ ); ++ consoleOut.Received(1).WriteLine( ++ Arg.Is(i => i.Contains("\"level\":\"Information\",\"service\":\"Attribute Service\",\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"message\":\"Service: Attribute Service\"")) ++ ); + } +- ++ + public void Dispose() + { +- ResetAllState(); +- } +- +- private IConsoleWrapper GetConsoleOutput() +- { +- // Create a new mock each time +- var output = Substitute.For(); +- return output; +- } +- +- private void ResetAllState() +- { +- // Clear environment variables +- Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_CASE", null); +- Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", null); +- Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", null); +- +- // Reset all logging components ++ Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_CASE", ""); ++ Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", ""); + LoggingAspect.ResetForTest(); +- Logger.Reset(); +- PowertoolsLoggingBuilderExtensions.ResetAllProviders(); +- LoggerFactoryHolder.Reset(); +- +- // Force default configuration +- var config = new PowertoolsLoggerConfiguration +- { +- MinimumLogLevel = LogLevel.Information, +- LoggerOutputCase = LoggerOutputCase.SnakeCase +- }; +- PowertoolsLoggingBuilderExtensions.UpdateConfiguration(config); +- LambdaLifecycleTracker.Reset(); ++ PowertoolsLoggingSerializer.ClearOptions(); + } + } + } +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Attributes/ServiceTests.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Attributes/ServiceTests.cs +deleted file mode 100644 +index 657730e0..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Attributes/ServiceTests.cs ++++ /dev/null +@@ -1,57 +0,0 @@ +-using System; +-using AWS.Lambda.Powertools.Common; +-using AWS.Lambda.Powertools.Logging.Internal; +-using AWS.Lambda.Powertools.Logging.Tests.Handlers; +-using NSubstitute; +-using Xunit; +- +-namespace AWS.Lambda.Powertools.Logging.Tests.Attributes; +- +-[Collection("A Sequential")] +-public class ServiceTests : IDisposable +-{ +- private readonly TestServiceHandler _testHandler; +- +- public ServiceTests() +- { +- _testHandler = new TestServiceHandler(); +- } +- +- [Fact] +- public void When_Setting_Service_Should_Override_Env() +- { +- Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", "Environment Service"); +- +- var consoleOut = Substitute.For(); +- Logger.Configure(options => +- options.LogOutput = consoleOut); +- +- // Act +- _testHandler.LogWithEnv(); +- _testHandler.Handler(); +- +- // Assert +- +- consoleOut.Received(1).WriteLine(Arg.Is(i => +- i.Contains("\"level\":\"Information\"") && +- i.Contains("\"service\":\"Environment Service\"") && +- i.Contains("\"name\":\"AWS.Lambda.Powertools.Logging.Logger\"") && +- i.Contains("\"message\":\"Service: Environment Service\"") +- )); +- consoleOut.Received(1).WriteLine(Arg.Is(i => +- i.Contains("\"level\":\"Information\"") && +- i.Contains("\"service\":\"Attribute Service\"") && +- i.Contains("\"name\":\"AWS.Lambda.Powertools.Logging.Logger\"") && +- i.Contains("\"message\":\"Service: Attribute Service\"") +- )); +- } +- +- public void Dispose() +- { +- Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_CASE", ""); +- Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", ""); +- LoggingAspect.ResetForTest(); +- Logger.Reset(); +- PowertoolsLoggingBuilderExtensions.ResetAllProviders(); +- } +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Buffering/LambdaContextBufferingTests.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Buffering/LambdaContextBufferingTests.cs +deleted file mode 100644 +index b6172371..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Buffering/LambdaContextBufferingTests.cs ++++ /dev/null +@@ -1,543 +0,0 @@ +-using System; +-using System.Diagnostics.CodeAnalysis; +-using System.Threading.Tasks; +-using Amazon.Lambda.Core; +-using Amazon.Lambda.TestUtilities; +-using AWS.Lambda.Powertools.Common.Tests; +-using AWS.Lambda.Powertools.Logging.Internal; +-using Microsoft.Extensions.Logging; +-using Xunit; +-using Xunit.Abstractions; +-using LogLevel = Microsoft.Extensions.Logging.LogLevel; +- +-namespace AWS.Lambda.Powertools.Logging.Tests.Buffering +-{ +- [Collection("Sequential")] +- public class LambdaContextBufferingTests : IDisposable +- { +- private readonly ITestOutputHelper _output; +- private readonly TestLoggerOutput _consoleOut; +- +- public LambdaContextBufferingTests(ITestOutputHelper output) +- { +- _output = output; +- _consoleOut = new TestLoggerOutput(); +- LogBufferManager.ResetForTesting(); +- } +- +- [Fact] +- public void FlushOnErrorEnabled_AutomaticallyFlushesBuffer() +- { +- // Arrange +- var logger = CreateLoggerWithFlushOnError(true); +- var handler = new ErrorOnlyHandler(logger); +- var context = CreateTestContext("test-request-3"); +- +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-invocation"); +- // Act +- handler.TestMethod("Event", context); +- +- // Assert +- var output = _consoleOut.ToString(); +- Assert.Contains("Debug message", output); +- Assert.Contains("Error triggering flush", output); +- } +- +- [Fact] +- public void Decorator_Clears_Buffer_On_Exit() +- { +- // Arrange +- var logger = CreateLoggerWithFlushOnError(false); +- var handler = new NoFlushHandler(logger); +- var context = CreateTestContext("test-request-3"); +- +- // Act +- handler.TestMethod("Event", context); +- +- // Assert +- var output = _consoleOut.ToString(); +- Assert.DoesNotContain("Debug message", output); +- +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-request-3"); +- Logger.FlushBuffer(); +- +- var debugNotFlushed = _consoleOut.ToString(); +- Assert.DoesNotContain("Debug message", debugNotFlushed); +- +- // second event +- handler.TestMethod("Event", context); +- +- // Assert +- var output2 = _consoleOut.ToString(); +- Assert.DoesNotContain("Debug message", output2); +- +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-request-4"); +- Logger.FlushBuffer(); +- +- var debugNotFlushed2 = _consoleOut.ToString(); +- Assert.DoesNotContain("Debug message", debugNotFlushed2); +- } +- +- [Fact] +- public async Task AsyncOperations_MaintainBufferContext() +- { +- // Arrange +- var logger = CreateLogger(LogLevel.Information, LogLevel.Debug); +- var handler = new AsyncLambdaHandler(logger); +- var context = CreateTestContext("async-test"); +- +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-invocation"); +- +- // Act +- await handler.TestMethodAsync("Event", context); +- +- // Assert +- var output = _consoleOut.ToString(); +- Assert.Contains("Async info message", output); +- Assert.Contains("Debug from task 1", output); +- Assert.Contains("Debug from task 2", output); +- } +- +- [Fact] +- public async Task Should_Log_All_Levels_Bellow() +- { +- // Arrange +- var logger = CreateLogger(LogLevel.Information, LogLevel.Information); +- var handler = new AsyncLambdaHandler(logger); +- var context = CreateTestContext("async-test"); +- +- // Act +- await handler.TestMethodAsync("Event", context); +- +- // Assert +- var output = _consoleOut.ToString(); +- Assert.Contains("Async info message", output); +- Assert.Contains("Async debug message", output); +- Assert.Contains("Async trace message", output); +- Assert.Contains("Async warning message", output); +- Assert.Contains("Debug from task 1", output); +- Assert.Contains("Debug from task 2", output); +- } +- +- private TestLambdaContext CreateTestContext(string requestId) +- { +- return new TestLambdaContext +- { +- FunctionName = "test-function", +- FunctionVersion = "1", +- AwsRequestId = requestId, +- InvokedFunctionArn = "arn:aws:lambda:us-east-1:123456789012:function:test-function" +- }; +- } +- +- private ILogger CreateLogger(LogLevel minimumLevel, LogLevel bufferAtLevel) +- { +- return LoggerFactory.Create(builder => +- { +- builder.AddPowertoolsLogger(config => +- { +- config.Service = "test-service"; +- config.MinimumLogLevel = minimumLevel; +- config.LogOutput = _consoleOut; +- config.LogBuffering = new LogBufferingOptions +- { +- BufferAtLogLevel = bufferAtLevel +- }; +- }); +- }).CreatePowertoolsLogger(); +- } +- +- private ILogger CreateLoggerWithFlushOnError(bool flushOnError) +- { +- return LoggerFactory.Create(builder => +- { +- builder.AddPowertoolsLogger(config => +- { +- config.Service = "test-service"; +- config.MinimumLogLevel = LogLevel.Information; +- config.LogOutput = _consoleOut; +- config.LogBuffering = new LogBufferingOptions +- { +- BufferAtLogLevel = LogLevel.Debug, +- FlushOnErrorLog = flushOnError +- }; +- }); +- }).CreatePowertoolsLogger(); +- } +- +- public void Dispose() +- { +- Logger.ClearBuffer(); +- LogBufferManager.ResetForTesting(); +- Logger.Reset(); +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", null); +- } +- } +- +- +- [Collection("Sequential")] +- [SuppressMessage("Usage", "xUnit1031:Do not use blocking task operations in test method")] +- public class StaticLoggerBufferingTests : IDisposable +- { +- private readonly TestLoggerOutput _consoleOut; +- private readonly ITestOutputHelper _output; +- +- public StaticLoggerBufferingTests(ITestOutputHelper output) +- { +- _output = output; +- _consoleOut = new TestLoggerOutput(); +- +- // Configure static Logger with our test output +- Logger.Configure(options => +- options.LogOutput = _consoleOut); +- } +- +- [Fact] +- public void StaticLogger_BasicBufferingBehavior() +- { +- // Arrange - explicitly configure Logger for this test +- // First reset any existing configuration +- Logger.Reset(); +- +- // Configure the logger with the test output +- Logger.Configure(options => +- { +- options.LogOutput = _consoleOut; +- options.MinimumLogLevel = LogLevel.Information; +- options.LogBuffering = new LogBufferingOptions +- { +- +- BufferAtLogLevel = LogLevel.Debug, +- FlushOnErrorLog = false // Disable auto-flush to test manual flush +- }; +- }); +- +- // Set invocation ID manually +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-static-request-1"); +- +- // Act - log messages +- Logger.AppendKey("custom-key", "custom-value"); +- Logger.LogInformation("Information message"); +- Logger.LogDebug("Debug message"); // Should be buffered +- +- // Check the internal state before flush +- var outputBeforeFlush = _consoleOut.ToString(); +- _output.WriteLine($"Before flush: {outputBeforeFlush}"); +- Assert.DoesNotContain("Debug message", outputBeforeFlush); +- +- // Flush the buffer +- Logger.FlushBuffer(); +- +- // Assert after flush +- var outputAfterFlush = _consoleOut.ToString(); +- _output.WriteLine($"After flush: {outputAfterFlush}"); +- Assert.Contains("Debug message", outputAfterFlush); +- } +- +- [Fact] +- public void StaticLogger_WithLoggingDecoratedHandler() +- { +- // Arrange +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "invocation-1"); +- Logger.Configure(options => +- { +- options.LogOutput = _consoleOut; +- options.LogBuffering = new LogBufferingOptions +- { +- +- BufferAtLogLevel = LogLevel.Debug, +- FlushOnErrorLog = true +- }; +- }); +- +- var handler = new StaticLambdaHandler(); +- var context = new TestLambdaContext +- { +- AwsRequestId = "test-static-request-2", +- FunctionName = "test-function" +- }; +- +- // Act +- handler.TestMethod("test-event", context); +- +- // Assert +- var output = _consoleOut.ToString(); +- Assert.Contains("Information message", output); +- Assert.Contains("Debug message", output); +- Assert.Contains("Error message", output); +- Assert.Contains("custom-key", output); +- Assert.Contains("custom-value", output); +- } +- +- [Fact] +- public void StaticLogger_ClearBufferRemovesLogs() +- { +- // Arrange +- Logger.Configure(options => +- { +- options.LogOutput = _consoleOut; +- options.MinimumLogLevel = LogLevel.Information; +- options.LogBuffering = new LogBufferingOptions +- { +- +- BufferAtLogLevel = LogLevel.Debug +- }; +- }); +- +- // Set invocation ID +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-static-request-3"); +- +- // Act - log message and clear buffer +- Logger.LogDebug("Debug message before clear"); +- Logger.ClearBuffer(); +- Logger.LogDebug("Debug message after clear"); +- Logger.FlushBuffer(); +- +- // Assert +- var output = _consoleOut.ToString(); +- Assert.DoesNotContain("Debug message before clear", output); +- Assert.Contains("Debug message after clear", output); +- } +- +- [Fact] +- public void StaticLogger_FlushOnErrorLogEnabled() +- { +- // Arrange +- Logger.Configure(options => +- { +- options.LogOutput = _consoleOut; +- options.MinimumLogLevel = LogLevel.Information; +- options.LogBuffering = new LogBufferingOptions +- { +- +- BufferAtLogLevel = LogLevel.Debug, +- FlushOnErrorLog = true +- }; +- }); +- +- // Set invocation ID +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-static-request-4"); +- +- // Act - log debug then error +- Logger.LogDebug("Debug message"); +- Logger.LogError("Error message"); +- +- // Assert - error should trigger flush +- var output = _consoleOut.ToString(); +- Assert.Contains("Debug message", output); +- Assert.Contains("Error message", output); +- } +- +- [Fact] +- public void StaticLogger_MultipleInvocationsIsolated_And_Clear() +- { +- // Arrange +- Logger.Configure(options => +- { +- options.LogOutput = _consoleOut; +- options.MinimumLogLevel = LogLevel.Information; +- options.LogBuffering = new LogBufferingOptions +- { +- +- BufferAtLogLevel = LogLevel.Debug +- }; +- }); +- +- // Act - first invocation +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-static-request-5A"); +- Logger.LogDebug("Debug from invocation A"); +- +- // Switch to second invocation +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-static-request-5B"); +- Logger.LogDebug("Debug from invocation B"); +- +- // Switch back to first invocation and flush +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-static-request-5C"); +- Logger.LogDebug("Debug from invocation C"); +- +- Logger.FlushBuffer(); +- +- // Assert - after second flush +- var outputAfterSecondFlush = _consoleOut.ToString(); +- Assert.DoesNotContain("Debug from invocation A", outputAfterSecondFlush); +- Assert.DoesNotContain("Debug from invocation B", outputAfterSecondFlush); +- Assert.Contains("Debug from invocation C", outputAfterSecondFlush); +- } +- +- [Fact] +- public void StaticLogger_FlushOnErrorDisabled() +- { +- // Arrange +- Logger.Reset(); +- Logger.Configure(options => +- { +- options.LogOutput = _consoleOut; +- options.MinimumLogLevel = LogLevel.Information; +- options.LogBuffering = new LogBufferingOptions +- { +- BufferAtLogLevel = LogLevel.Debug, +- FlushOnErrorLog = false +- }; +- }); +- +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-static-request-6"); +- +- // Act - log debug then error +- Logger.LogDebug("Debug message with auto-flush disabled"); +- Logger.LogError("Error message that should not trigger flush"); +- +- // Assert - debug message should remain buffered +- var output = _consoleOut.ToString(); +- Assert.DoesNotContain("Debug message with auto-flush disabled", output); +- Assert.Contains("Error message that should not trigger flush", output); +- +- // Now manually flush and verify debug message appears +- Logger.FlushBuffer(); +- output = _consoleOut.ToString(); +- Assert.Contains("Debug message with auto-flush disabled", output); +- } +- +- [Fact] +- public void StaticLogger_AsyncOperationsMaintainContext() +- { +- // Arrange +- // Logger.Reset(); +- Logger.Configure(options => +- { +- options.LogOutput = _consoleOut; +- options.MinimumLogLevel = LogLevel.Information; +- options.LogBuffering = new LogBufferingOptions +- { +- BufferAtLogLevel = LogLevel.Debug, +- FlushOnErrorLog = false +- }; +- }); +- +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-static-request-8"); +- +- // Act - simulate async operations +- Task.Run(() => { Logger.LogDebug("Debug from task 1"); }).Wait(); +- +- Task.Run(() => { Logger.LogDebug("Debug from task 2"); }).Wait(); +- +- Logger.LogInformation("Main thread info message"); +- +- // Flush buffers +- Logger.FlushBuffer(); +- +- // Assert +- var output = _consoleOut.ToString(); +- Assert.Contains("Debug from task 1", output); +- Assert.Contains("Debug from task 2", output); +- Assert.Contains("Main thread info message", output); +- } +- +- public void Dispose() +- { +- // Clean up all state between tests +- Logger.ClearBuffer(); +- LogBufferManager.ResetForTesting(); +- LoggerFactoryHolder.Reset(); +- _consoleOut.Clear(); +- Logger.Reset(); +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", null); +- } +- } +- +- public class StaticLambdaHandler +- { +- [Logging(LogEvent = true)] +- public void TestMethod(string message, ILambdaContext lambdaContext) +- { +- Logger.AppendKey("custom-key", "custom-value"); +- Logger.LogInformation("Information message"); +- Logger.LogDebug("Debug message"); +- Logger.LogError("Error message"); +- Logger.FlushBuffer(); +- } +- } +- +- // Lambda handlers for testing +- public class LambdaHandler +- { +- private readonly ILogger _logger; +- +- public LambdaHandler(ILogger logger) +- { +- _logger = logger; +- } +- +- [Logging(LogEvent = true)] +- public void TestMethod(string message, ILambdaContext lambdaContext) +- { +- _logger.AppendKey("custom-key", "custom-value"); +- _logger.LogInformation("Information message"); +- _logger.LogDebug("Debug message"); +- _logger.LogError("Error message"); +- _logger.FlushBuffer(); +- } +- } +- +- public class ErrorOnlyHandler +- { +- private readonly ILogger _logger; +- +- public ErrorOnlyHandler(ILogger logger) +- { +- _logger = logger; +- } +- +- [Logging(LogEvent = true)] +- public void TestMethod(string message, ILambdaContext lambdaContext) +- { +- _logger.LogDebug("Debug message"); +- _logger.LogError("Error triggering flush"); +- } +- } +- +- public class NoFlushHandler +- { +- private readonly ILogger _logger; +- +- public NoFlushHandler(ILogger logger) +- { +- _logger = logger; +- } +- +- [Logging(LogEvent = true)] +- public void TestMethod(string message, ILambdaContext lambdaContext) +- { +- _logger.LogDebug("Debug message"); +- _logger.LogError("Error triggering flush"); +- // No flush here - Decorator clears buffer on exit +- } +- } +- +- public class AsyncLambdaHandler +- { +- private readonly ILogger _logger; +- +- public AsyncLambdaHandler(ILogger logger) +- { +- _logger = logger; +- } +- +- [Logging(LogEvent = true)] +- public async Task TestMethodAsync(string message, ILambdaContext lambdaContext) +- { +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-invocation"); +- +- _logger.LogInformation("Async info message"); +- _logger.LogDebug("Async debug message"); +- _logger.LogTrace("Async trace message"); +- _logger.LogWarning("Async warning message"); +- +- var task1 = Task.Run(() => { _logger.LogDebug("Debug from task 1"); }); +- +- var task2 = Task.Run(() => { _logger.LogDebug("Debug from task 2"); }); +- +- await Task.WhenAll(task1, task2); +- _logger.FlushBuffer(); +- } +- } +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Buffering/LogBufferCircularCacheTests.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Buffering/LogBufferCircularCacheTests.cs +deleted file mode 100644 +index c0640c92..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Buffering/LogBufferCircularCacheTests.cs ++++ /dev/null +@@ -1,270 +0,0 @@ +-using System; +-using System.IO; +-using AWS.Lambda.Powertools.Common; +-using AWS.Lambda.Powertools.Common.Tests; +-using AWS.Lambda.Powertools.Logging.Internal; +-using AWS.Lambda.Powertools.Logging.Internal.Helpers; +-using Microsoft.Extensions.Logging; +-using Xunit; +- +-namespace AWS.Lambda.Powertools.Logging.Tests.Buffering; +- +-public class LogBufferCircularCacheTests : IDisposable +-{ +- private readonly TestLoggerOutput _consoleOut; +- +- public LogBufferCircularCacheTests() +- { +- _consoleOut = new TestLoggerOutput(); +- LogBufferManager.ResetForTesting(); +- } +- +- [Trait("Category", "CircularBuffer")] +- [Fact] +- public void Buffer_WhenMaxSizeExceeded_DiscardOldestEntries() +- { +- // Arrange +- var config = new PowertoolsLoggerConfiguration +- { +- MinimumLogLevel = LogLevel.Information, +- LogBuffering = new LogBufferingOptions +- { +- BufferAtLogLevel = LogLevel.Debug, +- MaxBytes = 1200 // Small buffer size to trigger overflow - Needs to be adjusted based on the log message size +- }, +- LogOutput = _consoleOut +- }; +- +- var logger = LoggerFactoryHelper.CreateAndConfigureFactory(config).CreatePowertoolsLogger(); +- +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "circular-buffer-test"); +- +- // Act - add many debug logs to fill buffer +- for (int i = 0; i < 5; i++) +- { +- logger.LogDebug($"Old debug message {i} that should be removed"); +- } +- +- // Add more logs that should push out the older ones +- for (int i = 0; i < 5; i++) +- { +- logger.LogDebug($"New debug message {i} that should remain"); +- } +- +- // Flush buffer +- logger.FlushBuffer(); +- +- // Assert +- var output = _consoleOut.ToString(); +- +- // First entries should be discarded +- Assert.DoesNotContain("Old debug message 0", output); +- Assert.DoesNotContain("Old debug message 1", output); +- +- // Later entries should be present +- Assert.Contains("New debug message 3", output); +- Assert.Contains("New debug message 4", output); +- } +- +- [Trait("Category", "CircularBuffer")] +- [Fact] +- public void Buffer_WhenMaxSizeExceeded_DiscardOldestEntries_Warn() +- { +- // Arrange +- var config = new PowertoolsLoggerConfiguration +- { +- MinimumLogLevel = LogLevel.Information, +- LogBuffering = new LogBufferingOptions +- { +- BufferAtLogLevel = LogLevel.Debug, +- MaxBytes = 1024 // Small buffer size to trigger overflow +- }, +- LogOutput = _consoleOut +- }; +- +- var logger = LoggerFactoryHelper.CreateAndConfigureFactory(config).CreatePowertoolsLogger(); +- +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "circular-buffer-test"); +- +- var stringWriter = new StringWriter(); +- Console.SetOut(stringWriter); +- +- // Act - add many debug logs to fill buffer +- for (int i = 0; i < 5; i++) +- { +- logger.LogDebug($"Old debug message {i} that should be removed"); +- } +- +- // Add more logs that should push out the older ones +- for (int i = 0; i < 5; i++) +- { +- logger.LogDebug($"New debug message {i} that should remain"); +- } +- +- // Flush buffer +- logger.FlushBuffer(); +- +- // Assert +- var st = stringWriter.ToString(); +- Assert.Contains("Some logs are not displayed because they were evicted from the buffer. Increase buffer size to store more logs in the buffer", st); +- } +- +- [Trait("Category", "CircularBuffer")] +- [Fact] +- public void Buffer_WhenMaxSizeExceeded_DiscardOldestEntries_Warn_With_Warning_Level() +- { +- // Arrange +- var config = new PowertoolsLoggerConfiguration +- { +- MinimumLogLevel = LogLevel.Information, +- LogBuffering = new LogBufferingOptions +- { +- BufferAtLogLevel = LogLevel.Warning, +- MaxBytes = 1024 // Small buffer size to trigger overflow +- }, +- LogOutput = _consoleOut +- }; +- +- var logger = LoggerFactoryHelper.CreateAndConfigureFactory(config).CreatePowertoolsLogger(); +- +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "circular-buffer-test"); +- +- var stringWriter = new StringWriter(); +- Console.SetOut(stringWriter); +- +- // Act - add many debug logs to fill buffer +- for (int i = 0; i < 5; i++) +- { +- logger.LogDebug($"Old debug message {i} that should be removed"); +- } +- +- // Add more logs that should push out the older ones +- for (int i = 0; i < 5; i++) +- { +- logger.LogDebug($"New debug message {i} that should remain"); +- } +- +- // Flush buffer +- logger.FlushBuffer(); +- +- // Assert +- var st = stringWriter.ToString(); +- Assert.Contains("Some logs are not displayed because they were evicted from the buffer. Increase buffer size to store more logs in the buffer", st); +- } +- +- [Trait("Category", "CircularBuffer")] +- [Fact] +- public void Buffer_WithLargeLogEntry_DiscardsManySmallEntries() +- { +- // Arrange +- var config = new PowertoolsLoggerConfiguration +- { +- MinimumLogLevel = LogLevel.Information, +- LogBuffering = new LogBufferingOptions +- { +- BufferAtLogLevel = LogLevel.Debug, +- MaxBytes = 2048 // Small buffer size to trigger overflow +- }, +- LogOutput = _consoleOut +- }; +- +- var logger = LoggerFactoryHelper.CreateAndConfigureFactory(config).CreatePowertoolsLogger(); +- +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "large-entry-test"); +- +- // Act - add many small entries first +- for (int i = 0; i < 10; i++) +- { +- logger.LogDebug($"Small message {i}"); +- } +- +- // Add one very large entry that should displace many small ones +- var largeMessage = new string('X', 80); // Large enough to push out multiple small entries +- logger.LogDebug($"Large message: {largeMessage}"); +- +- // Flush buffer +- logger.FlushBuffer(); +- +- // Assert +- var output = _consoleOut.ToString(); +- +- // Several early small messages should be discarded +- for (int i = 0; i < 5; i++) +- { +- Assert.DoesNotContain($"Small message {i}", output); +- } +- +- // Large message should be present +- Assert.Contains("Large message: XXXX", output); +- +- // Some later small messages should remain +- Assert.Contains("Small message 9", output); +- } +- +- [Trait("Category", "CircularBuffer")] +- [Fact] +- public void Buffer_WithExtremelyLargeEntry_Logs_Directly_And_Warning() +- { +- // Arrange +- var config = new PowertoolsLoggerConfiguration +- { +- MinimumLogLevel = LogLevel.Information, +- LogBuffering = new LogBufferingOptions +- { +- BufferAtLogLevel = LogLevel.Debug, +- MaxBytes = 5096 // Even with a larger buffer +- }, +- LogOutput = _consoleOut +- }; +- +- var logger = LoggerFactoryHelper.CreateAndConfigureFactory(config).CreatePowertoolsLogger(); +- +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "extreme-entry-test"); +- +- var stringWriter = new StringWriter(); +- Console.SetOut(stringWriter); +- +- // Act - add some small entries first +- for (int i = 0; i < 4; i++) +- { +- logger.LogDebug($"Initial message {i}"); +- } +- +- // Add entry larger than the entire buffer - should displace everything +- var hugeMessage = new string('X', 3000); +- logger.LogDebug($"Huge message: {hugeMessage}"); +- +- var st = stringWriter.ToString(); +- Assert.Contains("Cannot add item to the buffer", st); +- +- // Add more entries after +- for (int i = 0; i < 4; i++) +- { +- logger.LogDebug($"Final message {i}"); +- } +- +- // Flush buffer +- logger.FlushBuffer(); +- +- // Assert +- var output = _consoleOut.ToString(); +- +- // Initial messages should be discarded +- for (int i = 0; i < 4; i++) +- { +- Assert.Contains($"Initial message {i}", output); +- } +- +- // Some of the final messages should be present +- Assert.Contains("Final message 3", output); +- } +- +- public void Dispose() +- { +- // Clean up all state between tests +- Logger.ClearBuffer(); +- LogBufferManager.ResetForTesting(); +- Logger.Reset(); +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", null); +- } +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Buffering/LogBufferingHandlerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Buffering/LogBufferingHandlerTests.cs +deleted file mode 100644 +index cbe95411..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Buffering/LogBufferingHandlerTests.cs ++++ /dev/null +@@ -1,342 +0,0 @@ +-using System; +-using System.Threading.Tasks; +-using AWS.Lambda.Powertools.Common; +-using AWS.Lambda.Powertools.Common.Tests; +-using AWS.Lambda.Powertools.Logging.Internal; +-using Microsoft.Extensions.Logging; +-using Xunit; +-using Xunit.Abstractions; +- +-namespace AWS.Lambda.Powertools.Logging.Tests.Buffering +-{ +- [Collection("Sequential")] +- public class LogBufferingHandlerTests : IDisposable +- { +- private readonly ITestOutputHelper _output; +- private readonly TestLoggerOutput _consoleOut; +- +- public LogBufferingHandlerTests(ITestOutputHelper output) +- { +- _output = output; +- _consoleOut = new TestLoggerOutput(); +- LogBufferManager.ResetForTesting(); +- } +- +- [Fact] +- public void BasicBufferingBehavior_BuffersDebugLogsOnly() +- { +- // Arrange +- var logger = CreateLogger(LogLevel.Information, LogLevel.Debug); +- var handler = new HandlerWithoutFlush(logger); // Use a handler that doesn't flush +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-invocation"); +- +- // Act - log messages without flushing +- handler.TestMethod(); +- +- // Assert - before flush +- var outputBeforeFlush = _consoleOut.ToString(); +- Assert.Contains("Information message", outputBeforeFlush); +- Assert.Contains("Error message", outputBeforeFlush); +- Assert.Contains("custom-key", outputBeforeFlush); +- Assert.Contains("custom-value", outputBeforeFlush); +- Assert.DoesNotContain("Debug message", outputBeforeFlush); // Debug should be buffered +- +- // Now flush the buffer +- Logger.FlushBuffer(); +- +- // Assert - after flush +- var outputAfterFlush = _consoleOut.ToString(); +- Assert.Contains("Debug message", outputAfterFlush); // Debug should now be present +- } +- +- [Fact] +- public void FlushOnErrorEnabled_AutomaticallyFlushesBuffer() +- { +- // Arrange +- var logger = CreateLoggerWithFlushOnError(true); +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-invocation"); +- +- // Act - with custom handler that doesn't manually flush +- var handler = new CustomHandlerWithoutFlush(logger); +- handler.TestMethod(); +- +- // Assert +- var output = _consoleOut.ToString(); +- Assert.Contains("Debug message", output); // Should be flushed by error log +- Assert.Contains("Error triggering flush", output); +- } +- +- [Fact] +- public void FlushOnErrorDisabled_DoesNotAutomaticallyFlushBuffer() +- { +- // Arrange +- var logger = CreateLoggerWithFlushOnError(false); +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-invocation"); +- +- // Act +- var handler = new CustomHandlerWithoutFlush(logger); +- handler.TestMethod(); +- +- // Assert +- var output = _consoleOut.ToString(); +- Assert.DoesNotContain("Debug message", output); // Should remain buffered +- Assert.Contains("Error triggering flush", output); +- } +- +- [Fact] +- public void ClearingBuffer_RemovesBufferedLogs() +- { +- // Arrange +- var logger = CreateLogger(LogLevel.Information, LogLevel.Debug); +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-invocation"); +- +- // Act +- var handler = new ClearBufferHandler(logger); +- handler.TestMethod(); +- +- // Assert +- var output = _consoleOut.ToString(); +- Assert.DoesNotContain("Debug message before clear", output); +- Assert.Contains("Debug message after clear", output); +- } +- +- [Fact] +- public void MultipleInvocations_IsolateLogBuffers() +- { +- // Arrange +- var logger = CreateLogger(LogLevel.Information, LogLevel.Debug); +- var handler = new Handlers(logger); +- +- // Act +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "invocation-1"); +- handler.TestMethod(); +- +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "invocation-2"); +- // Create a custom handler that logs different messages +- var customHandler = new MultipleInvocationHandler(logger); +- customHandler.TestMethod(); +- +- // Assert +- var output = _consoleOut.ToString(); +- Assert.Contains("Information message", output); // From first invocation +- Assert.Contains("Second invocation info", output); // From second invocation +- } +- +- [Fact] +- public void MultipleProviders_AllProvidersReceiveLogs() +- { +- // Arrange +- var config = new PowertoolsLoggerConfiguration +- { +- MinimumLogLevel = LogLevel.Information, +- LogBuffering = new LogBufferingOptions { BufferAtLogLevel = LogLevel.Debug }, +- LogOutput = _consoleOut +- }; +- +- var powertoolsConfig = new PowertoolsConfigurations(new PowertoolsEnvironment()); +- +- // Create two separate providers +- var provider1 = new BufferingLoggerProvider(config, powertoolsConfig); +- var provider2 = new BufferingLoggerProvider(config, powertoolsConfig); +- +- var logger1 = provider1.CreateLogger("Provider1"); +- var logger2 = provider2.CreateLogger("Provider2"); +- +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "multi-provider-test"); +- +- // Act +- logger1.LogDebug("Debug from provider 1"); +- logger2.LogDebug("Debug from provider 2"); +- +- // Flush logs from all providers +- Logger.FlushBuffer(); +- +- // Assert +- var output = _consoleOut.ToString(); +- Assert.Contains("Debug from provider 1", output); +- Assert.Contains("Debug from provider 2", output); +- } +- +- [Fact] +- public async Task AsyncOperations_MaintainBufferContext() +- { +- // Arrange +- var logger = CreateLogger(LogLevel.Information, LogLevel.Debug); +- var handler = new AsyncHandler(logger); +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "async-test"); +- +- // Act +- await handler.TestMethodAsync(); +- +- // Assert +- var output = _consoleOut.ToString(); +- Assert.Contains("Async info message", output); +- Assert.Contains("Debug from task 1", output); +- Assert.Contains("Debug from task 2", output); +- } +- +- private ILogger CreateLogger(LogLevel minimumLevel, LogLevel bufferAtLevel) +- { +- return LoggerFactory.Create(builder => +- { +- builder.AddPowertoolsLogger(config => +- { +- config.Service = "test-service"; +- config.MinimumLogLevel = minimumLevel; +- config.LogOutput = _consoleOut; +- config.LogBuffering = new LogBufferingOptions +- { +- BufferAtLogLevel = bufferAtLevel, +- FlushOnErrorLog = false +- }; +- }); +- }).CreatePowertoolsLogger(); +- } +- +- private ILogger CreateLoggerWithFlushOnError(bool flushOnError) +- { +- return LoggerFactory.Create(builder => +- { +- builder.AddPowertoolsLogger(config => +- { +- config.Service = "test-service"; +- config.MinimumLogLevel = LogLevel.Information; +- config.LogOutput = _consoleOut; +- config.LogBuffering = new LogBufferingOptions +- { +- BufferAtLogLevel = LogLevel.Debug, +- FlushOnErrorLog = flushOnError +- }; +- }); +- }).CreatePowertoolsLogger(); +- } +- +- public void Dispose() +- { +- // Clean up all state between tests +- Logger.ClearBuffer(); +- LogBufferManager.ResetForTesting(); +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", null); +- } +- } +- +- // Additional test handlers with specific behavior +- public class CustomHandlerWithoutFlush +- { +- private readonly ILogger _logger; +- +- public CustomHandlerWithoutFlush(ILogger logger) +- { +- _logger = logger; +- } +- +- public void TestMethod() +- { +- _logger.LogDebug("Debug message"); +- _logger.LogError("Error triggering flush"); +- // No manual flush +- } +- } +- +- public class ClearBufferHandler +- { +- private readonly ILogger _logger; +- +- public ClearBufferHandler(ILogger logger) +- { +- _logger = logger; +- } +- +- public void TestMethod() +- { +- _logger.LogDebug("Debug message before clear"); +- Logger.ClearBuffer(); // Clear the buffer +- _logger.LogDebug("Debug message after clear"); +- Logger.FlushBuffer(); // Flush only second message +- } +- } +- +- public class MultipleInvocationHandler +- { +- private readonly ILogger _logger; +- +- public MultipleInvocationHandler(ILogger logger) +- { +- _logger = logger; +- } +- +- public void TestMethod() +- { +- _logger.LogInformation("Second invocation info"); +- _logger.LogDebug("Second invocation debug"); +- _logger.FlushBuffer(); +- } +- } +- +- public class Handlers +- { +- private readonly ILogger _logger; +- +- public Handlers(ILogger logger) +- { +- _logger = logger; +- } +- +- public void TestMethod() +- { +- _logger.AppendKey("custom-key", "custom-value"); +- _logger.LogInformation("Information message"); +- _logger.LogDebug("Debug message"); +- +- _logger.LogError("Error message"); +- +- _logger.FlushBuffer(); +- } +- } +- +- public class HandlerWithoutFlush +- { +- private readonly ILogger _logger; +- +- public HandlerWithoutFlush(ILogger logger) +- { +- _logger = logger; +- } +- +- public void TestMethod() +- { +- _logger.AppendKey("custom-key", "custom-value"); +- _logger.LogInformation("Information message"); +- _logger.LogDebug("Debug message"); +- _logger.LogError("Error message"); +- // No flush here +- } +- } +- +- public class AsyncHandler +- { +- private readonly ILogger _logger; +- +- public AsyncHandler(ILogger logger) +- { +- _logger = logger; +- } +- +- public async Task TestMethodAsync() +- { +- _logger.LogInformation("Async info message"); +- _logger.LogDebug("Async debug message"); +- +- var task1 = Task.Run(() => { +- _logger.LogDebug("Debug from task 1"); +- }); +- +- var task2 = Task.Run(() => { +- _logger.LogDebug("Debug from task 2"); +- }); +- +- await Task.WhenAll(task1, task2); +- _logger.FlushBuffer(); +- } +- } +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Buffering/LogBufferingTests.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Buffering/LogBufferingTests.cs +deleted file mode 100644 +index 00d1e759..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Buffering/LogBufferingTests.cs ++++ /dev/null +@@ -1,499 +0,0 @@ +-using System; +-using AWS.Lambda.Powertools.Common; +-using AWS.Lambda.Powertools.Common.Tests; +-using AWS.Lambda.Powertools.Logging.Internal; +-using AWS.Lambda.Powertools.Logging.Internal.Helpers; +-using Microsoft.Extensions.Logging; +-using Xunit; +- +-namespace AWS.Lambda.Powertools.Logging.Tests.Buffering +-{ +- [Collection("Sequential")] +- public class LogBufferingTests : IDisposable +- { +- private readonly TestLoggerOutput _consoleOut; +- +- public LogBufferingTests() +- { +- _consoleOut = new TestLoggerOutput(); +- } +- +- [Trait("Category", "BufferManager")] +- [Fact] +- public void SetInvocationId_IsolatesLogsBetweenInvocations_And_Clear() +- { +- // Arrange +- var config = new PowertoolsLoggerConfiguration +- { +- LogBuffering = new LogBufferingOptions(), +- LogOutput = _consoleOut +- }; +- +- var logger = LoggerFactoryHelper.CreateAndConfigureFactory(config).CreatePowertoolsLogger(); +- +- // Act +- +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "invocation-1"); +- logger.LogDebug("Debug message from invocation 1"); +- +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "invocation-2"); +- logger.LogDebug("Debug message from invocation 2"); +- +- logger.FlushBuffer(); +- +- // Assert +- var output = _consoleOut.ToString(); +- Assert.DoesNotContain("Debug message from invocation 1", output); +- Assert.Contains("Debug message from invocation 2", output); +- } +- +- [Trait("Category", "BufferedLogger")] +- [Fact] +- public void BufferedLogger_OnlyBuffersConfiguredLogLevels() +- { +- // Arrange +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "invocation-1"); +- +- var config = new PowertoolsLoggerConfiguration +- { +- MinimumLogLevel = LogLevel.Information, +- LogBuffering = new LogBufferingOptions +- { +- BufferAtLogLevel = LogLevel.Trace +- }, +- LogOutput = _consoleOut +- }; +- var logger = LoggerFactoryHelper.CreateAndConfigureFactory(config).CreatePowertoolsLogger(); +- +- // Act +- logger.LogTrace("Trace message"); // should buffer +- logger.LogDebug("Debug message"); // Should be buffered +- logger.LogInformation("Info message"); // Above minimum, should be logged directly +- +- // Assert +- var output = _consoleOut.ToString(); +- Assert.DoesNotContain("Trace message", output); +- Assert.DoesNotContain("Debug message", output); // Not flushed yet +- Assert.Contains("Info message", output); +- +- // Flush the buffer +- Logger.FlushBuffer(); +- +- output = _consoleOut.ToString(); +- Assert.Contains("Trace message", output); // Now should be visible +- } +- +- [Trait("Category", "BufferedLogger")] +- [Fact] +- public void BufferedLogger_Buffer_Takes_Precedence_Same_Level() +- { +- // Arrange +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "invocation-1"); +- +- var config = new PowertoolsLoggerConfiguration +- { +- MinimumLogLevel = LogLevel.Information, +- LogBuffering = new LogBufferingOptions +- { +- BufferAtLogLevel = LogLevel.Information +- }, +- LogOutput = _consoleOut +- }; +- var logger = LoggerFactoryHelper.CreateAndConfigureFactory(config).CreatePowertoolsLogger(); +- +- // Act +- logger.LogTrace("Trace message"); // Below buffer threshold, should be ignored +- logger.LogDebug("Debug message"); // Should be buffered +- logger.LogInformation("Info message"); // Above minimum, should be logged directly +- +- // Assert +- var output = _consoleOut.ToString(); +- Assert.Empty(output); +- +- // Flush the buffer +- Logger.FlushBuffer(); +- +- output = _consoleOut.ToString(); +- Assert.Contains("Info message", output); // Now should be visible +- Assert.Contains("Debug message", output); // Now should be visible +- Assert.Contains("Trace message", output); // Now should be visible +- } +- +- [Trait("Category", "BufferedLogger")] +- [Fact] +- public void BufferedLogger_Buffer_Takes_Precedence_Higher_Level() +- { +- // Arrange +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "invocation-1"); +- +- var config = new PowertoolsLoggerConfiguration +- { +- MinimumLogLevel = LogLevel.Information, +- LogBuffering = new LogBufferingOptions +- { +- BufferAtLogLevel = LogLevel.Warning +- }, +- LogOutput = _consoleOut +- }; +- var logger = LoggerFactoryHelper.CreateAndConfigureFactory(config).CreatePowertoolsLogger(); +- +- // Act +- logger.LogWarning("Warning message"); // Should be buffered +- logger.LogInformation("Info message"); // Should be buffered +- logger.LogDebug("Debug message"); +- +- // Assert +- var output = _consoleOut.ToString(); +- Assert.Empty(output); +- +- // Flush the buffer +- Logger.FlushBuffer(); +- +- output = _consoleOut.ToString(); +- Assert.Contains("Info message", output); // Now should be visible +- Assert.Contains("Warning message", output); +- Assert.Contains("Debug message", output); +- } +- +- [Trait("Category", "BufferedLogger")] +- [Fact] +- public void BufferedLogger_Buffer_Log_Level_Error_Does_Not_Buffer() +- { +- // Arrange +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "invocation-1"); +- +- var config = new PowertoolsLoggerConfiguration +- { +- MinimumLogLevel = LogLevel.Information, +- LogBuffering = new LogBufferingOptions +- { +- BufferAtLogLevel = LogLevel.Error +- }, +- LogOutput = _consoleOut +- }; +- var logger = LoggerFactoryHelper.CreateAndConfigureFactory(config).CreatePowertoolsLogger(); +- +- // Act +- logger.LogError("Error message"); // Should be buffered +- logger.LogInformation("Info message"); // Should be buffered +- +- // Assert +- var output = _consoleOut.ToString(); +- Assert.Contains("Error message", output); +- Assert.Contains("Info message", output); +- } +- +- [Trait("Category", "BufferedLogger")] +- [Fact] +- public void FlushOnErrorLog_FlushesBufferWhenEnabled() +- { +- // Arrange +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "invocation-1"); +- var config = new PowertoolsLoggerConfiguration +- { +- MinimumLogLevel = LogLevel.Information, +- LogBuffering = new LogBufferingOptions +- { +- BufferAtLogLevel = LogLevel.Debug, +- FlushOnErrorLog = true +- }, +- LogOutput = _consoleOut +- }; +- +- var logger = LoggerFactoryHelper.CreateAndConfigureFactory(config).CreatePowertoolsLogger(); +- +- // Act +- logger.LogDebug("Debug message 1"); // Should be buffered +- logger.LogDebug("Debug message 2"); // Should be buffered +- logger.LogError("Error message"); // Should trigger flush of buffer +- +- // Assert +- var output = _consoleOut.ToString(); +- Assert.Contains("Debug message 1", output); +- Assert.Contains("Debug message 2", output); +- Assert.Contains("Error message", output); +- } +- +- [Trait("Category", "BufferedLogger")] +- [Fact] +- public void ClearBuffer_RemovesAllBufferedLogs() +- { +- // Arrange +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "invocation-1"); +- var config = new PowertoolsLoggerConfiguration +- { +- MinimumLogLevel = LogLevel.Information, +- LogBuffering = new LogBufferingOptions +- { +- BufferAtLogLevel = LogLevel.Debug +- }, +- LogOutput = _consoleOut +- }; +- +- var logger = LoggerFactoryHelper.CreateAndConfigureFactory(config).CreatePowertoolsLogger(); +- +- +- // Act +- logger.LogDebug("Debug message 1"); // Should be buffered +- logger.LogDebug("Debug message 2"); // Should be buffered +- +- Logger.ClearBuffer(); // Should clear all buffered logs +- Logger.FlushBuffer(); // No logs should be output +- +- logger.LogDebug("Debug message 3"); // Should be buffered +- Logger.FlushBuffer(); // Should output debug message 3 +- +- // Assert +- var output = _consoleOut.ToString(); +- Assert.DoesNotContain("Debug message 1", output); +- Assert.DoesNotContain("Debug message 2", output); +- Assert.Contains("Debug message 3", output); +- } +- +- [Trait("Category", "BufferedLogger")] +- [Fact] +- public void BufferSizeLimit_DiscardOldestEntriesWhenExceeded() +- { +- // Arrange +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "invocation-1"); +- var config = new PowertoolsLoggerConfiguration +- { +- MinimumLogLevel = LogLevel.Information, +- LogBuffering = new LogBufferingOptions +- { +- BufferAtLogLevel = LogLevel.Debug, +- MaxBytes = 1000 // Small buffer size to force overflow +- }, +- LogOutput = _consoleOut +- }; +- +- var logger = LoggerFactoryHelper.CreateAndConfigureFactory(config).CreatePowertoolsLogger(); +- +- // Act +- // Add enough logs to exceed buffer size +- for (int i = 0; i < 20; i++) +- { +- logger.LogDebug($"Debug message {i} with enough characters to consume space in the buffer"); +- } +- +- Logger.FlushBuffer(); +- +- // Assert +- var output = _consoleOut.ToString(); +- Assert.DoesNotContain("Debug message 0", output); // Older messages should be discarded +- Assert.Contains("Debug message 19", output); // Newest messages should be kept +- } +- +- [Trait("Category", "LoggerLifecycle")] +- [Fact] +- public void DisposingProvider_FlushesBufferedLogs() +- { +- // Arrange +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "invocation-1"); +- var config = new PowertoolsLoggerConfiguration +- { +- MinimumLogLevel = LogLevel.Information, +- LogBuffering = new LogBufferingOptions +- { +- BufferAtLogLevel = LogLevel.Debug +- }, +- LogOutput = _consoleOut +- }; +- +- var provider = LoggerFactoryHelper.CreateAndConfigureFactory(config); +- var logger = provider.CreatePowertoolsLogger(); +- +- // Act +- logger.LogDebug("Debug message before disposal"); // Should be buffered +- provider.Dispose(); // Should flush buffer +- +- // Assert +- var output = _consoleOut.ToString(); +- Assert.Contains("Debug message before disposal", output); +- } +- +- [Trait("Category", "LoggerConfiguration")] +- [Fact] +- public void LoggerInitialization_RegistersWithBufferManager() +- { +- // Arrange +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-id"); +- var config = new PowertoolsLoggerConfiguration +- { +- LogBuffering = new LogBufferingOptions(), +- LogOutput = _consoleOut +- }; +- +- var logger = LoggerFactoryHelper.CreateAndConfigureFactory(config).CreatePowertoolsLogger(); +- +- logger.LogDebug("Test message"); +- Logger.FlushBuffer(); +- +- // Assert +- var output = _consoleOut.ToString(); +- Assert.Contains("Test message", output); +- } +- +- [Trait("Category", "LoggerOutput")] +- [Fact] +- public void CustomLogOutput_ReceivesLogs() +- { +- // Arrange +- var customOutput = new TestLoggerOutput(); +- var config = new PowertoolsLoggerConfiguration +- { +- MinimumLogLevel = LogLevel.Debug, // Set to Debug to ensure we log directly +- LogOutput = customOutput +- }; +- +- var logger = LoggerFactoryHelper.CreateAndConfigureFactory(config).CreatePowertoolsLogger(); +- +- // Act +- logger.LogDebug("Direct debug message"); +- +- // Assert +- var output = customOutput.ToString(); +- Assert.Contains("Direct debug message", output); +- } +- +- [Trait("Category", "LoggerIntegration")] +- [Fact] +- public void RegisteringMultipleProviders_AllWorkCorrectly() +- { +- // Arrange - create a clean configuration for this test +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "shared-invocation"); +- var config = new PowertoolsLoggerConfiguration +- { +- MinimumLogLevel = LogLevel.Information, +- LogBuffering = new LogBufferingOptions +- { +- BufferAtLogLevel = LogLevel.Debug +- }, +- LogOutput = _consoleOut +- }; +- +- PowertoolsLoggingBuilderExtensions.UpdateConfiguration(config); +- +- // Create providers using the shared configuration +- var env = new PowertoolsEnvironment(); +- var powertoolsConfig = new PowertoolsConfigurations(env); +- +- var provider1 = new BufferingLoggerProvider(config, powertoolsConfig); +- var provider2 = new BufferingLoggerProvider(config, powertoolsConfig); +- +- var logger1 = provider1.CreateLogger("Logger1"); +- var logger2 = provider2.CreateLogger("Logger2"); +- +- // Act +- logger1.LogDebug("Debug from logger1"); +- logger2.LogDebug("Debug from logger2"); +- Logger.FlushBuffer(); +- +- // Assert +- var output = _consoleOut.ToString(); +- Assert.Contains("Debug from logger1", output); +- Assert.Contains("Debug from logger2", output); +- } +- +- [Trait("Category", "LoggerLifecycle")] +- [Fact] +- public void RegisteringLogBufferManager_HandlesMultipleProviders() +- { +- // Ensure we start with clean state +- LogBufferManager.ResetForTesting(); +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-invocation"); +- // Arrange +- var config = new PowertoolsLoggerConfiguration +- { +- LogBuffering = new LogBufferingOptions(), +- LogOutput = _consoleOut +- }; +- +- var powertoolsConfig = new PowertoolsConfigurations(new PowertoolsEnvironment()); +- +- // Create and register first provider +- var provider1 = new BufferingLoggerProvider(config, powertoolsConfig); +- var logger1 = provider1.CreateLogger("Logger1"); +- // Explicitly dispose and unregister first provider +- provider1.Dispose(); +- +- // Now create and register a second provider +- var provider2 = new BufferingLoggerProvider(config, powertoolsConfig); +- var logger2 = provider2.CreateLogger("Logger2"); +- +- // Act +- logger1.LogDebug("Debug from first provider"); +- logger2.LogDebug("Debug from second provider"); +- +- // Only the second provider should be registered with the LogBufferManager +- Logger.FlushBuffer(); +- +- // Assert +- var output = _consoleOut.ToString(); +- // Only the second provider's logs should be flushed +- Assert.DoesNotContain("Debug from first provider", output); +- Assert.Contains("Debug from second provider", output); +- } +- +- [Trait("Category", "BufferEmpty")] +- [Fact] +- public void FlushingEmptyBuffer_DoesNotCauseErrors() +- { +- // Arrange +- LogBufferManager.ResetForTesting(); +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "empty-test"); +- var config = new PowertoolsLoggerConfiguration +- { +- LogBuffering = new LogBufferingOptions(), +- LogOutput = _consoleOut +- }; +- var powertoolsConfig = new PowertoolsConfigurations(new PowertoolsEnvironment()); +- var provider = new BufferingLoggerProvider(config, powertoolsConfig); +- +- // Act - flush without any logs +- Logger.FlushBuffer(); +- +- // Assert - should not throw exceptions +- Assert.Empty(_consoleOut.ToString()); +- } +- +- [Trait("Category", "LogLevelThreshold")] +- [Fact] +- public void LogsAtExactBufferThreshold_AreBuffered() +- { +- // Arrange +- LogBufferManager.ResetForTesting(); +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "threshold-test"); +- var config = new PowertoolsLoggerConfiguration +- { +- MinimumLogLevel = LogLevel.Information, +- LogBuffering = new LogBufferingOptions +- { +- BufferAtLogLevel = LogLevel.Debug +- }, +- LogOutput = _consoleOut +- }; +- var powertoolsConfig = new PowertoolsConfigurations(new PowertoolsEnvironment()); +- var provider = new BufferingLoggerProvider(config, powertoolsConfig); +- var logger = provider.CreateLogger("TestLogger"); +- +- // Act +- logger.LogDebug("Debug message exactly at threshold"); // Should be buffered +- +- // Assert before flush +- Assert.DoesNotContain("Debug message exactly at threshold", _consoleOut.ToString()); +- +- // After flush +- Logger.FlushBuffer(); +- Assert.Contains("Debug message exactly at threshold", _consoleOut.ToString()); +- } +- +- public void Dispose() +- { +- // Clean up all state between tests +- Logger.ClearBuffer(); +- LogBufferManager.ResetForTesting(); +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", null); +- } +- } +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Context/LambdaContextTest.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Context/LambdaContextTest.cs +index 31e980ba..feb9283e 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Context/LambdaContextTest.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Context/LambdaContextTest.cs +@@ -56,33 +56,6 @@ public class LambdaContextTest + Assert.Null(LoggingLambdaContext.Instance); + } + +- [Fact] +- public void Extract_When_LambdaContext_Is_Null_But_Not_First_Parameter_Returns_False() +- { +- // Arrange +- ILambdaContext lambdaContext = null; +- var args = Substitute.For(); +- var method = Substitute.For(); +- var parameter1 = Substitute.For(); +- var parameter2 = Substitute.For(); +- +- // Setup parameters +- parameter1.ParameterType.Returns(typeof(string)); +- parameter2.ParameterType.Returns(typeof(ILambdaContext)); +- +- // Setup method +- method.GetParameters().Returns(new[] { parameter1, parameter2 }); +- +- // Setup args +- args.Method = method; +- args.Args = new object[] { "requestContext", lambdaContext }; +- +- // Act && Assert +- LoggingLambdaContext.Clear(); +- Assert.Null(LoggingLambdaContext.Instance); +- Assert.False(LoggingLambdaContext.Extract(args)); +- } +- + [Fact] + public void Extract_When_Args_Null_Returns_False() + { +diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/FactoryTests.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/FactoryTests.cs +deleted file mode 100644 +index c113ff07..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/FactoryTests.cs ++++ /dev/null +@@ -1,148 +0,0 @@ +-using AWS.Lambda.Powertools.Logging.Internal; +-using Microsoft.Extensions.Logging; +-using NSubstitute; +-using Xunit; +- +-namespace AWS.Lambda.Powertools.Logging.Tests; +- +-public class LoggingAspectFactoryTests +-{ +- [Fact] +- public void GetInstance_ShouldReturnLoggingAspectInstance() +- { +- // Act +- var result = LoggingAspectFactory.GetInstance(typeof(LoggingAspectFactoryTests)); +- +- // Assert +- Assert.NotNull(result); +- Assert.IsType(result); +- } +-} +- +-public class PowertoolsLoggerFactoryTests +- { +- [Fact] +- public void Constructor_WithLoggerFactory_CreatesPowertoolsLoggerFactory() +- { +- // Arrange +- var mockFactory = Substitute.For(); +- +- // Act +- var factory = new PowertoolsLoggerFactory(mockFactory); +- +- // Assert +- Assert.NotNull(factory); +- } +- +- [Fact] +- public void DefaultConstructor_CreatesPowertoolsLoggerFactory() +- { +- // Act +- var factory = new PowertoolsLoggerFactory(); +- +- // Assert +- Assert.NotNull(factory); +- } +- +- [Fact] +- public void Create_WithConfigAction_ReturnsPowertoolsLoggerFactory() +- { +- // Act +- var factory = PowertoolsLoggerFactory.Create(options => +- { +- options.Service = "TestService"; +- }); +- +- // Assert +- Assert.NotNull(factory); +- } +- +- [Fact] +- public void Create_WithConfiguration_ReturnsLoggerFactory() +- { +- // Arrange +- var configuration = new PowertoolsLoggerConfiguration +- { +- Service = "TestService" +- }; +- +- // Act +- var factory = PowertoolsLoggerFactory.Create(configuration); +- +- // Assert +- Assert.NotNull(factory); +- } +- +- [Fact] +- public void CreateBuilder_ReturnsLoggerBuilder() +- { +- // Act +- var builder = PowertoolsLoggerFactory.CreateBuilder(); +- +- // Assert +- Assert.NotNull(builder); +- Assert.IsType(builder); +- } +- +- [Fact] +- public void CreateLogger_Generic_ReturnsLogger() +- { +- // Arrange +- var mockFactory = Substitute.For(); +- mockFactory.CreateLogger(Arg.Any()).Returns(Substitute.For()); +- var factory = new PowertoolsLoggerFactory(mockFactory); +- +- // Act +- var logger = factory.CreateLogger(); +- +- // Assert +- Assert.NotNull(logger); +- mockFactory.Received(1).CreateLogger(typeof(PowertoolsLoggerFactoryTests).FullName); +- } +- +- [Fact] +- public void CreateLogger_WithCategory_ReturnsLogger() +- { +- // Arrange +- var mockFactory = Substitute.For(); +- mockFactory.CreateLogger("TestCategory").Returns(Substitute.For()); +- var factory = new PowertoolsLoggerFactory(mockFactory); +- +- // Act +- var logger = factory.CreateLogger("TestCategory"); +- +- // Assert +- Assert.NotNull(logger); +- mockFactory.Received(1).CreateLogger("TestCategory"); +- } +- +- [Fact] +- public void CreatePowertoolsLogger_ReturnsPowertoolsLogger() +- { +- // Arrange +- var mockFactory = Substitute.For(); +- mockFactory.CreatePowertoolsLogger().Returns(Substitute.For()); +- var factory = new PowertoolsLoggerFactory(mockFactory); +- +- // Act +- var logger = factory.CreatePowertoolsLogger(); +- +- // Assert +- Assert.NotNull(logger); +- mockFactory.Received(1).CreatePowertoolsLogger(); +- } +- +- [Fact] +- public void Dispose_DisposesInnerFactory() +- { +- // Arrange +- var mockFactory = Substitute.For(); +- var factory = new PowertoolsLoggerFactory(mockFactory); +- +- // Act +- factory.Dispose(); +- +- // Assert +- mockFactory.Received(1).Dispose(); +- } +- } +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Formatter/LogFormatterTest.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Formatter/LogFormatterTest.cs +index 85250bed..a1f055f9 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Formatter/LogFormatterTest.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Formatter/LogFormatterTest.cs +@@ -1,5 +1,21 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.Collections.Generic; ++using System.IO; + using System.Linq; + using System.Reflection; + using System.Text.Json; +@@ -10,8 +26,6 @@ using AWS.Lambda.Powertools.Common; + using AWS.Lambda.Powertools.Logging.Internal; + using AWS.Lambda.Powertools.Logging.Serializers; + using AWS.Lambda.Powertools.Logging.Tests.Handlers; +-using Microsoft.Extensions.Logging; +-using Microsoft.Extensions.Options; + using NSubstitute; + using NSubstitute.ExceptionExtensions; + using NSubstitute.ReturnsExtensions; +@@ -33,12 +47,8 @@ namespace AWS.Lambda.Powertools.Logging.Tests.Formatter + [Fact] + public void Serialize_ShouldHandleEnumValues() + { +- var consoleOut = Substitute.For(); +- Logger.Configure(options => +- { +- options.LogOutput = consoleOut; +- }); +- ++ var consoleOut = Substitute.For(); ++ SystemWrapper.Instance.SetOut(consoleOut); + var lambdaContext = new TestLambdaContext + { + FunctionName = "funtionName", +@@ -58,7 +68,7 @@ namespace AWS.Lambda.Powertools.Logging.Tests.Formatter + i.Contains("\"message\":\"Dog\"") + )); + +- var json = JsonSerializer.Serialize(Pet.Dog, new PowertoolsLoggingSerializer().GetSerializerOptions()); ++ var json = JsonSerializer.Serialize(Pet.Dog, PowertoolsLoggingSerializer.GetSerializerOptions()); + Assert.Contains("Dog", json); + } + +@@ -97,6 +107,13 @@ namespace AWS.Lambda.Powertools.Logging.Tests.Formatter + var configurations = Substitute.For(); + configurations.Service.Returns(service); + ++ var loggerConfiguration = new LoggerConfiguration ++ { ++ Service = service, ++ MinimumLevel = minimumLevel, ++ LoggerOutputCase = LoggerOutputCase.PascalCase ++ }; ++ + var globalExtraKeys = new Dictionary + { + { Guid.NewGuid().ToString(), Guid.NewGuid().ToString() }, +@@ -156,20 +173,12 @@ namespace AWS.Lambda.Powertools.Logging.Tests.Formatter + } + }; + +- var systemWrapper = Substitute.For(); + logFormatter.FormatLogEntry(new LogEntry()).ReturnsForAnyArgs(formattedLogEntry); +- +- var config = new PowertoolsLoggerConfiguration +- { +- Service = service, +- MinimumLogLevel = minimumLevel, +- LoggerOutputCase = LoggerOutputCase.PascalCase, +- LogFormatter = logFormatter, +- LogOutput = systemWrapper +- }; ++ Logger.UseFormatter(logFormatter); + ++ var systemWrapper = Substitute.For(); + +- var provider = new PowertoolsLoggerProvider(config, configurations); ++ var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); + var logger = provider.CreateLogger(loggerName); + + var scopeExtraKeys = new Dictionary +@@ -179,7 +188,7 @@ namespace AWS.Lambda.Powertools.Logging.Tests.Formatter + }; + + // Act +- logger.LogInformation(message, scopeExtraKeys); ++ logger.LogInformation(scopeExtraKeys, message); + + // Assert + logFormatter.Received(1).FormatLogEntry(Arg.Is +@@ -212,20 +221,14 @@ namespace AWS.Lambda.Powertools.Logging.Tests.Formatter + x.LambdaContext.AwsRequestId == lambdaContext.AwsRequestId + )); + +- systemWrapper.Received(1).WriteLine(JsonSerializer.Serialize(formattedLogEntry)); ++ systemWrapper.Received(1).LogLine(JsonSerializer.Serialize(formattedLogEntry)); + } + + [Fact] + public void Should_Log_CustomFormatter_When_Decorated() + { +- ResetAllState(); +- var consoleOut = Substitute.For(); +- Logger.Configure(options => +- { +- options.LogOutput = consoleOut; +- options.LogFormatter = new CustomLogFormatter(); +- }); +- ++ var consoleOut = Substitute.For(); ++ SystemWrapper.Instance.SetOut(consoleOut); + var lambdaContext = new TestLambdaContext + { + FunctionName = "funtionName", +@@ -235,38 +238,24 @@ namespace AWS.Lambda.Powertools.Logging.Tests.Formatter + MemoryLimitInMB = 128 + }; + +- // Logger.UseFormatter(new CustomLogFormatter()); ++ Logger.UseFormatter(new CustomLogFormatter()); + _testHandler.TestCustomFormatterWithDecorator("test", lambdaContext); + + // serializer works differently in .net 8 and AOT. In .net 6 it writes properties that have null + // in .net 8 it removes null properties + +-#if NET8_0_OR_GREATER + consoleOut.Received(1).WriteLine( + Arg.Is(i => + i.Contains( + "\"correlation_ids\":{\"aws_request_id\":\"requestId\"},\"lambda_function\":{\"name\":\"funtionName\",\"arn\":\"function::arn\",\"memory_limit_in_mb\":128,\"version\":\"version\",\"cold_start\":true},\"level\":\"Information\"")) + ); +-#else +- consoleOut.Received(1).WriteLine( +- Arg.Is(i => +- i.Contains( +- "{\"message\":\"test\",\"service\":\"my_service\",\"correlation_ids\":{\"aws_request_id\":\"requestId\",\"x_ray_trace_id\":null,\"correlation_id\":null},\"lambda_function\":{\"name\":\"funtionName\",\"arn\":\"function::arn\",\"memory_limit_in_m_b\":128,\"version\":\"version\",\"cold_start\":true},\"level\":\"Information\",\"timestamp\":\"2024-01-01T00:00:00.0000000\",\"logger\":{\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"sample_rate\"")) +- ); +-#endif + } + + [Fact] + public void Should_Log_CustomFormatter_When_No_Decorated_Just_Log() + { +- ResetAllState(); +- var consoleOut = Substitute.For(); +- Logger.Configure(options => +- { +- options.LogOutput = consoleOut; +- options.LogFormatter = new CustomLogFormatter(); +- }); +- ++ var consoleOut = Substitute.For(); ++ SystemWrapper.Instance.SetOut(consoleOut); + var lambdaContext = new TestLambdaContext + { + FunctionName = "funtionName", +@@ -276,82 +265,44 @@ namespace AWS.Lambda.Powertools.Logging.Tests.Formatter + MemoryLimitInMB = 128 + }; + +- // Logger.UseFormatter(new CustomLogFormatter()); ++ Logger.UseFormatter(new CustomLogFormatter()); + + _testHandler.TestCustomFormatterNoDecorator("test", lambdaContext); + + // serializer works differently in .net 8 and AOT. In .net 6 it writes properties that have null + // in .net 8 it removes null properties + +-#if NET8_0_OR_GREATER + consoleOut.Received(1).WriteLine( + Arg.Is(i => + i == + "{\"message\":\"test\",\"service\":\"service_undefined\",\"correlation_ids\":{},\"lambda_function\":{\"cold_start\":true},\"level\":\"Information\",\"timestamp\":\"2024-01-01T00:00:00.0000000\",\"logger\":{\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"sample_rate\":0}}") + ); +-#else +- consoleOut.Received(1).WriteLine( +- Arg.Is(i => +- i == +- "{\"message\":\"test\",\"service\":\"service_undefined\",\"correlation_ids\":{\"aws_request_id\":null,\"x_ray_trace_id\":null,\"correlation_id\":null},\"lambda_function\":{\"name\":null,\"arn\":null,\"memory_limit_in_m_b\":null,\"version\":null,\"cold_start\":true},\"level\":\"Information\",\"timestamp\":\"2024-01-01T00:00:00.0000000\",\"logger\":{\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"sample_rate\":0}}") +- ); +-#endif + } + + [Fact] + public void Should_Log_CustomFormatter_When_Decorated_No_Context() + { +- var consoleOut = Substitute.For(); +- Logger.Configure(options => +- { +- options.LogOutput = consoleOut; +- options.LogFormatter = new CustomLogFormatter(); +- }); +- +- // Logger.UseFormatter(new CustomLogFormatter()); ++ var consoleOut = Substitute.For(); ++ SystemWrapper.Instance.SetOut(consoleOut); ++ ++ Logger.UseFormatter(new CustomLogFormatter()); + + _testHandler.TestCustomFormatterWithDecoratorNoContext("test"); + +-#if NET8_0_OR_GREATER + consoleOut.Received(1).WriteLine( + Arg.Is(i => + i == + "{\"message\":\"test\",\"service\":\"my_service\",\"correlation_ids\":{},\"lambda_function\":{\"cold_start\":true},\"level\":\"Information\",\"timestamp\":\"2024-01-01T00:00:00.0000000\",\"logger\":{\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"sample_rate\":0.2}}") + ); +-#else +- consoleOut.Received(1).WriteLine( +- Arg.Is(i => +- i == +- "{\"message\":\"test\",\"service\":\"my_service\",\"correlation_ids\":{\"aws_request_id\":null,\"x_ray_trace_id\":null,\"correlation_id\":null},\"lambda_function\":{\"name\":null,\"arn\":null,\"memory_limit_in_m_b\":null,\"version\":null,\"cold_start\":true},\"level\":\"Information\",\"timestamp\":\"2024-01-01T00:00:00.0000000\",\"logger\":{\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"sample_rate\":0.2}}") +- ); +-#endif + } + + public void Dispose() + { +- ResetAllState(); +- } +- +- private static void ResetAllState() +- { +- // Clear environment variables +- Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_CASE", null); +- Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", null); +- Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", null); +- +- // Reset all logging components ++ Logger.UseDefaultFormatter(); ++ Logger.RemoveAllKeys(); ++ LoggingLambdaContext.Clear(); + LoggingAspect.ResetForTest(); +- Logger.Reset(); +- PowertoolsLoggingBuilderExtensions.ResetAllProviders(); +- LoggerFactoryHolder.Reset(); +- +- // Force default configuration +- var config = new PowertoolsLoggerConfiguration +- { +- MinimumLogLevel = LogLevel.Information, +- LoggerOutputCase = LoggerOutputCase.SnakeCase +- }; +- PowertoolsLoggingBuilderExtensions.UpdateConfiguration(config); ++ PowertoolsLoggingSerializer.ClearOptions(); + } + } + +@@ -375,16 +326,15 @@ namespace AWS.Lambda.Powertools.Logging.Tests.Formatter + logFormatter.FormatLogEntry(new LogEntry()).ReturnsNullForAnyArgs(); + Logger.UseFormatter(logFormatter); + +- var systemWrapper = Substitute.For(); +- var config = new PowertoolsLoggerConfiguration ++ var systemWrapper = Substitute.For(); ++ var loggerConfiguration = new LoggerConfiguration + { + Service = service, +- MinimumLogLevel = LogLevel.Information, +- LoggerOutputCase = LoggerOutputCase.PascalCase, +- LogFormatter = logFormatter ++ MinimumLevel = LogLevel.Information, ++ LoggerOutputCase = LoggerOutputCase.PascalCase + }; + +- var provider = new PowertoolsLoggerProvider(config, configurations); ++ var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); + var logger = provider.CreateLogger(loggerName); + + // Act +@@ -393,7 +343,7 @@ namespace AWS.Lambda.Powertools.Logging.Tests.Formatter + // Assert + Assert.Throws(Act); + logFormatter.Received(1).FormatLogEntry(Arg.Any()); +- systemWrapper.DidNotReceiveWithAnyArgs().WriteLine(Arg.Any()); ++ systemWrapper.DidNotReceiveWithAnyArgs().LogLine(Arg.Any()); + + //Clean up + Logger.UseDefaultFormatter(); +@@ -419,17 +369,17 @@ namespace AWS.Lambda.Powertools.Logging.Tests.Formatter + + var logFormatter = Substitute.For(); + logFormatter.FormatLogEntry(new LogEntry()).ThrowsForAnyArgs(new Exception(errorMessage)); ++ Logger.UseFormatter(logFormatter); + +- var systemWrapper = Substitute.For(); +- var config = new PowertoolsLoggerConfiguration ++ var systemWrapper = Substitute.For(); ++ var loggerConfiguration = new LoggerConfiguration + { + Service = service, +- MinimumLogLevel = LogLevel.Information, +- LoggerOutputCase = LoggerOutputCase.PascalCase, +- LogFormatter = logFormatter ++ MinimumLevel = LogLevel.Information, ++ LoggerOutputCase = LoggerOutputCase.PascalCase + }; + +- var provider = new PowertoolsLoggerProvider(config, configurations); ++ var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); + var logger = provider.CreateLogger(loggerName); + + // Act +@@ -438,7 +388,7 @@ namespace AWS.Lambda.Powertools.Logging.Tests.Formatter + // Assert + Assert.Throws(Act); + logFormatter.Received(1).FormatLogEntry(Arg.Any()); +- systemWrapper.DidNotReceiveWithAnyArgs().WriteLine(Arg.Any()); ++ systemWrapper.DidNotReceiveWithAnyArgs().LogLine(Arg.Any()); + + //Clean up + Logger.UseDefaultFormatter(); +diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Formatter/LogFormattingTests.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Formatter/LogFormattingTests.cs +deleted file mode 100644 +index 46a5a459..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Formatter/LogFormattingTests.cs ++++ /dev/null +@@ -1,735 +0,0 @@ +-using System; +-using System.Collections.Generic; +-using AWS.Lambda.Powertools.Common.Core; +-using AWS.Lambda.Powertools.Common.Tests; +-using AWS.Lambda.Powertools.Logging.Tests.Handlers; +-using Microsoft.Extensions.Logging; +-using Xunit; +-using Xunit.Abstractions; +- +-namespace AWS.Lambda.Powertools.Logging.Tests.Formatter +-{ +- [Collection("Sequential")] +- public class LogFormattingTests +- { +- private readonly ITestOutputHelper _output; +- +- public LogFormattingTests(ITestOutputHelper output) +- { +- _output = output; +- } +- +- [Fact] +- public void TestNumericFormatting() +- { +- // Set culture for thread and format provider +- var originalCulture = System.Threading.Thread.CurrentThread.CurrentCulture; +- System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US"); +- +- +- var output = new TestLoggerOutput(); +- var logger = LoggerFactory.Create(builder => +- { +- builder.AddPowertoolsLogger(config => +- { +- config.Service = "format-test-service"; +- config.MinimumLogLevel = LogLevel.Debug; +- config.LoggerOutputCase = LoggerOutputCase.SnakeCase; +- config.TimestampFormat = "yyyy-MM-dd HH:mm:ss.fff"; +- config.LogOutput = output; +- }); +- }).CreatePowertoolsLogger(); +- +- // Test numeric format specifiers +- logger.LogInformation("Price: {price:0.00}", 123.4567); +- logger.LogInformation("Percentage: {percent:0.0%}", 0.1234); +- // Use explicit dollar sign instead of culture-dependent 'C' +- // The logger explicitly uses InvariantCulture when formatting values, which uses "¤" as the currency symbol. +- // This is by design to ensure consistent logging output regardless of server culture settings. +- // By using $ directly in the format string as shown above, you bypass the culture-specific currency symbol and get the expected output in your tests. +- logger.LogInformation("Currency: {amount:$#,##0.00}", 42.5); +- +- logger.LogInformation("Hex: {hex:X}", 255); +- +- var logOutput = output.ToString(); +- _output.WriteLine(logOutput); +- +- // These should all be properly formatted in the log +- Assert.Contains("\"price\":123.46", logOutput); +- Assert.Contains("\"percent\":\"12.3%\"", logOutput); +- Assert.Contains("\"amount\":\"$42.50\"", logOutput); +- Assert.Contains("\"hex\":\"FF\"", logOutput); +- } +- +- [Fact] +- public void TestCustomObjectFormatting() +- { +- var output = new TestLoggerOutput(); +- var logger = LoggerFactory.Create(builder => +- { +- builder.AddPowertoolsLogger(config => +- { +- config.Service = "object-format-service"; +- config.MinimumLogLevel = LogLevel.Debug; +- config.LoggerOutputCase = LoggerOutputCase.CamelCase; +- config.TimestampFormat = "yyyy-MM-dd HH:mm:ss.fff"; +- config.LogOutput = output; +- }); +- }).CreatePowertoolsLogger(); +- +- var user = new User +- { +- FirstName = "John", +- LastName = "Doe", +- Age = 42 +- }; +- +- // Regular object formatting (uses ToString()) +- logger.LogInformation("User data: {user}", user); +- +- // Object serialization with @ prefix +- logger.LogInformation("User object: {@user}", user); +- +- var logOutput = output.ToString(); +- _output.WriteLine(logOutput); +- +- // First log should use ToString() +- Assert.Contains("\"message\":\"User data: Doe, John (42)\"", logOutput); +- Assert.Contains("\"user\":\"Doe, John (42)\"", logOutput); +- +- // Second log should serialize the object +- Assert.Contains("\"user\":{", logOutput); +- Assert.Contains("\"firstName\":\"John\"", logOutput); +- Assert.Contains("\"lastName\":\"Doe\"", logOutput); +- Assert.Contains("\"age\":42", logOutput); +- } +- +- [Fact] +- public void TestComplexObjectWithIgnoredProperties() +- { +- var output = new TestLoggerOutput(); +- var logger = LoggerFactory.Create(builder => +- { +- builder.AddPowertoolsLogger(config => +- { +- config.Service = "complex-object-service"; +- config.MinimumLogLevel = LogLevel.Debug; +- config.LoggerOutputCase = LoggerOutputCase.SnakeCase; +- config.TimestampFormat = "yyyy-MM-dd HH:mm:ss.fff"; +- config.LogOutput = output; +- }); +- }).CreatePowertoolsLogger(); +- +- var example = new ExampleClass +- { +- Name = "test", +- Price = 1.999, +- ThisIsBig = "big", +- ThisIsHidden = "hidden" +- }; +- +- // Test with @ prefix for serialization +- logger.LogInformation("Example serialized: {@example}", example); +- +- var logOutput = output.ToString(); +- _output.WriteLine(logOutput); +- +- // Should serialize the object properties +- Assert.Contains("\"example\":{", logOutput); +- Assert.Contains("\"name\":\"test\"", logOutput); +- Assert.Contains("\"price\":1.999", logOutput); +- Assert.Contains("\"this_is_big\":\"big\"", logOutput); +- +- // The JsonIgnore property should be excluded +- Assert.DoesNotContain("this_is_hidden", logOutput); +- } +- +- [Fact] +- public void TestMixedFormatting() +- { +- var output = new TestLoggerOutput(); +- var logger = LoggerFactory.Create(builder => +- { +- builder.AddPowertoolsLogger(config => +- { +- config.Service = "mixed-format-service"; +- config.MinimumLogLevel = LogLevel.Debug; +- config.LoggerOutputCase = LoggerOutputCase.PascalCase; +- config.TimestampFormat = "yyyy-MM-dd HH:mm:ss.fff"; +- config.LogOutput = output; +- }); +- }).CreatePowertoolsLogger(); +- +- var user = new User +- { +- FirstName = "Jane", +- LastName = "Smith", +- Age = 35 +- }; +- +- // Mix regular values with formatted values and objects +- logger.LogInformation( +- "Details: User={@user}, Price={price:$#,##0.00}, Date={date:yyyy-MM-dd}", +- user, +- 123.45, +- new DateTime(2023, 4, 5) +- ); +- +- var logOutput = output.ToString(); +- _output.WriteLine(logOutput); +- +- // Verify all formatted parts +- Assert.Contains("\"User\":{", logOutput); +- Assert.Contains("\"FirstName\":\"Jane\"", logOutput); +- Assert.Contains("\"Price\":\"$123.45\"", logOutput); +- Assert.Contains("\"Date\":\"2023-04-05\"", logOutput); +- } +- +- [Fact] +- public void TestNestedObjectSerialization() +- { +- var output = new TestLoggerOutput(); +- var logger = LoggerFactory.Create(builder => +- { +- builder.AddPowertoolsLogger(config => +- { +- config.Service = "nested-object-service"; +- config.MinimumLogLevel = LogLevel.Debug; +- config.LoggerOutputCase = LoggerOutputCase.SnakeCase; +- config.TimestampFormat = "yyyy-MM-dd HH:mm:ss.fff"; +- config.LogOutput = output; +- }); +- }).CreatePowertoolsLogger(); +- +- var parent = new ParentClass +- { +- Name = "Parent", +- Child = new ChildClass { Name = "Child" } +- }; +- +- // Regular object formatting (uses ToString()) +- logger.LogInformation("Parent: {parent}", parent); +- +- // Object serialization with @ prefix +- logger.LogInformation("Parent with child: {@parent}", parent); +- +- var logOutput = output.ToString(); +- _output.WriteLine(logOutput); +- +- // Regular formatting should use ToString() +- Assert.Contains("\"parent\":\"Parent with Child\"", logOutput); +- +- // Serialized object should include nested structure +- Assert.Contains("\"parent\":{", logOutput); +- Assert.Contains("\"name\":\"Parent\"", logOutput); +- Assert.Contains("\"child\":{", logOutput); +- Assert.Contains("\"name\":\"Child\"", logOutput); +- } +- +- [Fact] +- public void TestCollectionFormatting() +- { +- var output = new TestLoggerOutput(); +- var logger = LoggerFactory.Create(builder => +- { +- builder.AddPowertoolsLogger(config => +- { +- config.Service = "collection-format-service"; +- config.MinimumLogLevel = LogLevel.Debug; +- config.LoggerOutputCase = LoggerOutputCase.CamelCase; +- config.TimestampFormat = "yyyy-MM-dd HH:mm:ss.fff"; +- config.LogOutput = output; +- }); +- }).CreatePowertoolsLogger(); +- +- var items = new[] { 1, 2, 3 }; +- var dict = new Dictionary { ["key1"] = "value1", ["key2"] = 42 }; +- +- // Regular array formatting +- logger.LogInformation("Array: {items}", items); +- +- // Serialized array with @ prefix +- logger.LogInformation("Array serialized: {@items}", items); +- +- // Dictionary formatting +- logger.LogInformation("Dictionary: {dict}", dict); +- +- // Serialized dictionary +- logger.LogInformation("Dictionary serialized: {@dict}", dict); +- +- var logOutput = output.ToString(); +- _output.WriteLine(logOutput); +- +- // Regular array formatting uses ToString() +- Assert.Contains("\"items\":\"System.Int32[]\"", logOutput); +- +- // Serialized array should include all items +- Assert.Contains("\"items\":[1,2,3]", logOutput); +- +- // Dictionary formatting depends on ToString() implementation +- Assert.Contains("\"dict\":\"System.Collections.Generic.Dictionary", logOutput); +- +- // Serialized dictionary should include all key-value pairs +- Assert.Contains("\"dict\":{", logOutput); +- Assert.Contains("\"key1\":\"value1\"", logOutput); +- Assert.Contains("\"key2\":42", logOutput); +- } +- +- [Fact] +- public void TestNullAndEdgeCases() +- { +- var output = new TestLoggerOutput(); +- var logger = LoggerFactory.Create(builder => +- { +- builder.AddPowertoolsLogger(config => +- { +- config.Service = "null-edge-case-service"; +- config.MinimumLogLevel = LogLevel.Debug; +- config.LoggerOutputCase = LoggerOutputCase.SnakeCase; +- config.TimestampFormat = "yyyy-MM-dd HH:mm:ss.fff"; +- config.LogOutput = output; +- }); +- }).CreatePowertoolsLogger(); +- +- User user = null; +- +- // Test null formatting +- logger.LogInformation("Null object: {user}", user); +- logger.LogInformation("Null serialized: {@user}", user); +- +- // Extreme values +- logger.LogInformation("Max value: {max}", int.MaxValue); +- logger.LogInformation("Min value: {min}", int.MinValue); +- logger.LogInformation("Max double: {maxDouble}", double.MaxValue); +- +- var logOutput = output.ToString(); +- _output.WriteLine(logOutput); +- +- // Null objects should be null in output +- Assert.Contains("\"user\":null", logOutput); +- +- // Extreme values should be preserved +- Assert.Contains("\"max\":2147483647", logOutput); +- Assert.Contains("\"min\":-2147483648", logOutput); +- Assert.Contains("\"max_double\":1.7976931348623157E+308", logOutput); +- } +- +- [Fact] +- public void TestDateTimeFormats() +- { +- var output = new TestLoggerOutput(); +- var logger = LoggerFactory.Create(builder => +- { +- builder.AddPowertoolsLogger(config => +- { +- config.Service = "datetime-format-service"; +- config.MinimumLogLevel = LogLevel.Debug; +- config.LoggerOutputCase = LoggerOutputCase.CamelCase; +- config.TimestampFormat = "yyyy-MM-dd HH:mm:ss.fff"; +- config.LogOutput = output; +- }); +- }).CreatePowertoolsLogger(); +- +- var date = new DateTime(2023, 12, 31, 23, 59, 59); +- +- // Test different date formats +- logger.LogInformation("ISO: {date:o}", date); +- logger.LogInformation("Short date: {date:d}", date); +- logger.LogInformation("Custom: {date:yyyy-MM-dd'T'HH:mm:ss.fff}", date); +- logger.LogInformation("Time only: {date:HH:mm:ss}", date); +- +- var logOutput = output.ToString(); +- _output.WriteLine(logOutput); +- +- // Verify different formats +- Assert.Contains("\"date\":\"2023-12-31T23:59:59", logOutput); // ISO format +- Assert.Contains("\"date\":\"12/31/2023\"", logOutput); // Short date +- Assert.Contains("\"date\":\"2023-12-31T23:59:59.000\"", logOutput); // Custom +- Assert.Contains("\"date\":\"23:59:59\"", logOutput); // Time only +- } +- +- [Fact] +- public void TestExceptionLogging() +- { +- var output = new TestLoggerOutput(); +- var logger = LoggerFactory.Create(builder => +- { +- builder.AddPowertoolsLogger(config => +- { +- config.Service = "exception-test-service"; +- config.MinimumLogLevel = LogLevel.Debug; +- config.LoggerOutputCase = LoggerOutputCase.SnakeCase; +- config.TimestampFormat = "yyyy-MM-dd HH:mm:ss.fff"; +- config.LogOutput = output; +- }); +- }).CreatePowertoolsLogger(); +- +- try +- { +- throw new InvalidOperationException("Test exception"); +- } +- catch (Exception ex) +- { +- logger.LogError(ex, "An error occurred with {data}", "test value"); +- +- // Test with nested exceptions +- var outerEx = new Exception("Outer exception", ex); +- logger.LogError(outerEx, "Nested exception test"); +- } +- +- var logOutput = output.ToString(); +- _output.WriteLine(logOutput); +- +- // Verify exception details are included +- Assert.Contains("\"message\":\"An error occurred with test value\"", logOutput); +- Assert.Contains("\"exception\":{", logOutput); +- Assert.Contains("\"type\":\"System.InvalidOperationException\"", logOutput); +- Assert.Contains("\"message\":\"Test exception\"", logOutput); +- Assert.Contains("\"stack_trace\":", logOutput); +- +- // Verify nested exception details +- Assert.Contains("\"message\":\"Nested exception test\"", logOutput); +- Assert.Contains("\"inner_exception\":{", logOutput); +- } +- +- [Fact] +- public void TestScopedLogging() +- { +- var output = new TestLoggerOutput(); +- var logger = LoggerFactory.Create(builder => +- { +- builder.AddPowertoolsLogger(config => +- { +- config.Service = "scope-test-service"; +- config.MinimumLogLevel = LogLevel.Debug; +- config.LoggerOutputCase = LoggerOutputCase.SnakeCase; +- config.TimestampFormat = "yyyy-MM-dd HH:mm:ss.fff"; +- config.LogOutput = output; +- }); +- }).CreatePowertoolsLogger(); +- +- // Log without any scope +- logger.LogInformation("Outside any scope"); +- +- // Create a scope and log within it +- using (logger.BeginScope(new { RequestId = "req-123", UserId = "user-456" })) +- { +- logger.LogInformation("Inside first scope"); +- +- // Nested scope +- using (logger.BeginScope(new { OperationId = "op-789" })) +- { +- logger.LogInformation("Inside nested scope"); +- } +- +- logger.LogInformation("Back to first scope"); +- } +- +- // Back outside all scopes +- logger.LogInformation("Outside all scopes again"); +- +- var logOutput = output.ToString(); +- _output.WriteLine(logOutput); +- +- // Verify scope information is included correctly +- Assert.Contains("\"message\":\"Inside first scope\"", logOutput); +- Assert.Contains("\"request_id\":\"req-123\"", logOutput); +- Assert.Contains("\"user_id\":\"user-456\"", logOutput); +- +- // Nested scope should include both scopes' data +- Assert.Contains("\"message\":\"Inside nested scope\"", logOutput); +- Assert.Contains("\"operation_id\":\"op-789\"", logOutput); +- } +- +- [Fact] +- public void TestDifferentLogLevels() +- { +- var output = new TestLoggerOutput(); +- var logger = LoggerFactory.Create(builder => +- { +- builder.AddPowertoolsLogger(config => +- { +- config.Service = "log-level-test-service"; +- config.MinimumLogLevel = LogLevel.Debug; +- config.LoggerOutputCase = LoggerOutputCase.SnakeCase; +- config.TimestampFormat = "yyyy-MM-dd HH:mm:ss.fff"; +- config.LogOutput = output; +- }); +- }).CreatePowertoolsLogger(); +- +- logger.LogTrace("This is a trace message"); +- logger.LogDebug("This is a debug message"); +- logger.LogInformation("This is an info message"); +- logger.LogWarning("This is a warning message"); +- logger.LogError("This is an error message"); +- logger.LogCritical("This is a critical message"); +- +- var logOutput = output.ToString(); +- _output.WriteLine(logOutput); +- +- // Trace shouldn't be logged (below default) +- Assert.DoesNotContain("\"level\":\"Trace\"", logOutput); +- +- // Debug and above should be logged +- Assert.Contains("\"level\":\"Debug\"", logOutput); +- Assert.Contains("\"level\":\"Information\"", logOutput); +- Assert.Contains("\"level\":\"Warning\"", logOutput); +- Assert.Contains("\"level\":\"Error\"", logOutput); +- Assert.Contains("\"level\":\"Critical\"", logOutput); +- } +- +- [Fact] +- public void Should_Log_Multiple_Formats_No_Duplicates() +- { +- var output = new TestLoggerOutput(); +- LambdaLifecycleTracker.Reset(); +- LoggerFactory.Create(builder => +- { +- builder.AddPowertoolsLogger(config => +- { +- config.Service = "log-level-test-service"; +- config.MinimumLogLevel = LogLevel.Debug; +- config.LoggerOutputCase = LoggerOutputCase.SnakeCase; +- config.LogOutput = output; +- }); +- }).CreatePowertoolsLogger(); +- +- var user = new User +- { +- FirstName = "John", +- LastName = "Doe", +- Age = 42, +- TimeStamp = "FakeTime" +- }; +- +- Logger.LogInformation("{Name} is {Age} years old", user.Name, user.Age); +- +- Assert.Contains("\"message\":\"John Doe is 42 years old\"", output.ToString()); +- Assert.Contains("\"name\":\"AWS.Lambda.Powertools.Logging.Logger\"", output.ToString()); // does not override name +- +- output.Clear(); +- +- Logger.LogInformation("{level}", user); +- Assert.Contains("\"level\":\"Information\"", output.ToString()); // does not override level +- Assert.Contains("\"message\":\"Doe, John (42)\"", output.ToString()); // does not override message +- Assert.DoesNotContain("\"timestamp\":\"FakeTime\"", output.ToString()); +- +- output.Clear(); +- +- Logger.LogInformation("{coldstart}", user); // still not sure if convert to PascalCase to compare or not +- Assert.Contains("\"cold_start\":true", output.ToString()); +- +- output.Clear(); +- +- Logger.AppendKey("level", "Override"); +- Logger.AppendKey("message", "Override"); +- Logger.AppendKey("timestamp", "Override"); +- Logger.AppendKey("name", "Override"); +- Logger.AppendKey("service", "Override"); +- Logger.AppendKey("cold_start", "Override"); +- Logger.AppendKey("message2", "Its ok!"); +- +- Logger.LogInformation("no override"); +- Assert.DoesNotContain("\"level\":\"Override\"", output.ToString()); +- Assert.DoesNotContain("\"message\":\"Override\"", output.ToString()); +- Assert.DoesNotContain("\"timestamp\":\"Override\"", output.ToString()); +- Assert.DoesNotContain("\"name\":\"Override\"", output.ToString()); +- Assert.DoesNotContain("\"service\":\"Override\"", output.ToString()); +- Assert.DoesNotContain("\"cold_start\":\"Override\"", output.ToString()); +- Assert.Contains("\"message2\":\"Its ok!\"", output.ToString()); +- Assert.Contains("\"level\":\"Information\"", output.ToString()); +- } +- +- [Fact] +- public void Should_Log_Multiple_Formats() +- { +- LambdaLifecycleTracker.Reset(); +- var output = new TestLoggerOutput(); +- var logger = LoggerFactory.Create(builder => +- { +- builder.AddPowertoolsLogger(config => +- { +- config.Service = "log-level-test-service"; +- config.MinimumLogLevel = LogLevel.Debug; +- config.LoggerOutputCase = LoggerOutputCase.SnakeCase; +- config.LogOutput = output; +- }); +- }).CreatePowertoolsLogger(); +- +- var user = new User +- { +- FirstName = "John", +- LastName = "Doe", +- Age = 42 +- }; +- +- Logger.LogInformation("{Name} is {Age} years old", user.FirstName, user.Age); +- +- var logOutput = output.ToString(); +- Assert.Contains("\"level\":\"Information\"", logOutput); +- Assert.Contains("\"message\":\"John is 42 years old\"", logOutput); +- Assert.Contains("\"service\":\"log-level-test-service\"", logOutput); +- Assert.Contains("\"name\":\"AWS.Lambda.Powertools.Logging.Logger\"", logOutput); +- +- output.Clear(); +- +- // Message template string +- Logger.LogInformation("{user}", user); +- +- logOutput = output.ToString(); +- Assert.Contains("\"level\":\"Information\"", logOutput); +- Assert.Contains("\"message\":\"Doe, John (42)\"", logOutput); +- Assert.Contains("\"service\":\"log-level-test-service\"", logOutput); +- Assert.Contains("\"name\":\"AWS.Lambda.Powertools.Logging.Logger\"", logOutput); +- Assert.Contains("\"user\":\"Doe, John (42)\"", logOutput); +- // Verify user properties are NOT included in output (since @ prefix wasn't used) +- Assert.DoesNotContain("\"first_name\":", logOutput); +- Assert.DoesNotContain("\"last_name\":", logOutput); +- Assert.DoesNotContain("\"age\":", logOutput); +- +- output.Clear(); +- +- // Object serialization with @ prefix +- Logger.LogInformation("{@user}", user); +- +- logOutput = output.ToString(); +- Assert.Contains("\"level\":\"Information\"", logOutput); +- Assert.Contains("\"message\":\"Doe, John (42)\"", logOutput); +- Assert.Contains("\"service\":\"log-level-test-service\"", logOutput); +- Assert.Contains("\"cold_start\":true", logOutput); +- Assert.Contains("\"name\":\"AWS.Lambda.Powertools.Logging.Logger\"", logOutput); +- // Verify serialized user object with all properties +- Assert.Contains("\"user\":{", logOutput); +- Assert.Contains("\"first_name\":\"John\"", logOutput); +- Assert.Contains("\"last_name\":\"Doe\"", logOutput); +- Assert.Contains("\"age\":42", logOutput); +- Assert.Contains("\"name\":\"John Doe\"", logOutput); +- Assert.Contains("\"time_stamp\":null", logOutput); +- Assert.Contains("}", logOutput); +- +- output.Clear(); +- +- Logger.LogInformation("{cold_start}", false); +- +- logOutput = output.ToString(); +- // Assert that the reserved field wasn't replaced +- Assert.Contains("\"cold_start\":true", logOutput); +- Assert.DoesNotContain("\"cold_start\":false", logOutput); +- +- output.Clear(); +- +- Logger.AppendKey("level", "fakeLevel"); +- Logger.LogInformation("no override"); +- +- logOutput = output.ToString(); +- +- Assert.Contains("\"level\":\"Information\"", logOutput); +- Assert.DoesNotContain("\"level\":\"fakeLevel\"", logOutput); +- +- output.Clear(); +- +- Logger.LogInformation("{Name} is {Age} years old and {@user}", user.FirstName, user.Age, user); +- +- logOutput = output.ToString(); +- +- Assert.Contains("\"message\":\"John is 42 years old and Doe, John (42)\"", logOutput); +- // Verify serialized user object with all properties +- Assert.Contains("\"user\":{", logOutput); +- Assert.Contains("\"first_name\":\"John\"", logOutput); +- Assert.Contains("\"last_name\":\"Doe\"", logOutput); +- Assert.Contains("\"age\":42", logOutput); +- Assert.Contains("\"name\":\"John Doe\"", logOutput); +- Assert.Contains("\"time_stamp\":null", logOutput); +- Assert.Contains("}", logOutput); +- +- _output.WriteLine(logOutput); +- } +- +- [Fact] +- public void TestMessageTemplateFormatting() +- { +- var output = new TestLoggerOutput(); +- var logger = LoggerFactory.Create(builder => +- { +- builder.AddPowertoolsLogger(config => +- { +- config.Service = "template-format-service"; +- config.MinimumLogLevel = LogLevel.Debug; +- config.LoggerOutputCase = LoggerOutputCase.SnakeCase; +- config.LogOutput = output; +- }); +- }).CreatePowertoolsLogger(); +- +- // Simple template with one parameter +- logger.LogInformation("This is a test with {param}", "Hello"); +- +- var logOutput = output.ToString(); +- _output.WriteLine(logOutput); +- +- // Verify full formatted message appears correctly +- Assert.Contains("\"message\":\"This is a test with Hello\"", logOutput); +- // Verify parameter is also included separately +- Assert.Contains("\"param\":\"Hello\"", logOutput); +- +- output.Clear(); +- +- // Multiple parameters +- logger.LogInformation("Test with {first} and {second}", "One", "Two"); +- +- logOutput = output.ToString(); +- _output.WriteLine(logOutput); +- +- // Verify message with multiple parameters +- Assert.Contains("\"message\":\"Test with One and Two\"", logOutput); +- Assert.Contains("\"first\":\"One\"", logOutput); +- Assert.Contains("\"second\":\"Two\"", logOutput); +- } +- +- public class ParentClass +- { +- public string Name { get; set; } +- public ChildClass Child { get; set; } +- +- public override string ToString() +- { +- return $"Parent with Child"; +- } +- } +- +- public class ChildClass +- { +- public string Name { get; set; } +- +- public override string ToString() +- { +- return $"Child: {Name}"; +- } +- } +- +- public class Node +- { +- public string Name { get; set; } +- public Node Parent { get; set; } +- public List Children { get; set; } = new List(); +- +- public override string ToString() +- { +- return $"Node: {Name}"; +- } +- } +- +- public class User +- { +- public string FirstName { get; set; } +- public string LastName { get; set; } +- public int Age { get; set; } +- public string Name => $"{FirstName} {LastName}"; +- public string TimeStamp { get; set; } +- +- public override string ToString() +- { +- return $"{LastName}, {FirstName} ({Age})"; +- } +- } +- } +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/ExceptionFunctionHandler.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/ExceptionFunctionHandler.cs +index 56dfcc87..170f2a92 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/ExceptionFunctionHandler.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/ExceptionFunctionHandler.cs +@@ -34,14 +34,14 @@ public class ExceptionFunctionHandler + Logger.LogDebug("Hello {input}", input); + Logger.LogTrace("Hello {input}", input); + +- Logger.LogInformation("Testing with parameter Log Information Method {company}", "AWS" ); ++ Logger.LogInformation("Testing with parameter Log Information Method {company}", new[] { "AWS" }); + + var customKeys = new Dictionary + { + {"test1", "value1"}, + {"test2", "value2"} + }; +- Logger.LogInformation("Retrieved data for city {cityName} with count {company}", "AWS", customKeys); ++ Logger.LogInformation(customKeys, "Retrieved data for city {cityName} with count {company}", "AWS"); + + Logger.AppendKey("aws",1); + Logger.AppendKey("aws",3); +@@ -52,4 +52,10 @@ public class ExceptionFunctionHandler + + return "OK"; + } ++ ++ [Logging(LogEvent = true)] ++ public string HandleOk(string input) ++ { ++ return input.ToUpper(CultureInfo.InvariantCulture); ++ } + } +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/ExceptionFunctionHandlerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/ExceptionFunctionHandlerTests.cs +index 7622ac23..f9ffd5eb 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/ExceptionFunctionHandlerTests.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/ExceptionFunctionHandlerTests.cs +@@ -7,7 +7,6 @@ using Xunit; + + namespace AWS.Lambda.Powertools.Logging.Tests.Handlers; + +-[Collection("Sequential")] + public sealed class ExceptionFunctionHandlerTests : IDisposable + { + [Fact] +@@ -43,6 +42,6 @@ public sealed class ExceptionFunctionHandlerTests : IDisposable + public void Dispose() + { + LoggingAspect.ResetForTest(); +- Logger.Reset(); ++ PowertoolsLoggingSerializer.ClearOptions(); + } + } +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/HandlerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/HandlerTests.cs +deleted file mode 100644 +index 0046ce5c..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/HandlerTests.cs ++++ /dev/null +@@ -1,572 +0,0 @@ +-using System.Text.Json.Serialization; +-#if NET8_0_OR_GREATER +-using System; +-using System.Collections.Generic; +-using System.IO; +-using System.Text.Json; +-using System.Text.Json.Serialization; +-using System.Threading.Tasks; +-using Amazon.Lambda.Core; +-using Amazon.Lambda.TestUtilities; +-using AWS.Lambda.Powertools.Common; +-using AWS.Lambda.Powertools.Common.Tests; +-using AWS.Lambda.Powertools.Logging.Internal.Helpers; +-using AWS.Lambda.Powertools.Logging.Tests.Formatter; +-using AWS.Lambda.Powertools.Logging.Tests.Utilities; +-using Microsoft.Extensions.Logging; +-using Xunit; +-using Xunit.Abstractions; +-using LogLevel = Microsoft.Extensions.Logging.LogLevel; +- +-namespace AWS.Lambda.Powertools.Logging.Tests.Handlers; +- +-public class Handlers +-{ +- private readonly ILogger _logger; +- +- public Handlers(ILogger logger) +- { +- _logger = logger; +- PowertoolsLoggingBuilderExtensions.ResetAllProviders(); +- } +- +- [Logging(LogEvent = true)] +- public void TestMethod(string message, ILambdaContext lambdaContext) +- { +- _logger.AppendKey("custom-key", "custom-value"); +- _logger.LogInformation("Information message"); +- _logger.LogDebug("debug message"); +- +- var example = new ExampleClass +- { +- Name = "test", +- Price = 1.999, +- ThisIsBig = "big", +- ThisIsHidden = "hidden" +- }; +- +- _logger.LogInformation("Example object: {example}", example); +- _logger.LogInformation("Another JSON log {d:0.000}", 1.2333); +- +- _logger.LogDebug(example); +- _logger.LogInformation(example); +- } +- +- [Logging(LogEvent = true, CorrelationIdPath = "price")] +- public void TestMethodCorrelation(ExampleClass message, ILambdaContext lambdaContext) +- { +- } +-} +- +-public class StaticHandler +-{ +- [Logging(LogEvent = true, LoggerOutputCase = LoggerOutputCase.PascalCase, Service = "my-service122")] +- public void TestMethod(string message, ILambdaContext lambdaContext) +- { +- Logger.LogInformation("Static method"); +- } +-} +- +-public class HandlerTests +-{ +- private readonly ITestOutputHelper _output; +- +- public HandlerTests(ITestOutputHelper output) +- { +- _output = output; +- } +- +- [Fact] +- public void TestMethod() +- { +- var output = new TestLoggerOutput(); +- +- var logger = LoggerFactory.Create(builder => +- { +- builder.AddPowertoolsLogger(config => +- { +- config.Service = "my-service122"; +- config.SamplingRate = 0.002; +- config.MinimumLogLevel = LogLevel.Debug; +- config.LoggerOutputCase = LoggerOutputCase.PascalCase; +- config.TimestampFormat = "yyyy-MM-dd HH:mm:ss.fff"; +- config.JsonOptions = new JsonSerializerOptions +- { +- WriteIndented = true +- // PropertyNamingPolicy = null, +- // DictionaryKeyPolicy = PascalCaseNamingPolicy.Instance, +- }; +- config.LogOutput = output; +- }); +- }).CreateLogger(); +- +- +- var handler = new Handlers(logger); +- +- handler.TestMethod("Event", new TestLambdaContext +- { +- FunctionName = "test-function", +- FunctionVersion = "1", +- AwsRequestId = "123", +- InvokedFunctionArn = "arn:aws:lambda:us-east-1:123456789012:function:test-function" +- }); +- +- handler.TestMethodCorrelation(new ExampleClass +- { +- Name = "test-function", +- Price = 1.999, +- ThisIsBig = "big", +- }, null); +- +- var logOutput = output.ToString(); +- _output.WriteLine(logOutput); +- +- // Check if the output contains newlines and spacing (indentation) +- Assert.Contains("\n", logOutput); +- Assert.Contains(" ", logOutput); +- +- // Verify write indented JSON +- Assert.Contains("\"Level\": \"Information\"", logOutput); +- Assert.Contains("\"Service\": \"my-service122\"", logOutput); +- Assert.Contains("\"Message\": \"Information message\"", logOutput); +- Assert.Contains("\"Custom-key\": \"custom-value\"", logOutput); +- Assert.Contains("\"FunctionName\": \"test-function\"", logOutput); +- Assert.Contains("\"SamplingRate\": 0.002", logOutput); +- } +- +- [Fact] +- public void TestMethodCustom() +- { +- var output = new TestLoggerOutput(); +- var logger = LoggerFactory.Create(builder => +- { +- builder.AddPowertoolsLogger(config => +- { +- config.Service = "my-service122"; +- config.SamplingRate = 0.002; +- config.MinimumLogLevel = LogLevel.Debug; +- config.LoggerOutputCase = LoggerOutputCase.CamelCase; +- config.JsonOptions = new JsonSerializerOptions +- { +- // PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, +- // DictionaryKeyPolicy = JsonNamingPolicy.KebabCaseLower +- }; +- +- config.LogFormatter = new CustomLogFormatter(); +- config.LogOutput = output; +- }); +- }).CreatePowertoolsLogger(); +- +- var handler = new Handlers(logger); +- +- handler.TestMethod("Event", new TestLambdaContext +- { +- FunctionName = "test-function", +- FunctionVersion = "1", +- AwsRequestId = "123", +- InvokedFunctionArn = "arn:aws:lambda:us-east-1:123456789012:function:test-function" +- }); +- +- var logOutput = output.ToString(); +- _output.WriteLine(logOutput); +- +- // Verify CamelCase formatting (custom formatter) +- Assert.Contains("\"service\":\"my-service122\"", logOutput); +- Assert.Contains("\"level\":\"Information\"", logOutput); +- Assert.Contains("\"message\":\"Information message\"", logOutput); +- Assert.Contains("\"correlationIds\":{\"awsRequestId\":\"123\"}", logOutput); +- } +- +- [Fact] +- public void TestBuffer() +- { +- var output = new TestLoggerOutput(); +- var logger = LoggerFactory.Create(builder => +- { +- // builder.AddFilter("AWS.Lambda.Powertools.Logging.Tests.Handlers.Handlers", LogLevel.Debug); +- builder.AddPowertoolsLogger(config => +- { +- config.Service = "my-service122"; +- config.SamplingRate = 0.002; +- config.MinimumLogLevel = LogLevel.Information; +- config.JsonOptions = new JsonSerializerOptions +- { +- WriteIndented = true, +- // PropertyNamingPolicy = JsonNamingPolicy.KebabCaseUpper, +- DictionaryKeyPolicy = JsonNamingPolicy.KebabCaseUpper +- }; +- config.LogOutput = output; +- config.LogBuffering = new LogBufferingOptions +- { +- BufferAtLogLevel = LogLevel.Debug +- }; +- }); +- }).CreatePowertoolsLogger(); +- +- var handler = new Handlers(logger); +- +- handler.TestMethod("Event", new TestLambdaContext +- { +- FunctionName = "test-function", +- FunctionVersion = "1", +- AwsRequestId = "123", +- InvokedFunctionArn = "arn:aws:lambda:us-east-1:123456789012:function:test-function" +- }); +- +- var logOutput = output.ToString(); +- _output.WriteLine(logOutput); +- +- // Verify buffering behavior - only Information logs or higher should be in output +- Assert.Contains("Information message", logOutput); +- Assert.DoesNotContain("debug message", logOutput); // Debug should be buffered +- +- // Verify JSON options with indentation +- Assert.Contains("\n", logOutput); +- Assert.Contains(" ", logOutput); // Check for indentation +- +- // Check that kebab-case dictionary keys are working +- Assert.Contains("\"CUSTOM-KEY\"", logOutput); +- } +- +- [Fact] +- public void TestMethodStatic() +- { +- var output = new TestLoggerOutput(); +- var handler = new StaticHandler(); +- +- Logger.Configure(options => +- { +- options.LogOutput = output; +- options.LoggerOutputCase = LoggerOutputCase.CamelCase; +- }); +- +- handler.TestMethod("Event", new TestLambdaContext +- { +- FunctionName = "test-function", +- FunctionVersion = "1", +- AwsRequestId = "123", +- InvokedFunctionArn = "arn:aws:lambda:us-east-1:123456789012:function:test-function" +- }); +- +- var logOutput = output.ToString(); +- _output.WriteLine(logOutput); +- +- // Verify static logger configuration +- // Verify override of LoggerOutputCase from attribute +- Assert.Contains("\"Service\":\"my-service122\"", logOutput); +- Assert.Contains("\"Level\":\"Information\"", logOutput); +- Assert.Contains("\"Message\":\"Static method\"", logOutput); +- } +- +- [Fact] +- public async Task Should_Log_Properties_Setup_Constructor() +- { +- var output = new TestLoggerOutput(); +- _ = new SimpleFunctionWithStaticConfigure(output); +- +- await SimpleFunctionWithStaticConfigure.FunctionHandler(); +- +- var logOutput = output.ToString(); +- _output.WriteLine(logOutput); +- +- +- Assert.Contains("\"service\":\"MyServiceName\"", logOutput); +- Assert.Contains("\"level\":\"Information\"", logOutput); +- Assert.Contains("\"message\":\"Starting up!\"", logOutput); +- Assert.Contains("\"xray_trace_id\"", logOutput); +- } +- +- [Fact] +- public async Task Should_Flush_On_Exception_Async() +- { +- var output = new TestLoggerOutput(); +- var handler = new SimpleFunctionWithStaticConfigure(output); +- +- try +- { +- await handler.AsyncException(); +- } +- catch +- { +- } +- +- var logOutput = output.ToString(); +- _output.WriteLine(logOutput); +- +- Assert.Contains("\"level\":\"Debug\"", logOutput); +- Assert.Contains("\"message\":\"Debug!!\"", logOutput); +- Assert.Contains("\"xray_trace_id\"", logOutput); +- } +- +- [Fact] +- public void Should_Flush_On_Exception() +- { +- var output = new TestLoggerOutput(); +- var handler = new SimpleFunctionWithStaticConfigure(output); +- +- try +- { +- handler.SyncException(); +- } +- catch +- { +- } +- +- var logOutput = output.ToString(); +- _output.WriteLine(logOutput); +- +- Assert.Contains("\"level\":\"Debug\"", logOutput); +- Assert.Contains("\"message\":\"Debug!!\"", logOutput); +- Assert.Contains("\"xray_trace_id\"", logOutput); +- } +- +- [Fact] +- public void TestJsonOptionsPropertyNaming() +- { +- var output = new TestLoggerOutput(); +- var logger = LoggerFactory.Create(builder => +- { +- builder.AddPowertoolsLogger(config => +- { +- config.Service = "json-options-service"; +- config.MinimumLogLevel = LogLevel.Debug; +- config.JsonOptions = new JsonSerializerOptions +- { +- PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, +- WriteIndented = false +- }; +- config.LogOutput = output; +- }); +- }).CreatePowertoolsLogger(); +- +- var handler = new Handlers(logger); +- var example = new ExampleClass +- { +- Name = "TestValue", +- Price = 29.99, +- ThisIsBig = "LargeValue" +- }; +- +- logger.LogInformation("Testing JSON options with example: {@example}", example); +- +- var logOutput = output.ToString(); +- _output.WriteLine(logOutput); +- +- // Verify snake_case naming policy is applied +- Assert.Contains("\"this_is_big\":\"LargeValue\"", logOutput); +- Assert.Contains("\"name\":\"TestValue\"", logOutput); +- } +- +- [Fact] +- public void TestJsonOptionsDictionaryKeyPolicy() +- { +- var output = new TestLoggerOutput(); +- var logger = LoggerFactory.Create(builder => +- { +- builder.AddPowertoolsLogger(config => +- { +- config.Service = "json-dictionary-service"; +- config.JsonOptions = new JsonSerializerOptions +- { +- DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, +- WriteIndented = false +- }; +- config.LogOutput = output; +- }); +- }).CreatePowertoolsLogger(); +- +- var dictionary = new Dictionary +- { +- { "UserID", 12345 }, +- { "OrderDetails", new { ItemCount = 3, Total = 150.75 } }, +- { "ShippingAddress", "123 Main St" } +- }; +- +- logger.LogInformation("Dictionary with custom key policy: {@dictionary}", dictionary); +- +- var logOutput = output.ToString(); +- _output.WriteLine(logOutput); +- +- // Fix assertion to match actual camelCase behavior with acronyms +- Assert.Contains("\"userID\":12345", logOutput); // ID remains uppercase +- Assert.Contains("\"orderDetails\":", logOutput); +- Assert.Contains("\"shippingAddress\":", logOutput); +- } +- +- [Fact] +- public void TestJsonOptionsWriteIndented() +- { +- var output = new TestLoggerOutput(); +- var logger = LoggerFactory.Create(builder => +- { +- builder.AddPowertoolsLogger(config => +- { +- config.Service = "json-indented-service"; +- config.JsonOptions = new JsonSerializerOptions +- { +- WriteIndented = true +- }; +- config.LogOutput = output; +- }); +- }).CreatePowertoolsLogger(); +- +- var example = new ExampleClass +- { +- Name = "IndentedTest", +- Price = 59.99, +- ThisIsBig = "IndentedValue" +- }; +- +- logger.LogInformation("Testing indented JSON: {@example}", example); +- +- var logOutput = output.ToString(); +- _output.WriteLine(logOutput); +- +- // Check if the output contains newlines and spacing (indentation) +- Assert.Contains("\n", logOutput); +- Assert.Contains(" ", logOutput); +- } +- +- /// +- /// Test sampling behavior with environment variables using the [Logging] attribute +- /// POWERTOOLS_LOG_LEVEL=Error and POWERTOOLS_LOGGER_SAMPLE_RATE=0.9 +- /// +- [Fact] +- public void EnvironmentVariableSampling_HandlerWithSampling_ShouldElevateInfoLogs() +- { +- // Arrange - Set environment variables for sampling test +- var originalLogLevel = Environment.GetEnvironmentVariable("POWERTOOLS_LOG_LEVEL"); +- var originalSampleRate = Environment.GetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE"); +- +- try +- { +- Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", "Error"); +- Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE", "0.9"); +- +- var output = new TestLoggerOutput(); +- Logger.Configure(options => { options.LogOutput = output; }); +- +- var handler = new EnvironmentVariableSamplingHandler(); +- +- // Act - Try multiple times to trigger sampling (90% chance each time) +- bool samplingTriggered = false; +- string logOutput = ""; +- +- // Try up to 20 times to trigger sampling +- for (int i = 0; i < 20 && !samplingTriggered; i++) +- { +- output.Clear(); +- Logger.Reset(); +- Logger.Configure(options => { options.LogOutput = output; }); +- +- handler.HandleWithSampling(new string[] { }); +- +- logOutput = output.ToString(); +- samplingTriggered = logOutput.Contains("Changed log level to DEBUG based on Sampling configuration"); +- } +- +- // Assert +- Assert.True(samplingTriggered, "Sampling should have been triggered within 20 attempts with 90% rate"); +- Assert.Contains("This is an info message — should not appear", logOutput); +- } +- finally +- { +- // Cleanup +- Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", originalLogLevel); +- Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE", originalSampleRate); +- Logger.Reset(); +- } +- } +- +- /// +- /// Test with 100% sampling rate to guarantee sampling works consistently +- /// +- [Fact] +- public void EnvironmentVariableSampling_HandlerWithFullSampling_ShouldAlwaysElevateInfoLogs() +- { +- // Arrange +- var originalLogLevel = Environment.GetEnvironmentVariable("POWERTOOLS_LOG_LEVEL"); +- var originalSampleRate = Environment.GetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE"); +- +- try +- { +- Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", "Error"); +- Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE", "1.0"); +- +- var output = new TestLoggerOutput(); +- Logger.Configure(options => { options.LogOutput = output; }); +- +- var handler = new EnvironmentVariableSamplingHandler(); +- +- // Act +- handler.HandleWithFullSampling(new string[] { }); +- +- // Assert +- var logOutput = output.ToString(); +- _output.WriteLine(logOutput); +- +- Assert.Contains("Changed log level to DEBUG based on Sampling configuration", logOutput); +- Assert.Contains("This is an info message — should appear with 100% sampling", logOutput); +- Assert.Contains("\"service\":\"HelloWorldService\"", logOutput); +- } +- finally +- { +- // Cleanup +- Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", originalLogLevel); +- Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE", originalSampleRate); +- Logger.Reset(); +- } +- } +- +- /// +- /// Test with 0% sampling rate to ensure info logs are not elevated +- /// +- [Fact] +- public void EnvironmentVariableSampling_HandlerWithNoSampling_ShouldNotElevateInfoLogs() +- { +- // Arrange +- var originalLogLevel = Environment.GetEnvironmentVariable("POWERTOOLS_LOG_LEVEL"); +- var originalSampleRate = Environment.GetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE"); +- +- try +- { +- Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", "Error"); +- Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE", "0"); +- +- var output = new TestLoggerOutput(); +- Logger.Configure(options => { options.LogOutput = output; }); +- +- var handler = new EnvironmentVariableSamplingHandler(); +- +- // Act +- handler.HandleWithNoSampling(new string[] { }); +- +- // Assert +- var logOutput = output.ToString(); +- _output.WriteLine(logOutput); +- +- Assert.DoesNotContain("Changed log level to DEBUG based on Sampling configuration", logOutput); +- Assert.DoesNotContain("This is an info message — should NOT appear with 0% sampling", logOutput); +- } +- finally +- { +- // Cleanup +- Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", originalLogLevel); +- Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE", originalSampleRate); +- Logger.Reset(); +- } +- } +-} +- +-#endif +- +-public class ExampleClass +-{ +- public string Name { get; set; } +- +- public double Price { get; set; } +- +- public string ThisIsBig { get; set; } +- +- [JsonIgnore] public string ThisIsHidden { get; set; } +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/TestHandlers.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/TestHandlers.cs +index dd332ea4..08fe54d4 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/TestHandlers.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/TestHandlers.cs +@@ -1,12 +1,25 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.Text.Json.Serialization; +-using System.Threading.Tasks; + using Amazon.Lambda.APIGatewayEvents; + using Amazon.Lambda.ApplicationLoadBalancerEvents; + using Amazon.Lambda.CloudWatchEvents; + using Amazon.Lambda.CloudWatchEvents.S3Events; + using Amazon.Lambda.Core; +-using AWS.Lambda.Powertools.Common; + using AWS.Lambda.Powertools.Logging.Tests.Serializers; + using LogLevel = Microsoft.Extensions.Logging.LogLevel; + +@@ -176,103 +189,23 @@ public class TestServiceHandler + { + public void LogWithEnv() + { +- Logger.LogInformation("Service: Environment Service"); +- } +- +- [Logging(Service = "Attribute Service")] +- public void Handler() +- { +- Logger.LogInformation("Service: Attribute Service"); +- } +-} +- +-public class SimpleFunctionWithStaticConfigure +-{ +- public SimpleFunctionWithStaticConfigure(IConsoleWrapper output) +- { +- // Constructor logic can go here if needed +- Logger.Configure(logger => +- { +- logger.LogOutput = output; +- logger.Service = "MyServiceName"; +- logger.LogBuffering = new LogBufferingOptions +- { +- BufferAtLogLevel = LogLevel.Debug, +- }; +- }); +- } +- +- [Logging] +- public static async Task FunctionHandler() +- { +- // only set on handler +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-invocation"); +- +- Logger.LogInformation("Starting up!"); ++ Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", "Environment Service"); + +- return new APIGatewayHttpApiV2ProxyResponse +- { +- Body = "Hello", +- StatusCode = 200 +- }; +- } +- +- [Logging(FlushBufferOnUncaughtError = true)] +- public APIGatewayHttpApiV2ProxyResponse SyncException() +- { +- // only set on handler +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-invocation"); +- +- Logger.LogDebug("Debug!!"); +- Logger.LogInformation("Starting up!"); +- +- throw new Exception(); ++ Logger.LogInformation("Service: Environment Service"); + } + +- [Logging(FlushBufferOnUncaughtError = true)] +- public async Task AsyncException() ++ public void LogWithAndWithoutEnv() + { +- // only set on handler +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-invocation"); +- +- Logger.LogDebug("Debug!!"); +- Logger.LogInformation("Starting up!"); +- +- throw new Exception(); +- } +-} +- +-public class EnvironmentVariableSamplingHandler +-{ +- /// +- /// Handler that tests sampling behavior with environment variables: +- /// Using environment variables POWERTOOLS_LOG_LEVEL=Error and POWERTOOLS_LOGGER_SAMPLE_RATE=0.9 +- /// +- [Logging(Service = "HelloWorldService", LoggerOutputCase = LoggerOutputCase.CamelCase, LogEvent = true)] +- public void HandleWithSampling(string[] args) +- { +- var logLevel = Environment.GetEnvironmentVariable("POWERTOOLS_LOG_LEVEL"); +- var sampleRate = Environment.GetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE"); ++ Logger.LogInformation("Service: service_undefined"); + +- // This should NOT be logged (Info < Error) unless sampling elevates the log level +- Logger.LogInformation("This is an info message — should not appear"); +- } +- +- /// +- /// Handler for testing with guaranteed sampling (100%) +- /// +- [Logging(Service = "HelloWorldService", LoggerOutputCase = LoggerOutputCase.CamelCase, LogEvent = true)] +- public void HandleWithFullSampling(string[] args) +- { +- Logger.LogInformation("This is an info message — should appear with 100% sampling"); ++ Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", "Environment Service"); ++ ++ Logger.LogInformation("Service: service_undefined"); + } +- +- /// +- /// Handler for testing with no sampling (0%) +- /// +- [Logging(Service = "HelloWorldService", LoggerOutputCase = LoggerOutputCase.CamelCase, LogEvent = true)] +- public void HandleWithNoSampling(string[] args) ++ ++ [Logging(Service = "Attribute Service")] ++ public void Handler() + { +- Logger.LogInformation("This is an info message — should NOT appear with 0% sampling"); ++ Logger.LogInformation("Service: Attribute Service"); + } + } +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerBuilderTests.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerBuilderTests.cs +deleted file mode 100644 +index bbb1c43b..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerBuilderTests.cs ++++ /dev/null +@@ -1,208 +0,0 @@ +-using System; +-using System.Text.Json; +-using AWS.Lambda.Powertools.Common.Tests; +-using AWS.Lambda.Powertools.Logging.Internal; +-using AWS.Lambda.Powertools.Logging.Tests.Formatter; +-using AWS.Lambda.Powertools.Logging.Tests.Handlers; +-using Microsoft.Extensions.Logging; +-using Xunit; +-using Xunit.Abstractions; +- +-namespace AWS.Lambda.Powertools.Logging.Tests; +- +-public class PowertoolsLoggerBuilderTests +-{ +- private readonly ITestOutputHelper _output; +- +- public PowertoolsLoggerBuilderTests(ITestOutputHelper output) +- { +- _output = output; +- } +- +- [Fact] +- public void WithService_SetsServiceName() +- { +- var output = new TestLoggerOutput(); +- var logger = new PowertoolsLoggerBuilder() +- .WithLogOutput(output) +- .WithService("test-builder-service") +- .Build(); +- +- logger.LogInformation("Testing service name"); +- +- var logOutput = output.ToString(); +- _output.WriteLine(logOutput); +- +- Assert.Contains("\"service\":\"test-builder-service\"", logOutput, StringComparison.OrdinalIgnoreCase); +- } +- +- [Fact] +- public void WithSamplingRate_SetsSamplingRate() +- { +- var output = new TestLoggerOutput(); +- var logger = new PowertoolsLoggerBuilder() +- .WithLogOutput(output) +- .WithService("sampling-test") +- .WithSamplingRate(0.5) +- .Build(); +- +- // We can't directly test sampling rate in a deterministic way, +- // but we can verify the logger is created successfully +- logger.LogInformation("Testing sampling rate"); +- +- var logOutput = output.ToString(); +- _output.WriteLine(logOutput); +- +- Assert.Contains("\"message\":\"Testing sampling rate\"", logOutput, StringComparison.OrdinalIgnoreCase); +- } +- +- [Fact] +- public void WithMinimumLogLevel_FiltersLowerLevels() +- { +- var output = new TestLoggerOutput(); +- var logger = new PowertoolsLoggerBuilder() +- .WithLogOutput(output) +- .WithService("log-level-test") +- .WithMinimumLogLevel(LogLevel.Warning) +- .Build(); +- +- logger.LogDebug("Debug message"); +- logger.LogInformation("Info message"); +- logger.LogWarning("Warning message"); +- +- var logOutput = output.ToString(); +- _output.WriteLine(logOutput); +- +- Assert.DoesNotContain("Debug message", logOutput); +- Assert.DoesNotContain("Info message", logOutput); +- Assert.Contains("Warning message", logOutput); +- } +- +-#if NET8_0_OR_GREATER +- [Fact] +- public void WithJsonOptions_AppliesFormatting() +- { +- var output = new TestLoggerOutput(); +- var logger = new PowertoolsLoggerBuilder() +- .WithService("json-options-test") +- .WithLogOutput(output) +- .WithJsonOptions(new JsonSerializerOptions +- { +- PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, +- }) +- .Build(); +- +- var testObject = new ExampleClass +- { +- Name = "TestName", +- ThisIsBig = "BigValue" +- }; +- +- logger.LogInformation("Test object: {@testObject}", testObject); +- +- var logOutput = output.ToString(); +- _output.WriteLine(logOutput); +- +- Assert.Contains("\"this_is_big\":\"BigValue\"", logOutput); +- Assert.Contains("\"name\":\"TestName\"", logOutput); +- Assert.Contains("\n", logOutput); // Indentation includes newlines +- } +-#endif +- +- [Fact] +- public void WithTimestampFormat_FormatsTimestamp() +- { +- var output = new TestLoggerOutput(); +- var logger = new PowertoolsLoggerBuilder() +- .WithLogOutput(output) +- .WithService("timestamp-test") +- .WithTimestampFormat("yyyy-MM-dd") +- .Build(); +- +- logger.LogInformation("Testing timestamp format"); +- +- var logOutput = output.ToString(); +- _output.WriteLine(logOutput); +- +- // Should match yyyy-MM-dd format (e.g., "2023-04-25") +- Assert.Matches("\"timestamp\":\"\\d{4}-\\d{2}-\\d{2}\"", logOutput); +- } +- +- [Fact] +- public void WithOutputCase_ChangesPropertyCasing() +- { +- var output = new TestLoggerOutput(); +- var logger = new PowertoolsLoggerBuilder() +- .WithLogOutput(output) +- .WithService("case-test") +- .WithOutputCase(LoggerOutputCase.PascalCase) +- .Build(); +- +- logger.LogInformation("Testing output case"); +- +- var logOutput = output.ToString(); +- _output.WriteLine(logOutput); +- +- Assert.Contains("\"Service\":\"case-test\"", logOutput); +- Assert.Contains("\"Level\":\"Information\"", logOutput); +- Assert.Contains("\"Message\":\"Testing output case\"", logOutput); +- } +- +- [Fact] +- public void WithLogBuffering_BuffersLowLevelLogs() +- { +- var output = new TestLoggerOutput(); +- var logger = new PowertoolsLoggerBuilder() +- .WithLogOutput(output) +- .WithService("buffer-test") +- .WithLogBuffering(options => +- { +- options.BufferAtLogLevel = LogLevel.Debug; +- }) +- .Build(); +- +- Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "config-test"); +- logger.LogDebug("Debug buffered message"); +- logger.LogInformation("Info message"); +- +- // Without FlushBuffer(), the debug message should be buffered +- var initialOutput = output.ToString(); +- _output.WriteLine("Before flush: " + initialOutput); +- +- Assert.DoesNotContain("Debug buffered message", initialOutput); +- Assert.Contains("Info message", initialOutput); +- +- // After flushing, the debug message should appear +- logger.FlushBuffer(); +- var afterFlushOutput = output.ToString(); +- _output.WriteLine("After flush: " + afterFlushOutput); +- +- Assert.Contains("Debug buffered message", afterFlushOutput); +- } +- +- [Fact] +- public void BuilderChaining_ConfiguresAllProperties() +- { +- var output = new TestLoggerOutput(); +- var customFormatter = new CustomLogFormatter(); +- +- var logger = new PowertoolsLoggerBuilder() +- .WithService("chained-config-service") +- .WithSamplingRate(0.1) +- .WithMinimumLogLevel(LogLevel.Information) +- .WithOutputCase(LoggerOutputCase.SnakeCase) +- .WithFormatter(customFormatter) +- .WithLogOutput(output) +- .Build(); +- +- logger.LogInformation("Testing fully configured logger"); +- +- var logOutput = output.ToString(); +- _output.WriteLine(logOutput); +- +- // Verify multiple configured properties are applied +- Assert.Contains("\"service\":\"chained-config-service\"", logOutput); +- Assert.Contains("\"message\":\"Testing fully configured logger\"", logOutput); +- Assert.Contains("\"sample_rate\":0.1", logOutput); +- } +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerExtensionsTests.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerExtensionsTests.cs +deleted file mode 100644 +index 8e1ea3c6..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerExtensionsTests.cs ++++ /dev/null +@@ -1,72 +0,0 @@ +-using System; +-using System.Linq; +-using AWS.Lambda.Powertools.Logging.Internal; +-using Microsoft.Extensions.DependencyInjection; +-using Microsoft.Extensions.Logging; +-using Xunit; +- +-namespace AWS.Lambda.Powertools.Logging.Tests; +- +-public class PowertoolsLoggerExtensionsTests +-{ +- [Fact] +- public void AddPowertoolsLogger_WithClearExistingProviders_False_KeepsExistingProviders() +- { +- // Arrange +- var serviceCollection = new ServiceCollection(); +- serviceCollection.AddLogging(builder => +- { +- // Add a mock existing provider first +- builder.Services.AddSingleton(); +- +- // Act +- builder.AddPowertoolsLogger(clearExistingProviders: false); +- }); +- +- var serviceProvider = serviceCollection.BuildServiceProvider(); +- var loggerProviders = serviceProvider.GetServices(); +- +- // Assert +- var collection = loggerProviders as ILoggerProvider[] ?? loggerProviders.ToArray(); +- Assert.Contains(collection, p => p is MockLoggerProvider); +- Assert.Contains(collection, p => p is PowertoolsLoggerProvider); +- Assert.True(collection.Count() >= 2); // Should have both providers +- } +- +- [Fact] +- public void AddPowertoolsLogger_WithClearExistingProviders_True_RemovesExistingProviders() +- { +- // Arrange +- var serviceCollection = new ServiceCollection(); +- serviceCollection.AddLogging(builder => +- { +- // Add a mock existing provider first +- builder.Services.AddSingleton(); +- +- // Act +- builder.AddPowertoolsLogger(clearExistingProviders: true); +- }); +- +- var serviceProvider = serviceCollection.BuildServiceProvider(); +- var loggerProviders = serviceProvider.GetServices(); +- +- // Assert +- var collection = loggerProviders as ILoggerProvider[] ?? loggerProviders.ToArray(); +- Assert.DoesNotContain(collection, p => p is MockLoggerProvider); +- Assert.Contains(collection, p => p is PowertoolsLoggerProvider); +- Assert.Single(collection); // Should only have Powertools provider +- } +- +- private class MockLoggerProvider : ILoggerProvider +- { +- public ILogger CreateLogger(string categoryName) => new MockLogger(); +- public void Dispose() { } +- } +- +- private class MockLogger : ILogger +- { +- public IDisposable BeginScope(TState state) => null; +- public bool IsEnabled(LogLevel logLevel) => true; +- public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { } +- } +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerTest.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerTest.cs +index 05141510..e034ce33 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerTest.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerTest.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.Collections.Generic; + using System.Globalization; +@@ -5,8 +20,6 @@ using System.IO; + using System.Linq; + using System.Text; + using AWS.Lambda.Powertools.Common; +-using AWS.Lambda.Powertools.Common.Core; +-using AWS.Lambda.Powertools.Common.Tests; + using AWS.Lambda.Powertools.Logging.Internal; + using AWS.Lambda.Powertools.Logging.Serializers; + using AWS.Lambda.Powertools.Logging.Tests.Utilities; +@@ -21,32 +34,30 @@ namespace AWS.Lambda.Powertools.Logging.Tests + { + public PowertoolsLoggerTest() + { +- // Logger.UseDefaultFormatter(); ++ Logger.UseDefaultFormatter(); + } + +- private static void Log_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel logLevel, LogLevel MinimumLogLevel) ++ private static void Log_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel logLevel, LogLevel minimumLevel) + { + // Arrange + var loggerName = Guid.NewGuid().ToString(); + var service = Guid.NewGuid().ToString(); + + var configurations = Substitute.For(); +- var systemWrapper = Substitute.For(); ++ var systemWrapper = Substitute.For(); + + // Configure the substitute for IPowertoolsConfigurations + configurations.Service.Returns(service); + configurations.LoggerOutputCase.Returns(LoggerOutputCase.PascalCase.ToString()); +- configurations.LogLevel.Returns(MinimumLogLevel.ToString()); ++ configurations.LogLevel.Returns(minimumLevel.ToString()); + +- var loggerConfiguration = new PowertoolsLoggerConfiguration ++ var loggerConfiguration = new LoggerConfiguration + { +- Service = service, +- LoggerOutputCase = LoggerOutputCase.PascalCase, +- MinimumLogLevel = MinimumLogLevel, +- LogOutput = systemWrapper // Set the output directly on configuration ++ Service = null, ++ MinimumLevel = LogLevel.None + }; +- +- var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); ++ ++ var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); + var logger = provider.CreateLogger(loggerName); + + switch (logLevel) +@@ -77,34 +88,32 @@ namespace AWS.Lambda.Powertools.Logging.Tests + } + + // Assert +- systemWrapper.Received(1).WriteLine( ++ systemWrapper.Received(1).LogLine( + Arg.Is(s => s.Contains(service)) + ); + } + +- private static void Log_WhenMinimumLogLevelIsAboveLogLevel_DoesNotLog(LogLevel logLevel, +- LogLevel MinimumLogLevel) ++ private static void Log_WhenMinimumLevelIsAboveLogLevel_DoesNotLog(LogLevel logLevel, LogLevel minimumLevel) + { + // Arrange + var loggerName = Guid.NewGuid().ToString(); + var service = Guid.NewGuid().ToString(); + + var configurations = Substitute.For(); +- var systemWrapper = Substitute.For(); ++ var systemWrapper = Substitute.For(); + + // Configure the substitute for IPowertoolsConfigurations + configurations.Service.Returns(service); + configurations.LoggerOutputCase.Returns(LoggerOutputCase.PascalCase.ToString()); +- configurations.LogLevel.Returns(MinimumLogLevel.ToString()); ++ configurations.LogLevel.Returns(minimumLevel.ToString()); + +- var loggerConfiguration = new PowertoolsLoggerConfiguration ++ var loggerConfiguration = new LoggerConfiguration + { + Service = service, +- MinimumLogLevel = MinimumLogLevel, +- LogOutput = systemWrapper // Set the output directly on configuration ++ MinimumLevel = minimumLevel + }; +- +- var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); ++ ++ var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); + var logger = provider.CreateLogger(loggerName); + + switch (logLevel) +@@ -135,33 +144,33 @@ namespace AWS.Lambda.Powertools.Logging.Tests + } + + // Assert +- systemWrapper.DidNotReceive().WriteLine( ++ systemWrapper.DidNotReceive().LogLine( + Arg.Any() + ); + } + + [Theory] + [InlineData(LogLevel.Trace)] +- public void LogTrace_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel MinimumLogLevel) ++ public void LogTrace_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel minimumLevel) + { +- Log_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel.Trace, MinimumLogLevel); ++ Log_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel.Trace, minimumLevel); + } + + [Theory] + [InlineData(LogLevel.Trace)] + [InlineData(LogLevel.Debug)] +- public void LogDebug_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel MinimumLogLevel) ++ public void LogDebug_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel minimumLevel) + { +- Log_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel.Debug, MinimumLogLevel); ++ Log_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel.Debug, minimumLevel); + } + + [Theory] + [InlineData(LogLevel.Trace)] + [InlineData(LogLevel.Debug)] + [InlineData(LogLevel.Information)] +- public void LogInformation_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel MinimumLogLevel) ++ public void LogInformation_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel minimumLevel) + { +- Log_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel.Information, MinimumLogLevel); ++ Log_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel.Information, minimumLevel); + } + + [Theory] +@@ -169,9 +178,9 @@ namespace AWS.Lambda.Powertools.Logging.Tests + [InlineData(LogLevel.Debug)] + [InlineData(LogLevel.Information)] + [InlineData(LogLevel.Warning)] +- public void LogWarning_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel MinimumLogLevel) ++ public void LogWarning_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel minimumLevel) + { +- Log_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel.Warning, MinimumLogLevel); ++ Log_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel.Warning, minimumLevel); + } + + [Theory] +@@ -180,9 +189,9 @@ namespace AWS.Lambda.Powertools.Logging.Tests + [InlineData(LogLevel.Information)] + [InlineData(LogLevel.Warning)] + [InlineData(LogLevel.Error)] +- public void LogError_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel MinimumLogLevel) ++ public void LogError_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel minimumLevel) + { +- Log_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel.Error, MinimumLogLevel); ++ Log_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel.Error, minimumLevel); + } + + [Theory] +@@ -192,9 +201,9 @@ namespace AWS.Lambda.Powertools.Logging.Tests + [InlineData(LogLevel.Warning)] + [InlineData(LogLevel.Error)] + [InlineData(LogLevel.Critical)] +- public void LogCritical_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel MinimumLogLevel) ++ public void LogCritical_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel minimumLevel) + { +- Log_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel.Critical, MinimumLogLevel); ++ Log_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel.Critical, minimumLevel); + } + + [Theory] +@@ -203,9 +212,9 @@ namespace AWS.Lambda.Powertools.Logging.Tests + [InlineData(LogLevel.Warning)] + [InlineData(LogLevel.Error)] + [InlineData(LogLevel.Critical)] +- public void LogTrace_WhenMinimumLogLevelIsAboveLogLevel_DoesNotLog(LogLevel MinimumLogLevel) ++ public void LogTrace_WhenMinimumLevelIsAboveLogLevel_DoesNotLog(LogLevel minimumLevel) + { +- Log_WhenMinimumLogLevelIsAboveLogLevel_DoesNotLog(LogLevel.Trace, MinimumLogLevel); ++ Log_WhenMinimumLevelIsAboveLogLevel_DoesNotLog(LogLevel.Trace, minimumLevel); + } + + [Theory] +@@ -213,33 +222,33 @@ namespace AWS.Lambda.Powertools.Logging.Tests + [InlineData(LogLevel.Warning)] + [InlineData(LogLevel.Error)] + [InlineData(LogLevel.Critical)] +- public void LogDebug_WhenMinimumLogLevelIsAboveLogLevel_DoesNotLog(LogLevel MinimumLogLevel) ++ public void LogDebug_WhenMinimumLevelIsAboveLogLevel_DoesNotLog(LogLevel minimumLevel) + { +- Log_WhenMinimumLogLevelIsAboveLogLevel_DoesNotLog(LogLevel.Debug, MinimumLogLevel); ++ Log_WhenMinimumLevelIsAboveLogLevel_DoesNotLog(LogLevel.Debug, minimumLevel); + } + + [Theory] + [InlineData(LogLevel.Warning)] + [InlineData(LogLevel.Error)] + [InlineData(LogLevel.Critical)] +- public void LogInformation_WhenMinimumLogLevelIsAboveLogLevel_DoesNotLog(LogLevel MinimumLogLevel) ++ public void LogInformation_WhenMinimumLevelIsAboveLogLevel_DoesNotLog(LogLevel minimumLevel) + { +- Log_WhenMinimumLogLevelIsAboveLogLevel_DoesNotLog(LogLevel.Information, MinimumLogLevel); ++ Log_WhenMinimumLevelIsAboveLogLevel_DoesNotLog(LogLevel.Information, minimumLevel); + } + + [Theory] + [InlineData(LogLevel.Error)] + [InlineData(LogLevel.Critical)] +- public void LogWarning_WhenMinimumLogLevelIsAboveLogLevel_DoesNotLog(LogLevel MinimumLogLevel) ++ public void LogWarning_WhenMinimumLevelIsAboveLogLevel_DoesNotLog(LogLevel minimumLevel) + { +- Log_WhenMinimumLogLevelIsAboveLogLevel_DoesNotLog(LogLevel.Warning, MinimumLogLevel); ++ Log_WhenMinimumLevelIsAboveLogLevel_DoesNotLog(LogLevel.Warning, minimumLevel); + } + + [Theory] + [InlineData(LogLevel.Critical)] +- public void LogError_WhenMinimumLogLevelIsAboveLogLevel_DoesNotLog(LogLevel MinimumLogLevel) ++ public void LogError_WhenMinimumLevelIsAboveLogLevel_DoesNotLog(LogLevel minimumLevel) + { +- Log_WhenMinimumLogLevelIsAboveLogLevel_DoesNotLog(LogLevel.Error, MinimumLogLevel); ++ Log_WhenMinimumLevelIsAboveLogLevel_DoesNotLog(LogLevel.Error, minimumLevel); + } + + [Theory] +@@ -249,9 +258,9 @@ namespace AWS.Lambda.Powertools.Logging.Tests + [InlineData(LogLevel.Warning)] + [InlineData(LogLevel.Error)] + [InlineData(LogLevel.Critical)] +- public void LogNone_WithAnyMinimumLogLevel_DoesNotLog(LogLevel MinimumLogLevel) ++ public void LogNone_WithAnyMinimumLevel_DoesNotLog(LogLevel minimumLevel) + { +- Log_WhenMinimumLogLevelIsAboveLogLevel_DoesNotLog(LogLevel.None, MinimumLogLevel); ++ Log_WhenMinimumLevelIsAboveLogLevel_DoesNotLog(LogLevel.None, minimumLevel); + } + + [Fact] +@@ -261,29 +270,32 @@ namespace AWS.Lambda.Powertools.Logging.Tests + var service = Guid.NewGuid().ToString(); + var logLevel = LogLevel.Trace; + var loggerSampleRate = 0.7; ++ var randomSampleRate = 0.5; + + var configurations = Substitute.For(); + configurations.Service.Returns(service); + configurations.LogLevel.Returns(logLevel.ToString()); + configurations.LoggerSampleRate.Returns(loggerSampleRate); + +- var systemWrapper = Substitute.For(); ++ var systemWrapper = Substitute.For(); ++ systemWrapper.GetRandom().Returns(randomSampleRate); + +- var loggerConfiguration = new PowertoolsLoggerConfiguration ++ var loggerConfiguration = new LoggerConfiguration + { +- Service = service, +- MinimumLogLevel = logLevel, +- LogOutput = systemWrapper, +- SamplingRate = loggerSampleRate ++ Service = null, ++ MinimumLevel = LogLevel.None + }; + +- var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); ++ // Act ++ ++ var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); ++ + var logger = provider.CreateLogger("test"); + + logger.LogInformation("Test"); + + // Assert +- systemWrapper.Received(1).WriteLine( ++ systemWrapper.Received(1).LogLine( + Arg.Is(s => + s.Contains(service) && + s.Contains(loggerSampleRate.ToString(CultureInfo.InvariantCulture)) +@@ -297,38 +309,36 @@ namespace AWS.Lambda.Powertools.Logging.Tests + // Arrange + var service = Guid.NewGuid().ToString(); + var logLevel = LogLevel.Trace; +- var loggerSampleRate = 1.0; // Use 100% to guarantee activation ++ var loggerSampleRate = 0.7; ++ var randomSampleRate = 0.5; + + var configurations = Substitute.For(); + configurations.Service.Returns(service); + configurations.LogLevel.Returns(logLevel.ToString()); + configurations.LoggerSampleRate.Returns(loggerSampleRate); + +- var systemWrapper = Substitute.For(); +- +- var loggerConfiguration = new PowertoolsLoggerConfiguration ++ var systemWrapper = Substitute.For(); ++ systemWrapper.GetRandom().Returns(randomSampleRate); ++ ++ var loggerConfiguration = new LoggerConfiguration + { +- Service = service, +- MinimumLogLevel = logLevel, +- LogOutput = systemWrapper, +- SamplingRate = loggerSampleRate ++ Service = null, ++ MinimumLevel = LogLevel.None + }; +- ++ + // Act +- var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); +- var logger = provider.CreateLogger("test"); ++ ++ var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); + +- // First call - skipped due to cold start protection +- logger.LogInformation("Test1"); ++ var logger = provider.CreateLogger("test"); + +- // Second call - should trigger sampling with 100% rate +- logger.LogInformation("Test2"); ++ logger.LogInformation("Test"); + +- // Assert - Check that the debug message was printed (with any sampler value since it's random) +- systemWrapper.Received(1).WriteLine( ++ // Assert ++ systemWrapper.Received(1).LogLine( + Arg.Is(s => +- s.Contains("Changed log level to DEBUG based on Sampling configuration") && +- s.Contains($"Sampling Rate: {loggerSampleRate}") ++ s == ++ $"Changed log level to DEBUG based on Sampling configuration. Sampling Rate: {loggerSampleRate}, Sampler Value: {randomSampleRate}." + ) + ); + } +@@ -347,23 +357,22 @@ namespace AWS.Lambda.Powertools.Logging.Tests + configurations.LogLevel.Returns(logLevel.ToString()); + configurations.LoggerSampleRate.Returns(loggerSampleRate); + +- var systemWrapper = Substitute.For(); ++ var systemWrapper = Substitute.For(); + +- var loggerConfiguration = new PowertoolsLoggerConfiguration ++ var loggerConfiguration = new LoggerConfiguration + { +- Service = service, +- MinimumLogLevel = logLevel, +- LogOutput = systemWrapper, +- SamplingRate = loggerSampleRate +- }; +- +- var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); ++ Service = null, ++ MinimumLevel = LogLevel.None ++ }; ++ ++ // Act ++ var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); + var logger = provider.CreateLogger(loggerName); + + logger.LogInformation("Test"); + + // Assert +- systemWrapper.Received(1).WriteLine( ++ systemWrapper.Received(1).LogLine( + Arg.Is(s => + s == + $"Skipping sampling rate configuration because of invalid value. Sampling rate: {loggerSampleRate}" +@@ -378,23 +387,24 @@ namespace AWS.Lambda.Powertools.Logging.Tests + var loggerName = Guid.NewGuid().ToString(); + var service = Guid.NewGuid().ToString(); + var logLevel = LogLevel.Information; ++ var randomSampleRate = 0.5; + + var configurations = Substitute.For(); + configurations.Service.Returns(service); + configurations.LogLevel.Returns(logLevel.ToString()); + configurations.LoggerOutputCase.Returns(LoggerOutputCase.CamelCase.ToString()); + +- var systemWrapper = Substitute.For(); ++ var systemWrapper = Substitute.For(); ++ systemWrapper.GetRandom().Returns(randomSampleRate); + +- var loggerConfiguration = new PowertoolsLoggerConfiguration ++ var loggerConfiguration = new LoggerConfiguration + { + Service = null, +- MinimumLogLevel = LogLevel.None, +- LogOutput = systemWrapper ++ MinimumLevel = LogLevel.None + }; + + // Act +- var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); ++ var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); + var logger = provider.CreateLogger(loggerName); + + var message = new +@@ -406,7 +416,7 @@ namespace AWS.Lambda.Powertools.Logging.Tests + logger.LogInformation(message); + + // Assert +- systemWrapper.Received(1).WriteLine( ++ systemWrapper.Received(1).LogLine( + Arg.Is(s => + s.Contains("\"message\":{\"propOne\":\"Value 1\",\"propTwo\":\"Value 2\"}") + ) +@@ -420,23 +430,24 @@ namespace AWS.Lambda.Powertools.Logging.Tests + var loggerName = Guid.NewGuid().ToString(); + var service = Guid.NewGuid().ToString(); + var logLevel = LogLevel.Information; ++ var randomSampleRate = 0.5; + + var configurations = Substitute.For(); + configurations.Service.Returns(service); + configurations.LogLevel.Returns(logLevel.ToString()); + +- var systemWrapper = Substitute.For(); ++ var systemWrapper = Substitute.For(); ++ systemWrapper.GetRandom().Returns(randomSampleRate); + +- var loggerConfiguration = new PowertoolsLoggerConfiguration ++ var loggerConfiguration = new LoggerConfiguration + { + Service = null, +- MinimumLogLevel = LogLevel.None, +- LoggerOutputCase = LoggerOutputCase.CamelCase, +- LogOutput = systemWrapper ++ MinimumLevel = LogLevel.None, ++ LoggerOutputCase = LoggerOutputCase.CamelCase + }; +- ++ + // Act +- var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); ++ var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); + var logger = provider.CreateLogger(loggerName); + + var message = new +@@ -448,7 +459,7 @@ namespace AWS.Lambda.Powertools.Logging.Tests + logger.LogInformation(message); + + // Assert +- systemWrapper.Received(1).WriteLine( ++ systemWrapper.Received(1).LogLine( + Arg.Is(s => + s.Contains("\"message\":{\"propOne\":\"Value 1\",\"propTwo\":\"Value 2\"}") + ) +@@ -462,22 +473,23 @@ namespace AWS.Lambda.Powertools.Logging.Tests + var loggerName = Guid.NewGuid().ToString(); + var service = Guid.NewGuid().ToString(); + var logLevel = LogLevel.Information; ++ var randomSampleRate = 0.5; + + var configurations = Substitute.For(); + configurations.Service.Returns(service); + configurations.LogLevel.Returns(logLevel.ToString()); + configurations.LoggerOutputCase.Returns(LoggerOutputCase.PascalCase.ToString()); + +- var systemWrapper = Substitute.For(); ++ var systemWrapper = Substitute.For(); ++ systemWrapper.GetRandom().Returns(randomSampleRate); + +- var loggerConfiguration = new PowertoolsLoggerConfiguration ++ var loggerConfiguration = new LoggerConfiguration + { + Service = null, +- MinimumLogLevel = LogLevel.None, +- LogOutput = systemWrapper ++ MinimumLevel = LogLevel.None + }; +- +- var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); ++ ++ var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); + var logger = provider.CreateLogger(loggerName); + + var message = new +@@ -489,7 +501,7 @@ namespace AWS.Lambda.Powertools.Logging.Tests + logger.LogInformation(message); + + // Assert +- systemWrapper.Received(1).WriteLine( ++ systemWrapper.Received(1).LogLine( + Arg.Is(s => + s.Contains("\"Message\":{\"PropOne\":\"Value 1\",\"PropTwo\":\"Value 2\"}") + ) +@@ -503,22 +515,23 @@ namespace AWS.Lambda.Powertools.Logging.Tests + var loggerName = Guid.NewGuid().ToString(); + var service = Guid.NewGuid().ToString(); + var logLevel = LogLevel.Information; ++ var randomSampleRate = 0.5; + + var configurations = Substitute.For(); + configurations.Service.Returns(service); + configurations.LogLevel.Returns(logLevel.ToString()); + +- var systemWrapper = Substitute.For(); ++ var systemWrapper = Substitute.For(); ++ systemWrapper.GetRandom().Returns(randomSampleRate); + +- var loggerConfiguration = new PowertoolsLoggerConfiguration ++ var loggerConfiguration = new LoggerConfiguration + { + Service = null, +- MinimumLogLevel = LogLevel.None, +- LoggerOutputCase = LoggerOutputCase.PascalCase, +- LogOutput = systemWrapper ++ MinimumLevel = LogLevel.None, ++ LoggerOutputCase = LoggerOutputCase.PascalCase + }; +- +- var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); ++ ++ var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); + var logger = provider.CreateLogger(loggerName); + + var message = new +@@ -530,7 +543,7 @@ namespace AWS.Lambda.Powertools.Logging.Tests + logger.LogInformation(message); + + // Assert +- systemWrapper.Received(1).WriteLine(Arg.Is(s => ++ systemWrapper.Received(1).LogLine(Arg.Is(s => + s.Contains("\"Message\":{\"PropOne\":\"Value 1\",\"PropTwo\":\"Value 2\"}") + )); + } +@@ -542,22 +555,23 @@ namespace AWS.Lambda.Powertools.Logging.Tests + var loggerName = Guid.NewGuid().ToString(); + var service = Guid.NewGuid().ToString(); + var logLevel = LogLevel.Information; ++ var randomSampleRate = 0.5; + + var configurations = Substitute.For(); + configurations.Service.Returns(service); + configurations.LogLevel.Returns(logLevel.ToString()); + configurations.LoggerOutputCase.Returns(LoggerOutputCase.SnakeCase.ToString()); + +- var systemWrapper = Substitute.For(); ++ var systemWrapper = Substitute.For(); ++ systemWrapper.GetRandom().Returns(randomSampleRate); + +- var loggerConfiguration = new PowertoolsLoggerConfiguration ++ var loggerConfiguration = new LoggerConfiguration + { + Service = null, +- MinimumLogLevel = LogLevel.None, +- LogOutput = systemWrapper ++ MinimumLevel = LogLevel.None + }; +- +- var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); ++ ++ var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); + var logger = provider.CreateLogger(loggerName); + + var message = new +@@ -569,7 +583,7 @@ namespace AWS.Lambda.Powertools.Logging.Tests + logger.LogInformation(message); + + // Assert +- systemWrapper.Received(1).WriteLine(Arg.Is(s => ++ systemWrapper.Received(1).LogLine(Arg.Is(s => + s.Contains("\"message\":{\"prop_one\":\"Value 1\",\"prop_two\":\"Value 2\"}") + )); + } +@@ -581,22 +595,23 @@ namespace AWS.Lambda.Powertools.Logging.Tests + var loggerName = Guid.NewGuid().ToString(); + var service = Guid.NewGuid().ToString(); + var logLevel = LogLevel.Information; ++ var randomSampleRate = 0.5; + + var configurations = Substitute.For(); + configurations.Service.Returns(service); + configurations.LogLevel.Returns(logLevel.ToString()); + +- var systemWrapper = Substitute.For(); ++ var systemWrapper = Substitute.For(); ++ systemWrapper.GetRandom().Returns(randomSampleRate); + +- var loggerConfiguration = new PowertoolsLoggerConfiguration ++ var loggerConfiguration = new LoggerConfiguration + { + Service = null, +- MinimumLogLevel = LogLevel.None, +- LoggerOutputCase = LoggerOutputCase.SnakeCase, +- LogOutput = systemWrapper ++ MinimumLevel = LogLevel.None, ++ LoggerOutputCase = LoggerOutputCase.SnakeCase + }; +- +- var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); ++ ++ var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); + var logger = provider.CreateLogger(loggerName); + + var message = new +@@ -608,7 +623,7 @@ namespace AWS.Lambda.Powertools.Logging.Tests + logger.LogInformation(message); + + // Assert +- systemWrapper.Received(1).WriteLine(Arg.Is(s => ++ systemWrapper.Received(1).LogLine(Arg.Is(s => + s.Contains("\"message\":{\"prop_one\":\"Value 1\",\"prop_two\":\"Value 2\"}") + )); + } +@@ -620,21 +635,22 @@ namespace AWS.Lambda.Powertools.Logging.Tests + var loggerName = Guid.NewGuid().ToString(); + var service = Guid.NewGuid().ToString(); + var logLevel = LogLevel.Information; ++ var randomSampleRate = 0.5; + + var configurations = Substitute.For(); + configurations.Service.Returns(service); + configurations.LogLevel.Returns(logLevel.ToString()); + +- var systemWrapper = Substitute.For(); ++ var systemWrapper = Substitute.For(); ++ systemWrapper.GetRandom().Returns(randomSampleRate); + +- var loggerConfiguration = new PowertoolsLoggerConfiguration ++ var loggerConfiguration = new LoggerConfiguration + { + Service = null, +- MinimumLogLevel = LogLevel.None, +- LogOutput = systemWrapper +- }; +- +- var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); ++ MinimumLevel = LogLevel.None ++ }; ++ ++ var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); + var logger = provider.CreateLogger(loggerName); + + var message = new +@@ -646,7 +662,7 @@ namespace AWS.Lambda.Powertools.Logging.Tests + logger.LogInformation(message); + + // Assert +- systemWrapper.Received(1).WriteLine(Arg.Is(s => ++ systemWrapper.Received(1).LogLine(Arg.Is(s => + s.Contains("\"message\":{\"prop_one\":\"Value 1\",\"prop_two\":\"Value 2\"}"))); + } + +@@ -661,15 +677,15 @@ namespace AWS.Lambda.Powertools.Logging.Tests + var configurations = Substitute.For(); + configurations.Service.Returns(service); + configurations.LogLevel.Returns(logLevel.ToString()); +- var systemWrapper = Substitute.For(); ++ var systemWrapper = Substitute.For(); + +- var loggerConfiguration = new PowertoolsLoggerConfiguration ++ var loggerConfiguration = new LoggerConfiguration + { + Service = service, +- MinimumLogLevel = logLevel ++ MinimumLevel = logLevel + }; +- +- var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); ++ ++ var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); + var logger = (PowertoolsLogger)provider.CreateLogger(loggerName); + + var scopeKeys = new +@@ -704,15 +720,15 @@ namespace AWS.Lambda.Powertools.Logging.Tests + var configurations = Substitute.For(); + configurations.Service.Returns(service); + configurations.LogLevel.Returns(logLevel.ToString()); +- var systemWrapper = Substitute.For(); ++ var systemWrapper = Substitute.For(); + +- var loggerConfiguration = new PowertoolsLoggerConfiguration ++ var loggerConfiguration = new LoggerConfiguration + { + Service = service, +- MinimumLogLevel = logLevel ++ MinimumLevel = logLevel + }; +- +- var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); ++ ++ var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); + var logger = (PowertoolsLogger)provider.CreateLogger(loggerName); + + var scopeKeys = new Dictionary +@@ -747,15 +763,15 @@ namespace AWS.Lambda.Powertools.Logging.Tests + var configurations = Substitute.For(); + configurations.Service.Returns(service); + configurations.LogLevel.Returns(logLevel.ToString()); +- var systemWrapper = Substitute.For(); ++ var systemWrapper = Substitute.For(); + +- var loggerConfiguration = new PowertoolsLoggerConfiguration ++ var loggerConfiguration = new LoggerConfiguration + { + Service = service, +- MinimumLogLevel = logLevel ++ MinimumLevel = logLevel + }; +- +- var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); ++ ++ var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); + var logger = (PowertoolsLogger)provider.CreateLogger(loggerName); + + var scopeKeys = new Dictionary +@@ -797,21 +813,20 @@ namespace AWS.Lambda.Powertools.Logging.Tests + // Arrange + var loggerName = Guid.NewGuid().ToString(); + var service = Guid.NewGuid().ToString(); +- var message = "{@keys}"; ++ var message = Guid.NewGuid().ToString(); + + var configurations = Substitute.For(); + configurations.Service.Returns(service); + configurations.LogLevel.Returns(logLevel.ToString()); + configurations.LoggerOutputCase.Returns(LoggerOutputCase.PascalCase.ToString()); +- var systemWrapper = Substitute.For(); ++ var systemWrapper = Substitute.For(); + +- var loggerConfiguration = new PowertoolsLoggerConfiguration ++ var loggerConfiguration = new LoggerConfiguration + { + Service = service, +- MinimumLogLevel = LogLevel.Trace, +- LogOutput = systemWrapper ++ MinimumLevel = LogLevel.Trace, + }; +- var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); ++ var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); + var logger = (PowertoolsLogger)provider.CreateLogger(loggerName); + + var scopeKeys = new Dictionary +@@ -822,29 +837,29 @@ namespace AWS.Lambda.Powertools.Logging.Tests + + if (logMethod) + { +- logger.Log(logLevel, message,scopeKeys); ++ logger.Log(logLevel, scopeKeys, message); + } + else + { + switch (logLevel) + { + case LogLevel.Trace: +- logger.LogTrace(message,scopeKeys); ++ logger.LogTrace(scopeKeys, message); + break; + case LogLevel.Debug: +- logger.LogDebug(message,scopeKeys); ++ logger.LogDebug(scopeKeys, message); + break; + case LogLevel.Information: +- logger.LogInformation(message,scopeKeys); ++ logger.LogInformation(scopeKeys, message); + break; + case LogLevel.Warning: +- logger.LogWarning(message,scopeKeys); ++ logger.LogWarning(scopeKeys, message); + break; + case LogLevel.Error: +- logger.LogError(message,scopeKeys); ++ logger.LogError(scopeKeys, message); + break; + case LogLevel.Critical: +- logger.LogCritical(message,scopeKeys); ++ logger.LogCritical(scopeKeys, message); + break; + case LogLevel.None: + break; +@@ -853,7 +868,7 @@ namespace AWS.Lambda.Powertools.Logging.Tests + } + } + +- systemWrapper.Received(1).WriteLine(Arg.Is(s => ++ systemWrapper.Received(1).LogLine(Arg.Is(s => + s.Contains(scopeKeys.Keys.First()) && + s.Contains(scopeKeys.Keys.Last()) && + s.Contains(scopeKeys.Values.First().ToString()) && +@@ -881,22 +896,21 @@ namespace AWS.Lambda.Powertools.Logging.Tests + // Arrange + var loggerName = Guid.NewGuid().ToString(); + var service = Guid.NewGuid().ToString(); +- var message = "{@keys}"; ++ var message = Guid.NewGuid().ToString(); + + var configurations = Substitute.For(); + configurations.Service.Returns(service); + configurations.LogLevel.Returns(logLevel.ToString()); + configurations.LoggerOutputCase.Returns(LoggerOutputCase.PascalCase.ToString()); +- var systemWrapper = Substitute.For(); ++ var systemWrapper = Substitute.For(); + +- var loggerConfiguration = new PowertoolsLoggerConfiguration ++ var loggerConfiguration = new LoggerConfiguration + { + Service = service, +- MinimumLogLevel = LogLevel.Trace, +- LogOutput = systemWrapper ++ MinimumLevel = LogLevel.Trace, + }; +- +- var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); ++ ++ var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); + var logger = (PowertoolsLogger)provider.CreateLogger(loggerName); + + var scopeKeys = new Dictionary +@@ -907,29 +921,29 @@ namespace AWS.Lambda.Powertools.Logging.Tests + + if (logMethod) + { +- logger.Log(logLevel, message,scopeKeys); ++ logger.Log(logLevel, scopeKeys, message); + } + else + { + switch (logLevel) + { + case LogLevel.Trace: +- logger.LogTrace(message,scopeKeys); ++ logger.LogTrace(scopeKeys, message); + break; + case LogLevel.Debug: +- logger.LogDebug(message,scopeKeys); ++ logger.LogDebug(scopeKeys, message); + break; + case LogLevel.Information: +- logger.LogInformation(message,scopeKeys); ++ logger.LogInformation(scopeKeys, message); + break; + case LogLevel.Warning: +- logger.LogWarning(message,scopeKeys); ++ logger.LogWarning(scopeKeys, message); + break; + case LogLevel.Error: +- logger.LogError(message,scopeKeys); ++ logger.LogError(scopeKeys, message); + break; + case LogLevel.Critical: +- logger.LogCritical(message,scopeKeys); ++ logger.LogCritical(scopeKeys, message); + break; + case LogLevel.None: + break; +@@ -938,7 +952,7 @@ namespace AWS.Lambda.Powertools.Logging.Tests + } + } + +- systemWrapper.Received(1).WriteLine(Arg.Is(s => ++ systemWrapper.Received(1).LogLine(Arg.Is(s => + s.Contains(scopeKeys.Keys.First()) && + s.Contains(scopeKeys.Keys.Last()) && + s.Contains(scopeKeys.Values.First()) && +@@ -966,22 +980,21 @@ namespace AWS.Lambda.Powertools.Logging.Tests + // Arrange + var loggerName = Guid.NewGuid().ToString(); + var service = Guid.NewGuid().ToString(); +- var message = "{@keys}"; ++ var message = Guid.NewGuid().ToString(); + + var configurations = Substitute.For(); + configurations.Service.Returns(service); + configurations.LogLevel.Returns(logLevel.ToString()); + configurations.LoggerOutputCase.Returns(LoggerOutputCase.PascalCase.ToString()); +- var systemWrapper = Substitute.For(); ++ var systemWrapper = Substitute.For(); + +- var loggerConfiguration = new PowertoolsLoggerConfiguration ++ var loggerConfiguration = new LoggerConfiguration + { + Service = service, +- MinimumLogLevel = LogLevel.Trace, +- LogOutput = systemWrapper ++ MinimumLevel = LogLevel.Trace, + }; +- +- var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); ++ ++ var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); + var logger = (PowertoolsLogger)provider.CreateLogger(loggerName); + + var scopeKeys = new +@@ -992,29 +1005,29 @@ namespace AWS.Lambda.Powertools.Logging.Tests + + if (logMethod) + { +- logger.Log(logLevel, message, scopeKeys); ++ logger.Log(logLevel, scopeKeys, message); + } + else + { + switch (logLevel) + { + case LogLevel.Trace: +- logger.LogTrace(message,scopeKeys); ++ logger.LogTrace(scopeKeys, message); + break; + case LogLevel.Debug: +- logger.LogDebug(message,scopeKeys); ++ logger.LogDebug(scopeKeys, message); + break; + case LogLevel.Information: +- logger.LogInformation(message,scopeKeys); ++ logger.LogInformation(scopeKeys, message); + break; + case LogLevel.Warning: +- logger.LogWarning(message,scopeKeys); ++ logger.LogWarning(scopeKeys, message); + break; + case LogLevel.Error: +- logger.LogError(message,scopeKeys); ++ logger.LogError(scopeKeys, message); + break; + case LogLevel.Critical: +- logger.LogCritical(message,scopeKeys); ++ logger.LogCritical(scopeKeys, message); + break; + case LogLevel.None: + break; +@@ -1023,7 +1036,7 @@ namespace AWS.Lambda.Powertools.Logging.Tests + } + } + +- systemWrapper.Received(1).WriteLine(Arg.Is(s => ++ systemWrapper.Received(1).LogLine(Arg.Is(s => + s.Contains("PropOne") && + s.Contains("PropTwo") && + s.Contains(scopeKeys.PropOne) && +@@ -1041,21 +1054,22 @@ namespace AWS.Lambda.Powertools.Logging.Tests + var service = Guid.NewGuid().ToString(); + var error = new InvalidOperationException("TestError"); + var logLevel = LogLevel.Information; ++ var randomSampleRate = 0.5; + + var configurations = Substitute.For(); + configurations.Service.Returns(service); + configurations.LogLevel.Returns(logLevel.ToString()); + +- var systemWrapper = Substitute.For(); ++ var systemWrapper = Substitute.For(); ++ systemWrapper.GetRandom().Returns(randomSampleRate); + +- var loggerConfiguration = new PowertoolsLoggerConfiguration ++ var loggerConfiguration = new LoggerConfiguration + { + Service = null, +- MinimumLogLevel = LogLevel.None, +- LogOutput = systemWrapper ++ MinimumLevel = LogLevel.None + }; +- +- var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); ++ ++ var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); + var logger = provider.CreateLogger(loggerName); + + try +@@ -1068,16 +1082,15 @@ namespace AWS.Lambda.Powertools.Logging.Tests + } + + // Assert +- systemWrapper.Received(1).WriteLine(Arg.Is(s => ++ systemWrapper.Received(1).LogLine(Arg.Is(s => + s.Contains("\"exception\":{\"type\":\"" + error.GetType().FullName + "\",\"message\":\"" + + error.Message + "\"") + )); +- systemWrapper.Received(1).WriteLine(Arg.Is(s => +- s.Contains( +- "\"exception\":{\"type\":\"System.InvalidOperationException\",\"message\":\"TestError\",\"source\":\"AWS.Lambda.Powertools.Logging.Tests\",\"stack_trace\":\" at AWS.Lambda.Powertools.Logging.Tests.PowertoolsLoggerTest.Log_WhenException_LogsExceptionDetails()") ++ systemWrapper.Received(1).LogLine(Arg.Is(s => ++ s.Contains("\"exception\":{\"type\":\"System.InvalidOperationException\",\"message\":\"TestError\",\"source\":\"AWS.Lambda.Powertools.Logging.Tests\",\"stack_trace\":\" at AWS.Lambda.Powertools.Logging.Tests.PowertoolsLoggerTest.Log_WhenException_LogsExceptionDetails()") + )); + } +- ++ + [Fact] + public void Log_Inner_Exception() + { +@@ -1087,48 +1100,40 @@ namespace AWS.Lambda.Powertools.Logging.Tests + var error = new InvalidOperationException("Parent exception message", + new ArgumentNullException(nameof(service), "Very important inner exception message")); + var logLevel = LogLevel.Information; ++ var randomSampleRate = 0.5; + + var configurations = Substitute.For(); + configurations.Service.Returns(service); + configurations.LogLevel.Returns(logLevel.ToString()); + +- var systemWrapper = Substitute.For(); ++ var systemWrapper = Substitute.For(); ++ systemWrapper.GetRandom().Returns(randomSampleRate); + +- var loggerConfiguration = new PowertoolsLoggerConfiguration ++ var loggerConfiguration = new LoggerConfiguration + { + Service = null, +- MinimumLogLevel = LogLevel.None, +- LogOutput = systemWrapper ++ MinimumLevel = LogLevel.None + }; +- +- var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); ++ ++ var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); + var logger = provider.CreateLogger(loggerName); + + logger.LogError( +- error, ++ error, + "Something went wrong and we logged an exception itself with an inner exception. This is a param {arg}", + 12345); + + // Assert +- systemWrapper.Received(1).WriteLine(Arg.Is(s => ++ systemWrapper.Received(1).LogLine(Arg.Is(s => + s.Contains("\"exception\":{\"type\":\"" + error.GetType().FullName + "\",\"message\":\"" + + error.Message + "\"") + )); +- +- systemWrapper.Received(1).WriteLine(Arg.Is(s => +- s.Contains("\"level\":\"Error\"") && +- s.Contains("\"service\":\"" + service + "\"") && +- s.Contains("\"name\":\"" + loggerName + "\"") && +- s.Contains("\"message\":\"Something went wrong and we logged an exception itself with an inner exception. This is a param 12345\"") && +- s.Contains("\"exception\":{") && +- s.Contains("\"type\":\"System.InvalidOperationException\"") && +- s.Contains("\"message\":\"Parent exception message\"") && +- s.Contains("\"inner_exception\":{") && +- s.Contains("\"type\":\"System.ArgumentNullException\"") && +- s.Contains("\"message\":\"Very important inner exception message (Parameter 'service')\"") ++ ++ systemWrapper.Received(1).LogLine(Arg.Is(s => ++ s.Contains("\"level\":\"Error\",\"service\":\"" + service+ "\",\"name\":\"" + loggerName + "\",\"message\":\"Something went wrong and we logged an exception itself with an inner exception. This is a param 12345\",\"exception\":{\"type\":\"System.InvalidOperationException\",\"message\":\"Parent exception message\",\"inner_exception\":{\"type\":\"System.ArgumentNullException\",\"message\":\"Very important inner exception message (Parameter 'service')\"}}}") + )); + } +- ++ + [Fact] + public void Log_Nested_Inner_Exception() + { +@@ -1138,43 +1143,35 @@ namespace AWS.Lambda.Powertools.Logging.Tests + var error = new InvalidOperationException("Parent exception message", + new ArgumentNullException(nameof(service), + new Exception("Very important nested inner exception message"))); +- ++ + var logLevel = LogLevel.Information; ++ var randomSampleRate = 0.5; + + var configurations = Substitute.For(); + configurations.Service.Returns(service); + configurations.LogLevel.Returns(logLevel.ToString()); + +- var systemWrapper = Substitute.For(); ++ var systemWrapper = Substitute.For(); ++ systemWrapper.GetRandom().Returns(randomSampleRate); + +- var loggerConfiguration = new PowertoolsLoggerConfiguration ++ var loggerConfiguration = new LoggerConfiguration + { + Service = null, +- MinimumLogLevel = LogLevel.None, +- LogOutput = systemWrapper ++ MinimumLevel = LogLevel.None + }; +- +- var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); ++ ++ var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); + var logger = provider.CreateLogger(loggerName); +- ++ + + logger.LogError( +- error, ++ error, + "Something went wrong and we logged an exception itself with an inner exception. This is a param {arg}", + 12345); + + // Assert +- systemWrapper.Received(1).WriteLine(Arg.Is(s => +- s.Contains("\"message\":\"Something went wrong and we logged an exception itself with an inner exception. This is a param 12345\"") && +- s.Contains("\"exception\":{") && +- s.Contains("\"type\":\"System.InvalidOperationException\"") && +- s.Contains("\"message\":\"Parent exception message\"") && +- s.Contains("\"inner_exception\":{") && +- s.Contains("\"type\":\"System.ArgumentNullException\"") && +- s.Contains("\"message\":\"service\"") && +- s.Contains("\"inner_exception\":{") && +- s.Contains("\"type\":\"System.Exception\"") && +- s.Contains("\"message\":\"Very important nested inner exception message\"") ++ systemWrapper.Received(1).LogLine(Arg.Is(s => ++ s.Contains("\"message\":\"Something went wrong and we logged an exception itself with an inner exception. This is a param 12345\",\"exception\":{\"type\":\"System.InvalidOperationException\",\"message\":\"Parent exception message\",\"inner_exception\":{\"type\":\"System.ArgumentNullException\",\"message\":\"service\",\"inner_exception\":{\"type\":\"System.Exception\",\"message\":\"Very important nested inner exception message\"}}}}") + )); + } + +@@ -1186,21 +1183,22 @@ namespace AWS.Lambda.Powertools.Logging.Tests + var service = Guid.NewGuid().ToString(); + var error = new InvalidOperationException("TestError"); + var logLevel = LogLevel.Information; ++ var randomSampleRate = 0.5; + + var configurations = Substitute.For(); + configurations.Service.Returns(service); + configurations.LogLevel.Returns(logLevel.ToString()); + +- var systemWrapper = Substitute.For(); ++ var systemWrapper = Substitute.For(); ++ systemWrapper.GetRandom().Returns(randomSampleRate); + +- var loggerConfiguration = new PowertoolsLoggerConfiguration ++ var loggerConfiguration = new LoggerConfiguration + { + Service = null, +- MinimumLogLevel = LogLevel.None, +- LogOutput = systemWrapper ++ MinimumLevel = LogLevel.None + }; +- +- var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); ++ ++ var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); + var logger = provider.CreateLogger(loggerName); + + try +@@ -1213,14 +1211,14 @@ namespace AWS.Lambda.Powertools.Logging.Tests + } + + // Assert +- systemWrapper.Received(1).WriteLine(Arg.Is(s => ++ systemWrapper.Received(1).LogLine(Arg.Is(s => + s.Contains("\"error\":{\"type\":\"" + error.GetType().FullName + "\",\"message\":\"" + error.Message + + "\"") + )); + } + + [Fact] +- public void Log_WhenByteArray_LogsBase64EncodedString() ++ public void Log_WhenByteArray_LogsByteArrayNumbers() + { + // Arrange + var loggerName = Guid.NewGuid().ToString(); +@@ -1228,29 +1226,29 @@ namespace AWS.Lambda.Powertools.Logging.Tests + var bytes = new byte[10]; + new Random().NextBytes(bytes); + var logLevel = LogLevel.Information; ++ var randomSampleRate = 0.5; + + var configurations = Substitute.For(); + configurations.Service.Returns(service); + configurations.LogLevel.Returns(logLevel.ToString()); + +- var systemWrapper = Substitute.For(); ++ var systemWrapper = Substitute.For(); ++ systemWrapper.GetRandom().Returns(randomSampleRate); + +- var loggerConfiguration = new PowertoolsLoggerConfiguration ++ var loggerConfiguration = new LoggerConfiguration + { + Service = null, +- MinimumLogLevel = LogLevel.None, +- LogOutput = systemWrapper ++ MinimumLevel = LogLevel.None + }; +- var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); ++ var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); + var logger = provider.CreateLogger(loggerName); + + // Act + logger.LogInformation(new { Name = "Test Object", Bytes = bytes }); + + // Assert +- var base64String = Convert.ToBase64String(bytes); +- systemWrapper.Received(1).WriteLine(Arg.Is(s => +- s.Contains($"\"bytes\":\"{base64String}\"") ++ systemWrapper.Received(1).LogLine(Arg.Is(s => ++ s.Contains("\"bytes\":[" + string.Join(",", bytes) + "]") + )); + } + +@@ -1267,28 +1265,29 @@ namespace AWS.Lambda.Powertools.Logging.Tests + Position = 0 + }; + var logLevel = LogLevel.Information; ++ var randomSampleRate = 0.5; + + var configurations = Substitute.For(); + configurations.Service.Returns(service); + configurations.LogLevel.Returns(logLevel.ToString()); + +- var systemWrapper = Substitute.For(); ++ var systemWrapper = Substitute.For(); ++ systemWrapper.GetRandom().Returns(randomSampleRate); + +- var loggerConfiguration = new PowertoolsLoggerConfiguration ++ var loggerConfiguration = new LoggerConfiguration + { + Service = null, +- MinimumLogLevel = LogLevel.None, +- LogOutput = systemWrapper ++ MinimumLevel = LogLevel.None + }; +- +- var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); ++ ++ var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); + var logger = provider.CreateLogger(loggerName); + + // Act + logger.LogInformation(new { Name = "Test Object", Stream = memoryStream }); + + // Assert +- systemWrapper.Received(1).WriteLine(Arg.Is(s => ++ systemWrapper.Received(1).LogLine(Arg.Is(s => + s.Contains("\"stream\":\"" + Convert.ToBase64String(bytes) + "\"") + )); + } +@@ -1308,28 +1307,29 @@ namespace AWS.Lambda.Powertools.Logging.Tests + Position = 0 + }; + var logLevel = LogLevel.Information; ++ var randomSampleRate = 0.5; + + var configurations = Substitute.For(); + configurations.Service.Returns(service); + configurations.LogLevel.Returns(logLevel.ToString()); + +- var systemWrapper = Substitute.For(); ++ var systemWrapper = Substitute.For(); ++ systemWrapper.GetRandom().Returns(randomSampleRate); + +- var loggerConfiguration = new PowertoolsLoggerConfiguration ++ var loggerConfiguration = new LoggerConfiguration + { + Service = null, +- MinimumLogLevel = LogLevel.None, +- LogOutput = systemWrapper ++ MinimumLevel = LogLevel.None + }; +- +- var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); ++ ++ var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); + var logger = provider.CreateLogger(loggerName); + + // Act + logger.LogInformation(new { Name = "Test Object", Stream = memoryStream }); + + // Assert +- systemWrapper.Received(1).WriteLine(Arg.Is(s => ++ systemWrapper.Received(1).LogLine(Arg.Is(s => + s.Contains("\"stream\":\"" + Convert.ToBase64String(bytes) + "\"") + )); + } +@@ -1337,55 +1337,35 @@ namespace AWS.Lambda.Powertools.Logging.Tests + [Fact] + public void Log_Set_Execution_Environment_Context() + { ++ var _originalValue = Environment.GetEnvironmentVariable("POWERTOOLS_SERVICE_NAME"); ++ + // Arrange + var loggerName = Guid.NewGuid().ToString(); ++ var assemblyName = "AWS.Lambda.Powertools.Logger"; ++ var assemblyVersion = "1.0.0"; + +- var env = new PowertoolsEnvironment(); +- // Act +- var configurations = new PowertoolsConfigurations(env); +- +- var loggerConfiguration = new PowertoolsLoggerConfiguration +- { +- Service = null, +- MinimumLogLevel = LogLevel.None +- }; +- +- var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); +- var logger = provider.CreateLogger(loggerName); +- logger.LogInformation("Test"); +- +- // Assert +- Assert.Contains($"{Constants.FeatureContextIdentifier}/Logging/", +- env.GetEnvironmentVariable("AWS_EXECUTION_ENV")); +- } +- +- [Fact] +- public void Log_Skip_If_Exists_Execution_Environment_Context() +- { +- // Arrange +- var loggerName = Guid.NewGuid().ToString(); +- +- var env = new PowertoolsEnvironment(); +- env.SetEnvironmentVariable("AWS_EXECUTION_ENV", +- $"{Constants.FeatureContextIdentifier}/Logging/AlreadyThere"); ++ var env = Substitute.For(); ++ env.GetAssemblyName(Arg.Any()).Returns(assemblyName); ++ env.GetAssemblyVersion(Arg.Any()).Returns(assemblyVersion); + + // Act +- var configurations = new PowertoolsConfigurations(env); ++ var systemWrapper = new SystemWrapper(env); ++ var configurations = new PowertoolsConfigurations(systemWrapper); + +- var loggerConfiguration = new PowertoolsLoggerConfiguration ++ var loggerConfiguration = new LoggerConfiguration + { + Service = null, +- MinimumLogLevel = LogLevel.None ++ MinimumLevel = LogLevel.None + }; +- +- var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); ++ ++ var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); + var logger = provider.CreateLogger(loggerName); + logger.LogInformation("Test"); + + // Assert +- Assert.Equal($"{Constants.FeatureContextIdentifier}/Logging/AlreadyThere", +- env.GetEnvironmentVariable("AWS_EXECUTION_ENV")); +- env.SetEnvironmentVariable("AWS_EXECUTION_ENV", null); ++ env.Received(1).SetEnvironmentVariable("AWS_EXECUTION_ENV", ++ $"{Constants.FeatureContextIdentifier}/Logger/{assemblyVersion}"); ++ env.Received(1).GetEnvironmentVariable("AWS_EXECUTION_ENV"); + } + + [Fact] +@@ -1395,22 +1375,23 @@ namespace AWS.Lambda.Powertools.Logging.Tests + var loggerName = Guid.NewGuid().ToString(); + var service = Guid.NewGuid().ToString(); + var logLevel = LogLevel.Information; ++ var randomSampleRate = 0.5; + + var configurations = Substitute.For(); + configurations.Service.Returns(service); + configurations.LogLevel.Returns(logLevel.ToString()); + +- var systemWrapper = Substitute.For(); ++ var systemWrapper = Substitute.For(); ++ systemWrapper.GetRandom().Returns(randomSampleRate); + +- var loggerConfiguration = new PowertoolsLoggerConfiguration ++ var loggerConfiguration = new LoggerConfiguration + { + Service = null, +- MinimumLogLevel = LogLevel.None, +- LoggerOutputCase = LoggerOutputCase.CamelCase, +- LogOutput = systemWrapper ++ MinimumLevel = LogLevel.None, ++ LoggerOutputCase = LoggerOutputCase.CamelCase + }; +- +- var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); ++ ++ var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); + var logger = provider.CreateLogger(loggerName); + + var message = new +@@ -1427,10 +1408,9 @@ namespace AWS.Lambda.Powertools.Logging.Tests + logger.LogInformation(message); + + // Assert +- systemWrapper.Received(1).WriteLine( ++ systemWrapper.Received(1).LogLine( + Arg.Is(s => +- s.Contains( +- "\"message\":{\"propOne\":\"Value 1\",\"propTwo\":\"Value 2\",\"propThree\":{\"propFour\":1},\"date\":\"2022-01-01\"}") ++ s.Contains("\"message\":{\"propOne\":\"Value 1\",\"propTwo\":\"Value 2\",\"propThree\":{\"propFour\":1},\"date\":\"2022-01-01\"}}") + ) + ); + } +@@ -1448,17 +1428,17 @@ namespace AWS.Lambda.Powertools.Logging.Tests + configurations.Service.Returns(service); + configurations.LogLevel.Returns(logLevel.ToString()); + +- var systemWrapper = Substitute.For(); ++ var systemWrapper = Substitute.For(); ++ systemWrapper.GetRandom().Returns(randomSampleRate); + +- var loggerConfiguration = new PowertoolsLoggerConfiguration ++ var loggerConfiguration = new LoggerConfiguration + { + Service = null, +- MinimumLogLevel = LogLevel.None, +- LoggerOutputCase = LoggerOutputCase.CamelCase, +- LogOutput = systemWrapper ++ MinimumLevel = LogLevel.None, ++ LoggerOutputCase = LoggerOutputCase.CamelCase + }; +- +- var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); ++ ++ var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); + var logger = provider.CreateLogger(loggerName); + + var message = new +@@ -1471,21 +1451,20 @@ namespace AWS.Lambda.Powertools.Logging.Tests + logger.LogInformation(message); + + // Assert +- systemWrapper.Received(1).WriteLine( ++ systemWrapper.Received(1).LogLine( + Arg.Is(s => + s.Contains("\"message\":{\"propOne\":\"Value 1\",\"propTwo\":\"Value 2\",\"time\":\"12:00:00\"}") + ) + ); + } + +- ++ + [Theory] +- [InlineData("WARN", LogLevel.Warning)] +- [InlineData("Fatal", LogLevel.Critical)] +- [InlineData("NotValid", LogLevel.Critical)] +- [InlineData("NotValid", LogLevel.Warning)] +- public void Log_Should_Use_Powertools_Log_Level_When_Lambda_Log_Level_Enabled(string awsLogLevel, +- LogLevel logLevel) ++ [InlineData(true, "WARN", LogLevel.Warning)] ++ [InlineData(false, "Fatal", LogLevel.Critical)] ++ [InlineData(false, "NotValid", LogLevel.Critical)] ++ [InlineData(true, "NotValid", LogLevel.Warning)] ++ public void Log_Should_Use_Powertools_Log_Level_When_Lambda_Log_Level_Enabled(bool willLog, string awsLogLevel, LogLevel logLevel) + { + // Arrange + var loggerName = Guid.NewGuid().ToString(); +@@ -1495,14 +1474,15 @@ namespace AWS.Lambda.Powertools.Logging.Tests + environment.GetEnvironmentVariable("POWERTOOLS_LOG_LEVEL").Returns(logLevel.ToString()); + environment.GetEnvironmentVariable("AWS_LAMBDA_LOG_LEVEL").Returns(awsLogLevel); + +- var configurations = new PowertoolsConfigurations(environment); ++ var systemWrapper = new SystemWrapperMock(environment); ++ var configurations = new PowertoolsConfigurations(systemWrapper); + +- var loggerConfiguration = new PowertoolsLoggerConfiguration ++ var loggerConfiguration = new LoggerConfiguration + { + LoggerOutputCase = LoggerOutputCase.CamelCase + }; +- +- var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); ++ ++ var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); + var logger = provider.CreateLogger(loggerName); + + var message = new +@@ -1514,17 +1494,17 @@ namespace AWS.Lambda.Powertools.Logging.Tests + + // Act + logger.LogWarning(message); +- ++ + // Assert + Assert.True(logger.IsEnabled(logLevel)); + Assert.Equal(logLevel, configurations.GetLogLevel()); ++ Assert.Equal(willLog, systemWrapper.LogMethodCalled); + } +- ++ + [Theory] +- [InlineData("WARN", LogLevel.Warning)] +- [InlineData("Fatal", LogLevel.Critical)] +- public void Log_Should_Use_AWS_Lambda_Log_Level_When_Enabled(string awsLogLevel, +- LogLevel logLevel) ++ [InlineData(true, "WARN", LogLevel.Warning)] ++ [InlineData(true, "Fatal", LogLevel.Critical)] ++ public void Log_Should_Use_AWS_Lambda_Log_Level_When_Enabled(bool willLog, string awsLogLevel, LogLevel logLevel) + { + // Arrange + var loggerName = Guid.NewGuid().ToString(); +@@ -1534,14 +1514,15 @@ namespace AWS.Lambda.Powertools.Logging.Tests + environment.GetEnvironmentVariable("POWERTOOLS_LOG_LEVEL").Returns(string.Empty); + environment.GetEnvironmentVariable("AWS_LAMBDA_LOG_LEVEL").Returns(awsLogLevel); + +- var configurations = new PowertoolsConfigurations(environment); ++ var systemWrapper = new SystemWrapperMock(environment); ++ var configurations = new PowertoolsConfigurations(systemWrapper); + +- var loggerConfiguration = new PowertoolsLoggerConfiguration ++ var loggerConfiguration = new LoggerConfiguration + { + LoggerOutputCase = LoggerOutputCase.CamelCase, + }; +- +- var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); ++ ++ var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); + var logger = provider.CreateLogger(loggerName); + + var message = new +@@ -1553,13 +1534,14 @@ namespace AWS.Lambda.Powertools.Logging.Tests + + // Act + logger.LogWarning(message); +- ++ + // Assert + Assert.True(logger.IsEnabled(logLevel)); + Assert.Equal(LogLevel.Information, configurations.GetLogLevel()); //default + Assert.Equal(logLevel, configurations.GetLambdaLogLevel()); ++ Assert.Equal(willLog, systemWrapper.LogMethodCalled); + } +- ++ + [Fact] + public void Log_Should_Show_Warning_When_AWS_Lambda_Log_Level_Enabled() + { +@@ -1570,53 +1552,49 @@ namespace AWS.Lambda.Powertools.Logging.Tests + environment.GetEnvironmentVariable("POWERTOOLS_LOG_LEVEL").Returns("Debug"); + environment.GetEnvironmentVariable("AWS_LAMBDA_LOG_LEVEL").Returns("Warn"); + +- var systemWrapper = new TestLoggerOutput(); +- var configurations = new PowertoolsConfigurations(environment); ++ var systemWrapper = new SystemWrapperMock(environment); ++ var configurations = new PowertoolsConfigurations(systemWrapper); + +- var loggerConfiguration = new PowertoolsLoggerConfiguration ++ var loggerConfiguration = new LoggerConfiguration + { +- LoggerOutputCase = LoggerOutputCase.CamelCase, +- LogOutput = systemWrapper ++ LoggerOutputCase = LoggerOutputCase.CamelCase + }; +- +- var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); ++ ++ var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); + var logger = provider.CreateLogger(loggerName); + + var logLevel = configurations.GetLogLevel(); + var lambdaLogLevel = configurations.GetLambdaLogLevel(); +- ++ + // Assert + Assert.True(logger.IsEnabled(LogLevel.Warning)); + Assert.Equal(LogLevel.Debug, logLevel); + Assert.Equal(LogLevel.Warning, lambdaLogLevel); + +- Assert.Contains( +- $"Current log level ({logLevel}) does not match AWS Lambda Advanced Logging Controls minimum log level ({lambdaLogLevel}). This can lead to data loss, consider adjusting them.", +- systemWrapper.ToString()); ++ Assert.Contains($"Current log level ({logLevel}) does not match AWS Lambda Advanced Logging Controls minimum log level ({lambdaLogLevel}). This can lead to data loss, consider adjusting them.", ++ systemWrapper.LogMethodCalledWithArgument); + } +- ++ + [Theory] +- [InlineData(true, "LogLevel")] +- [InlineData(false, "Level")] +- public void Log_PascalCase_Outputs_Correct_Level_Property_When_AWS_Lambda_Log_Level_Enabled_Or_Disabled( +- bool alcEnabled, string levelProp) ++ [InlineData(true,"LogLevel")] ++ [InlineData(false,"Level")] ++ public void Log_PascalCase_Outputs_Correct_Level_Property_When_AWS_Lambda_Log_Level_Enabled_Or_Disabled(bool alcEnabled, string levelProp) + { + // Arrange + var loggerName = Guid.NewGuid().ToString(); +- ++ + var environment = Substitute.For(); + environment.GetEnvironmentVariable("POWERTOOLS_LOG_LEVEL").Returns("Information"); +- if (alcEnabled) ++ if(alcEnabled) + environment.GetEnvironmentVariable("AWS_LAMBDA_LOG_LEVEL").Returns("Info"); + +- var systemWrapper = new TestLoggerOutput(); +- var configurations = new PowertoolsConfigurations(environment); +- var loggerConfiguration = new PowertoolsLoggerConfiguration ++ var systemWrapper = new SystemWrapperMock(environment); ++ var configurations = new PowertoolsConfigurations(systemWrapper); ++ var loggerConfiguration = new LoggerConfiguration + { +- LoggerOutputCase = LoggerOutputCase.PascalCase, +- LogOutput = systemWrapper ++ LoggerOutputCase = LoggerOutputCase.PascalCase + }; +- var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); ++ var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); + var logger = provider.CreateLogger(loggerName); + + var message = new +@@ -1627,9 +1605,10 @@ namespace AWS.Lambda.Powertools.Logging.Tests + logger.LogInformation(message); + + // Assert +- Assert.Contains($"\"{levelProp}\":\"Information\"", systemWrapper.ToString()); ++ Assert.True(systemWrapper.LogMethodCalled); ++ Assert.Contains($"\"{levelProp}\":\"Information\"",systemWrapper.LogMethodCalledWithArgument); + } +- ++ + [Theory] + [InlineData(LoggerOutputCase.CamelCase)] + [InlineData(LoggerOutputCase.SnakeCase)] +@@ -1637,22 +1616,21 @@ namespace AWS.Lambda.Powertools.Logging.Tests + { + // Arrange + var loggerName = Guid.NewGuid().ToString(); +- ++ + var environment = Substitute.For(); + environment.GetEnvironmentVariable("POWERTOOLS_LOG_LEVEL").Returns(string.Empty); + environment.GetEnvironmentVariable("AWS_LAMBDA_LOG_LEVEL").Returns("Info"); + +- var systemWrapper = new TestLoggerOutput(); +- var configurations = new PowertoolsConfigurations(environment); ++ var systemWrapper = new SystemWrapperMock(environment); ++ var configurations = new PowertoolsConfigurations(systemWrapper); + configurations.LoggerOutputCase.Returns(casing.ToString()); +- +- var loggerConfiguration = new PowertoolsLoggerConfiguration ++ ++ var loggerConfiguration = new LoggerConfiguration + { +- LoggerOutputCase = casing, +- LogOutput = systemWrapper ++ LoggerOutputCase = casing + }; +- +- var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); ++ ++ var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); + var logger = provider.CreateLogger(loggerName); + + var message = new +@@ -1663,9 +1641,10 @@ namespace AWS.Lambda.Powertools.Logging.Tests + logger.LogInformation(message); + + // Assert +- Assert.Contains("\"level\":\"Information\"", systemWrapper.ToString()); ++ Assert.True(systemWrapper.LogMethodCalled); ++ Assert.Contains("\"level\":\"Information\"",systemWrapper.LogMethodCalledWithArgument); + } +- ++ + [Theory] + [InlineData("TRACE", LogLevel.Trace)] + [InlineData("debug", LogLevel.Debug)] +@@ -1680,11 +1659,12 @@ namespace AWS.Lambda.Powertools.Logging.Tests + var environment = Substitute.For(); + environment.GetEnvironmentVariable("AWS_LAMBDA_LOG_LEVEL").Returns(awsLogLevel); + +- var configuration = new PowertoolsConfigurations(environment); ++ var systemWrapper = new SystemWrapperMock(environment); ++ var configuration = new PowertoolsConfigurations(systemWrapper); + + // Act + var logLvl = configuration.GetLambdaLogLevel(); +- ++ + // Assert + Assert.Equal(logLevel, logLvl); + } +@@ -1699,14 +1679,15 @@ namespace AWS.Lambda.Powertools.Logging.Tests + var environment = Substitute.For(); + environment.GetEnvironmentVariable("POWERTOOLS_LOG_LEVEL").Returns(logLevel.ToString()); + +- var configurations = new PowertoolsConfigurations(environment); ++ var systemWrapper = new SystemWrapperMock(environment); ++ var configurations = new PowertoolsConfigurations(systemWrapper); + +- var loggerConfiguration = new PowertoolsLoggerConfiguration ++ var loggerConfiguration = new LoggerConfiguration + { + LoggerOutputCase = LoggerOutputCase.CamelCase + }; +- +- var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); ++ ++ var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); + var logger = provider.CreateLogger(loggerName); + + var message = new +@@ -1722,185 +1703,13 @@ namespace AWS.Lambda.Powertools.Logging.Tests + // Assert + Assert.True(logger.IsEnabled(logLevel)); + Assert.Equal(logLevel.ToString(), configurations.LogLevel); +- } +- +- [Theory] +- [InlineData(true, "on-demand")] +- [InlineData(false, "provisioned-concurrency")] +- public void Log_Cold_Start(bool willLog, string awsInitType) +- { +- // Arrange +- var logOutput = new TestLoggerOutput(); +- Environment.SetEnvironmentVariable("AWS_LAMBDA_INITIALIZATION_TYPE", awsInitType); +- var configurations = new PowertoolsConfigurations(new PowertoolsEnvironment()); +- +- var loggerConfiguration = new PowertoolsLoggerConfiguration +- { +- LoggerOutputCase = LoggerOutputCase.CamelCase, +- LogOutput = logOutput +- }; +- +- var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); +- var logger = provider.CreateLogger("temp"); +- +- // Act +- logger.LogInformation("Hello"); +- +- var outPut = logOutput.ToString(); +- // Assert +- Assert.Contains($"\"coldStart\":{willLog.ToString().ToLower()}", outPut); +- } +- +- [Fact] +- public void Log_WhenDuplicateKeysInState_LastValueWins() +- { +- // Arrange +- var loggerName = Guid.NewGuid().ToString(); +- var service = Guid.NewGuid().ToString(); +- var logLevel = LogLevel.Information; +- +- var configurations = Substitute.For(); +- configurations.Service.Returns(service); +- configurations.LogLevel.Returns(logLevel.ToString()); +- configurations.LoggerOutputCase.Returns(LoggerOutputCase.PascalCase.ToString()); +- +- var systemWrapper = Substitute.For(); +- +- var loggerConfiguration = new PowertoolsLoggerConfiguration +- { +- Service = service, +- MinimumLogLevel = logLevel, +- LoggerOutputCase = LoggerOutputCase.PascalCase, +- LogOutput = systemWrapper +- }; +- +- var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); +- var logger = provider.CreateLogger(loggerName); +- +- // Create state with duplicate keys (simulating duplicate HTTP headers) +- var stateWithDuplicates = new List> +- { +- new("Content-Type", "application/json"), +- new("Content-Type", "application/x-www-form-urlencoded"), // This should win +- new("Accept", "text/html"), +- new("Accept", "*/*") // This should win +- }; +- +- // Act - This should not throw an exception +- logger.Log(logLevel, new EventId(), stateWithDuplicates, null, (state, ex) => "Test message"); +- +- // Assert +- systemWrapper.Received(1).WriteLine(Arg.Any()); +- } +- +- [Fact] +- public void GetSafeRandom_ShouldReturnValueBetweenZeroAndOne() +- { +- // Act & Assert - Test multiple times to ensure consistency +- for (int i = 0; i < 1000; i++) +- { +- var randomValue = PowertoolsLoggerConfiguration.GetSafeRandom(); +- +- Assert.True(randomValue >= 0.0, $"Random value {randomValue} should be >= 0.0"); +- Assert.True(randomValue <= 1.0, $"Random value {randomValue} should be <= 1.0"); +- } +- } +- +- [Fact] +- public void GetSafeRandom_ShouldReturnDifferentValues() +- { +- // Arrange +- var values = new HashSet(); +- +- // Act - Generate multiple random values +- for (int i = 0; i < 100; i++) +- { +- values.Add(PowertoolsLoggerConfiguration.GetSafeRandom()); +- } +- +- // Assert - Should have generated multiple different values +- Assert.True(values.Count > 50, "Should generate diverse random values"); +- } +- +- [Fact] +- public void Log_SamplingWithRealRandomGenerator_ShouldWorkCorrectly() +- { +- // Arrange +- var service = Guid.NewGuid().ToString(); +- var logLevel = LogLevel.Error; // Set high log level +- var loggerSampleRate = 1.0; // 100% sampling rate to ensure activation +- +- var configurations = Substitute.For(); +- configurations.Service.Returns(service); +- configurations.LogLevel.Returns(logLevel.ToString()); +- configurations.LoggerSampleRate.Returns(loggerSampleRate); +- +- var systemWrapper = Substitute.For(); +- +- var loggerConfiguration = new PowertoolsLoggerConfiguration +- { +- Service = service, +- MinimumLogLevel = logLevel, +- LogOutput = systemWrapper, +- SamplingRate = loggerSampleRate +- }; +- +- // Act +- var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); +- var logger = provider.CreateLogger("test"); +- +- // First call - skipped due to cold start protection +- logger.LogError("Test1"); +- +- // Second call - should trigger sampling with 100% rate +- logger.LogError("Test2"); +- +- // Assert - With 100% sampling rate, should always activate sampling +- systemWrapper.Received(1).WriteLine( +- Arg.Is(s => +- s.Contains("Changed log level to DEBUG based on Sampling configuration") && +- s.Contains($"Sampling Rate: {loggerSampleRate}") +- ) +- ); +- } +- +- [Fact] +- public void Log_SamplingWithZeroRate_ShouldNeverActivate() +- { +- // Arrange +- var service = Guid.NewGuid().ToString(); +- var logLevel = LogLevel.Error; +- var loggerSampleRate = 0.0; // 0% sampling rate +- +- var configurations = Substitute.For(); +- configurations.Service.Returns(service); +- configurations.LogLevel.Returns(logLevel.ToString()); +- configurations.LoggerSampleRate.Returns(loggerSampleRate); +- +- var systemWrapper = Substitute.For(); +- +- var loggerConfiguration = new PowertoolsLoggerConfiguration +- { +- Service = service, +- MinimumLogLevel = logLevel, +- LogOutput = systemWrapper, +- SamplingRate = loggerSampleRate +- }; +- +- // Act +- var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); +- var logger = provider.CreateLogger("test"); +- +- // Assert - With 0% sampling rate, should never activate sampling +- systemWrapper.DidNotReceive().WriteLine( +- Arg.Is(s => s.Contains("Changed log level to DEBUG based on Sampling configuration")) +- ); ++ Assert.Equal(willLog, systemWrapper.LogMethodCalled); + } + + public void Dispose() + { +- // Environment.SetEnvironmentVariable("AWS_LAMBDA_INITIALIZATION_TYPE", null); +- LambdaLifecycleTracker.Reset(); ++ PowertoolsLoggingSerializer.ClearOptions(); ++ LoggingAspect.ResetForTest(); + } + } +-} ++} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Sampling/EnvironmentVariableSamplingTests.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Sampling/EnvironmentVariableSamplingTests.cs +deleted file mode 100644 +index 03d191ec..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Sampling/EnvironmentVariableSamplingTests.cs ++++ /dev/null +@@ -1,260 +0,0 @@ +-using System; +-using AWS.Lambda.Powertools.Common; +-using AWS.Lambda.Powertools.Common.Tests; +-using Microsoft.Extensions.DependencyInjection; +-using Microsoft.Extensions.Logging; +-using Xunit; +-using LogLevel = Microsoft.Extensions.Logging.LogLevel; +- +-namespace AWS.Lambda.Powertools.Logging.Tests; +- +-/// +-/// Tests for sampling behavior when using environment variables +-/// This covers the specific use case described in the GitHub issue +-/// +-public class EnvironmentVariableSamplingTests : IDisposable +-{ +- private readonly string _originalLogLevel; +- private readonly string _originalSampleRate; +- +- public EnvironmentVariableSamplingTests() +- { +- // Store original environment variables +- _originalLogLevel = Environment.GetEnvironmentVariable("POWERTOOLS_LOG_LEVEL"); +- _originalSampleRate = Environment.GetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE"); +- +- // Reset logger before each test +- Logger.Reset(); +- } +- +- public void Dispose() +- { +- // Restore original environment variables +- if (_originalLogLevel != null) +- Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", _originalLogLevel); +- else +- Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", null); +- +- if (_originalSampleRate != null) +- Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE", _originalSampleRate); +- else +- Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE", null); +- +- Logger.Reset(); +- } +- +- /// +- /// Creates a logger factory that properly processes environment variables +- /// +- private ILoggerFactory CreateLoggerFactoryWithEnvironmentVariables(TestLoggerOutput output) +- { +- var services = new ServiceCollection(); +- +- services.AddLogging(builder => +- { +- builder.AddPowertoolsLogger(config => +- { +- config.Service = "HelloWorldService"; +- config.LoggerOutputCase = LoggerOutputCase.CamelCase; +- config.LogEvent = true; +- config.LogOutput = output; +- }); +- }); +- +- var serviceProvider = services.BuildServiceProvider(); +- return serviceProvider.GetRequiredService(); +- } +- +- /// +- /// Test the exact scenario described in the GitHub issue: +- /// POWERTOOLS_LOG_LEVEL=Error and POWERTOOLS_LOGGER_SAMPLE_RATE=0.9 +- /// Information logs should be elevated to debug and logged when sampling is triggered +- /// +- [Fact] +- public void EnvironmentVariables_ErrorLevelWithSampling_ShouldLogInfoWhenSamplingTriggered() +- { +- // Arrange +- var originalLogLevel = Environment.GetEnvironmentVariable("POWERTOOLS_LOG_LEVEL"); +- var originalSampleRate = Environment.GetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE"); +- +- try +- { +- Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", "Error"); +- Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE", "0.9"); +- +- var output = new TestLoggerOutput(); +- bool samplingTriggered = false; +- string logOutput = ""; +- +- // Try multiple times to trigger sampling (90% chance each time) +- for (int attempt = 0; attempt < 20 && !samplingTriggered; attempt++) +- { +- output.Clear(); +- Logger.Reset(); +- Logger.Configure(options => { options.LogOutput = output; }); +- +- Logger.LogError("This is an error message"); +- Logger.LogInformation("Another info message"); +- +- logOutput = output.ToString(); +- samplingTriggered = logOutput.Contains("Another info message"); +- } +- +- // Assert +- Assert.True(samplingTriggered, +- $"Sampling should have been triggered within 20 attempts with 90% rate. " + +- $"Last output: {logOutput}"); +- +- // Only verify the content if sampling was triggered +- if (samplingTriggered) +- { +- Assert.Contains("This is an error message", logOutput); +- Assert.Contains("Another info message", logOutput); +- } +- } +- finally +- { +- // Cleanup +- Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", originalLogLevel); +- Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE", originalSampleRate); +- Logger.Reset(); +- } +- } +- +- +- /// +- /// Test with POWERTOOLS_LOGGER_SAMPLE_RATE=1.0 (100% sampling) +- /// This should always trigger sampling - guarantees the fix works +- /// +- [Fact] +- public void EnvironmentVariables_ErrorLevelWithFullSampling_ShouldAlwaysLogInfo() +- { +- // Arrange +- Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", "Error"); +- Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE", "1.0"); +- +- var output = new TestLoggerOutput(); +- +- using var loggerFactory = CreateLoggerFactoryWithEnvironmentVariables(output); +- var logger = loggerFactory.CreateLogger(); +- +- // Act +- logger.LogError("This is an error message"); +- logger.LogInformation("This is an info message — should appear with 100% sampling"); +- +- // Assert +- var logOutput = output.ToString(); +- Assert.Contains("Changed log level to DEBUG based on Sampling configuration", logOutput); +- Assert.Contains("This is an error message", logOutput); +- Assert.Contains("This is an info message — should appear with 100% sampling", logOutput); +- } +- +- /// +- /// Test with POWERTOOLS_LOGGER_SAMPLE_RATE=0 (no sampling) +- /// Info messages should not be logged - ensures sampling is required +- /// +- [Fact] +- public void EnvironmentVariables_ErrorLevelWithNoSampling_ShouldNotLogInfo() +- { +- // Arrange +- Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", "Error"); +- Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE", "0"); +- +- var output = new TestLoggerOutput(); +- +- using var loggerFactory = CreateLoggerFactoryWithEnvironmentVariables(output); +- var logger = loggerFactory.CreateLogger(); +- +- // Act +- logger.LogError("This is an error message"); +- logger.LogInformation("This is an info message — should NOT appear with 0% sampling"); +- +- // Assert +- var logOutput = output.ToString(); +- Assert.DoesNotContain("Changed log level to DEBUG based on Sampling configuration", logOutput); +- Assert.Contains("This is an error message", logOutput); +- Assert.DoesNotContain("This is an info message — should NOT appear with 0% sampling", logOutput); +- } +- +- /// +- /// Test the ShouldEnableDebugSampling() method without out parameter +- /// +- [Fact] +- public void ShouldEnableDebugSampling_WithoutOutParameter_ShouldReturnCorrectValue() +- { +- // Arrange +- var config = new PowertoolsLoggerConfiguration +- { +- SamplingRate = 1.0 // 100% sampling +- }; +- +- // Act +- var result = config.ShouldEnableDebugSampling(); +- +- // Assert +- Assert.True(result); +- } +- +- /// +- /// Test the ShouldEnableDebugSampling() method with zero sampling rate +- /// +- [Fact] +- public void ShouldEnableDebugSampling_WithZeroSamplingRate_ShouldReturnFalse() +- { +- // Arrange +- var config = new PowertoolsLoggerConfiguration +- { +- SamplingRate = 0.0 // 0% sampling +- }; +- +- // Act +- var result = config.ShouldEnableDebugSampling(); +- +- // Assert +- Assert.False(result); +- } +- +- /// +- /// Test the RefreshSampleRateCalculation() method without out parameter +- /// +- [Fact] +- public void RefreshSampleRateCalculation_WithoutOutParameter_ShouldReturnCorrectValue() +- { +- // Arrange +- var config = new PowertoolsLoggerConfiguration +- { +- SamplingRate = 1.0, // 100% sampling +- InitialLogLevel = LogLevel.Error, +- MinimumLogLevel = LogLevel.Error +- }; +- +- // Act - First call should return false due to cold start protection +- var firstResult = config.RefreshSampleRateCalculation(); +- +- // Second call should return true with 100% sampling +- var secondResult = config.RefreshSampleRateCalculation(); +- +- // Assert +- Assert.False(firstResult); // Cold start protection +- Assert.True(secondResult); // Should enable sampling +- } +- +- /// +- /// Test the RefreshSampleRateCalculation() method with zero sampling rate +- /// +- [Fact] +- public void RefreshSampleRateCalculation_WithZeroSamplingRate_ShouldReturnFalse() +- { +- // Arrange +- var config = new PowertoolsLoggerConfiguration +- { +- SamplingRate = 0.0 // 0% sampling +- }; +- +- // Act +- var result = config.RefreshSampleRateCalculation(); +- +- // Assert +- Assert.False(result); +- } +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Sampling/SamplingTests.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Sampling/SamplingTests.cs +deleted file mode 100644 +index 671fab4c..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Sampling/SamplingTests.cs ++++ /dev/null +@@ -1,373 +0,0 @@ +-using System; +-using System.Collections.Generic; +-using System.Linq; +-using AWS.Lambda.Powertools.Common.Tests; +-using Xunit; +-using LogLevel = Microsoft.Extensions.Logging.LogLevel; +- +-namespace AWS.Lambda.Powertools.Logging.Tests.Sampling; +- +-public class SamplingTests : IDisposable +-{ +- private readonly string _originalLogLevel; +- private readonly string _originalSampleRate; +- +- public SamplingTests() +- { +- // Store original environment variables +- _originalLogLevel = Environment.GetEnvironmentVariable("POWERTOOLS_LOG_LEVEL"); +- _originalSampleRate = Environment.GetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE"); +- +- // Reset logger before each test +- Logger.Reset(); +- } +- +- public void Dispose() +- { +- // Restore original environment variables +- if (_originalLogLevel != null) +- Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", _originalLogLevel); +- else +- Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", null); +- +- if (_originalSampleRate != null) +- Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE", _originalSampleRate); +- else +- Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE", null); +- +- Logger.Reset(); +- } +- +- [Fact] +- public void SamplingRate_WhenConfigured_ShouldEnableDebugSampling() +- { +- // Arrange +- var output = new TestLoggerOutput(); +- var config = new PowertoolsLoggerConfiguration +- { +- SamplingRate = 1.0, // 100% sampling rate +- LogOutput = output +- }; +- +- // Act +- var result = config.ShouldEnableDebugSampling(); +- +- // Assert +- Assert.True(result); +- } +- +- [Fact] +- public void SamplingRate_WhenZero_ShouldNotEnableDebugSampling() +- { +- // Arrange +- var config = new PowertoolsLoggerConfiguration +- { +- SamplingRate = 0.0 // 0% sampling rate +- }; +- +- // Act +- var result = config.ShouldEnableDebugSampling(); +- +- // Assert +- Assert.False(result); +- } +- +- [Fact] +- public void RefreshSampleRateCalculation_FirstCall_ShouldReturnFalseDueToColdStartProtection() +- { +- // Arrange +- var config = new PowertoolsLoggerConfiguration +- { +- SamplingRate = 1.0, // 100% sampling rate +- InitialLogLevel = LogLevel.Error, +- MinimumLogLevel = LogLevel.Error +- }; +- +- // Act +- var result = config.RefreshSampleRateCalculation(); +- +- // Assert +- Assert.False(result); // Cold start protection should prevent sampling on first call +- } +- +- [Fact] +- public void RefreshSampleRateCalculation_SecondCall_WithFullSampling_ShouldReturnTrue() +- { +- // Arrange +- var config = new PowertoolsLoggerConfiguration +- { +- SamplingRate = 1.0, // 100% sampling rate +- InitialLogLevel = LogLevel.Error, +- MinimumLogLevel = LogLevel.Error +- }; +- +- // Act +- var firstResult = config.RefreshSampleRateCalculation(); // Cold start protection +- var secondResult = config.RefreshSampleRateCalculation(); // Should enable sampling +- +- // Assert +- Assert.False(firstResult); // Cold start protection +- Assert.True(secondResult); // Should enable sampling +- Assert.Equal(LogLevel.Debug, config.MinimumLogLevel); // Should have changed to Debug +- } +- +- [Fact] +- public void RefreshSampleRateCalculation_WithZeroSampling_ShouldNeverEnableSampling() +- { +- // Arrange +- var config = new PowertoolsLoggerConfiguration +- { +- SamplingRate = 0.0, // 0% sampling rate +- InitialLogLevel = LogLevel.Error, +- MinimumLogLevel = LogLevel.Error +- }; +- +- // Act +- var firstResult = config.RefreshSampleRateCalculation(); +- var secondResult = config.RefreshSampleRateCalculation(); +- +- // Assert +- Assert.False(firstResult); +- Assert.False(secondResult); +- Assert.Equal(LogLevel.Error, config.MinimumLogLevel); // Should remain unchanged +- } +- +- [Fact] +- public void Logger_WithSamplingEnabled_ShouldLogDebugWhenSamplingTriggered() +- { +- // Arrange +- var output = new TestLoggerOutput(); +- Logger.Configure(options => +- { +- options.Service = "TestService"; +- options.SamplingRate = 1.0; // 100% sampling +- options.MinimumLogLevel = LogLevel.Error; +- options.LogOutput = output; +- }); +- +- // Act +- Logger.LogError("This is an error"); // Trigger first call (cold start protection) +- Logger.LogInformation("This should be logged due to sampling"); // Should trigger sampling +- +- // Assert +- var logOutput = output.ToString(); +- Assert.Contains("This is an error", logOutput); +- Assert.Contains("This should be logged due to sampling", logOutput); +- Assert.Contains("Changed log level to DEBUG based on Sampling configuration", logOutput); +- } +- +- [Fact] +- public void Logger_WithNoSampling_ShouldNotLogDebugMessages() +- { +- // Arrange +- var output = new TestLoggerOutput(); +- Logger.Configure(options => +- { +- options.Service = "TestService"; +- options.SamplingRate = 0.0; // 0% sampling +- options.MinimumLogLevel = LogLevel.Error; +- options.LogOutput = output; +- }); +- +- // Act +- Logger.LogError("This is an error"); +- Logger.LogInformation("This should NOT be logged"); +- +- // Assert +- var logOutput = output.ToString(); +- Assert.Contains("This is an error", logOutput); +- Assert.DoesNotContain("This should NOT be logged", logOutput); +- Assert.DoesNotContain("Changed log level to DEBUG based on Sampling configuration", logOutput); +- } +- +- [Fact] +- public void ShouldEnableDebugSampling_WithOutParameter_ShouldReturnSamplerValue() +- { +- // Arrange +- var config = new PowertoolsLoggerConfiguration +- { +- SamplingRate = 1.0 // 100% sampling +- }; +- +- // Act +- var result = config.ShouldEnableDebugSampling(out double samplerValue); +- +- // Assert +- Assert.True(result); +- Assert.True(samplerValue >= 0.0 && samplerValue <= 1.0); +- Assert.True(samplerValue <= config.SamplingRate); +- } +- +- [Fact] +- public void RefreshSampleRateCalculation_WithOutParameter_ShouldProvideSamplerValue() +- { +- // Arrange +- var config = new PowertoolsLoggerConfiguration +- { +- SamplingRate = 1.0, // 100% sampling +- InitialLogLevel = LogLevel.Error, +- MinimumLogLevel = LogLevel.Error +- }; +- +- // Act +- var firstResult = config.RefreshSampleRateCalculation(out double firstSamplerValue); +- var secondResult = config.RefreshSampleRateCalculation(out double secondSamplerValue); +- +- // Assert +- Assert.False(firstResult); // Cold start protection +- Assert.Equal(0.0, firstSamplerValue); // Should be 0 during cold start protection +- +- Assert.True(secondResult); // Should enable sampling +- Assert.True(secondSamplerValue >= 0.0 && secondSamplerValue <= 1.0); +- } +- +- [Fact] +- public void GetSafeRandom_ShouldReturnValueBetweenZeroAndOne() +- { +- // Act +- var randomValue = PowertoolsLoggerConfiguration.GetSafeRandom(); +- +- // Assert +- Assert.True(randomValue >= 0.0); +- Assert.True(randomValue <= 1.0); +- } +- +- [Fact] +- public void SamplingRefreshCount_ShouldIncrementCorrectly() +- { +- // Arrange +- var config = new PowertoolsLoggerConfiguration +- { +- SamplingRate = 1.0 +- }; +- +- // Act & Assert +- Assert.Equal(0, config.SamplingRefreshCount); +- +- config.RefreshSampleRateCalculation(); +- Assert.Equal(1, config.SamplingRefreshCount); +- +- config.RefreshSampleRateCalculation(); +- Assert.Equal(2, config.SamplingRefreshCount); +- } +- +- [Fact] +- public void RefreshSampleRateCalculation_ShouldEnableDebugLogging() +- { +- // Arrange +- var output = new TestLoggerOutput(); +- Logger.Configure(options => +- { +- options.Service = "TestService"; +- options.SamplingRate = 1.0; // 100% sampling +- options.MinimumLogLevel = LogLevel.Error; +- options.LogOutput = output; +- }); +- +- // Act - First refresh (cold start protection) +- Logger.RefreshSampleRateCalculation(); +- Logger.LogDebug("This should not appear"); +- +- // Clear output from first attempt +- output.Clear(); +- +- // Second refresh (should enable sampling) +- Logger.RefreshSampleRateCalculation(); +- Logger.LogDebug("This should appear after sampling"); +- +- // Assert +- var logOutput = output.ToString(); +- Assert.Contains("This should appear after sampling", logOutput); +- Assert.Contains("Changed log level to DEBUG based on Sampling configuration", logOutput); +- } +- +- [Fact] +- public void Logger_RefreshSampleRateCalculation_ShouldTriggerConfigurationUpdate() +- { +- // Arrange +- var output = new TestLoggerOutput(); +- Logger.Configure(options => +- { +- options.Service = "TestService"; +- options.SamplingRate = 1.0; // 100% sampling +- options.MinimumLogLevel = LogLevel.Warning; +- options.LogOutput = output; +- }); +- +- // Verify initial state - debug logs should not appear (sampling not yet triggered) +- Logger.LogDebug("Initial debug - should not appear"); +- Assert.DoesNotContain("Initial debug", output.ToString()); +- +- output.Clear(); +- +- // Act - Trigger sampling refresh +- // First call is protected by cold start logic, second call should enable sampling +- Logger.RefreshSampleRateCalculation(); // Cold start protection - no effect +- var samplingEnabled = Logger.RefreshSampleRateCalculation(); // Should enable debug sampling +- +- // Verify sampling was enabled +- Assert.True(samplingEnabled, "Sampling should be enabled with 100% rate"); +- +- // Now debug logs should appear because sampling elevated the log level +- Logger.LogDebug("Debug after sampling - should appear"); +- +- // Assert +- var logOutput = output.ToString(); +- Assert.Contains("Debug after sampling - should appear", logOutput); +- Assert.Contains("Changed log level to DEBUG based on Sampling configuration", logOutput); +- } +- +- +- +- +- +- [Fact] +- public void RefreshSampleRateCalculation_ShouldGenerateRandomValues_OverMultipleIterations() +- { +- // Arrange +- var config = new PowertoolsLoggerConfiguration +- { +- SamplingRate = 0.5, // 50% sampling rate +- MinimumLogLevel = LogLevel.Error, +- InitialLogLevel = LogLevel.Error +- }; +- +- var samplerValues = new List(); +- bool samplingTriggeredAtLeastOnce = false; +- bool samplingNotTriggeredAtLeastOnce = false; +- +- // Act - Try up to 20 times to verify random behavior +- for (int i = 0; i < 20; i++) +- { +- // Reset for each iteration +- config.SamplingRefreshCount = 1; // Skip cold start protection +- +- bool wasTriggered = config.RefreshSampleRateCalculation(out double samplerValue); +- samplerValues.Add(samplerValue); +- +- if (wasTriggered) +- { +- samplingTriggeredAtLeastOnce = true; +- } +- else +- { +- samplingNotTriggeredAtLeastOnce = true; +- } +- } +- +- // Assert +- // Verify that we got different random values (not all the same) +- var uniqueValues = samplerValues.Distinct().Count(); +- Assert.True(uniqueValues > 1, "Should generate different random values across iterations"); +- +- // With 50% sampling rate over 20 iterations, we should see both triggered and not triggered cases +- // (probability of all same outcome is extremely low: 0.5^20 ≈ 0.000001) +- Assert.True(samplingTriggeredAtLeastOnce, +- "Sampling should have been triggered at least once in 20 iterations with 50% rate"); +- Assert.True(samplingNotTriggeredAtLeastOnce, +- "Sampling should have been skipped at least once in 20 iterations with 50% rate"); +- +- // Verify all sampler values are within valid range [0, 1] +- Assert.True((bool)samplerValues.All(v => v >= 0.0 && v <= 1.0), "All sampler values should be between 0 and 1"); +- } +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Serializers/PowertoolsLambdaSerializerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Serializers/PowertoolsLambdaSerializerTests.cs +index 4e422a67..48950492 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Serializers/PowertoolsLambdaSerializerTests.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Serializers/PowertoolsLambdaSerializerTests.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + + using AWS.Lambda.Powertools.Logging.Serializers; + using System; +@@ -15,23 +30,29 @@ namespace AWS.Lambda.Powertools.Logging.Tests.Serializers; + + public class PowertoolsLambdaSerializerTests : IDisposable + { +- private readonly PowertoolsLoggingSerializer _serializer; +- +- public PowertoolsLambdaSerializerTests() +- { +- _serializer = new PowertoolsLoggingSerializer(); +- } +- +-#if NET8_0_OR_GREATER + [Fact] + public void Constructor_ShouldNotThrowException() + { + // Arrange & Act & Assert + var exception = +- Record.Exception(() => _serializer.AddSerializerContext(TestJsonContext.Default)); ++ Record.Exception(() => PowertoolsLoggingSerializer.AddSerializerContext(TestJsonContext.Default)); + Assert.Null(exception); + } + ++ [Fact] ++ public void Constructor_ShouldAddCustomerContext() ++ { ++ // Arrange ++ var customerContext = new TestJsonContext(); ++ ++ // Act ++ PowertoolsLoggingSerializer.AddSerializerContext(customerContext); ++ ; ++ ++ // Assert ++ Assert.True(PowertoolsLoggingSerializer.HasContext(customerContext)); ++ } ++ + [Theory] + [InlineData(LoggerOutputCase.CamelCase, "{\"fullName\":\"John\",\"age\":30}", "John", 30)] + [InlineData(LoggerOutputCase.PascalCase, "{\"FullName\":\"Jane\",\"Age\":25}", "Jane", 25)] +@@ -59,7 +80,7 @@ public class PowertoolsLambdaSerializerTests : IDisposable + var serializer = new PowertoolsSourceGeneratorSerializer(); + ; + +- _serializer.ConfigureNamingPolicy(LoggerOutputCase.PascalCase); ++ PowertoolsLoggingSerializer.ConfigureNamingPolicy(LoggerOutputCase.PascalCase); + + var json = "{\"FullName\":\"John\",\"Age\":30}"; + var stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); +@@ -187,7 +208,7 @@ public class PowertoolsLambdaSerializerTests : IDisposable + stream.Position = 0; + var outputExternalSerializer = new StreamReader(stream).ReadToEnd(); + +- var outptuMySerializer = _serializer.Serialize(log, typeof(LogEntry)); ++ var outptuMySerializer = PowertoolsLoggingSerializer.Serialize(log, typeof(LogEntry)); + + // Assert + Assert.Equal( +@@ -199,40 +220,10 @@ public class PowertoolsLambdaSerializerTests : IDisposable + } + + +-#endif + public void Dispose() + { +- ++ PowertoolsLoggingSerializer.ConfigureNamingPolicy(LoggingConstants.DefaultLoggerOutputCase); ++ PowertoolsLoggingSerializer.ClearOptions(); + } + +-#if NET6_0 +- +- [Fact] +- public void Should_Serialize_Net6() +- { +- // Arrange +- _serializer.ConfigureNamingPolicy(LoggingConstants.DefaultLoggerOutputCase); +- var testObject = new APIGatewayProxyRequest +- { +- Path = "asda", +- RequestContext = new APIGatewayProxyRequest.ProxyRequestContext +- { +- RequestId = "asdas" +- } +- }; +- +- var log = new LogEntry +- { +- Name = "dasda", +- Message = testObject +- }; +- +- var outptuMySerializer = _serializer.Serialize(log, null); +- +- // Assert +- Assert.Equal( +- "{\"cold_start\":false,\"x_ray_trace_id\":null,\"correlation_id\":null,\"timestamp\":\"0001-01-01T00:00:00\",\"level\":\"Trace\",\"service\":null,\"name\":\"dasda\",\"message\":{\"resource\":null,\"path\":\"asda\",\"http_method\":null,\"headers\":null,\"multi_value_headers\":null,\"query_string_parameters\":null,\"multi_value_query_string_parameters\":null,\"path_parameters\":null,\"stage_variables\":null,\"request_context\":{\"path\":null,\"account_id\":null,\"resource_id\":null,\"stage\":null,\"request_id\":\"asdas\",\"identity\":null,\"resource_path\":null,\"http_method\":null,\"api_id\":null,\"extended_request_id\":null,\"connection_id\":null,\"connected_at\":0,\"domain_name\":null,\"domain_prefix\":null,\"event_type\":null,\"message_id\":null,\"route_key\":null,\"authorizer\":null,\"operation_name\":null,\"error\":null,\"integration_latency\":null,\"message_direction\":null,\"request_time\":null,\"request_time_epoch\":0,\"status\":null},\"body\":null,\"is_base64_encoded\":false},\"sampling_rate\":null,\"extra_keys\":null,\"exception\":null,\"lambda_context\":null}", +- outptuMySerializer); +- } +-#endif + } +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Serializers/PowertoolsLoggingSerializerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Serializers/PowertoolsLoggingSerializerTests.cs +index 58b42e3f..be583077 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Serializers/PowertoolsLoggingSerializerTests.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Serializers/PowertoolsLoggingSerializerTests.cs +@@ -1,12 +1,25 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.Collections.Generic; +-using System.IO; ++using System.Runtime.CompilerServices; + using System.Text.Encodings.Web; + using System.Text.Json; + using System.Text.Json.Serialization; +-using System.Text.Json.Serialization.Metadata; + using Amazon.Lambda.Serialization.SystemTextJson; +-using AWS.Lambda.Powertools.Common; + using AWS.Lambda.Powertools.Common.Utils; + using AWS.Lambda.Powertools.Logging.Internal; + using AWS.Lambda.Powertools.Logging.Internal.Converters; +@@ -18,21 +31,17 @@ namespace AWS.Lambda.Powertools.Logging.Tests.Serializers; + + public class PowertoolsLoggingSerializerTests : IDisposable + { +- private readonly PowertoolsLoggingSerializer _serializer; + + public PowertoolsLoggingSerializerTests() + { +- _serializer = new PowertoolsLoggingSerializer(); +- _serializer.ConfigureNamingPolicy(LoggingConstants.DefaultLoggerOutputCase); +-#if NET8_0_OR_GREATER +- ClearContext(); +-#endif ++ PowertoolsLoggingSerializer.ConfigureNamingPolicy(LoggingConstants.DefaultLoggerOutputCase); ++ PowertoolsLoggingSerializer.ClearContext(); + } +- ++ + [Fact] + public void SerializerOptions_ShouldNotBeNull() + { +- var options = _serializer.GetSerializerOptions(); ++ var options = PowertoolsLoggingSerializer.GetSerializerOptions(); + Assert.NotNull(options); + } + +@@ -40,9 +49,9 @@ public class PowertoolsLoggingSerializerTests : IDisposable + public void SerializerOptions_ShouldHaveCorrectDefaultSettings() + { + RuntimeFeatureWrapper.SetIsDynamicCodeSupported(false); +- +- var options = _serializer.GetSerializerOptions(); +- ++ ++ var options = PowertoolsLoggingSerializer.GetSerializerOptions(); ++ + Assert.Collection(options.Converters, + converter => Assert.IsType(converter), + converter => Assert.IsType(converter), +@@ -50,27 +59,21 @@ public class PowertoolsLoggingSerializerTests : IDisposable + converter => Assert.IsType(converter), + converter => Assert.IsType(converter), + converter => Assert.IsType(converter), +-#if NET8_0_OR_GREATER + converter => Assert.IsType(converter)); +-#elif NET6_0 +- converter => Assert.IsType(converter)); +-#endif + + Assert.Equal(JavaScriptEncoder.UnsafeRelaxedJsonEscaping, options.Encoder); + +-#if NET8_0_OR_GREATER + Assert.Collection(options.TypeInfoResolverChain, +- resolver => Assert.IsType(resolver)); +-#endif ++ resolver => Assert.IsType(resolver)); + } +- ++ + [Fact] + public void SerializerOptions_ShouldHaveCorrectDefaultSettings_WhenDynamic() + { + RuntimeFeatureWrapper.SetIsDynamicCodeSupported(true); +- +- var options = _serializer.GetSerializerOptions(); +- ++ ++ var options = PowertoolsLoggingSerializer.GetSerializerOptions(); ++ + Assert.Collection(options.Converters, + converter => Assert.IsType(converter), + converter => Assert.IsType(converter), +@@ -78,17 +81,11 @@ public class PowertoolsLoggingSerializerTests : IDisposable + converter => Assert.IsType(converter), + converter => Assert.IsType(converter), + converter => Assert.IsType(converter), +-#if NET8_0_OR_GREATER +- converter => Assert.IsType(converter)); +-#elif NET6_0 + converter => Assert.IsType(converter)); +-#endif + + Assert.Equal(JavaScriptEncoder.UnsafeRelaxedJsonEscaping, options.Encoder); + +-#if NET8_0_OR_GREATER + Assert.Empty(options.TypeInfoResolverChain); +-#endif + } + + [Fact] +@@ -121,7 +118,7 @@ public class PowertoolsLoggingSerializerTests : IDisposable + public void ConfigureNamingPolicy_ShouldNotChangeWhenPassedSameCase() + { + var originalJson = SerializeTestObject(LoggerOutputCase.SnakeCase); +- _serializer.ConfigureNamingPolicy(LoggerOutputCase.SnakeCase); ++ PowertoolsLoggingSerializer.ConfigureNamingPolicy(LoggerOutputCase.SnakeCase); + var newJson = SerializeTestObject(LoggerOutputCase.SnakeCase); + Assert.Equal(originalJson, newJson); + } +@@ -129,7 +126,7 @@ public class PowertoolsLoggingSerializerTests : IDisposable + [Fact] + public void Serialize_ShouldHandleNestedObjects() + { +- _serializer.ConfigureNamingPolicy(LoggerOutputCase.SnakeCase); ++ PowertoolsLoggingSerializer.ConfigureNamingPolicy(LoggerOutputCase.SnakeCase); + + var testObject = new LogEntry + { +@@ -140,7 +137,7 @@ public class PowertoolsLoggingSerializerTests : IDisposable + } + }; + +- var json = JsonSerializer.Serialize(testObject, _serializer.GetSerializerOptions()); ++ var json = JsonSerializer.Serialize(testObject, PowertoolsLoggingSerializer.GetSerializerOptions()); + Assert.Contains("\"cold_start\":true", json); + Assert.Contains("\"nested_object\":{\"property_name\":\"Value\"}", json); + } +@@ -152,11 +149,10 @@ public class PowertoolsLoggingSerializerTests : IDisposable + { + Level = LogLevel.Error + }; +- var json = JsonSerializer.Serialize(testObject, _serializer.GetSerializerOptions()); ++ var json = JsonSerializer.Serialize(testObject, PowertoolsLoggingSerializer.GetSerializerOptions()); + Assert.Contains("\"level\":\"Error\"", json); + } + +-#if NET8_0_OR_GREATER + [Fact] + public void Serialize_UnknownType_ThrowsInvalidOperationException() + { +@@ -166,409 +162,47 @@ public class PowertoolsLoggingSerializerTests : IDisposable + RuntimeFeatureWrapper.SetIsDynamicCodeSupported(false); + // Act & Assert + var exception = Assert.Throws(() => +- _serializer.Serialize(unknownObject, typeof(UnknownType))); ++ PowertoolsLoggingSerializer.Serialize(unknownObject, typeof(UnknownType))); + + Assert.Contains("is not known to the serializer", exception.Message); + Assert.Contains(typeof(UnknownType).ToString(), exception.Message); + } +- ++ + [Fact] + public void Serialize_UnknownType_Should_Not_Throw_InvalidOperationException_When_Dynamic() + { + // Arrange +- var unknownObject = new UnknownType { SomeProperty = "Hello" }; ++ var unknownObject = new UnknownType{ SomeProperty = "Hello"}; + + RuntimeFeatureWrapper.SetIsDynamicCodeSupported(true); + // Act & Assert + var expected = +- _serializer.Serialize(unknownObject, typeof(UnknownType)); ++ PowertoolsLoggingSerializer.Serialize(unknownObject, typeof(UnknownType)); + + Assert.Equal("{\"some_property\":\"Hello\"}", expected); + } + +- [Fact] +- public void AddSerializerContext_ShouldUpdateTypeInfoResolver() +- { +- // Arrange +- RuntimeFeatureWrapper.SetIsDynamicCodeSupported(false); +- var testContext = new TestSerializerContext(new JsonSerializerOptions()); +- +- // Get the initial resolver +- var beforeOptions = _serializer.GetSerializerOptions(); +- var beforeResolver = beforeOptions.TypeInfoResolver; +- +- // Act +- _serializer.AddSerializerContext(testContext); +- +- // Get the updated resolver +- var afterOptions = _serializer.GetSerializerOptions(); +- var afterResolver = afterOptions.TypeInfoResolver; +- +- // Assert - adding a context should create a new resolver +- Assert.NotSame(beforeResolver, afterResolver); +- Assert.IsType(afterResolver); +- } +- + private class UnknownType + { + public string SomeProperty { get; set; } + } + +- private class TestSerializerContext : JsonSerializerContext +- { +- private readonly JsonSerializerOptions _options; +- +- public TestSerializerContext(JsonSerializerOptions options) : base(options) +- { +- _options = options; +- } +- +- public override JsonTypeInfo? GetTypeInfo(Type type) +- { +- return null; // For testing purposes only +- } +- +- protected override JsonSerializerOptions? GeneratedSerializerOptions => _options; +- } +- +- private void ClearContext() +- { +- // Create a new serializer to clear any existing contexts +- _serializer.SetOptions(new JsonSerializerOptions()); +- } +-#endif +- + private string SerializeTestObject(LoggerOutputCase? outputCase) + { + if (outputCase.HasValue) + { +- _serializer.ConfigureNamingPolicy(outputCase.Value); ++ PowertoolsLoggingSerializer.ConfigureNamingPolicy(outputCase.Value); + } + + LogEntry testObject = new LogEntry { ColdStart = true }; +- return JsonSerializer.Serialize(testObject, _serializer.GetSerializerOptions()); +- } +- +- [Fact] +- public void ByteArrayConverter_ShouldProduceBase64EncodedString() +- { +- // Arrange +- var testObject = new { BinaryData = new byte[] { 1, 2, 3, 4, 5 } }; +- +- // Act +- var json = JsonSerializer.Serialize(testObject, _serializer.GetSerializerOptions()); +- +- // Assert +- Assert.Contains("\"binary_data\":\"AQIDBAU=\"", json); +- } +- +- [Fact] +- public void ExceptionConverter_ShouldSerializeExceptionDetails() +- { +- // Arrange +- var exception = new InvalidOperationException("Test error message", new Exception("Inner exception")); +- var testObject = new { Error = exception }; +- +- // Act +- var json = JsonSerializer.Serialize(testObject, _serializer.GetSerializerOptions()); +- +- // Assert +- Assert.Equal("{\"error\":{\"type\":\"System.InvalidOperationException\",\"message\":\"Test error message\",\"inner_exception\":{\"type\":\"System.Exception\",\"message\":\"Inner exception\"}}}", json); +- } +- +- [Fact] +- public void MemoryStreamConverter_ShouldConvertToBase64() +- { +- // Arrange +- var bytes = new byte[] { 10, 20, 30, 40, 50 }; +- var memoryStream = new MemoryStream(bytes); +- var testObject = new { Stream = memoryStream }; +- +- // Act +- var json = JsonSerializer.Serialize(testObject, _serializer.GetSerializerOptions()); +- +- // Assert +- Assert.Contains("\"stream\":\"ChQeKDI=\"", json); ++ return JsonSerializer.Serialize(testObject, PowertoolsLoggingSerializer.GetSerializerOptions()); + } + +- [Fact] +- public void ConstantClassConverter_ShouldSerializeToString() +- { +- // Arrange +- var testObject = new { Level = LogLevel.Warning }; +- +- // Act +- var json = JsonSerializer.Serialize(testObject, _serializer.GetSerializerOptions()); +- +- // Assert +- Assert.Contains("\"level\":\"Warning\"", json); +- } +- +-#if NET6_0_OR_GREATER +- [Fact] +- public void DateOnlyConverter_ShouldSerializeToIsoDate() +- { +- // Arrange +- var date = new DateOnly(2023, 10, 15); +- var testObject = new { Date = date }; +- +- // Act +- var json = JsonSerializer.Serialize(testObject, _serializer.GetSerializerOptions()); +- +- // Assert +- Assert.Contains("\"date\":\"2023-10-15\"", json); +- } +- +- [Fact] +- public void TimeOnlyConverter_ShouldSerializeToIsoTime() +- { +- // Arrange +- var time = new TimeOnly(13, 45, 30); +- var testObject = new { Time = time }; +- +- // Act +- var json = JsonSerializer.Serialize(testObject, _serializer.GetSerializerOptions()); +- +- // Assert +- Assert.Contains("\"time\":\"13:45:30\"", json); +- } +-#endif +- +- [Fact] +- public void LogLevelJsonConverter_ShouldSerializeAllLogLevels() +- { +- // Arrange +- var levels = new Dictionary +- { +- { "trace", LogLevel.Trace }, +- { "debug", LogLevel.Debug }, +- { "info", LogLevel.Information }, +- { "warning", LogLevel.Warning }, +- { "error", LogLevel.Error }, +- { "critical", LogLevel.Critical } +- }; +- +- // Act +- var json = JsonSerializer.Serialize(levels, _serializer.GetSerializerOptions()); +- +- // Assert +- Assert.Contains("\"trace\":\"Trace\"", json); +- Assert.Contains("\"debug\":\"Debug\"", json); +- Assert.Contains("\"info\":\"Information\"", json); +- Assert.Contains("\"warning\":\"Warning\"", json); +- Assert.Contains("\"error\":\"Error\"", json); +- Assert.Contains("\"critical\":\"Critical\"", json); +- } +- +- [Fact] +- public void Serialize_ComplexObjectWithMultipleConverters_ShouldConvertAllProperties() +- { +- // Arrange +- var testObject = new ComplexTestObject +- { +- BinaryData = new byte[] { 1, 2, 3 }, +- Exception = new ArgumentException("Test argument"), +- Stream = new MemoryStream(new byte[] { 4, 5, 6 }), +- Level = LogLevel.Information, +-#if NET6_0_OR_GREATER +- Date = new DateOnly(2023, 1, 15), +- Time = new TimeOnly(14, 30, 0), +-#endif +- }; +- +- // Act +- var json = JsonSerializer.Serialize(testObject, _serializer.GetSerializerOptions()); +- +- // Assert +- Assert.Contains("\"binary_data\":\"AQID\"", json); +- Assert.Contains("\"exception\":{\"type\":\"System.ArgumentException\"", json); +- Assert.Contains("\"stream\":\"BAUG\"", json); +- Assert.Contains("\"level\":\"Information\"", json); +-#if NET6_0_OR_GREATER +- Assert.Contains("\"date\":\"2023-01-15\"", json); +- Assert.Contains("\"time\":\"14:30:00\"", json); +-#endif +- } +- +- private class ComplexTestObject +- { +- public byte[] BinaryData { get; set; } +- public Exception Exception { get; set; } +- public MemoryStream Stream { get; set; } +- public LogLevel Level { get; set; } +-#if NET6_0_OR_GREATER +- public DateOnly Date { get; set; } +- public TimeOnly Time { get; set; } +-#endif +- } +- +- [Fact] +- public void ConfigureNamingPolicy_WhenChanged_RebuildsOptions() +- { +- // Arrange +- var serializer = new PowertoolsLoggingSerializer(); +- +- // Force initialization of _jsonOptions +- _ = serializer.GetSerializerOptions(); +- +- // Act +- serializer.ConfigureNamingPolicy(LoggerOutputCase.CamelCase); +- var options = serializer.GetSerializerOptions(); +- +- // Assert +- Assert.Equal(JsonNamingPolicy.CamelCase, options.PropertyNamingPolicy); +- Assert.Equal(JsonNamingPolicy.CamelCase, options.DictionaryKeyPolicy); +- } +- +- [Fact] +- public void ConfigureNamingPolicy_WhenAlreadySet_DoesNothing() +- { +- // Arrange +- var serializer = new PowertoolsLoggingSerializer(); +- serializer.ConfigureNamingPolicy(LoggerOutputCase.CamelCase); +- +- // Get the initial options +- var initialOptions = serializer.GetSerializerOptions(); +- +- // Act - set the same case again +- serializer.ConfigureNamingPolicy(LoggerOutputCase.CamelCase); +- var newOptions = serializer.GetSerializerOptions(); +- +- // Assert - should be the same instance +- Assert.Same(initialOptions, newOptions); +- } +- +- [Fact] +- public void Serialize_WithValidObject_ReturnsJsonString() +- { +- // Arrange +- var serializer = new PowertoolsLoggingSerializer(); +- var testObj = new TestClass { Name = "Test", Value = 123 }; +- +- // Act +- var json = serializer.Serialize(testObj, typeof(TestClass)); +- +- // Assert +- Assert.Contains("\"name\"", json); +- Assert.Contains("\"value\"", json); +- Assert.Contains("123", json); +- Assert.Contains("Test", json); +- } +- +-#if NET8_0_OR_GREATER +- +- [Fact] +- public void SetOptions_WithTypeInfoResolver_SetsCustomResolver() +- { +- // Arrange +- var serializer = new PowertoolsLoggingSerializer(); +- +- // Explicitly disable dynamic code - important to set before creating options +- RuntimeFeatureWrapper.SetIsDynamicCodeSupported(false); +- +- var context = new TestJsonContext(new JsonSerializerOptions()); +- var options = new JsonSerializerOptions +- { +- TypeInfoResolver = context +- }; +- +- // Act +- serializer.SetOptions(options); +- var serializerOptions = serializer.GetSerializerOptions(); +- +- // Assert - options are properly configured +- Assert.NotNull(serializerOptions.TypeInfoResolver); +- } +-#endif +- +- [Fact] +- public void SetOutputCase_CamelCase_SetsPoliciesCorrectly() +- { +- // Arrange +- var serializer = new PowertoolsLoggingSerializer(); +- serializer.ConfigureNamingPolicy(LoggerOutputCase.CamelCase); +- +- // Act +- var options = serializer.GetSerializerOptions(); +- +- // Assert +- Assert.Equal(JsonNamingPolicy.CamelCase, options.PropertyNamingPolicy); +- Assert.Equal(JsonNamingPolicy.CamelCase, options.DictionaryKeyPolicy); +- } +- +- [Fact] +- public void SetOutputCase_PascalCase_SetsPoliciesCorrectly() +- { +- // Arrange +- var serializer = new PowertoolsLoggingSerializer(); +- serializer.ConfigureNamingPolicy(LoggerOutputCase.PascalCase); +- +- // Act +- var options = serializer.GetSerializerOptions(); +- +- // Assert +- Assert.IsType(options.PropertyNamingPolicy); +- Assert.IsType(options.DictionaryKeyPolicy); +- } +- +- [Fact] +- public void SetOutputCase_SnakeCase_SetsPoliciesCorrectly() +- { +- // Arrange +- var serializer = new PowertoolsLoggingSerializer(); +- serializer.ConfigureNamingPolicy(LoggerOutputCase.SnakeCase); +- +- // Act +- var options = serializer.GetSerializerOptions(); +- +-#if NET8_0_OR_GREATER +- // Assert - in .NET 8 we use built-in SnakeCaseLower +- Assert.Equal(JsonNamingPolicy.SnakeCaseLower, options.PropertyNamingPolicy); +- Assert.Equal(JsonNamingPolicy.SnakeCaseLower, options.DictionaryKeyPolicy); +-#else +- // Assert - in earlier versions, we use custom SnakeCaseNamingPolicy +- Assert.IsType(options.PropertyNamingPolicy); +- Assert.IsType(options.DictionaryKeyPolicy); +-#endif +- } +- +- [Fact] +- public void GetSerializerOptions_AddsAllConverters() +- { +- // Arrange +- var serializer = new PowertoolsLoggingSerializer(); +- +- // Act +- var options = serializer.GetSerializerOptions(); +- +- // Assert +- Assert.Contains(options.Converters, c => c is ByteArrayConverter); +- Assert.Contains(options.Converters, c => c is ExceptionConverter); +- Assert.Contains(options.Converters, c => c is MemoryStreamConverter); +- Assert.Contains(options.Converters, c => c is ConstantClassConverter); +- Assert.Contains(options.Converters, c => c is DateOnlyConverter); +- Assert.Contains(options.Converters, c => c is TimeOnlyConverter); +-#if NET8_0_OR_GREATER || NET6_0 +- Assert.Contains(options.Converters, c => c is LogLevelJsonConverter); +-#endif +- } +- +- // Test class for serialization +- private class TestClass +- { +- public string Name { get; set; } +- public int Value { get; set; } +- } +- +- +- +- + public void Dispose() + { +-#if NET8_0_OR_GREATER +- ClearContext(); +-#endif +- _serializer.SetOptions(null); ++ PowertoolsLoggingSerializer.ConfigureNamingPolicy(LoggingConstants.DefaultLoggerOutputCase); ++ PowertoolsLoggingSerializer.ClearContext(); ++ PowertoolsLoggingSerializer.ClearOptions(); + RuntimeFeatureWrapper.Reset(); + } + } +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/TestSetup.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/TestSetup.cs +index f8654599..708c63c2 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/TestSetup.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/TestSetup.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System.Collections.Generic; + using System.Linq; + using Xunit; +diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Utilities/Converters.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Utilities/Converters.cs +deleted file mode 100644 +index b7e975e4..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Utilities/Converters.cs ++++ /dev/null +@@ -1,181 +0,0 @@ +-using System; +-using System.Text; +-using System.Text.Json; +-using System.Text.Json.Serialization; +-using AWS.Lambda.Powertools.Logging.Internal.Converters; +-using Xunit; +- +-namespace AWS.Lambda.Powertools.Logging.Tests.Utilities; +- +-public class ByteArrayConverterTests +-{ +- private readonly JsonSerializerOptions _options; +- +- public ByteArrayConverterTests() +- { +- _options = new JsonSerializerOptions(); +- _options.Converters.Add(new ByteArrayConverter()); +- } +- +- [Fact] +- public void Write_WhenByteArrayIsNull_WritesNullValue() +- { +- // Arrange +- var testObject = new TestClass { Data = null }; +- +- // Act +- var json = JsonSerializer.Serialize(testObject, _options); +- +- // Assert +- Assert.Contains("\"data\":null", json); +- } +- +- [Fact] +- public void Write_WithByteArray_WritesBase64String() +- { +- // Arrange +- byte[] testData = { 1, 2, 3, 4, 5 }; +- var testObject = new TestClass { Data = testData }; +- var expectedBase64 = Convert.ToBase64String(testData); +- +- // Act +- var json = JsonSerializer.Serialize(testObject, _options); +- +- // Assert +- Assert.Contains($"\"data\":\"{expectedBase64}\"", json); +- } +- +- [Fact] +- public void Read_WithBase64String_ReturnsByteArray() +- { +- // Arrange +- byte[] expectedData = { 1, 2, 3, 4, 5 }; +- var base64 = Convert.ToBase64String(expectedData); +- var json = $"{{\"data\":\"{base64}\"}}"; +- +- // Act +- var result = JsonSerializer.Deserialize(json, _options); +- +- // Assert +- Assert.Equal(expectedData, result.Data); +- } +- +- [Fact] +- public void Read_WithInvalidType_ThrowsJsonException() +- { +- // Arrange +- var json = "{\"data\":123}"; +- +- // Act & Assert +- Assert.Throws(() => +- JsonSerializer.Deserialize(json, _options)); +- } +- +- [Fact] +- public void Read_WithEmptyString_ReturnsEmptyByteArray() +- { +- // Arrange +- var json = "{\"data\":\"\"}"; +- +- // Act +- var result = JsonSerializer.Deserialize(json, _options); +- +- // Assert +- Assert.NotNull(result.Data); +- Assert.Empty(result.Data); +- } +- +- [Fact] +- public void WriteAndRead_RoundTrip_PreservesData() +- { +- // Arrange +- byte[] originalData = Encoding.UTF8.GetBytes("Test data with special chars: !@#$%^&*()"); +- var testObject = new TestClass { Data = originalData }; +- +- // Act +- var json = JsonSerializer.Serialize(testObject, _options); +- var deserializedObject = JsonSerializer.Deserialize(json, _options); +- +- // Assert +- Assert.Equal(originalData, deserializedObject.Data); +- } +- +- private class TestClass +- { +- [JsonPropertyName("data")] public byte[] Data { get; set; } +- } +- +- [Fact] +- public void ByteArrayConverter_Write_ShouldHandleNullValue() +- { +- // Arrange +- var converter = new ByteArrayConverter(); +- var options = new JsonSerializerOptions(); +- var testObject = new { Data = (byte[])null }; +- +- // Act +- var json = JsonSerializer.Serialize(testObject, options); +- +- // Assert +- Assert.Contains("\"Data\":null", json); +- } +- +- [Fact] +- public void ByteArrayConverter_Read_ShouldHandleNullToken() +- { +- // Arrange +- var converter = new ByteArrayConverter(); +- var json = "{\"Data\":null}"; +- var options = new JsonSerializerOptions(); +- options.Converters.Add(converter); +- +- // Act +- var result = JsonSerializer.Deserialize(json, options); +- +- // Assert +- Assert.Null(result.Data); +- } +- +- [Fact] +- public void ByteArrayConverter_Read_ShouldHandleStringToken() +- { +- // Arrange +- var converter = new ByteArrayConverter(); +- var expectedBytes = new byte[] { 1, 2, 3, 4 }; +- var base64String = Convert.ToBase64String(expectedBytes); +- var json = $"{{\"Data\":\"{base64String}\"}}"; +- +- var options = new JsonSerializerOptions(); +- options.Converters.Add(converter); +- +- // Act +- var result = JsonSerializer.Deserialize(json, options); +- +- // Assert +- Assert.NotNull(result.Data); +- Assert.Equal(expectedBytes, result.Data); +- } +- +- [Fact] +- public void ByteArrayConverter_Read_ShouldThrowOnInvalidToken() +- { +- // Arrange +- var converter = new ByteArrayConverter(); +- var json = "{\"Data\":123}"; // Number instead of string +- +- var options = new JsonSerializerOptions(); +- options.Converters.Add(converter); +- +- // Act & Assert +- var ex = Assert.Throws(() => +- JsonSerializer.Deserialize(json, options)); +- +- Assert.Contains("Expected string value for byte array", ex.Message); +- } +- +-// Helper class for testing byte array deserialization +- private class TestByteArrayClass +- { +- public byte[] Data { get; set; } +- } +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Utilities/PowertoolsConfigurationExtensionsTests.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Utilities/PowertoolsConfigurationExtensionsTests.cs +index f8ee0985..6a719d1b 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Utilities/PowertoolsConfigurationExtensionsTests.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Utilities/PowertoolsConfigurationExtensionsTests.cs +@@ -1,6 +1,24 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using Xunit; ++using NSubstitute; ++using AWS.Lambda.Powertools.Common; + using AWS.Lambda.Powertools.Logging.Internal; ++using AWS.Lambda.Powertools.Logging.Serializers; + + namespace AWS.Lambda.Powertools.Logging.Tests.Utilities; + +@@ -13,8 +31,12 @@ public class PowertoolsConfigurationExtensionsTests : IDisposable + [InlineData(LoggerOutputCase.SnakeCase, "testString", "test_string")] // Default case + public void ConvertToOutputCase_ShouldConvertCorrectly(LoggerOutputCase outputCase, string input, string expected) + { ++ // Arrange ++ var systemWrapper = Substitute.For(); ++ var configurations = new PowertoolsConfigurations(systemWrapper); ++ + // Act +- var result = input.ToCase(outputCase); ++ var result = configurations.ConvertToOutputCase(input, outputCase); + + // Assert + Assert.Equal(expected, result); +@@ -44,7 +66,7 @@ public class PowertoolsConfigurationExtensionsTests : IDisposable + public void ToSnakeCase_ShouldConvertCorrectly(string input, string expected) + { + // Act +- var result = input.ToSnake(); ++ var result = PrivateMethod.InvokeStatic(typeof(PowertoolsConfigurationsExtension), "ToSnakeCase", input); + + // Assert + Assert.Equal(expected, result); +@@ -75,7 +97,7 @@ public class PowertoolsConfigurationExtensionsTests : IDisposable + public void ToPascalCase_ShouldConvertCorrectly(string input, string expected) + { + // Act +- var result = input.ToPascal(); ++ var result = PrivateMethod.InvokeStatic(typeof(PowertoolsConfigurationsExtension), "ToPascalCase", input); + + // Assert + Assert.Equal(expected, result); +@@ -113,7 +135,7 @@ public class PowertoolsConfigurationExtensionsTests : IDisposable + public void ToCamelCase_ShouldConvertCorrectly(string input, string expected) + { + // Act +- var result = input.ToCamel(); ++ var result = PrivateMethod.InvokeStatic(typeof(PowertoolsConfigurationsExtension), "ToCamelCase", input); + + // Assert + Assert.Equal(expected, result); +@@ -122,6 +144,7 @@ public class PowertoolsConfigurationExtensionsTests : IDisposable + public void Dispose() + { + LoggingAspect.ResetForTest(); ++ PowertoolsLoggingSerializer.ClearOptions(); + } + } + +diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Utilities/PowertoolsLoggerHelpersTests.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Utilities/PowertoolsLoggerHelpersTests.cs +index f35753f8..da3dd696 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Utilities/PowertoolsLoggerHelpersTests.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Utilities/PowertoolsLoggerHelpersTests.cs +@@ -1,19 +1,16 @@ +-#if NET8_0_OR_GREATER +- + using System; + using System.Collections.Generic; + using System.IO; + using AWS.Lambda.Powertools.Common; + using AWS.Lambda.Powertools.Logging.Internal.Helpers; + using AWS.Lambda.Powertools.Logging.Serializers; +-using Microsoft.Extensions.Logging; + using NSubstitute; + using Xunit; + + namespace AWS.Lambda.Powertools.Logging.Tests.Utilities; + + public class PowertoolsLoggerHelpersTests : IDisposable +-{ ++{ + [Fact] + public void ObjectToDictionary_AnonymousObjectWithSimpleProperties_ReturnsDictionary() + { +@@ -74,12 +71,9 @@ public class PowertoolsLoggerHelpersTests : IDisposable + [Fact] + public void Should_Log_With_Anonymous() + { +- var consoleOut = Substitute.For(); +- Logger.Configure(options => +- { +- options.LogOutput = consoleOut; +- }); +- ++ var consoleOut = Substitute.For(); ++ SystemWrapper.Instance.SetOut(consoleOut); ++ + // Act & Assert + Logger.AppendKey("newKey", new + { +@@ -97,12 +91,9 @@ public class PowertoolsLoggerHelpersTests : IDisposable + [Fact] + public void Should_Log_With_Complex_Anonymous() + { +- var consoleOut = Substitute.For(); +- Logger.Configure(options => +- { +- options.LogOutput = consoleOut; +- }); +- ++ var consoleOut = Substitute.For(); ++ SystemWrapper.Instance.SetOut(consoleOut); ++ + // Act & Assert + Logger.AppendKey("newKey", new + { +@@ -208,28 +199,7 @@ public class PowertoolsLoggerHelpersTests : IDisposable + + public void Dispose() + { +- ResetAllState(); +- } +- +- private static void ResetAllState() +- { +- // Clear environment variables +- Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_CASE", null); +- Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", null); +- Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", null); +- +- // Reset all logging components +- Logger.Reset(); +- PowertoolsLoggingBuilderExtensions.ResetAllProviders(); +- +- // Force default configuration +- var config = new PowertoolsLoggerConfiguration +- { +- MinimumLogLevel = LogLevel.Information, +- LoggerOutputCase = LoggerOutputCase.SnakeCase +- }; +- PowertoolsLoggingBuilderExtensions.UpdateConfiguration(config); ++ PowertoolsLoggingSerializer.ConfigureNamingPolicy(LoggerOutputCase.Default); ++ PowertoolsLoggingSerializer.ClearOptions(); + } + } +- +-#endif +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Utilities/SystemWrapperMock.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Utilities/SystemWrapperMock.cs +new file mode 100644 +index 00000000..1ab2b94e +--- /dev/null ++++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Utilities/SystemWrapperMock.cs +@@ -0,0 +1,68 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ ++using System.IO; ++using AWS.Lambda.Powertools.Common; ++ ++namespace AWS.Lambda.Powertools.Logging.Tests.Utilities; ++ ++public class SystemWrapperMock : ISystemWrapper ++{ ++ private readonly IPowertoolsEnvironment _powertoolsEnvironment; ++ public bool LogMethodCalled { get; private set; } ++ public string LogMethodCalledWithArgument { get; private set; } ++ ++ public SystemWrapperMock(IPowertoolsEnvironment powertoolsEnvironment) ++ { ++ _powertoolsEnvironment = powertoolsEnvironment; ++ } ++ ++ public string GetEnvironmentVariable(string variable) ++ { ++ return _powertoolsEnvironment.GetEnvironmentVariable(variable); ++ } ++ ++ public void Log(string value) ++ { ++ LogMethodCalledWithArgument = value; ++ LogMethodCalled = true; ++ } ++ ++ public void LogLine(string value) ++ { ++ LogMethodCalledWithArgument = value; ++ LogMethodCalled = true; ++ } ++ ++ ++ public double GetRandom() ++ { ++ return 0.7; ++ } ++ ++ public void SetEnvironmentVariable(string variable, string value) ++ { ++ throw new System.NotImplementedException(); ++ } ++ ++ public void SetExecutionEnvironment(T type) ++ { ++ } ++ ++ public void SetOut(TextWriter writeTo) ++ { ++ ++ } ++} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests.csproj b/libraries/tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests.csproj +deleted file mode 100644 +index 15ac1312..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests.csproj ++++ /dev/null +@@ -1,35 +0,0 @@ +- +- +- +- +- AWS.Lambda.Powertools.Metrics.AspNetCore.Tests +- AWS.Lambda.Powertools.Metrics.AspNetCore.Tests +- net8.0 +- enable +- enable +- +- false +- true +- +- +- +- +- +- +- +- +- +- all +- runtime; build; native; contentfiles; analyzers; buildtransitive +- +- +- all +- runtime; build; native; contentfiles; analyzers; buildtransitive +- +- +- +- +- +- +- +- +diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests/MetricsEndpointExtensionsTests.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests/MetricsEndpointExtensionsTests.cs +deleted file mode 100644 +index 18ef4c2c..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests/MetricsEndpointExtensionsTests.cs ++++ /dev/null +@@ -1,161 +0,0 @@ +-using Amazon.Lambda.TestUtilities; +-using AWS.Lambda.Powertools.Common; +-using AWS.Lambda.Powertools.Metrics.AspNetCore.Http; +-using Microsoft.AspNetCore.Builder; +-using Microsoft.AspNetCore.Http; +-using Microsoft.AspNetCore.TestHost; +-using Microsoft.Extensions.DependencyInjection; +-using NSubstitute; +-using Xunit; +- +-namespace AWS.Lambda.Powertools.Metrics.AspNetCore.Tests; +- +-[Collection("Metrics")] +-public class MetricsEndpointExtensionsTests : IDisposable +-{ +- [Fact] +- public async Task When_WithMetrics_Should_Add_ColdStart() +- { +- // Arrange +- var options = new MetricsOptions +- { +- CaptureColdStart = true, +- Namespace = "TestNamespace" +- }; +- +- var conf = Substitute.For(); +- var consoleWrapper = Substitute.For(); +- var metrics = new Metrics(conf, consoleWrapper: consoleWrapper, options: options); +- var builder = WebApplication.CreateBuilder(); +- builder.Services.AddSingleton(metrics); +- builder.WebHost.UseTestServer(); +- +- var app = builder.Build(); +- +- app.MapGet("/test", () => Results.Ok(new { success = true })).WithMetrics(); +- +- await app.StartAsync(); +- var client = app.GetTestClient(); +- +- // Act +- var response = await client.GetAsync("/test"); +- +- // Assert +- Assert.Equal(200, (int)response.StatusCode); +- +- // Assert metrics calls +- consoleWrapper.Received(1).WriteLine( +- Arg.Is(s => s.Contains("CloudWatchMetrics\":[{\"Namespace\":\"TestNamespace\",\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[[]]}]},\"ColdStart\":1}")) +- ); +- +- +- await app.StopAsync(); +- } +- +- [Fact] +- public async Task When_WithMetrics_Should_Add_ColdStart_Dimensions() +- { +- // Arrange +- var options = new MetricsOptions +- { +- CaptureColdStart = true, +- Namespace = "TestNamespace", +- }; +- +- var conf = Substitute.For(); +- var consoleWrapper = Substitute.For(); +- var metrics = new Metrics(conf, consoleWrapper: consoleWrapper, options: options); +- +- var builder = WebApplication.CreateBuilder(); +- builder.Services.AddSingleton(metrics); +- builder.WebHost.UseTestServer(); +- +- +- var app = builder.Build(); +- app.Use(async (context, next) => +- { +- var lambdaContext = new TestLambdaContext +- { +- FunctionName = "TestFunction" +- }; +- context.Items["LambdaContext"] = lambdaContext; +- await next(); +- }); +- +- app.MapGet("/test", () => Results.Ok(new { success = true })).WithMetrics(); +- +- await app.StartAsync(); +- var client = app.GetTestClient(); +- +- // Act +- var response = await client.GetAsync("/test"); +- +- // Assert +- Assert.Equal(200, (int)response.StatusCode); +- +- // Assert metrics calls +- consoleWrapper.Received(1).WriteLine( +- Arg.Is(s => s.Contains("CloudWatchMetrics\":[{\"Namespace\":\"TestNamespace\",\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"FunctionName\"]]}]},\"FunctionName\":\"TestFunction\",\"ColdStart\":1}")) +- ); +- +- await app.StopAsync(); +- } +- +- [Fact] +- public async Task When_WithMetrics_Should_Add_ColdStart_Default_Dimensions() +- { +- // Arrange +- var options = new MetricsOptions +- { +- CaptureColdStart = true, +- Namespace = "TestNamespace", +- DefaultDimensions = new Dictionary +- { +- { "Environment", "Prod" } +- } +- }; +- +- var conf = Substitute.For(); +- var consoleWrapper = Substitute.For(); +- var metrics = new Metrics(conf, consoleWrapper: consoleWrapper, options: options); +- +- var builder = WebApplication.CreateBuilder(); +- builder.Services.AddSingleton(metrics); +- builder.WebHost.UseTestServer(); +- +- +- var app = builder.Build(); +- app.Use(async (context, next) => +- { +- var lambdaContext = new TestLambdaContext +- { +- FunctionName = "TestFunction" +- }; +- context.Items["LambdaContext"] = lambdaContext; +- await next(); +- }); +- +- app.MapGet("/test", () => Results.Ok(new { success = true })).WithMetrics(); +- +- await app.StartAsync(); +- var client = app.GetTestClient(); +- +- // Act +- var response = await client.GetAsync("/test"); +- +- // Assert +- Assert.Equal(200, (int)response.StatusCode); +- +- // Assert metrics calls +- consoleWrapper.Received(1).WriteLine( +- Arg.Is(s => s.Contains("CloudWatchMetrics\":[{\"Namespace\":\"TestNamespace\",\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Environment\",\"FunctionName\"]]}]},\"Environment\":\"Prod\",\"FunctionName\":\"TestFunction\",\"ColdStart\":1}")) +- ); +- +- await app.StopAsync(); +- } +- +- public void Dispose() +- { +- ColdStartTracker.ResetColdStart(); +- } +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests/MetricsFilterTests.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests/MetricsFilterTests.cs +deleted file mode 100644 +index 9951034a..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests/MetricsFilterTests.cs ++++ /dev/null +@@ -1,76 +0,0 @@ +-using Amazon.Lambda.Core; +-using AWS.Lambda.Powertools.Metrics.AspNetCore.Http; +-using Microsoft.AspNetCore.Http; +-using NSubstitute; +-using Xunit; +- +-namespace AWS.Lambda.Powertools.Metrics.AspNetCore.Tests; +- +-[Collection("Metrics")] +-public class MetricsFilterTests : IDisposable +-{ +- private readonly IMetrics _metrics; +- private readonly EndpointFilterInvocationContext _context; +- +- public MetricsFilterTests() +- { +- ColdStartTracker.ResetColdStart(); // Reset before each test +- _metrics = Substitute.For(); +- _context = Substitute.For(); +- var lambdaContext = Substitute.For(); +- +- var httpContext = new DefaultHttpContext(); +- httpContext.Items["LambdaContext"] = lambdaContext; +- _context.HttpContext.Returns(httpContext); +- } +- +- [Fact] +- public async Task InvokeAsync_Second_Call_DoesNotRecord_ColdStart_Metric() +- { +- // Arrange +- var options = new MetricsOptions { CaptureColdStart = false }; +- _metrics.Options.Returns(options); +- +- var filter = new MetricsFilter(_metrics); +- var next = new EndpointFilterDelegate(_ => ValueTask.FromResult("result")); +- +- // Act +- _ = await filter.InvokeAsync(_context, next); +- var result = await filter.InvokeAsync(_context, next); +- +- // Assert +- _metrics.Received(1).CaptureColdStartMetric(Arg.Any() ); +- Assert.Equal("result", result); +- } +- +- [Fact] +- public async Task InvokeAsync_ShouldCallNextAndContinue() +- { +- // Arrange +- var metrics = Substitute.For(); +- metrics.Options.Returns(new MetricsOptions { CaptureColdStart = true }); +- +- var httpContext = new DefaultHttpContext(); +- var context = new DefaultEndpointFilterInvocationContext(httpContext); +- var filter = new MetricsFilter(metrics); +- +- var called = false; +- EndpointFilterDelegate next = _ => +- { +- called = true; +- return ValueTask.FromResult("result"); +- }; +- +- // Act +- var result = await filter.InvokeAsync(context, next); +- +- // Assert +- Assert.True(called); +- Assert.Equal("result", result); +- } +- +- public void Dispose() +- { +- ColdStartTracker.ResetColdStart(); +- } +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests/MetricsMiddlewareExtensionsTests.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests/MetricsMiddlewareExtensionsTests.cs +deleted file mode 100644 +index 128a5c42..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests/MetricsMiddlewareExtensionsTests.cs ++++ /dev/null +@@ -1,103 +0,0 @@ +-using Amazon.Lambda.TestUtilities; +-using AWS.Lambda.Powertools.Common; +-using AWS.Lambda.Powertools.Metrics.AspNetCore.Http; +-using Microsoft.AspNetCore.Builder; +-using Microsoft.AspNetCore.Http; +-using Microsoft.AspNetCore.TestHost; +-using Microsoft.Extensions.DependencyInjection; +-using NSubstitute; +-using Xunit; +- +-namespace AWS.Lambda.Powertools.Metrics.AspNetCore.Tests; +- +-[Collection("Metrics")] +-public class MetricsMiddlewareExtensionsTests : IDisposable +-{ +- [Fact] +- public async Task When_UseMetrics_Should_Add_ColdStart() +- { +- // Arrange +- var options = new MetricsOptions +- { +- CaptureColdStart = true, +- Namespace = "TestNamespace", +- }; +- +- var conf = Substitute.For(); +- var consoleWrapper = Substitute.For(); +- var metrics = new Metrics(conf, consoleWrapper: consoleWrapper, options: options); +- +- var builder = WebApplication.CreateBuilder(); +- builder.Services.AddSingleton(metrics); +- builder.WebHost.UseTestServer(); +- +- var app = builder.Build(); +- app.UseMetrics(); +- app.MapGet("/test", () => Results.Ok()); +- +- await app.StartAsync(); +- var client = app.GetTestClient(); +- +- // Act +- var response = await client.GetAsync("/test"); +- +- // Assert +- Assert.Equal(200, (int)response.StatusCode); +- consoleWrapper.Received(1).WriteLine( +- Arg.Is(s => s.Contains("CloudWatchMetrics\":[{\"Namespace\":\"TestNamespace\",\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[[]]}]},\"ColdStart\":1}")) +- ); +- +- await app.StopAsync(); +- } +- +- [Fact] +- public async Task When_UseMetrics_Should_Add_ColdStart_With_LambdaContext() +- { +- // Arrange +- var options = new MetricsOptions +- { +- CaptureColdStart = true, +- Namespace = "TestNamespace", +- }; +- +- var conf = Substitute.For(); +- var consoleWrapper = Substitute.For(); +- var metrics = new Metrics(conf, consoleWrapper:consoleWrapper, options: options); +- +- var builder = WebApplication.CreateBuilder(); +- builder.Services.AddSingleton(metrics); +- builder.WebHost.UseTestServer(); +- +- var app = builder.Build(); +- app.Use(async (context, next) => +- { +- var lambdaContext = new TestLambdaContext +- { +- FunctionName = "TestFunction" +- }; +- context.Items["LambdaContext"] = lambdaContext; +- await next(); +- }); +- app.UseMetrics(); +- app.MapGet("/test", () => Results.Ok()); +- +- await app.StartAsync(); +- var client = app.GetTestClient(); +- +- // Act +- var response = await client.GetAsync("/test"); +- +- // Assert +- Assert.Equal(200, (int)response.StatusCode); +- consoleWrapper.Received(1).WriteLine( +- Arg.Is(s => s.Contains("CloudWatchMetrics\":[{\"Namespace\":\"TestNamespace\",\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"FunctionName\"]]}]},\"FunctionName\":\"TestFunction\",\"ColdStart\":1}")) +- ); +- +- await app.StopAsync(); +- } +- +- public void Dispose() +- { +- ColdStartTracker.ResetColdStart(); +- } +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/ClearDimensionsTests.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/ClearDimensionsTests.cs +index 90a3547a..1ac09fbe 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/ClearDimensionsTests.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/ClearDimensionsTests.cs +@@ -13,7 +13,7 @@ public class ClearDimensionsTests + { + // Arrange + var consoleOut = new StringWriter(); +- ConsoleWrapper.SetOut(consoleOut); ++ SystemWrapper.Instance.SetOut(consoleOut); + + // Act + var handler = new FunctionHandler(); +@@ -22,7 +22,7 @@ public class ClearDimensionsTests + var metricsOutput = consoleOut.ToString(); + + // Assert +- Assert.Contains("{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"Metric Name\",\"Unit\":\"Count\"}],\"Dimensions\":[[]]", metricsOutput); ++ Assert.Contains("{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"Metric Name\",\"Unit\":\"Count\"}],\"Dimensions\":[]", metricsOutput); + + // Reset + MetricsAspect.ResetForTest(); +diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/EMFValidationTests.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/EMFValidationTests.cs +index f879de8b..79c275ae 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/EMFValidationTests.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/EMFValidationTests.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.Collections.Generic; + using System.IO; +@@ -20,7 +35,7 @@ namespace AWS.Lambda.Powertools.Metrics.Tests + { + _handler = new FunctionHandler(); + _consoleOut = new CustomConsoleWriter(); +- ConsoleWrapper.SetOut(_consoleOut); ++ SystemWrapper.Instance.SetOut(_consoleOut); + } + + [Trait("Category", value: "SchemaValidation")] +@@ -64,12 +79,12 @@ namespace AWS.Lambda.Powertools.Metrics.Tests + + // flush when it reaches MaxMetrics + Assert.Contains( +- "{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"Metric Name 1\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 2\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 3\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 4\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 5\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 6\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 7\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 8\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 9\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 10\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 11\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 12\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 13\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 14\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 15\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 16\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 17\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 18\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 19\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 20\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 21\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 22\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 23\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 24\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 25\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 26\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 27\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 28\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 29\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 30\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 31\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 32\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 33\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 34\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 35\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 36\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 37\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 38\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 39\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 40\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 41\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 42\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 43\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 44\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 45\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 46\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 47\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 48\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 49\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 50\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 51\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 52\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 53\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 54\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 55\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 56\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 57\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 58\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 59\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 60\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 61\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 62\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 63\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 64\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 65\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 66\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 67\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 68\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 69\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 70\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 71\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 72\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 73\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 74\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 75\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 76\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 77\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 78\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 79\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 80\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 81\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 82\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 83\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 84\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 85\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 86\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 87\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 88\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 89\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 90\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 91\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 92\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 93\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 94\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 95\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 96\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 97\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 98\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 99\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 100\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\"]]", ++ "{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"Metric Name 1\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 2\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 3\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 4\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 5\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 6\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 7\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 8\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 9\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 10\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 11\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 12\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 13\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 14\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 15\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 16\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 17\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 18\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 19\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 20\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 21\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 22\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 23\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 24\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 25\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 26\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 27\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 28\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 29\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 30\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 31\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 32\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 33\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 34\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 35\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 36\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 37\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 38\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 39\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 40\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 41\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 42\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 43\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 44\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 45\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 46\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 47\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 48\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 49\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 50\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 51\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 52\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 53\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 54\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 55\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 56\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 57\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 58\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 59\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 60\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 61\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 62\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 63\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 64\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 65\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 66\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 67\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 68\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 69\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 70\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 71\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 72\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 73\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 74\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 75\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 76\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 77\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 78\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 79\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 80\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 81\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 82\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 83\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 84\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 85\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 86\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 87\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 88\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 89\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 90\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 91\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 92\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 93\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 94\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 95\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 96\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 97\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 98\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 99\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 100\",\"Unit\":\"Count\"}],\"Dimensions\":[\"Service\"]", + metricsOutput[0]); + + // flush the (MaxMetrics + 1) item only + Assert.Contains( +- "{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"Metric Name 101\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\"]]", ++ "{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"Metric Name 101\",\"Unit\":\"Count\"}],\"Dimensions\":[\"Service\"]", + metricsOutput[1]); + } + +@@ -90,15 +105,15 @@ namespace AWS.Lambda.Powertools.Metrics.Tests + metricsOutput[0]); + + // flush the (MaxMetrics + 1) item only +- Assert.Contains("\"Dimensions\":[[\"Service\"]]}]},\"Service\":\"testService\",\"Metric Name\":100}", metricsOutput[1]); ++ Assert.Contains("[\"Service\"]}]},\"Service\":\"testService\",\"Metric Name\":100}", metricsOutput[1]); + } + + [Trait("Category", "EMFLimits")] + [Fact] +- public void WhenMoreThan29DimensionsAdded_ThrowArgumentOutOfRangeException() ++ public void WhenMoreThan9DimensionsAdded_ThrowArgumentOutOfRangeException() + { + // Act +- var act = () => { _handler.MaxDimensions(29); }; ++ var act = () => { _handler.MaxDimensions(9); }; + + // Assert + Assert.Throws(act); +@@ -126,7 +141,7 @@ namespace AWS.Lambda.Powertools.Metrics.Tests + var metricsOutput = _consoleOut.ToString(); + + // Assert +- Assert.Contains("\"Dimensions\":[[\"Service\",\"functionVersion\"]]" ++ Assert.Contains("\"Dimensions\":[\"Service\",\"functionVersion\"]" + , metricsOutput); + Assert.Contains("\"Service\":\"testService\",\"functionVersion\":\"$LATEST\"" + , metricsOutput); +@@ -142,71 +157,11 @@ namespace AWS.Lambda.Powertools.Metrics.Tests + var metricsOutput = _consoleOut.ToString(); + + // Assert +- Assert.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\"]]}]},\"Service\":\"ServiceName\",\"ColdStart\":1}", metricsOutput); +- Assert.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"SingleMetric1\",\"Unit\":\"Count\",\"StorageResolution\":1}],\"Dimensions\":[[\"Default1\"]]}]},\"Default1\":\"SingleMetric1\",\"SingleMetric1\":1}", metricsOutput); +- Assert.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"ns2\",\"Metrics\":[{\"Name\":\"SingleMetric2\",\"Unit\":\"Count\",\"StorageResolution\":1}],\"Dimensions\":[[\"Default1\",\"Default2\"]]}]},\"Default1\":\"SingleMetric2\",\"Default2\":\"SingleMetric2\",\"SingleMetric2\":1}", metricsOutput); +- Assert.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"AddMetric\",\"Unit\":\"Count\",\"StorageResolution\":1},{\"Name\":\"AddMetric2\",\"Unit\":\"Count\",\"StorageResolution\":1}],\"Dimensions\":[[\"Service\"]]}]},\"Service\":\"ServiceName\",\"AddMetric\":1,\"AddMetric2\":1}", metricsOutput); +- } +- +- [Trait("Category", "SchemaValidation")] +- [Fact] +- public void When_PushSingleMetric_With_Namespace() +- { +- // Act +- _handler.PushSingleMetricWithNamespace(); +- +- var metricsOutput = _consoleOut.ToString(); +- +- // Assert +- Assert.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"ExampleApplication\",\"Metrics\":[{\"Name\":\"SingleMetric\",\"Unit\":\"Count\",\"StorageResolution\":1}],\"Dimensions\":[[\"Default\"]]}]},\"Default\":\"SingleMetric\",\"SingleMetric\":1}", metricsOutput); +- } +- +- [Trait("Category", "SchemaValidation")] +- [Fact] +- public void When_PushSingleMetric_With_No_DefaultDimensions() +- { +- // Act +- _handler.PushSingleMetricNoDefaultDimensions(); +- +- var metricsOutput = _consoleOut.ToString(); +- +- // Assert +- Assert.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"ExampleApplication\",\"Metrics\":[{\"Name\":\"SingleMetric\",\"Unit\":\"Count\"}],\"Dimensions\":[[]]}]},\"SingleMetric\":1}", metricsOutput); +- } +- +- [Trait("Category", "SchemaValidation")] +- [Fact] +- public void When_PushSingleMetric_With_DefaultDimensions() +- { +- // Act +- _handler.PushSingleMetricDefaultDimensions(); +- +- var metricsOutput = _consoleOut.ToString(); +- +- // Assert +- Assert.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"ExampleApplication\",\"Metrics\":[{\"Name\":\"SingleMetric\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Default\"]]}]},\"Default\":\"SingleMetric\",\"SingleMetric\":1}", metricsOutput); +- } +- +- [Trait("Category", "SchemaValidation")] +- [Fact] +- public void When_PushSingleMetric_With_Env_Namespace() +- { +- // Arrange +- Environment.SetEnvironmentVariable("POWERTOOLS_METRICS_NAMESPACE", "EnvNamespace"); ++ Assert.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"ns1\",\"Metrics\":[{\"Name\":\"Lambda Execute\",\"Unit\":\"Count\",\"StorageResolution\":1}],\"Dimensions\":[\"Type\",\"Service\"]}]},\"Type\":\"Start\",\"Service\":\"service_undefined\",\"Lambda Execute\":1}", metricsOutput); + +- // Act +- _handler.PushSingleMetricWithEnvNamespace(); +- +- var metricsOutput = _consoleOut.ToString(); +- +- // Assert +- Assert.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"EnvNamespace\",\"Metrics\":[{\"Name\":\"SingleMetric\",\"Unit\":\"Count\",\"StorageResolution\":1}],\"Dimensions\":[[\"Default\"]]}]},\"Default\":\"SingleMetric\",\"SingleMetric\":1}", metricsOutput); +- +- // assert with different service name +- Assert.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"EnvNamespace\",\"Metrics\":[{\"Name\":\"SingleMetric2\",\"Unit\":\"Count\",\"StorageResolution\":1}],\"Dimensions\":[[\"Service\",\"Default\"]]}]},\"Service\":\"service1\",\"Default\":\"SingleMetric\",\"SingleMetric2\":1}", metricsOutput); ++ Assert.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"ns2\",\"Metrics\":[{\"Name\":\"Lambda Execute\",\"Unit\":\"Count\",\"StorageResolution\":1}],\"Dimensions\":[\"Type\",\"SessionId\",\"Service\"]}]},\"Type\":\"Start\",\"SessionId\":\"Unset\",\"Service\":\"service_undefined\",\"Lambda Execute\":1}", metricsOutput); + +- // assert with different service name +- Assert.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"EnvNamespace\",\"Metrics\":[{\"Name\":\"SingleMetric3\",\"Unit\":\"Count\",\"StorageResolution\":1}],\"Dimensions\":[[\"Service\",\"Default\"]]}]},\"Service\":\"service2\",\"Default\":\"SingleMetric\",\"SingleMetric3\":1}", metricsOutput); ++ Assert.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"Lambda Execute\",\"Unit\":\"Count\",\"StorageResolution\":1}],\"Dimensions\":[\"Service\",\"Default\",\"SessionId\",\"Type\"]}]},\"Service\":\"testService\",\"Default\":\"Initial\",\"SessionId\":\"MySessionId\",\"Type\":\"Start\",\"Lambda Execute\":1}", metricsOutput); + } + + [Trait("Category", "MetricsImplementation")] +@@ -218,7 +173,7 @@ namespace AWS.Lambda.Powertools.Metrics.Tests + + var metricsOutput = _consoleOut.ToString(); + +- var result = Metrics.Instance.Options.Namespace; ++ var result = Metrics.GetNamespace(); + + // Assert + Assert.Equal("dotnet-powertools-test", result); +@@ -254,7 +209,7 @@ namespace AWS.Lambda.Powertools.Metrics.Tests + var result = _consoleOut.ToString(); + + // Assert +- Assert.Contains($"\"Dimensions\":[[\"Service\",\"{key}\"]]", result); ++ Assert.Contains($"\"Dimensions\":[\"Service\",\"{key}\"]", result); + Assert.Contains($"\"CustomDefaultDimension\":\"{value}\"", result); + } + +@@ -291,7 +246,7 @@ namespace AWS.Lambda.Powertools.Metrics.Tests + var result = _consoleOut.ToString(); + + // Assert +- Assert.Contains("\"Dimensions\":[[\"Service\",\"CustomDefaultDimension\"]]", result); ++ Assert.Contains("\"Dimensions\":[\"Service\",\"CustomDefaultDimension\"]", result); + Assert.Contains("\"CustomDefaultDimension\":\"CustomDefaultDimensionValue\"", result); + } + +@@ -305,7 +260,7 @@ namespace AWS.Lambda.Powertools.Metrics.Tests + + // Assert + Assert.Contains( +- "CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"Time\",\"Unit\":\"Milliseconds\"}],\"Dimensions\":[[\"Service\",\"functionVersion\"]]}]},\"Service\":\"testService\",\"functionVersion\":\"$LATEST\",\"Time\":100.7,\"env\":\"dev\"}" ++ "CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"Time\",\"Unit\":\"Milliseconds\"}],\"Dimensions\":[\"Service\",\"functionVersion\"]}]},\"Service\":\"testService\",\"functionVersion\":\"$LATEST\",\"Time\":100.7,\"env\":\"dev\"}" + , result); + } + +@@ -320,7 +275,7 @@ namespace AWS.Lambda.Powertools.Metrics.Tests + // Assert + // No Metadata key was added + Assert.Contains( +- "CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"Time\",\"Unit\":\"Milliseconds\"}],\"Dimensions\":[[\"Service\",\"functionVersion\"]]}]},\"Service\":\"testService\",\"functionVersion\":\"$LATEST\",\"Time\":100.7}" ++ "CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"Time\",\"Unit\":\"Milliseconds\"}],\"Dimensions\":[\"Service\",\"functionVersion\"]}]},\"Service\":\"testService\",\"functionVersion\":\"$LATEST\",\"Time\":100.7}" + , result); + } + +@@ -382,99 +337,9 @@ namespace AWS.Lambda.Powertools.Metrics.Tests + + // Assert + Assert.Contains( +- "{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"Metric Name\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\"]]", ++ "{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"Metric Name\",\"Unit\":\"Count\"}],\"Dimensions\":[\"Service\"]", + metricsOutput); + } +- +- [Trait("Category", "MetricsImplementation")] +- [Fact] +- public void AddDimensions_WithMultipleValues_AddsDimensionsToSameDimensionSet() +- { +- // Act +- _handler.AddMultipleDimensionsInSameSet(); +- +- var result = _consoleOut.ToString(); +- +- // Assert +- Assert.Contains("\"Dimensions\":[[\"Service\",\"Environment\",\"Region\"]]", result); +- Assert.Contains("\"Service\":\"testService\",\"Environment\":\"test\",\"Region\":\"us-west-2\"", result); +- } +- +- [Trait("Category", "MetricsImplementation")] +- [Fact] +- public void AddDimensions_WithEmptyArray_DoesNotAddAnyDimensions() +- { +- // Act +- _handler.AddEmptyDimensions(); +- +- var result = _consoleOut.ToString(); +- +- // Assert +- Assert.Contains("\"Dimensions\":[[\"Service\"]]", result); +- Assert.DoesNotContain("\"Environment\":", result); +- } +- +- [Trait("Category", "MetricsImplementation")] +- [Fact] +- public void AddDimensions_WithNullOrEmptyKey_ThrowsArgumentNullException() +- { +- // Act & Assert +- Assert.Throws(() => _handler.AddDimensionsWithInvalidKey()); +- } +- +- [Trait("Category", "MetricsImplementation")] +- [Fact] +- public void AddDimensions_WithNullOrEmptyValue_ThrowsArgumentNullException() +- { +- // Act & Assert +- Assert.Throws(() => _handler.AddDimensionsWithInvalidValue()); +- } +- +- [Trait("Category", "MetricsImplementation")] +- [Fact] +- public void AddDimensions_OverwritesExistingDimensions_LastValueWins() +- { +- // Act +- _handler.AddDimensionsWithOverwrite(); +- +- var result = _consoleOut.ToString(); +- +- // Assert +- Assert.Contains("\"Service\":\"testService\",\"dimension1\":\"B\",\"dimension2\":\"2\"", result); +- Assert.DoesNotContain("\"dimension1\":\"A\"", result); +- } +- +- [Trait("Category", "MetricsImplementation")] +- [Fact] +- public void AddDimensions_IncludesDefaultDimensions() +- { +- // Act +- _handler.AddDimensionsWithDefaultDimensions(); +- +- var result = _consoleOut.ToString(); +- +- // Assert +- Assert.Contains("\"Dimensions\":[[\"Service\",\"environment\",\"dimension1\",\"dimension2\"]]", result); +- Assert.Contains("\"Service\":\"testService\",\"environment\":\"prod\",\"dimension1\":\"1\",\"dimension2\":\"2\"", result); +- } +- +- [Trait("Category", "MetricsImplementation")] +- [Fact] +- public void AddDefaultDimensionsAtRuntime_OnlyAppliedToNewDimensionSets() +- { +- // Act +- _handler.AddDefaultDimensionsAtRuntime(); +- +- var result = _consoleOut.ToString(); +- +- // First metric output should have original default dimensions +- Assert.Contains("\"Metrics\":[{\"Name\":\"FirstMetric\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\",\"environment\",\"dimension1\",\"dimension2\"]]", result); +- Assert.Contains("\"Service\":\"testService\",\"environment\":\"prod\",\"dimension1\":\"1\",\"dimension2\":\"2\",\"FirstMetric\":1", result); +- +- // Second metric output should have additional default dimensions +- Assert.Contains("\"Metrics\":[{\"Name\":\"SecondMetric\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\",\"environment\",\"tenantId\",\"foo\",\"bar\"]]", result); +- Assert.Contains("\"Service\":\"testService\",\"environment\":\"prod\",\"tenantId\":\"1\",\"foo\":\"1\",\"bar\":\"2\",\"SecondMetric\":1", result); +- } + + + #region Helpers +@@ -500,9 +365,7 @@ namespace AWS.Lambda.Powertools.Metrics.Tests + public void Dispose() + { + // need to reset instance after each test +- Metrics.ResetForTest(); + MetricsAspect.ResetForTest(); +- Environment.SetEnvironmentVariable("POWERTOOLS_METRICS_NAMESPACE", null); + } + } + } +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/DefaultDimensionsHandler.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/DefaultDimensionsHandler.cs +deleted file mode 100644 +index 95e6a9f3..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/DefaultDimensionsHandler.cs ++++ /dev/null +@@ -1,60 +0,0 @@ +-using System.Collections.Generic; +-using Amazon.Lambda.Core; +- +-namespace AWS.Lambda.Powertools.Metrics.Tests.Handlers; +- +-public class DefaultDimensionsHandler +-{ +- public DefaultDimensionsHandler() +- { +- Metrics.Configure(options => +- { +- options.Namespace = "dotnet-powertools-test"; +- options.Service = "testService"; +- options.CaptureColdStart = true; +- options.DefaultDimensions = new Dictionary +- { +- { "Environment", "Prod" }, +- { "Another", "One" } +- }; +- }); +- } +- +- [Metrics] +- public void Handler() +- { +- // Default dimensions are already set +- Metrics.AddMetric("SuccessfulBooking", 1, MetricUnit.Count); +- } +- +- [Metrics] +- public void HandlerWithContext(ILambdaContext context) +- { +- // Default dimensions are already set and adds FunctionName dimension +- Metrics.AddMetric("Memory", 10, MetricUnit.Megabytes); +- } +-} +- +-public class MetricsDependencyInjectionOptionsHandler +-{ +- private readonly IMetrics _metrics; +- +- // Allow injection of IMetrics for testing +- public MetricsDependencyInjectionOptionsHandler(IMetrics metrics = null) +- { +- _metrics = metrics ?? Metrics.Configure(options => +- { +- options.DefaultDimensions = new Dictionary +- { +- { "Environment", "Prod" }, +- { "Another", "One" } +- }; +- }); +- } +- +- [Metrics(Namespace = "dotnet-powertools-test", Service = "testService", CaptureColdStart = true)] +- public void Handler() +- { +- _metrics.AddMetric("SuccessfulBooking", 1, MetricUnit.Count); +- } +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandler.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandler.cs +index da949a78..e29e99a9 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandler.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandler.cs +@@ -1,9 +1,26 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; ++using System.Collections; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using System.Threading.Tasks; + using Amazon.Lambda.Core; ++using Amazon.Lambda.TestUtilities; + + namespace AWS.Lambda.Powertools.Metrics.Tests.Handlers; + +@@ -22,67 +39,25 @@ public class FunctionHandler + Metrics.AddMetric("TestMetric", 1, MetricUnit.Count); + } + +- [Metrics(Namespace = "dotnet-powertools-test", Service = "ServiceName", CaptureColdStart = true)] ++ [Metrics(Namespace = "dotnet-powertools-test", Service = "testService")] + public void AddMultipleDimensions() + { +- Metrics.PushSingleMetric("SingleMetric1", 1, MetricUnit.Count, resolution: MetricResolution.High, +- dimensions: new Dictionary { +- { "Default1", "SingleMetric1" } +- }); +- +- Metrics.PushSingleMetric("SingleMetric2", 1, MetricUnit.Count, resolution: MetricResolution.High, nameSpace: "ns2", +- dimensions: new Dictionary { +- { "Default1", "SingleMetric2" }, +- { "Default2", "SingleMetric2" } +- }); +- Metrics.AddMetric("AddMetric", 1, MetricUnit.Count, MetricResolution.High); +- Metrics.AddMetric("AddMetric2", 1, MetricUnit.Count, MetricResolution.High); +- } +- +- [Metrics(Namespace = "ExampleApplication")] +- public void PushSingleMetricWithNamespace() +- { +- Metrics.PushSingleMetric("SingleMetric", 1, MetricUnit.Count, resolution: MetricResolution.High, +- dimensions: new Dictionary { +- { "Default", "SingleMetric" } +- }); +- } +- +- [Metrics(Namespace = "ExampleApplication")] +- public void PushSingleMetricNoDefaultDimensions() +- { +- Metrics.PushSingleMetric("SingleMetric", 1, MetricUnit.Count); +- } +- +- [Metrics(Namespace = "ExampleApplication")] +- public void PushSingleMetricDefaultDimensions() +- { +- Metrics.SetDefaultDimensions(new Dictionary +- { +- { "Default", "SingleMetric" } ++ Metrics.SetDefaultDimensions(new Dictionary { ++ { "Default", "Initial" } + }); +- Metrics.PushSingleMetric("SingleMetric", 1, MetricUnit.Count, dimensions: Metrics.DefaultDimensions ); +- } +- +- [Metrics] +- public void PushSingleMetricWithEnvNamespace() +- { +- Metrics.PushSingleMetric("SingleMetric", 1, MetricUnit.Count, resolution: MetricResolution.High, +- dimensions: new Dictionary { +- { "Default", "SingleMetric" } ++ Metrics.PushSingleMetric("Lambda Execute", 1, MetricUnit.Count, metricResolution: MetricResolution.High, nameSpace: "ns1", ++ defaultDimensions: new Dictionary { ++ { "Type", "Start" } + }); + +- Metrics.PushSingleMetric("SingleMetric2", 1, MetricUnit.Count, resolution: MetricResolution.High, +- service: "service1", +- dimensions: new Dictionary { +- { "Default", "SingleMetric" } +- }); +- +- Metrics.PushSingleMetric("SingleMetric3", 1, MetricUnit.Count, resolution: MetricResolution.High, +- service: "service2", +- dimensions: new Dictionary { +- { "Default", "SingleMetric" } ++ Metrics.PushSingleMetric("Lambda Execute", 1, MetricUnit.Count, metricResolution: MetricResolution.High, nameSpace: "ns2", ++ defaultDimensions: new Dictionary { ++ { "Type", "Start" }, ++ { "SessionId", "Unset" } + }); ++ Metrics.AddMetric("Lambda Execute", 1, MetricUnit.Count, MetricResolution.High); ++ Metrics.AddDimension("SessionId", "MySessionId"); ++ Metrics.AddDimension("Type", "Start"); + } + + [Metrics(Namespace = "dotnet-powertools-test", Service = "testService")] +@@ -225,138 +200,4 @@ public class FunctionHandler + { + + } +- +- [Metrics(Namespace = "ns", Service = "svc", RaiseOnEmptyMetrics = true)] +- public void HandlerRaiseOnEmptyMetrics() +- { +- +- } +- +- [Metrics(Namespace = "ns", Service = "svc", CaptureColdStart = true)] +- public void HandleOnlyDimensionsInColdStart(ILambdaContext context) +- { +- Metrics.AddMetric("MyMetric", 1); +- } +- +- [Metrics(Namespace = "ns", Service = "svc", CaptureColdStart = true, FunctionName = "MyFunction")] +- public void HandleFunctionNameWithContext(ILambdaContext context) +- { +- +- } +- +- [Metrics(Namespace = "ns", Service = "svc", CaptureColdStart = true, FunctionName = "MyFunction")] +- public void HandleFunctionNameNoContext() +- { +- +- } +- +- [Metrics(Namespace = "dotnet-powertools-test", Service = "testService", CaptureColdStart = true)] +- public void AddMultipleDimensionsInSameSet() +- { +- // Add multiple dimensions at once +- Metrics.AddDimensions( +- ("Environment", "test"), +- ("Region", "us-west-2") +- ); +- +- Metrics.AddMetric("TestMetric", 1.0, MetricUnit.Count); +- } +- +- [Metrics(Namespace = "dotnet-powertools-test", Service = "testService", CaptureColdStart = true)] +- public void AddEmptyDimensions() +- { +- // Add empty dimensions array +- Metrics.AddDimensions(); +- +- Metrics.AddMetric("TestMetric", 1.0, MetricUnit.Count); +- } +- +- [Metrics(Namespace = "dotnet-powertools-test", Service = "testService", CaptureColdStart = true)] +- public void AddDimensionsWithInvalidKey() +- { +- // Add dimension with null key +- Metrics.AddDimensions(("", "value")); +- } +- +- [Metrics(Namespace = "dotnet-powertools-test", Service = "testService", CaptureColdStart = true)] +- public void AddDimensionsWithInvalidValue() +- { +- // Add dimension with null value +- Metrics.AddDimensions(("key", "")); +- } +- +- public void AddDimensionsWithOverwrite() +- { +- Metrics.SetNamespace("dotnet-powertools-test"); +- Metrics.SetService("testService"); +- +- // Add single dimension +- Metrics.AddDimension("dimension1", "A"); +- +- // Then add multiple dimensions, including the same key +- Metrics.AddDimensions( +- ("dimension1", "B"), +- ("dimension2", "2") +- ); +- +- Metrics.AddMetric("TestMetric", 1.0, MetricUnit.Count); +- Metrics.Flush(); +- } +- +- public void AddDimensionsWithDefaultDimensions() +- { +- Metrics.SetNamespace("dotnet-powertools-test"); +- Metrics.SetService("testService"); +- +- // Set default dimensions +- Metrics.SetDefaultDimensions(new Dictionary +- { +- { "environment", "prod" } +- }); +- +- // Add multiple dimensions +- Metrics.AddDimensions( +- ("dimension1", "1"), +- ("dimension2", "2") +- ); +- +- Metrics.AddMetric("TestMetric", 1.0, MetricUnit.Count); +- Metrics.Flush(); +- } +- +- public void AddDefaultDimensionsAtRuntime() +- { +- Metrics.SetNamespace("dotnet-powertools-test"); +- Metrics.SetService("testService"); +- +- // Set initial default dimensions +- Metrics.SetDefaultDimensions(new Dictionary +- { +- { "environment", "prod" } +- }); +- +- // Add first set of dimensions +- Metrics.AddDimensions( +- ("dimension1", "1"), +- ("dimension2", "2") +- ); +- Metrics.AddMetric("FirstMetric", 1.0, MetricUnit.Count); +- Metrics.Flush(); +- +- // Add more default dimensions +- Metrics.SetDefaultDimensions(new Dictionary +- { +- { "environment", "prod" }, +- { "tenantId", "1" } +- }); +- +- // Add second set of dimensions +- Metrics.AddDimensions( +- ("foo", "1"), +- ("bar", "2") +- ); +- Metrics.AddMetric("SecondMetric", 1.0, MetricUnit.Count); +- +- Metrics.Flush(); +- } + } +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandlerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandlerTests.cs +index 799aefdb..dd140b40 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandlerTests.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandlerTests.cs +@@ -1,10 +1,22 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; +-using System.Collections.Generic; + using System.Threading.Tasks; +-using Amazon.Lambda.Core; + using Amazon.Lambda.TestUtilities; + using AWS.Lambda.Powertools.Common; +-using NSubstitute; + using Xunit; + + namespace AWS.Lambda.Powertools.Metrics.Tests.Handlers; +@@ -14,35 +26,38 @@ public class FunctionHandlerTests : IDisposable + { + private readonly FunctionHandler _handler; + private readonly CustomConsoleWriter _consoleOut; +- ++ + public FunctionHandlerTests() + { + _handler = new FunctionHandler(); + _consoleOut = new CustomConsoleWriter(); +- ConsoleWrapper.SetOut(_consoleOut); ++ SystemWrapper.Instance.SetOut(_consoleOut); + } +- ++ + [Fact] + public async Task When_Metrics_Add_Metadata_Same_Key_Should_Ignore_Metadata() + { ++ // Arrange ++ ++ + // Act +- var exception = await Record.ExceptionAsync(() => _handler.HandleSameKey("whatever")); +- ++ var exception = await Record.ExceptionAsync( () => _handler.HandleSameKey("whatever")); ++ + // Assert + Assert.Null(exception); + } +- ++ + [Fact] + public async Task When_Metrics_Add_Metadata_Second_Invocation_Should_Not_Throw_Exception() + { + // Act +- var exception = await Record.ExceptionAsync(() => _handler.HandleTestSecondCall("whatever")); ++ var exception = await Record.ExceptionAsync( () => _handler.HandleTestSecondCall("whatever")); + Assert.Null(exception); +- +- exception = await Record.ExceptionAsync(() => _handler.HandleTestSecondCall("whatever")); ++ ++ exception = await Record.ExceptionAsync( () => _handler.HandleTestSecondCall("whatever")); + Assert.Null(exception); + } +- ++ + [Fact] + public async Task When_Metrics_Add_Metadata_FromMultipleThread_Should_Not_Throw_Exception() + { +@@ -50,7 +65,7 @@ public class FunctionHandlerTests : IDisposable + var exception = await Record.ExceptionAsync(() => _handler.HandleMultipleThreads("whatever")); + Assert.Null(exception); + } +- ++ + [Fact] + public void When_LambdaContext_Should_Add_FunctioName_Dimension_CaptureColdStart() + { +@@ -59,21 +74,21 @@ public class FunctionHandlerTests : IDisposable + { + FunctionName = "My Function with context" + }; +- ++ + // Act + _handler.HandleWithLambdaContext(context); + var metricsOutput = _consoleOut.ToString(); +- ++ + // Assert + Assert.Contains( + "\"FunctionName\":\"My Function with context\"", + metricsOutput); +- ++ + Assert.Contains( +- "\"CloudWatchMetrics\":[{\"Namespace\":\"ns\",\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\",\"FunctionName\"]]}]},\"Service\":\"svc\",\"FunctionName\":\"My Function with context\",\"ColdStart\":1}", ++ "\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[\"FunctionName\",\"Service\"]}]}", + metricsOutput); + } +- ++ + [Fact] + public void When_LambdaContext_And_Parameter_Should_Add_FunctioName_Dimension_CaptureColdStart() + { +@@ -82,326 +97,41 @@ public class FunctionHandlerTests : IDisposable + { + FunctionName = "My Function with context" + }; +- ++ + // Act +- _handler.HandleWithParamAndLambdaContext("Hello", context); ++ _handler.HandleWithParamAndLambdaContext("Hello",context); + var metricsOutput = _consoleOut.ToString(); +- ++ + // Assert + Assert.Contains( + "\"FunctionName\":\"My Function with context\"", + metricsOutput); +- ++ + Assert.Contains( +- "\"CloudWatchMetrics\":[{\"Namespace\":\"ns\",\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\",\"FunctionName\"]]}]},\"Service\":\"svc\",\"FunctionName\":\"My Function with context\",\"ColdStart\":1}", ++ "\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[\"FunctionName\",\"Service\"]}]}", + metricsOutput); + } +- ++ + [Fact] + public void When_No_LambdaContext_Should_Not_Add_FunctioName_Dimension_CaptureColdStart() + { + // Act + _handler.HandleColdStartNoContext(); + var metricsOutput = _consoleOut.ToString(); +- ++ + // Assert + Assert.DoesNotContain( + "\"FunctionName\"", + metricsOutput); +- +- Assert.Contains( +- "\"Metrics\":[{\"Name\":\"MyMetric\",\"Unit\":\"None\"}],\"Dimensions\":[[\"Service\"]]}]},\"Service\":\"svc\",\"MyMetric\":1}", +- metricsOutput); +- } +- +- [Fact] +- public void DefaultDimensions_AreAppliedCorrectly() +- { +- // Arrange +- var handler = new DefaultDimensionsHandler(); +- +- // Act +- handler.Handler(); +- +- // Get the output and parse it +- var metricsOutput = _consoleOut.ToString(); +- +- // Assert cold start +- Assert.Contains( +- "\"CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\",\"Environment\",\"Another\"]]}]},\"Service\":\"testService\",\"Environment\":\"Prod\",\"Another\":\"One\",\"ColdStart\":1}", +- metricsOutput); +- // Assert successful booking metrics +- Assert.Contains( +- "\"CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"SuccessfulBooking\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\",\"Environment\",\"Another\"]]}]},\"Service\":\"testService\",\"Environment\":\"Prod\",\"Another\":\"One\",\"SuccessfulBooking\":1}", +- metricsOutput); +- } +- +- [Fact] +- public void DefaultDimensions_AreAppliedCorrectly_WithContext_FunctionName() +- { +- // Arrange +- var handler = new DefaultDimensionsHandler(); +- +- // Act +- handler.HandlerWithContext(new TestLambdaContext +- { +- FunctionName = "My_Function_Name" +- }); +- +- // Get the output and parse it +- var metricsOutput = _consoleOut.ToString(); +- +- // Assert cold start +- Assert.Contains( +- "\"CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\",\"Environment\",\"Another\",\"FunctionName\"]]}]},\"Service\":\"testService\",\"Environment\":\"Prod\",\"Another\":\"One\",\"FunctionName\":\"My_Function_Name\",\"ColdStart\":1}", +- metricsOutput); +- // Assert successful Memory metrics +- Assert.Contains( +- "\"CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"Memory\",\"Unit\":\"Megabytes\"}],\"Dimensions\":[[\"Service\",\"Environment\",\"Another\"]]}]},\"Service\":\"testService\",\"Environment\":\"Prod\",\"Another\":\"One\",\"Memory\":10}", +- metricsOutput); +- } +- +- [Fact] +- public void Handler_WithMockedMetrics_ShouldCallAddMetric() +- { +- // Arrange +- var metricsMock = Substitute.For(); +- +- metricsMock.Options.Returns(new MetricsOptions +- { +- CaptureColdStart = true, +- Namespace = "dotnet-powertools-test", +- Service = "testService", +- DefaultDimensions = new Dictionary +- { +- { "Environment", "Prod" }, +- { "Another", "One" } +- } +- }); +- +- Metrics.UseMetricsForTests(metricsMock); +- +- +- var sut = new MetricsDependencyInjectionOptionsHandler(metricsMock); +- +- // Act +- sut.Handler(); +- +- // Assert +- metricsMock.Received(1).CaptureColdStartMetric(Arg.Any()); +- metricsMock.Received(1).AddMetric("SuccessfulBooking", 1, MetricUnit.Count); +- } +- +- [Fact] +- public void Handler_With_Builder_Should_Configure_In_Constructor() +- { +- // Arrange +- var handler = new MetricsnBuilderHandler(); +- +- // Act +- handler.Handler(new TestLambdaContext +- { +- FunctionName = "My_Function_Name" +- }); +- +- // Get the output and parse it +- var metricsOutput = _consoleOut.ToString(); +- +- // Assert cold start +- Assert.Contains( +- "\"CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\",\"Environment\",\"Another\",\"FunctionName\"]]}]},\"Service\":\"testService\",\"Environment\":\"Prod1\",\"Another\":\"One\",\"FunctionName\":\"My_Function_Name\",\"ColdStart\":1}", +- metricsOutput); +- // Assert successful Memory metrics +- Assert.Contains( +- "\"CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"SuccessfulBooking\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\",\"Environment\",\"Another\"]]}]},\"Service\":\"testService\",\"Environment\":\"Prod1\",\"Another\":\"One\",\"SuccessfulBooking\":1}", +- metricsOutput); +- } +- +- [Fact] +- public void Handler_With_Builder_Should_Configure_In_Constructor_Mock() +- { +- var metricsMock = Substitute.For(); +- +- metricsMock.Options.Returns(new MetricsOptions +- { +- CaptureColdStart = true, +- Namespace = "dotnet-powertools-test", +- Service = "testService", +- DefaultDimensions = new Dictionary +- { +- { "Environment", "Prod" }, +- { "Another", "One" } +- } +- }); +- +- Metrics.UseMetricsForTests(metricsMock); +- +- var sut = new MetricsnBuilderHandler(metricsMock); +- +- // Act +- sut.Handler(new TestLambdaContext +- { +- FunctionName = "My_Function_Name" +- }); +- +- metricsMock.Received(1).CaptureColdStartMetric(Arg.Any()); +- metricsMock.Received(1).AddMetric("SuccessfulBooking", 1, MetricUnit.Count); +- } +- +- [Fact] +- public void When_RaiseOnEmptyMetrics_And_NoMetrics_Should_ThrowException() +- { +- // Act & Assert +- var exception = Assert.Throws(() => _handler.HandlerRaiseOnEmptyMetrics()); +- Assert.Equal("No metrics have been provided.", exception.Message); +- } +- +- [Fact] +- public void Handler_With_Builder_Should_Raise_Empty_Metrics() +- { +- // Arrange +- var handler = new MetricsnBuilderHandler(); +- +- // Act & Assert +- var exception = Assert.Throws(() => handler.HandlerEmpty()); +- Assert.Equal("No metrics have been provided.", exception.Message); +- } +- +- [Fact] +- public void Handler_With_Builder_Push_Single_Metric_No_Dimensions() +- { +- // Arrange +- var handler = new MetricsnBuilderHandler(); +- +- // Act +- handler.HandlerSingleMetric(); +- +- // Get the output and parse it +- var metricsOutput = _consoleOut.ToString(); +- +- Assert.Contains( +- "\"CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"SuccessfulBooking\",\"Unit\":\"Count\"}],\"Dimensions\":[[]]}]},\"SuccessfulBooking\":1}", +- metricsOutput); +- } +- +- [Fact] +- public void Handler_With_Builder_Push_Single_Metric_Dimensions() +- { +- // Arrange +- var handler = new MetricsnBuilderHandler(); +- +- // Act +- handler.HandlerSingleMetricDimensions(); +- +- // Get the output and parse it +- var metricsOutput = _consoleOut.ToString(); +- +- Assert.Contains( +- "\"CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"SuccessfulBooking\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\",\"Environment\",\"Another\"]]}]},\"Service\":\"testService\",\"Environment\":\"Prod1\",\"Another\":\"One\",\"SuccessfulBooking\":1}", +- metricsOutput); +- } +- +- [Fact] +- public void Dimension_Only_Set_In_Cold_Start() +- { +- // Arrange +- var handler = new FunctionHandler(); +- +- // Act +- handler.HandleOnlyDimensionsInColdStart(new TestLambdaContext +- { +- FunctionName = "My_Function_Name" +- }); +- +- // Get the output and parse it +- var metricsOutput = _consoleOut.ToString(); +- +- // Assert cold start +- Assert.Contains( +- "\"CloudWatchMetrics\":[{\"Namespace\":\"ns\",\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\",\"FunctionName\"]]}]},\"Service\":\"svc\",\"FunctionName\":\"My_Function_Name\",\"ColdStart\":1}", +- metricsOutput); +- +- // Assert successful add metric without dimensions +- Assert.Contains( +- "\"CloudWatchMetrics\":[{\"Namespace\":\"ns\",\"Metrics\":[{\"Name\":\"MyMetric\",\"Unit\":\"None\"}],\"Dimensions\":[[\"Service\"]]}]},\"Service\":\"svc\",\"MyMetric\":1}", +- metricsOutput); +- } +- +- [Fact] +- public void When_Function_Name_Is_Set() +- { +- // Arrange +- var handler = new FunctionHandler(); +- +- // Act +- handler.HandleFunctionNameWithContext(new TestLambdaContext +- { +- FunctionName = "This_Will_Be_Overwritten" +- }); +- +- // Get the output and parse it +- var metricsOutput = _consoleOut.ToString(); + +- // Assert cold start function name is set MyFunction + Assert.Contains( +- "\"CloudWatchMetrics\":[{\"Namespace\":\"ns\",\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\",\"FunctionName\"]]}]},\"Service\":\"svc\",\"FunctionName\":\"MyFunction\",\"ColdStart\":1}", ++ "\"Metrics\":[{\"Name\":\"MyMetric\",\"Unit\":\"None\"}],\"Dimensions\":[\"Service\"]}]},\"Service\":\"svc\",\"MyMetric\":1}", + metricsOutput); + } +- +- [Fact] +- public void When_Function_Name_Is_Set_No_Context() +- { +- // Arrange +- var handler = new FunctionHandler(); +- +- // Act +- handler.HandleFunctionNameNoContext(); +- +- // Get the output and parse it +- var metricsOutput = _consoleOut.ToString(); +- +- // Assert cold start function name is set MyFunction +- Assert.Contains( +- "\"CloudWatchMetrics\":[{\"Namespace\":\"ns\",\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\",\"FunctionName\"]]}]},\"Service\":\"svc\",\"FunctionName\":\"MyFunction\",\"ColdStart\":1}", +- metricsOutput); +- } +- +- [Fact] +- public void Handler_With_Builder_Should_Configure_FunctionName_In_Constructor_Mock() +- { +- var metricsMock = Substitute.For(); +- +- metricsMock.Options.Returns(new MetricsOptions +- { +- CaptureColdStart = true, +- Namespace = "dotnet-powertools-test", +- Service = "testService", +- FunctionName = "My_Function_Custome_Name", +- DefaultDimensions = new Dictionary +- { +- { "Environment", "Prod" }, +- { "Another", "One" } +- } +- }); +- +- Metrics.UseMetricsForTests(metricsMock); +- +- var sut = new MetricsnBuilderHandler(metricsMock); +- +- // Act +- sut.Handler(new TestLambdaContext +- { +- FunctionName = "This_Will_Be_Overwritten" +- }); +- +- metricsMock.Received(1).CaptureColdStartMetric(Arg.Any()); +- metricsMock.Received(1).AddMetric("SuccessfulBooking", 1, MetricUnit.Count); +- } + + public void Dispose() + { + Metrics.ResetForTest(); + MetricsAspect.ResetForTest(); +- ConsoleWrapper.ResetForTest(); + } + } +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/MetricsnBuilderHandler.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/MetricsnBuilderHandler.cs +deleted file mode 100644 +index 5aae4cdc..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/MetricsnBuilderHandler.cs ++++ /dev/null +@@ -1,46 +0,0 @@ +-using System.Collections.Generic; +-using Amazon.Lambda.Core; +- +-namespace AWS.Lambda.Powertools.Metrics.Tests.Handlers; +- +-public class MetricsnBuilderHandler +-{ +- private readonly IMetrics _metrics; +- +- // Allow injection of IMetrics for testing +- public MetricsnBuilderHandler(IMetrics metrics = null) +- { +- _metrics = metrics ?? new MetricsBuilder() +- .WithCaptureColdStart(true) +- .WithService("testService") +- .WithNamespace("dotnet-powertools-test") +- .WithRaiseOnEmptyMetrics(true) +- .WithDefaultDimensions(new Dictionary +- { +- { "Environment", "Prod1" }, +- { "Another", "One" } +- }).Build(); +- } +- +- [Metrics] +- public void Handler(ILambdaContext context) +- { +- _metrics.AddMetric("SuccessfulBooking", 1, MetricUnit.Count); +- } +- +- [Metrics] +- public void HandlerEmpty() +- { +- } +- +- public void HandlerSingleMetric() +- { +- _metrics.PushSingleMetric("SuccessfulBooking", 1, MetricUnit.Count); +- } +- +- public void HandlerSingleMetricDimensions() +- { +- _metrics.PushSingleMetric("SuccessfulBooking", 1, MetricUnit.Count, dimensions: _metrics.Options.DefaultDimensions); +- } +- +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/MetricsAttributeTests.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/MetricsAttributeTests.cs +deleted file mode 100644 +index 04a1f86d..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/MetricsAttributeTests.cs ++++ /dev/null +@@ -1,72 +0,0 @@ +-using Xunit; +- +-namespace AWS.Lambda.Powertools.Metrics.Tests; +- +-[Collection("Sequential")] +-public class MetricsAttributeTests +-{ +- [Fact] +- public void MetricsAttribute_WhenCaptureColdStartSet_ShouldSetFlag() +- { +- // Arrange & Act +- var attribute = new MetricsAttribute +- { +- CaptureColdStart = true +- }; +- +- // Assert +- Assert.True(attribute.CaptureColdStart); +- Assert.True(attribute.IsCaptureColdStartSet); +- } +- +- [Fact] +- public void MetricsAttribute_WhenCaptureColdStartNotSet_ShouldNotSetFlag() +- { +- // Arrange & Act +- var attribute = new MetricsAttribute(); +- +- // Assert +- Assert.False(attribute.CaptureColdStart); +- Assert.False(attribute.IsCaptureColdStartSet); +- } +- +- [Fact] +- public void MetricsAttribute_WhenRaiseOnEmptyMetricsSet_ShouldSetFlag() +- { +- // Arrange & Act +- var attribute = new MetricsAttribute +- { +- RaiseOnEmptyMetrics = true +- }; +- +- // Assert +- Assert.True(attribute.RaiseOnEmptyMetrics); +- Assert.True(attribute.IsRaiseOnEmptyMetricsSet); +- } +- +- [Fact] +- public void MetricsAttribute_WhenRaiseOnEmptyMetricsNotSet_ShouldNotSetFlag() +- { +- // Arrange & Act +- var attribute = new MetricsAttribute(); +- +- // Assert +- Assert.False(attribute.RaiseOnEmptyMetrics); +- Assert.False(attribute.IsRaiseOnEmptyMetricsSet); +- } +- +- [Fact] +- public void MetricsAttribute_ShouldSetNamespaceAndService() +- { +- // Arrange & Act +- var attribute = new MetricsAttribute +- { +- Namespace = "TestNamespace", +- Service = "TestService" +- }; +- +- // Assert +- Assert.Equal("TestNamespace", attribute.Namespace); +- Assert.Equal("TestService", attribute.Service); +- } +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/MetricsTests.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/MetricsTests.cs +index 8f038dfc..97aa5bf8 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/MetricsTests.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/MetricsTests.cs +@@ -1,8 +1,3 @@ +-using System; +-using System.Collections.Generic; +-using System.IO; +-using Amazon.Lambda.Core; +-using Amazon.Lambda.TestUtilities; + using AWS.Lambda.Powertools.Common; + using NSubstitute; + using Xunit; +@@ -17,410 +12,22 @@ public class MetricsTests + { + // Arrange + Metrics.ResetForTest(); +- var env = new PowertoolsEnvironment(); ++ var assemblyName = "AWS.Lambda.Powertools.Metrics"; ++ var assemblyVersion = "1.0.0"; + +- var conf = new PowertoolsConfigurations(env); +- +- _ = new Metrics(conf); +- +- // Assert +- Assert.Contains($"{Constants.FeatureContextIdentifier}/Metrics/", +- env.GetEnvironmentVariable("AWS_EXECUTION_ENV")); +- } +- +- [Fact] +- public void Before_When_RaiseOnEmptyMetricsNotSet_Should_Configure_Null() +- { +- // Arrange +- MetricsAspect.ResetForTest(); +- var method = typeof(MetricsTests).GetMethod(nameof(TestMethod)); +- var trigger = new MetricsAttribute(); +- +- var metricsAspect = new MetricsAspect(); +- +- // Act +- metricsAspect.Before( +- this, +- "TestMethod", +- new object[] { new TestLambdaContext() }, +- typeof(MetricsTests), +- method, +- typeof(void), +- new Attribute[] { trigger } +- ); +- +- // Assert +- var metrics = Metrics.Instance; +- Assert.False(trigger.IsRaiseOnEmptyMetricsSet); +- Assert.False(metrics.Options.RaiseOnEmptyMetrics); +- } +- +- [Fact] +- public void When_MetricsDisabled_Should_Not_AddMetric() +- { +- // Arrange +- var conf = Substitute.For(); +- conf.MetricsDisabled.Returns(true); +- +- IMetrics metrics = new Metrics(conf); +- var stringWriter = new StringWriter(); +- Console.SetOut(stringWriter); +- +- // Act +- metrics.AddMetric("test", 1.0); +- metrics.Flush(); +- +- // Assert +- Assert.Empty(stringWriter.ToString()); +- +- // Cleanup +- stringWriter.Dispose(); +- Console.SetOut(new StreamWriter(Console.OpenStandardOutput())); +- } +- +- [Fact] +- public void When_MetricsDisabled_Should_Not_PushSingleMetric() +- { +- // Arrange +- var conf = Substitute.For(); +- conf.MetricsDisabled.Returns(true); +- +- IMetrics metrics = new Metrics(conf); +- var stringWriter = new StringWriter(); +- Console.SetOut(stringWriter); +- +- // Act +- metrics.PushSingleMetric("test", 1.0, MetricUnit.Count); +- +- // Assert +- Assert.Empty(stringWriter.ToString()); +- +- // Cleanup +- stringWriter.Dispose(); +- Console.SetOut(new StreamWriter(Console.OpenStandardOutput())); +- } +- +- // Helper method for the tests +- internal void TestMethod(ILambdaContext context) +- { +- } +- +- [Fact] +- public void When_Constructor_With_Null_Namespace_And_Service_Should_Not_Set() +- { +- // Arrange +- Substitute.For(); +- var powertoolsConfigMock = Substitute.For(); +- powertoolsConfigMock.MetricsNamespace.Returns((string)null); +- powertoolsConfigMock.Service.Returns("service_undefined"); +- +- // Act +- var metrics = new Metrics(powertoolsConfigMock); +- +- // Assert +- Assert.Null(metrics.GetNamespace()); +- Assert.Null(metrics.Options.Service); +- } +- +- [Fact] +- public void When_AddMetric_With_EmptyKey_Should_ThrowArgumentNullException() +- { +- // Arrange +- Substitute.For(); +- var powertoolsConfigMock = Substitute.For(); +- IMetrics metrics = new Metrics(powertoolsConfigMock); +- +- // Act & Assert +- var exception = Assert.Throws(() => metrics.AddMetric("", 1.0)); +- Assert.Equal("key", exception.ParamName); +- Assert.Contains("'AddMetric' method requires a valid metrics key. 'Null' or empty values are not allowed.", +- exception.Message); +- } +- +- [Theory] +- [InlineData(null)] +- [InlineData("")] +- [InlineData(" ")] +- public void When_AddMetric_With_InvalidKey_Should_ThrowArgumentNullException(string key) +- { +- // Arrange +- // var metricsMock = Substitute.For(); +- var powertoolsConfigMock = Substitute.For(); +- IMetrics metrics = new Metrics(powertoolsConfigMock); +- +- // Act & Assert +- var exception = Assert.Throws(() => metrics.AddMetric(key, 1.0)); +- Assert.Equal("key", exception.ParamName); +- Assert.Contains("'AddMetric' method requires a valid metrics key. 'Null' or empty values are not allowed.", +- exception.Message); +- } +- +- [Fact] +- public void When_AddMetric_With_TooLongKey_Should_ThrowArgumentOutOfRangeException() +- { +- // Arrange +- Substitute.For(); +- var powertoolsConfigMock = Substitute.For(); +- IMetrics metrics = new Metrics(powertoolsConfigMock); +- +- // Act & Assert +- var exception = Assert.Throws(() => metrics.AddMetric("Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem.", 1.0)); +- Assert.Equal("key", exception.ParamName); +- Assert.Contains("'AddMetric' method requires a valid metrics key. Key exceeds the allowed length constraint.", +- exception.Message); +- } +- +- [Fact] +- public void When_SetDefaultDimensions_With_InvalidKeyOrValue_Should_ThrowArgumentNullException() +- { +- // Arrange +- var powertoolsConfigMock = Substitute.For(); +- IMetrics metrics = new Metrics(powertoolsConfigMock); +- +- var invalidDimensions = new Dictionary +- { +- { "", "value" }, // empty key +- { "key", "" }, // empty value +- { " ", "value" }, // whitespace key +- { "key1", " " }, // whitespace value +- { "key2", null } // null value +- }; +- +- // Act & Assert +- foreach (var dimension in invalidDimensions) +- { +- var dimensions = new Dictionary { { dimension.Key, dimension.Value } }; +- var exception = Assert.Throws(() => metrics.SetDefaultDimensions(dimensions)); +- Assert.Equal("Key", exception.ParamName); +- Assert.Contains( +- "'SetDefaultDimensions' method requires a valid key pair. 'Null' or empty values are not allowed.", +- exception.Message); +- } +- } +- +- [Fact] +- public void When_PushSingleMetric_With_EmptyName_Should_ThrowArgumentNullException() +- { +- // Arrange +- var powertoolsConfigMock = Substitute.For(); +- IMetrics metrics = new Metrics(powertoolsConfigMock); +- +- // Act & Assert +- var exception = Assert.Throws(() => metrics.PushSingleMetric("", 1.0, MetricUnit.Count)); +- Assert.Equal("name", exception.ParamName); +- Assert.Contains( +- "'PushSingleMetric' method requires a valid metrics key. 'Null' or empty values are not allowed.", +- exception.Message); +- } +- +- [Theory] +- [InlineData(null)] +- [InlineData("")] +- [InlineData(" ")] +- public void When_PushSingleMetric_With_InvalidName_Should_ThrowArgumentNullException(string name) +- { +- // Arrange +- var powertoolsConfigMock = Substitute.For(); +- IMetrics metrics = new Metrics(powertoolsConfigMock); +- +- // Act & Assert +- var exception = +- Assert.Throws(() => metrics.PushSingleMetric(name, 1.0, MetricUnit.Count)); +- Assert.Equal("name", exception.ParamName); +- Assert.Contains( +- "'PushSingleMetric' method requires a valid metrics key. 'Null' or empty values are not allowed.", +- exception.Message); +- } +- +- +- [Fact] +- public void When_ColdStart_Should_Use_DefaultDimensions_From_Options() +- { +- // Arrange +- var options = new MetricsOptions +- { +- CaptureColdStart = true, +- Namespace = "dotnet-powertools-test", +- DefaultDimensions = new Dictionary +- { +- { "Environment", "Test" }, +- { "Region", "us-east-1" } +- } +- }; +- +- var conf = Substitute.For(); +- var consoleWrapper = Substitute.For(); +- IMetrics metrics = new Metrics(conf, consoleWrapper: consoleWrapper, options: options); +- +- var context = new TestLambdaContext +- { +- FunctionName = "TestFunction" +- }; +- +- // Act +- metrics.CaptureColdStartMetric(context); +- +- // Assert +- consoleWrapper.Received(1).WriteLine( +- Arg.Is(s => s.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Environment\",\"Region\",\"FunctionName\"]]}]},\"Environment\":\"Test\",\"Region\":\"us-east-1\",\"FunctionName\":\"TestFunction\",\"ColdStart\":1}")) +- ); +- } +- +- [Fact] +- public void When_ColdStart_And_DefaultDimensions_Is_Null_Should_Only_Add_Service_And_FunctionName() +- { +- // Arrange +- var options = new MetricsOptions +- { +- CaptureColdStart = true, +- Namespace = "dotnet-powertools-test", +- DefaultDimensions = null +- }; +- +- var conf = Substitute.For(); +- var consoleWrapper = Substitute.For(); +- IMetrics metrics = new Metrics(conf, consoleWrapper: consoleWrapper, options: options); +- +- var context = new TestLambdaContext +- { +- FunctionName = "TestFunction" +- }; +- +- // Act +- metrics.CaptureColdStartMetric(context); +- +- // Assert +- consoleWrapper.Received(1).WriteLine( +- Arg.Is(s => s.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"FunctionName\"]]}]},\"FunctionName\":\"TestFunction\",\"ColdStart\":1}")) +- ); +- } ++ var env = Substitute.For(); ++ env.GetAssemblyName(Arg.Any()).Returns(assemblyName); ++ env.GetAssemblyVersion(Arg.Any()).Returns(assemblyVersion); + +- [Fact] +- public void Namespace_Should_Return_OptionsNamespace() +- { +- // Arrange +- Metrics.ResetForTest(); +- var metricsMock = Substitute.For(); +- var optionsMock = new MetricsOptions +- { +- Namespace = "TestNamespace" +- }; +- +- metricsMock.Options.Returns(optionsMock); +- Metrics.UseMetricsForTests(metricsMock); +- +- // Act +- var result = Metrics.Namespace; +- +- // Assert +- Assert.Equal("TestNamespace", result); +- } +- +- [Fact] +- public void Service_Should_Return_OptionsService() +- { +- // Arrange +- Metrics.ResetForTest(); +- var metricsMock = Substitute.For(); +- var optionsMock = new MetricsOptions +- { +- Service = "TestService" +- }; ++ var conf = new PowertoolsConfigurations(new SystemWrapper(env)); + +- metricsMock.Options.Returns(optionsMock); +- Metrics.UseMetricsForTests(metricsMock); +- +- // Act +- var result = Metrics.Service; +- ++ var metrics = new Metrics(conf); ++ + // Assert +- Assert.Equal("TestService", result); +- } +- +- [Fact] +- public void Namespace_Should_Return_Null_When_Not_Set() +- { +- // Arrange +- Metrics.ResetForTest(); +- var metricsMock = Substitute.For(); +- var optionsMock = new MetricsOptions(); +- +- metricsMock.Options.Returns(optionsMock); +- Metrics.UseMetricsForTests(metricsMock); +- +- // Act +- var result = Metrics.Namespace; +- +- // Assert +- Assert.Null(result); +- } +- +- [Fact] +- public void Service_Should_Return_Null_When_Not_Set() +- { +- // Arrange +- Metrics.ResetForTest(); +- var metricsMock = Substitute.For(); +- var optionsMock = new MetricsOptions(); +- +- metricsMock.Options.Returns(optionsMock); +- Metrics.UseMetricsForTests(metricsMock); +- +- // Act +- var result = Metrics.Service; +- +- // Assert +- Assert.Null(result); +- } +- +- [Fact] +- public void WithFunctionName_Should_Set_FunctionName_In_Options() +- { +- // Arrange +- var builder = new MetricsBuilder(); +- var expectedFunctionName = "TestFunction"; +- +- // Act +- var result = builder.WithFunctionName(expectedFunctionName); +- var metrics = result.Build(); +- +- // Assert +- Assert.Equal(expectedFunctionName, metrics.Options.FunctionName); +- Assert.Same(builder, result); +- } +- +- [Theory] +- [InlineData(null)] +- [InlineData("")] +- [InlineData(" ")] +- public void WithFunctionName_Should_Allow_NullOrEmpty_FunctionName(string functionName) +- { +- // Arrange +- var builder = new MetricsBuilder(); +- +- // Act +- var result = builder.WithFunctionName(functionName); +- var metrics = result.Build(); +- +- // Assert +- // Assert +- Assert.Null(metrics.Options.FunctionName); // All invalid values should result in null +- Assert.Same(builder, result); +- } +- +- [Fact] +- public void Build_Should_Preserve_FunctionName_When_Set_Through_Builder() +- { +- // Arrange +- var builder = new MetricsBuilder() +- .WithNamespace("TestNamespace") +- .WithService("TestService") +- .WithFunctionName("TestFunction"); +- +- // Act +- var metrics = builder.Build(); ++ env.Received(1).SetEnvironmentVariable( ++ "AWS_EXECUTION_ENV", $"{Constants.FeatureContextIdentifier}/Metrics/{assemblyVersion}" ++ ); + +- // Assert +- Assert.Equal("TestFunction", metrics.Options.FunctionName); ++ env.Received(1).GetEnvironmentVariable("AWS_EXECUTION_ENV"); + } + } +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Utils.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Utils.cs +index e021dcc1..63fa1d4d 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Utils.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Utils.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System.Collections.Generic; + using System.IO; + +diff --git a/libraries/tests/AWS.Lambda.Powertools.Parameters.Tests/AppConfig/AppConfigProviderTest.cs b/libraries/tests/AWS.Lambda.Powertools.Parameters.Tests/AppConfig/AppConfigProviderTest.cs +index 1577b5b2..8c664e4e 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.Parameters.Tests/AppConfig/AppConfigProviderTest.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.Parameters.Tests/AppConfig/AppConfigProviderTest.cs +@@ -13,7 +13,6 @@ + * permissions and limitations under the License. + */ + +-using System.Diagnostics.CodeAnalysis; + using System.Text; + using System.Text.Json; + using System.Text.Json.Nodes; +@@ -33,7 +32,6 @@ using Xunit; + + namespace AWS.Lambda.Powertools.Parameters.Tests.AppConfig; + +-[SuppressMessage("Usage", "xUnit1030:Do not call ConfigureAwait(false) in test method")] + public class AppConfigProviderTest + { + [Fact] +diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/FullExampleHandler.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/FullExampleHandler.cs +index 943b2d94..4f84cc97 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/FullExampleHandler.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/FullExampleHandler.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System.Collections.Generic; + using System.Net.Http; + using System.Threading.Tasks; +diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/HandlerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/HandlerTests.cs +index 62e4b584..17e3af3a 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/HandlerTests.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/HandlerTests.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.Collections.Generic; + using System.Linq; +diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/Handlers.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/Handlers.cs +index 67b24c8e..38210ac9 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/Handlers.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/Handlers.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + + namespace AWS.Lambda.Powertools.Tracing.Tests; +diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Serializers/PowertoolsTracingSerializerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Serializers/PowertoolsTracingSerializerTests.cs +index fe766be3..be4f1237 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Serializers/PowertoolsTracingSerializerTests.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Serializers/PowertoolsTracingSerializerTests.cs +@@ -1,4 +1,18 @@ +-#if NET8_0_OR_GREATER ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.Collections.Generic; + using System.Text.Json; +@@ -236,5 +250,4 @@ public class TestNullableObject + public class TestArrayObject + { + public int[] Values { get; set; } +-} +-#endif +\ No newline at end of file ++} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Serializers/TestJsonContext.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Serializers/TestJsonContext.cs +index 1145fee4..81d9287b 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Serializers/TestJsonContext.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Serializers/TestJsonContext.cs +@@ -1,4 +1,17 @@ +-#if NET8_0_OR_GREATER ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ + + using System.Collections.Generic; + using System.Text.Json.Serialization; +@@ -28,7 +41,6 @@ public class TestComplexObject + public Dictionary NestedObject { get; set; } + } + +-#endif + + public class TestResponse + { +diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Serializers/TracingSerializerExtensionsTests.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Serializers/TracingSerializerExtensionsTests.cs +index 78e2adaa..999143a4 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Serializers/TracingSerializerExtensionsTests.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Serializers/TracingSerializerExtensionsTests.cs +@@ -1,5 +1,19 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + +-#if NET8_0_OR_GREATER + using Amazon.Lambda.Serialization.SystemTextJson; + using AWS.Lambda.Powertools.Tracing.Serializers; + using Xunit; +@@ -25,5 +39,4 @@ public class TracingSerializerExtensionsTests + var serialized = PowertoolsTracingSerializer.Serialize(testObject); + Assert.Contains("\"Name\":\"Test\"", serialized); + } +-} +-#endif +\ No newline at end of file ++} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAspectTests.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAspectTests.cs +index 11503275..e373278e 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAspectTests.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAspectTests.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.Text.Json; + using System.Threading; +@@ -8,10 +23,8 @@ using AWS.Lambda.Powertools.Tracing.Internal; + using NSubstitute; + using Xunit; + +-#if NET8_0_OR_GREATER + using AWS.Lambda.Powertools.Tracing.Serializers; + using AWS.Lambda.Powertools.Tracing.Tests.Serializers; +-#endif + + namespace AWS.Lambda.Powertools.Tracing.Tests; + +@@ -98,7 +111,6 @@ public class TracingAspectTests + _mockXRayRecorder.Received(1).EndSubsegment(); + } + +-#if NET8_0_OR_GREATER + [Fact] + public void Around_SyncMethod_HandlesResponseAndSegmentCorrectly_AOT() + { +@@ -162,7 +174,6 @@ public class TracingAspectTests + PowertoolsTracingSerializer.Serialize(result)); + _mockXRayRecorder.Received(1).EndSubsegment(); + } +-#endif + + [Fact] + public async Task Around_VoidAsyncMethod_HandlesSegmentCorrectly() +diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAttributeTest.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAttributeTest.cs +index aca8afc0..f02619c1 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAttributeTest.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAttributeTest.cs +@@ -1,8 +1,22 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using System.Linq; + using System.Text; + using Amazon.XRay.Recorder.Core; +-using AWS.Lambda.Powertools.Common.Core; + using AWS.Lambda.Powertools.Tracing.Internal; + using Xunit; + +@@ -36,8 +50,6 @@ namespace AWS.Lambda.Powertools.Tracing.Tests + var subSegmentCold = segmentCold.Subsegments[0]; + + // Warm Start Execution +- // Clear just the AsyncLocal value to simulate new invocation in same container +- LambdaLifecycleTracker.Reset(resetContainer: false); + // Start segment + var segmentWarm = AWSXRayRecorder.Instance.TraceContext.GetEntity(); + _handler.Handle(); +@@ -75,9 +87,6 @@ namespace AWS.Lambda.Powertools.Tracing.Tests + var subSegmentCold = segmentCold.Subsegments[0]; + + // Warm Start Execution +- // Clear just the AsyncLocal value to simulate new invocation in same container +- LambdaLifecycleTracker.Reset(resetContainer: false); +- + // Start segment + var segmentWarm = AWSXRayRecorder.Instance.TraceContext.GetEntity(); + _handler.Handle(); +diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingSubsegmentTests.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingSubsegmentTests.cs +deleted file mode 100644 +index 54300a6d..00000000 +--- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingSubsegmentTests.cs ++++ /dev/null +@@ -1,235 +0,0 @@ +-using AWS.Lambda.Powertools.Tracing.Internal; +-using Xunit; +-using Amazon.XRay.Recorder.Core.Internal.Entities; +-using System; +- +-namespace AWS.Lambda.Powertools.Tracing.Tests; +- +-[Collection("Sequential")] +-public class TracingSubsegmentTests +-{ +- +- [Fact] +- public void TracingSubsegment_Constructor_Should_Set_Name() +- { +- // Arrange +- var name = "test-segment"; +- +- // Act +- var subsegment = new TracingSubsegment(name); +- +- // Assert +- Assert.Equal("test-segment", subsegment.Name); +- Assert.True(Entity.IsIdValid(subsegment.Id)); +- Assert.Null(subsegment.TraceId); +- Assert.Null(subsegment.ParentId); +- Assert.False(subsegment.IsSubsegmentsAdded); +- } +- +- [Fact] +- public void Test_Add_Ref_And_Release_With_TracingSubsegment() +- { +- // Arrange +- var parent = new Segment("parent", TraceId.NewId()); +- var child = new TracingSubsegment("child"); +- +- // Act +- parent.AddSubsegment(child); +- +- // Assert +- Assert.Equal(2, parent.Reference); +- Assert.Equal(1, child.Reference); +- +- child.Release(); +- Assert.Equal(1, parent.Reference); +- Assert.Equal(0, child.Reference); +- Assert.False(parent.IsEmittable()); +- Assert.False(child.IsEmittable()); +- +- parent.Release(); +- Assert.Equal(0, parent.Reference); +- Assert.True(parent.IsEmittable()); +- Assert.True(child.IsEmittable()); +- } +- +- [Fact] +- public void IsEmittable_Returns_False_Without_Parent() +- { +- var subsegment = new TracingSubsegment("segment"); +- Assert.False(subsegment.IsEmittable()); +- } +- +- [Fact] +- public void TracingSubsegment_Is_Assignable_BaseClass() +- { +- // Arrange +- var subsegment = new TracingSubsegment("segment"); +- +- // Act & Assert +- Assert.IsAssignableFrom(subsegment); +- } +- +- [Fact] +- public void Tracing_WithSubsegment_Invokes_Delegate_With_TracingSubsegment() +- { +- // Arrange +- bool delegateInvoked = false; +- +- void TracingSubsegmentDelegate(TracingSubsegment subsegment) +- { +- delegateInvoked = true; +- } +- +- // Act +- Tracing.WithSubsegment("namespace", "test", TracingSubsegmentDelegate); +- +- // Assert +- Assert.True(delegateInvoked); +- } +- +- [Fact] +- public void WithSubsegment_WithEntity_ThrowsArgumentNullException_WhenNameIsNull() +- { +- // Arrange +- var parent = new Segment("parent", TraceId.NewId()); +- +- // Act & Assert +- Assert.Throws(() => +- Tracing.WithSubsegment(null, null, parent, _ => { })); +- } +- +- [Fact] +- public void WithSubsegment_WithEntity_ThrowsArgumentNullException_WhenNameIsEmpty() +- { +- // Arrange +- var parent = new Segment("parent", TraceId.NewId()); +- +- // Act & Assert +- Assert.Throws(() => +- Tracing.WithSubsegment(null, "", parent, _ => { })); +- } +- +- [Fact] +- public void WithSubsegment_WithEntity_ThrowsArgumentNullException_WhenEntityIsNull() +- { +- // Act & Assert +- Assert.Throws(() => +- Tracing.WithSubsegment(null, "test", null, _ => { })); +- } +- +- [Fact] +- public void WithSubsegment_WithEntity_CreatesSubsegmentWithCorrectName() +- { +- // Arrange +- var parent = new Segment("parent", TraceId.NewId()); +- TracingSubsegment capturedSubsegment = null; +- +- // Act +- Tracing.WithSubsegment("test-namespace", "test-name", parent, subsegment => +- { +- capturedSubsegment = subsegment; +- }); +- +- // Assert +- Assert.NotNull(capturedSubsegment); +- Assert.Equal("## test-name", capturedSubsegment.Name); +- Assert.Equal("test-namespace", capturedSubsegment.Namespace); +- } +- +- [Fact] +- public void WithSubsegment_WithEntity_SetsSubsegmentProperties() +- { +- // Arrange +- var parent = new Segment("parent", TraceId.NewId()); +- TracingSubsegment capturedSubsegment = null; +- +- // Act +- Tracing.WithSubsegment("test-namespace", "test-name", parent, subsegment => +- { +- capturedSubsegment = subsegment; +- }); +- +- // Assert +- Assert.NotNull(capturedSubsegment); +- Assert.Equal(parent.Sampled, capturedSubsegment.Sampled); +- Assert.False(capturedSubsegment.IsInProgress); +- Assert.True(capturedSubsegment.StartTime > 0); +- Assert.True(capturedSubsegment.EndTime > 0); +- } +- +- [Fact] +- public void WithSubsegment_WithEntity_AddsSubsegmentToParent() +- { +- // Arrange +- var parent = new Segment("parent", TraceId.NewId()); +- var initialSubsegmentCount = parent.Subsegments?.Count ?? 0; +- +- // Act +- Tracing.WithSubsegment("test-namespace", "test-name", parent, _ => { }); +- +- // Assert +- Assert.True(parent.IsSubsegmentsAdded); +- Assert.Equal(initialSubsegmentCount + 1, parent.Subsegments.Count); +- } +- +- [Fact] +- public void WithSubsegment_WithEntity_InvokesActionWithSubsegment() +- { +- // Arrange +- var parent = new Segment("parent", TraceId.NewId()); +- bool actionInvoked = false; +- TracingSubsegment passedSubsegment = null; +- +- // Act +- Tracing.WithSubsegment("test-namespace", "test-name", parent, subsegment => +- { +- actionInvoked = true; +- passedSubsegment = subsegment; +- }); +- +- // Assert +- Assert.True(actionInvoked); +- Assert.NotNull(passedSubsegment); +- Assert.IsType(passedSubsegment); +- } +- +- +- [Fact] +- public void WithSubsegment_WithEntity_UsesDefaultNamespaceWhenNull() +- { +- // Arrange +- var parent = new Segment("parent", TraceId.NewId()); +- TracingSubsegment capturedSubsegment = null; +- +- // Act +- Tracing.WithSubsegment(null, "test-name", parent, subsegment => +- { +- capturedSubsegment = subsegment; +- }); +- +- // Assert +- Assert.NotNull(capturedSubsegment); +- Assert.NotNull(capturedSubsegment.Namespace); +- } +- +- [Fact] +- public void WithSubsegment_WithEntity_HandlesExceptionInAction() +- { +- // Arrange +- var parent = new Segment("parent", TraceId.NewId()); +- var expectedException = new InvalidOperationException("Test exception"); +- +- // Act & Assert +- var actualException = Assert.Throws(() => +- { +- Tracing.WithSubsegment("test-namespace", "test-name", parent, subsegment => +- { +- throw expectedException; +- }); +- }); +- +- Assert.Equal(expectedException, actualException); +- // Verify subsegment was still properly cleaned up +- Assert.True(parent.IsSubsegmentsAdded); +- } +-} +\ No newline at end of file +diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/XRayRecorderTests.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/XRayRecorderTests.cs +index c40f4400..6a024334 100644 +--- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/XRayRecorderTests.cs ++++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/XRayRecorderTests.cs +@@ -1,3 +1,18 @@ ++/* ++ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"). ++ * You may not use this file except in compliance with the License. ++ * A copy of the License is located at ++ * ++ * http://aws.amazon.com/apache2.0 ++ * ++ * or in the "license" file accompanying this file. This file is distributed ++ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either ++ * express or implied. See the License for the specific language governing ++ * permissions and limitations under the License. ++ */ ++ + using System; + using Amazon.XRay.Recorder.Core; + using Amazon.XRay.Recorder.Core.Internal.Entities; +@@ -16,17 +31,27 @@ public class XRayRecorderTests + public void Tracing_Set_Execution_Environment_Context() + { + // Arrange +- var env = new PowertoolsEnvironment(); ++ var assemblyName = "AWS.Lambda.Powertools.Tracing"; ++ var assemblyVersion = "1.0.0"; ++ ++ var env = Substitute.For(); ++ env.GetAssemblyName(Arg.Any()).Returns(assemblyName); ++ env.GetAssemblyVersion(Arg.Any()).Returns(assemblyVersion); + +- var conf = new PowertoolsConfigurations(env); ++ var conf = new PowertoolsConfigurations(new SystemWrapper(env)); + var awsXray = Substitute.For(); + + // Act + var xRayRecorder = new XRayRecorder(awsXray, conf); + + // Assert +- Assert.Contains($"{Constants.FeatureContextIdentifier}/Tracing/", +- env.GetEnvironmentVariable("AWS_EXECUTION_ENV")); ++ env.Received(1).SetEnvironmentVariable( ++ "AWS_EXECUTION_ENV", $"{Constants.FeatureContextIdentifier}/Tracing/{assemblyVersion}" ++ ); ++ ++ env.Received(1).GetEnvironmentVariable( ++ "AWS_EXECUTION_ENV" ++ ); + + Assert.NotNull(xRayRecorder); + } +diff --git a/libraries/tests/Directory.Build.props b/libraries/tests/Directory.Build.props +index d662fc45..26ab08bb 100644 +--- a/libraries/tests/Directory.Build.props ++++ b/libraries/tests/Directory.Build.props +@@ -1,6 +1,6 @@ + + +- net6.0;net8.0 ++ net8.0 + false + + +diff --git a/libraries/tests/Directory.Packages.props b/libraries/tests/Directory.Packages.props +index 804b073e..e8c9a16e 100644 +--- a/libraries/tests/Directory.Packages.props ++++ b/libraries/tests/Directory.Packages.props +@@ -4,23 +4,21 @@ + + + +- ++ + +- +- + + + + + + +- ++ + + + + + + +- ++ + + +\ No newline at end of file +diff --git a/libraries/tests/e2e/InfraShared/FunctionConstruct.cs b/libraries/tests/e2e/InfraShared/FunctionConstruct.cs +index c3bb7d9e..6dfeb84b 100644 +--- a/libraries/tests/e2e/InfraShared/FunctionConstruct.cs ++++ b/libraries/tests/e2e/InfraShared/FunctionConstruct.cs +@@ -27,7 +27,6 @@ public class FunctionConstruct : Construct + Tracing = Tracing.ACTIVE, + Timeout = Duration.Seconds(10), + Environment = props.Environment, +- LoggingFormat = LoggingFormat.TEXT, + Code = Code.FromCustomCommand(distPath, + [ + command +diff --git a/libraries/tests/e2e/functions/core/logging/AOT-Function-ILogger/src/AOT-Function-ILogger/AOT-Function-ILogger.csproj b/libraries/tests/e2e/functions/core/logging/AOT-Function-ILogger/src/AOT-Function-ILogger/AOT-Function-ILogger.csproj +deleted file mode 100644 +index 8655735e..00000000 +--- a/libraries/tests/e2e/functions/core/logging/AOT-Function-ILogger/src/AOT-Function-ILogger/AOT-Function-ILogger.csproj ++++ /dev/null +@@ -1,33 +0,0 @@ +- +- +- Exe +- net8.0 +- enable +- enable +- Lambda +- +- true +- +- true +- +- true +- +- partial +- +- +- +- +- +- +- +- +- +- TestHelper.cs +- +- +- +- +- +- +\ No newline at end of file +diff --git a/libraries/tests/e2e/functions/core/logging/AOT-Function-ILogger/src/AOT-Function-ILogger/Function.cs b/libraries/tests/e2e/functions/core/logging/AOT-Function-ILogger/src/AOT-Function-ILogger/Function.cs +deleted file mode 100644 +index 16234c5b..00000000 +--- a/libraries/tests/e2e/functions/core/logging/AOT-Function-ILogger/src/AOT-Function-ILogger/Function.cs ++++ /dev/null +@@ -1,74 +0,0 @@ +-using System.Text.Json; +-using Amazon.Lambda.Core; +-using Amazon.Lambda.RuntimeSupport; +-using System.Text.Json.Serialization; +-using Amazon.Lambda.APIGatewayEvents; +-using Amazon.Lambda.Serialization.SystemTextJson; +-using AWS.Lambda.Powertools.Logging; +-using AWS.Lambda.Powertools.Logging.Serializers; +-using Helpers; +- +-namespace AOT_Function; +- +-public static class Function +-{ +- private static async Task Main() +- { +- Logger.Configure(logger => +- { +- logger.Service = "TestService"; +- logger.LoggerOutputCase = LoggerOutputCase.PascalCase; +- logger.JsonOptions = new JsonSerializerOptions +- { +- TypeInfoResolver = LambdaFunctionJsonSerializerContext.Default +- }; +- }); +- +- Func handler = FunctionHandler; +- await LambdaBootstrapBuilder.Create(handler, new SourceGeneratorLambdaJsonSerializer()) +- .Build() +- .RunAsync(); +- } +- +- [Logging(LogEvent = true, CorrelationIdPath = CorrelationIdPaths.ApiGatewayRest)] +- public static APIGatewayProxyResponse FunctionHandler(APIGatewayProxyRequest apigwProxyEvent, ILambdaContext context) +- { +- Logger.LogInformation("Processing request started"); +- +- var requestContextRequestId = apigwProxyEvent.RequestContext.RequestId; +- var lookupInfo = new Dictionary() +- { +- {"LookupInfo", new Dictionary{{ "LookupId", requestContextRequestId }}} +- }; +- +- var customKeys = new Dictionary +- { +- {"test1", "value1"}, +- {"test2", "value2"} +- }; +- +- Logger.AppendKeys(lookupInfo); +- Logger.AppendKeys(customKeys); +- +- Logger.LogWarning("Warn with additional keys"); +- +- Logger.RemoveKeys("test1", "test2"); +- +- var error = new InvalidOperationException("Parent exception message", +- new ArgumentNullException(nameof(apigwProxyEvent), +- new Exception("Very important nested inner exception message"))); +- Logger.LogError(error, "Oops something went wrong"); +- return new APIGatewayProxyResponse() +- { +- StatusCode = 200, +- Body = apigwProxyEvent.Body.ToUpper() +- }; +- } +-} +- +-[JsonSerializable(typeof(APIGatewayProxyRequest))] +-[JsonSerializable(typeof(APIGatewayProxyResponse))] +-public partial class LambdaFunctionJsonSerializerContext : JsonSerializerContext +-{ +- +-} +\ No newline at end of file +diff --git a/libraries/tests/e2e/functions/core/logging/AOT-Function-ILogger/src/AOT-Function-ILogger/aws-lambda-tools-defaults.json b/libraries/tests/e2e/functions/core/logging/AOT-Function-ILogger/src/AOT-Function-ILogger/aws-lambda-tools-defaults.json +deleted file mode 100644 +index be3c7ec1..00000000 +--- a/libraries/tests/e2e/functions/core/logging/AOT-Function-ILogger/src/AOT-Function-ILogger/aws-lambda-tools-defaults.json ++++ /dev/null +@@ -1,16 +0,0 @@ +-{ +- "Information": [ +- "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", +- "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", +- "dotnet lambda help", +- "All the command line options for the Lambda command can be specified in this file." +- ], +- "profile": "", +- "region": "", +- "configuration": "Release", +- "function-runtime": "dotnet8", +- "function-memory-size": 512, +- "function-timeout": 30, +- "function-handler": "AOT-Function", +- "msbuild-parameters": "--self-contained true" +-} +\ No newline at end of file +diff --git a/libraries/tests/e2e/functions/core/logging/AOT-Function/src/AOT-Function/AOT-Function.csproj b/libraries/tests/e2e/functions/core/logging/AOT-Function/src/AOT-Function/AOT-Function.csproj +index 8655735e..b2636d6b 100644 +--- a/libraries/tests/e2e/functions/core/logging/AOT-Function/src/AOT-Function/AOT-Function.csproj ++++ b/libraries/tests/e2e/functions/core/logging/AOT-Function/src/AOT-Function/AOT-Function.csproj +@@ -17,7 +17,7 @@ + partial + + +- ++ + + + +diff --git a/libraries/tests/e2e/functions/core/logging/Function/src/Function/Function.cs b/libraries/tests/e2e/functions/core/logging/Function/src/Function/Function.cs +index 958f36ff..8a4d3a8b 100644 +--- a/libraries/tests/e2e/functions/core/logging/Function/src/Function/Function.cs ++++ b/libraries/tests/e2e/functions/core/logging/Function/src/Function/Function.cs +@@ -2,191 +2,24 @@ using Amazon.Lambda.APIGatewayEvents; + using Amazon.Lambda.Core; + using AWS.Lambda.Powertools.Logging; + using Helpers; +-using Microsoft.Extensions.Logging; + + // Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class. + [assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] + +-namespace Function +-{ +- public class Function +- { +- [Logging(LogEvent = true, LoggerOutputCase = LoggerOutputCase.PascalCase, Service = "TestService", +- CorrelationIdPath = CorrelationIdPaths.ApiGatewayRest)] +- public APIGatewayProxyResponse FunctionHandler(APIGatewayProxyRequest apigwProxyEvent, ILambdaContext context) +- { +- TestHelper.TestMethod(apigwProxyEvent); +- +- return new APIGatewayProxyResponse() +- { +- StatusCode = 200, +- Body = apigwProxyEvent.Body.ToUpper() +- }; +- } +- } +-} +- +-namespace StaticConfiguration +-{ +- public class Function +- { +- public Function() +- { +- Logger.Configure(config => +- { +- config.Service = "TestService"; +- config.LoggerOutputCase = LoggerOutputCase.PascalCase; +- }); +- } +- +- [Logging(LogEvent = true, CorrelationIdPath = CorrelationIdPaths.ApiGatewayRest)] +- public APIGatewayProxyResponse FunctionHandler(APIGatewayProxyRequest apigwProxyEvent, ILambdaContext context) +- { +- TestHelper.TestMethod(apigwProxyEvent); +- +- return new APIGatewayProxyResponse() +- { +- StatusCode = 200, +- Body = apigwProxyEvent.Body.ToUpper() +- }; +- } +- } +-} ++namespace Function; + +-namespace StaticILoggerConfiguration ++public class Function + { +- public class Function +- { +- public Function() +- { +- LoggerFactory.Create(builder => +- { +- builder.AddPowertoolsLogger(config => +- { +- config.Service = "TestService"; +- config.LoggerOutputCase = LoggerOutputCase.PascalCase; +- }); +- }); +- } +- +- [Logging(LogEvent = true, CorrelationIdPath = CorrelationIdPaths.ApiGatewayRest)] +- public APIGatewayProxyResponse FunctionHandler(APIGatewayProxyRequest apigwProxyEvent, ILambdaContext context) +- { +- TestHelper.TestMethod(apigwProxyEvent); +- +- return new APIGatewayProxyResponse() +- { +- StatusCode = 200, +- Body = apigwProxyEvent.Body.ToUpper() +- }; +- } +- } +-} +- +-namespace ILoggerConfiguration +-{ +- public class Function +- { +- private readonly ILogger _logger; +- +- public Function() +- { +- _logger = LoggerFactory.Create(builder => +- { +- builder.AddPowertoolsLogger(config => +- { +- config.Service = "TestService"; +- config.LoggerOutputCase = LoggerOutputCase.PascalCase; +- }); +- }).CreatePowertoolsLogger(); +- } +- +- [Logging(LogEvent = true, CorrelationIdPath = CorrelationIdPaths.ApiGatewayRest)] +- public APIGatewayProxyResponse FunctionHandler(APIGatewayProxyRequest apigwProxyEvent, ILambdaContext context) +- { +- _logger.LogInformation("Processing request started"); +- +- var requestContextRequestId = apigwProxyEvent.RequestContext.RequestId; +- var lookupInfo = new Dictionary() +- { +- {"LookupInfo", new Dictionary{{ "LookupId", requestContextRequestId }}} +- }; +- +- var customKeys = new Dictionary +- { +- {"test1", "value1"}, +- {"test2", "value2"} +- }; +- +- _logger.AppendKeys(lookupInfo); +- _logger.AppendKeys(customKeys); +- +- _logger.LogWarning("Warn with additional keys"); +- +- _logger.RemoveKeys("test1", "test2"); +- +- var error = new InvalidOperationException("Parent exception message", +- new ArgumentNullException(nameof(apigwProxyEvent), +- new Exception("Very important nested inner exception message"))); +- _logger.LogError(error, "Oops something went wrong"); +- +- return new APIGatewayProxyResponse() +- { +- StatusCode = 200, +- Body = apigwProxyEvent.Body.ToUpper() +- }; +- } +- } +-} +- +-namespace ILoggerBuilder +-{ +- public class Function ++ [Logging(LogEvent = true, LoggerOutputCase = LoggerOutputCase.PascalCase, Service = "TestService", ++ CorrelationIdPath = CorrelationIdPaths.ApiGatewayRest)] ++ public APIGatewayProxyResponse FunctionHandler(APIGatewayProxyRequest apigwProxyEvent, ILambdaContext context) + { +- private readonly ILogger _logger; +- +- public Function() +- { +- _logger = new PowertoolsLoggerBuilder() +- .WithService("TestService") +- .WithOutputCase(LoggerOutputCase.PascalCase) +- .Build(); +- } ++ TestHelper.TestMethod(apigwProxyEvent); + +- [Logging(LogEvent = true, CorrelationIdPath = CorrelationIdPaths.ApiGatewayRest)] +- public APIGatewayProxyResponse FunctionHandler(APIGatewayProxyRequest apigwProxyEvent, ILambdaContext context) ++ return new APIGatewayProxyResponse() + { +- _logger.LogInformation("Processing request started"); +- +- var requestContextRequestId = apigwProxyEvent.RequestContext.RequestId; +- var lookupInfo = new Dictionary() +- { +- {"LookupInfo", new Dictionary{{ "LookupId", requestContextRequestId }}} +- }; +- +- var customKeys = new Dictionary +- { +- {"test1", "value1"}, +- {"test2", "value2"} +- }; +- +- _logger.AppendKeys(lookupInfo); +- _logger.AppendKeys(customKeys); +- +- _logger.LogWarning("Warn with additional keys"); +- +- _logger.RemoveKeys("test1", "test2"); +- +- var error = new InvalidOperationException("Parent exception message", +- new ArgumentNullException(nameof(apigwProxyEvent), +- new Exception("Very important nested inner exception message"))); +- _logger.LogError(error, "Oops something went wrong"); +- +- return new APIGatewayProxyResponse() +- { +- StatusCode = 200, +- Body = apigwProxyEvent.Body.ToUpper() +- }; +- } ++ StatusCode = 200, ++ Body = apigwProxyEvent.Body.ToUpper() ++ }; + } + } +\ No newline at end of file +diff --git a/libraries/tests/e2e/functions/core/logging/Function/src/Function/Function.csproj b/libraries/tests/e2e/functions/core/logging/Function/src/Function/Function.csproj +index 2e08d5e4..80be7fd5 100644 +--- a/libraries/tests/e2e/functions/core/logging/Function/src/Function/Function.csproj ++++ b/libraries/tests/e2e/functions/core/logging/Function/src/Function/Function.csproj +@@ -1,6 +1,6 @@ + + +- net6.0;net8.0 ++ net8.0 + enable + enable + true +diff --git a/libraries/tests/e2e/functions/core/logging/Function/test/Function.Tests/FunctionTests.cs b/libraries/tests/e2e/functions/core/logging/Function/test/Function.Tests/FunctionTests.cs +index f4d7c1e4..ca3a857a 100644 +--- a/libraries/tests/e2e/functions/core/logging/Function/test/Function.Tests/FunctionTests.cs ++++ b/libraries/tests/e2e/functions/core/logging/Function/test/Function.Tests/FunctionTests.cs +@@ -5,7 +5,6 @@ using Xunit; + using Amazon.Lambda.Model; + using TestUtils; + using Xunit.Abstractions; +-using Environment = Amazon.Lambda.Model.Environment; + + namespace Function.Tests; + +@@ -23,21 +22,10 @@ public class FunctionTests + + [Trait("Category", "AOT")] + [Theory] +- [InlineData("E2ETestLambda_X64_AOT_NET8_logging_AOT-Function")] +- [InlineData("E2ETestLambda_ARM_AOT_NET8_logging_AOT-Function")] ++ [InlineData("E2ETestLambda_X64_AOT_NET8_logging")] ++ [InlineData("E2ETestLambda_ARM_AOT_NET8_logging")] + public async Task AotFunctionTest(string functionName) + { +- // await ResetFunction(functionName); +- await TestFunction(functionName); +- } +- +- [Trait("Category", "AOT")] +- [Theory] +- [InlineData("E2ETestLambda_X64_AOT_NET8_logging_AOT-Function-ILogger")] +- [InlineData("E2ETestLambda_ARM_AOT_NET8_logging_AOT-Function-ILogger")] +- public async Task AotILoggerFunctionTest(string functionName) +- { +- // await ResetFunction(functionName); + await TestFunction(functionName); + } + +@@ -48,51 +36,6 @@ public class FunctionTests + [InlineData("E2ETestLambda_ARM_NET8_logging")] + public async Task FunctionTest(string functionName) + { +- await UpdateFunctionHandler(functionName, "Function::Function.Function::FunctionHandler"); +- await TestFunction(functionName); +- } +- +- [Theory] +- [InlineData("E2ETestLambda_X64_NET6_logging")] +- [InlineData("E2ETestLambda_ARM_NET6_logging")] +- [InlineData("E2ETestLambda_X64_NET8_logging")] +- [InlineData("E2ETestLambda_ARM_NET8_logging")] +- public async Task StaticConfigurationFunctionTest(string functionName) +- { +- await UpdateFunctionHandler(functionName, "Function::StaticConfiguration.Function::FunctionHandler"); +- await TestFunction(functionName); +- } +- +- [Theory] +- [InlineData("E2ETestLambda_X64_NET6_logging")] +- [InlineData("E2ETestLambda_ARM_NET6_logging")] +- [InlineData("E2ETestLambda_X64_NET8_logging")] +- [InlineData("E2ETestLambda_ARM_NET8_logging")] +- public async Task StaticILoggerConfigurationFunctionTest(string functionName) +- { +- await UpdateFunctionHandler(functionName, "Function::StaticILoggerConfiguration.Function::FunctionHandler"); +- await TestFunction(functionName); +- } +- +- [Theory] +- [InlineData("E2ETestLambda_X64_NET6_logging")] +- [InlineData("E2ETestLambda_ARM_NET6_logging")] +- [InlineData("E2ETestLambda_X64_NET8_logging")] +- [InlineData("E2ETestLambda_ARM_NET8_logging")] +- public async Task ILoggerConfigurationFunctionTest(string functionName) +- { +- await UpdateFunctionHandler(functionName, "Function::ILoggerConfiguration.Function::FunctionHandler"); +- await TestFunction(functionName); +- } +- +- [Theory] +- [InlineData("E2ETestLambda_X64_NET6_logging")] +- [InlineData("E2ETestLambda_ARM_NET6_logging")] +- [InlineData("E2ETestLambda_X64_NET8_logging")] +- [InlineData("E2ETestLambda_ARM_NET8_logging")] +- public async Task ILoggerBuilderFunctionTest(string functionName) +- { +- await UpdateFunctionHandler(functionName, "Function::ILoggerBuilder.Function::FunctionHandler"); + await TestFunction(functionName); + } + +@@ -173,17 +116,41 @@ public class FunctionTests + + Assert.True(messageElement.TryGetProperty("HttpMethod", out JsonElement httpMethodElement)); + Assert.Equal("POST", httpMethodElement.GetString()); +- ++ ++ Assert.True(messageElement.TryGetProperty("Headers", out JsonElement headersElement)); ++ Assert.True(headersElement.TryGetProperty("Accept-Encoding", out JsonElement acceptEncodingElement)); ++ Assert.Equal("gzip, deflate, sdch", acceptEncodingElement.GetString()); ++ ++ Assert.True(headersElement.TryGetProperty("Accept-Language", out JsonElement acceptLanguageElement)); ++ Assert.Equal("en-US,en;q=0.8", acceptLanguageElement.GetString()); ++ ++ Assert.True(headersElement.TryGetProperty("Cache-Control", out JsonElement cacheControlElement)); ++ Assert.Equal("max-age=0", cacheControlElement.GetString()); ++ ++ Assert.True( ++ messageElement.TryGetProperty("QueryStringParameters", out JsonElement queryStringParametersElement)); ++ Assert.True(queryStringParametersElement.TryGetProperty("Foo", out JsonElement fooElement)); ++ Assert.Equal("bar", fooElement.GetString()); ++ + Assert.True(messageElement.TryGetProperty("RequestContext", out JsonElement requestContextElement)); + Assert.True(requestContextElement.TryGetProperty("Path", out JsonElement requestContextPathElement)); + Assert.Equal("/prod/path/to/resource", requestContextPathElement.GetString()); + ++ Assert.True(requestContextElement.TryGetProperty("AccountId", out JsonElement accountIdElement)); ++ Assert.Equal("123456789012", accountIdElement.GetString()); ++ + Assert.True(requestContextElement.TryGetProperty("ResourceId", out JsonElement resourceIdElement)); + Assert.Equal("123456", resourceIdElement.GetString()); +- ++ ++ Assert.True(requestContextElement.TryGetProperty("Stage", out JsonElement stageElement)); ++ Assert.Equal("prod", stageElement.GetString()); ++ + Assert.True(requestContextElement.TryGetProperty("RequestId", out JsonElement requestIdElement)); + Assert.Equal("c6af9ac6-7b61-11e6-9a41-93e8deadbeef", requestIdElement.GetString()); +- ++ ++ Assert.True(requestContextElement.TryGetProperty("ResourcePath", out JsonElement resourcePathElement)); ++ Assert.Equal("/{proxy+}", resourcePathElement.GetString()); ++ + Assert.True( + requestContextElement.TryGetProperty("HttpMethod", out JsonElement requestContextHttpMethodElement)); + Assert.Equal("POST", requestContextHttpMethodElement.GetString()); +@@ -191,6 +158,12 @@ public class FunctionTests + Assert.True(requestContextElement.TryGetProperty("ApiId", out JsonElement apiIdElement)); + Assert.Equal("1234567890", apiIdElement.GetString()); + ++ Assert.True(requestContextElement.TryGetProperty("RequestTime", out JsonElement requestTimeElement)); ++ Assert.Equal("09/Apr/2015:12:34:56 +0000", requestTimeElement.GetString()); ++ ++ Assert.True(requestContextElement.TryGetProperty("RequestTimeEpoch", out JsonElement requestTimeEpochElement)); ++ Assert.Equal(1428582896000, requestTimeEpochElement.GetInt64()); ++ + Assert.True(messageElement.TryGetProperty("Body", out JsonElement bodyElement)); + Assert.Equal("hello world", bodyElement.GetString()); + +@@ -270,48 +243,4 @@ public class FunctionTests + Assert.False(root.TryGetProperty("Test1", out JsonElement _)); + Assert.False(root.TryGetProperty("Test2", out JsonElement _)); + } +- +- private async Task UpdateFunctionHandler(string functionName, string handler) +- { +- var updateRequest = new UpdateFunctionConfigurationRequest +- { +- FunctionName = functionName, +- Handler = handler +- }; +- +- var updateResponse = await _lambdaClient.UpdateFunctionConfigurationAsync(updateRequest); +- +- if (updateResponse.HttpStatusCode == System.Net.HttpStatusCode.OK) +- { +- Console.WriteLine($"Successfully updated the handler for function {functionName} to {handler}"); +- } +- else +- { +- Assert.Fail( +- $"Failed to update the handler for function {functionName}. Status code: {updateResponse.HttpStatusCode}"); +- } +- +- //wait a few seconds for the changes to take effect +- await Task.Delay(1000); +- } +- +- private async Task ResetFunction(string functionName) +- { +- var updateRequest = new UpdateFunctionConfigurationRequest +- { +- FunctionName = functionName, +- Environment = new Environment +- { +- Variables = +- { +- {"Updated", DateTime.UtcNow.ToString("G")} +- } +- } +- }; +- +- await _lambdaClient.UpdateFunctionConfigurationAsync(updateRequest); +- +- //wait a few seconds for the changes to take effect +- await Task.Delay(1000); +- } + } +\ No newline at end of file +diff --git a/libraries/tests/e2e/functions/core/metrics/Function/src/Function/Function.csproj b/libraries/tests/e2e/functions/core/metrics/Function/src/Function/Function.csproj +index 1664858e..ed95bbca 100644 +--- a/libraries/tests/e2e/functions/core/metrics/Function/src/Function/Function.csproj ++++ b/libraries/tests/e2e/functions/core/metrics/Function/src/Function/Function.csproj +@@ -1,6 +1,6 @@ + + +- net6.0;net8.0 ++ net8.0 + enable + enable + true +diff --git a/libraries/tests/e2e/functions/core/metrics/Function/src/Function/TestHelper.cs b/libraries/tests/e2e/functions/core/metrics/Function/src/Function/TestHelper.cs +index 38cb7438..b7059a62 100644 +--- a/libraries/tests/e2e/functions/core/metrics/Function/src/Function/TestHelper.cs ++++ b/libraries/tests/e2e/functions/core/metrics/Function/src/Function/TestHelper.cs +@@ -17,7 +17,6 @@ public static class TestHelper + Metrics.SetDefaultDimensions(DefaultDimensions); + Metrics.AddMetric("Invocation", 1, MetricUnit.Count); + +- Metrics.AddDimension("FunctionName", context.FunctionName); + Metrics.AddDimension("Memory","MemoryLimitInMB"); + Metrics.AddMetric("Memory with Environment dimension", context.MemoryLimitInMB, MetricUnit.Megabytes); + +@@ -33,14 +32,14 @@ public static class TestHelper + Metrics.AddMetadata("RequestId", apigwProxyEvent.RequestContext.RequestId); + + Metrics.PushSingleMetric( +- name: "SingleMetric", ++ metricName: "SingleMetric", + value: 1, + unit: MetricUnit.Count, + nameSpace: "Test", + service: "Test", +- dimensions: new Dictionary ++ defaultDimensions: new Dictionary + { +- {"FunctionName", context.FunctionName} ++ {"FunctionContext", "$LATEST"} + }); + } + } +\ No newline at end of file +diff --git a/libraries/tests/e2e/functions/core/metrics/Function/test/Function.Tests/Function.Tests.csproj b/libraries/tests/e2e/functions/core/metrics/Function/test/Function.Tests/Function.Tests.csproj +index aa3f3cb8..6881f4cb 100644 +--- a/libraries/tests/e2e/functions/core/metrics/Function/test/Function.Tests/Function.Tests.csproj ++++ b/libraries/tests/e2e/functions/core/metrics/Function/test/Function.Tests/Function.Tests.csproj +@@ -10,7 +10,6 @@ + + + +- + + + +diff --git a/libraries/tests/e2e/functions/core/metrics/Function/test/Function.Tests/FunctionTests.cs b/libraries/tests/e2e/functions/core/metrics/Function/test/Function.Tests/FunctionTests.cs +index f0afeef3..1670dceb 100644 +--- a/libraries/tests/e2e/functions/core/metrics/Function/test/Function.Tests/FunctionTests.cs ++++ b/libraries/tests/e2e/functions/core/metrics/Function/test/Function.Tests/FunctionTests.cs +@@ -1,14 +1,10 @@ + using System.Text.Json; +-using Amazon.CDK.AWS.CodeDeploy; +-using Amazon.CloudWatch; +-using Amazon.CloudWatch.Model; + using Amazon.Lambda; + using Amazon.Lambda.APIGatewayEvents; + using Xunit; + using Amazon.Lambda.Model; + using TestUtils; + using Xunit.Abstractions; +-using Environment = Amazon.Lambda.Model.Environment; + + namespace Function.Tests; + +@@ -17,7 +13,6 @@ public class FunctionTests + { + private readonly ITestOutputHelper _testOutputHelper; + private readonly AmazonLambdaClient _lambdaClient; +- private string? _functionName; + + public FunctionTests(ITestOutputHelper testOutputHelper) + { +@@ -27,13 +22,11 @@ public class FunctionTests + + [Trait("Category", "AOT")] + [Theory] +- [InlineData("E2ETestLambda_X64_AOT_NET8_metrics_AOT-Function")] +- [InlineData("E2ETestLambda_ARM_AOT_NET8_metrics_AOT-Function")] ++ [InlineData("E2ETestLambda_X64_AOT_NET8_metrics")] ++ [InlineData("E2ETestLambda_ARM_AOT_NET8_metrics")] + public async Task AotFunctionTest(string functionName) + { +- _functionName = functionName; +- await ForceColdStart(); +- await TestFunction(); ++ await TestFunction(functionName); + } + + [Theory] +@@ -43,147 +36,61 @@ public class FunctionTests + [InlineData("E2ETestLambda_ARM_NET8_metrics")] + public async Task FunctionTest(string functionName) + { +- _functionName = functionName; +- await ForceColdStart(); +- await TestFunction(); ++ await TestFunction(functionName); + } + +- internal async Task TestFunction() ++ internal async Task TestFunction(string functionName) + { + var request = new InvokeRequest + { +- FunctionName = _functionName, ++ FunctionName = functionName, + InvocationType = InvocationType.RequestResponse, + Payload = await File.ReadAllTextAsync("../../../../../../../../payload.json"), + LogType = LogType.Tail + }; + +- // Test cold start +- var coldStartResponse = await _lambdaClient.InvokeAsync(request); +- ValidateResponse(coldStartResponse, true); ++ // run twice for cold and warm start ++ for (int i = 0; i < 2; i++) ++ { ++ var response = await _lambdaClient.InvokeAsync(request); + +- // Test warm start +- var warmStartResponse = await _lambdaClient.InvokeAsync(request); +- ValidateResponse(warmStartResponse, false); ++ if (string.IsNullOrEmpty(response.LogResult)) ++ { ++ Assert.Fail("No LogResult field returned in the response of Lambda invocation."); ++ } + +- // Assert cloudwatch +- await AssertCloudWatch(); +- } ++ var payload = System.Text.Encoding.UTF8.GetString(response.Payload.ToArray()); ++ var parsedPayload = JsonSerializer.Deserialize(payload); + +- private void ValidateResponse(InvokeResponse response, bool isColdStart) +- { +- if (string.IsNullOrEmpty(response.LogResult)) +- { +- Assert.Fail("No LogResult field returned in the response of Lambda invocation."); +- } ++ if (parsedPayload == null) ++ { ++ Assert.Fail("Failed to parse payload."); ++ } + +- var payload = System.Text.Encoding.UTF8.GetString(response.Payload.ToArray()); +- var parsedPayload = JsonSerializer.Deserialize(payload); ++ Assert.Equal(200, parsedPayload.StatusCode); ++ Assert.Equal("HELLO WORLD", parsedPayload.Body); + +- if (parsedPayload == null) +- { +- Assert.Fail("Failed to parse payload."); ++ // Assert Output log from Lambda execution ++ AssertOutputLog(response); + } +- +- Assert.Equal(200, parsedPayload.StatusCode); +- Assert.Equal("HELLO WORLD", parsedPayload.Body); +- +- // Assert Output log from Lambda execution +- AssertOutputLog(response, isColdStart); + } + +- private void AssertOutputLog(InvokeResponse response, bool expectedColdStart) ++ private void AssertOutputLog(InvokeResponse response) + { ++ // Extract and parse log + var logResult = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(response.LogResult)); + _testOutputHelper.WriteLine(logResult); + var output = OutputLogParser.ParseLogSegments(logResult, out var report); + var isColdStart = report.initDuration != "N/A"; +- +- Assert.Equal(expectedColdStart, isColdStart); +- ++ var index = 0; + if (isColdStart) + { +- AssertColdStart(output[0]); +- AssertSingleMetric(output[1]); +- AssertMetricsDimensionsMetadata(output[2]); +- } +- else +- { +- AssertSingleMetric(output[0]); +- AssertMetricsDimensionsMetadata(output[1]); ++ AssertColdStart(output[index]); ++ index += 1; + } +- } + +- private async Task AssertCloudWatch() +- { +- using var cloudWatchClient = new AmazonCloudWatchClient(); +- var request = new ListMetricsRequest +- { +- Namespace = "Test", +- Dimensions = +- [ +- new DimensionFilter +- { +- Name = "Service", +- Value = "Test" +- }, +- +- new DimensionFilter +- { +- Name = "FunctionName", +- Value = _functionName +- } +- ] +- }; +- +- // retry n amount of times to ensure metrics are available +- var response = new ListMetricsResponse(); +- for (int i = 0; i < 5; i++) +- { +- try +- { +- response = await cloudWatchClient.ListMetricsAsync(request); +- if (response.Metrics.Count > 6) +- { +- break; +- } +- } +- catch (Exception ex) +- { +- _testOutputHelper.WriteLine($"Attempt {i + 1}: Failed to list metrics: {ex.Message}"); +- } +- +- await Task.Delay(5000); // wait for 5 seconds before retrying +- } +- +- Assert.Equal(7, response.Metrics.Count); +- +- foreach (var metric in response.Metrics) +- { +- Assert.Equal("Test", metric.Namespace); +- +- switch (metric.MetricName) +- { +- case "ColdStart": +- case "SingleMetric": +- Assert.Equal(2, metric.Dimensions.Count); +- Assert.Contains(metric.Dimensions, d => d.Name == "Service" && d.Value == "Test"); +- Assert.Contains(metric.Dimensions, d => d.Name == "FunctionName" && d.Value == _functionName); +- break; +- case "Invocation": +- case "Memory with Environment dimension": +- case "Standard resolution": +- case "High resolution": +- case "Default resolution": +- Assert.Equal(5, metric.Dimensions.Count); +- Assert.Contains(metric.Dimensions, d => d.Name == "Service" && d.Value == "Test"); +- Assert.Contains(metric.Dimensions, d => d.Name == "FunctionName" && d.Value == _functionName); +- Assert.Contains(metric.Dimensions, d => d.Name == "Memory" && d.Value == "MemoryLimitInMB"); +- Assert.Contains(metric.Dimensions, d => d.Name == "Environment" && d.Value == "Prod"); +- Assert.Contains(metric.Dimensions, d => d.Name == "Another" && d.Value == "One"); +- break; +- } +- } ++ AssertSingleMetric(output[index]); ++ AssertMetricsDimensionsMetadata(output[index + 1]); + } + + private void AssertMetricsDimensionsMetadata(string output) +@@ -229,11 +136,10 @@ public class FunctionTests + Assert.Equal("Count", unitElement5.GetString()); + + Assert.True(cloudWatchMetricsElement[0].TryGetProperty("Dimensions", out JsonElement dimensionsElement)); +- Assert.Equal("Service", dimensionsElement[0][0].GetString()); +- Assert.Equal("Environment", dimensionsElement[0][1].GetString()); +- Assert.Equal("Another", dimensionsElement[0][2].GetString()); +- Assert.Equal("FunctionName", dimensionsElement[0][3].GetString()); +- Assert.Equal("Memory", dimensionsElement[0][4].GetString()); ++ Assert.Equal("Service", dimensionsElement[0].GetString()); ++ Assert.Equal("Environment", dimensionsElement[1].GetString()); ++ Assert.Equal("Another", dimensionsElement[2].GetString()); ++ Assert.Equal("Memory", dimensionsElement[3].GetString()); + + Assert.True(root.TryGetProperty("Service", out JsonElement serviceElement)); + Assert.Equal("Test", serviceElement.GetString()); +@@ -244,9 +150,6 @@ public class FunctionTests + Assert.True(root.TryGetProperty("Another", out JsonElement anotherElement)); + Assert.Equal("One", anotherElement.GetString()); + +- Assert.True(root.TryGetProperty("FunctionName", out JsonElement functionNameElement)); +- Assert.Equal(_functionName, functionNameElement.GetString()); +- + Assert.True(root.TryGetProperty("Memory", out JsonElement memoryElement)); + Assert.Equal("MemoryLimitInMB", memoryElement.GetString()); + +@@ -288,11 +191,11 @@ public class FunctionTests + Assert.Equal("Count", unitElement.GetString()); + + Assert.True(cloudWatchMetricsElement[0].TryGetProperty("Dimensions", out JsonElement dimensionsElement)); +- Assert.Equal("Service", dimensionsElement[0][0].GetString()); +- Assert.Equal("FunctionName", dimensionsElement[0][1].GetString()); ++ Assert.Equal("FunctionContext", dimensionsElement[0].GetString()); ++ Assert.Equal("Service", dimensionsElement[1].GetString()); + +- Assert.True(root.TryGetProperty("FunctionName", out JsonElement functionNameElement)); +- Assert.Equal(_functionName, functionNameElement.GetString()); ++ Assert.True(root.TryGetProperty("FunctionContext", out JsonElement functionContextElement)); ++ Assert.Equal("$LATEST", functionContextElement.GetString()); + + Assert.True(root.TryGetProperty("Service", out JsonElement serviceElement)); + Assert.Equal("Test", serviceElement.GetString()); +@@ -318,23 +221,4 @@ public class FunctionTests + Assert.True(root.TryGetProperty("ColdStart", out JsonElement coldStartElement)); + Assert.Equal(1, coldStartElement.GetInt32()); + } +- +- private async Task ForceColdStart() +- { +- var updateRequest = new UpdateFunctionConfigurationRequest +- { +- FunctionName = _functionName, +- Environment = new Environment +- { +- Variables = new Dictionary +- { +- { "ForceColdStart", Guid.NewGuid().ToString() } +- } +- } +- }; +- +- _ = await _lambdaClient.UpdateFunctionConfigurationAsync(updateRequest); +- +- await Task.Delay(15000); +- } + } +\ No newline at end of file +diff --git a/libraries/tests/e2e/functions/core/tracing/AOT-Function/src/AOT-Function/AOT-Function.csproj b/libraries/tests/e2e/functions/core/tracing/AOT-Function/src/AOT-Function/AOT-Function.csproj +index 111b59c2..85b41ba2 100644 +--- a/libraries/tests/e2e/functions/core/tracing/AOT-Function/src/AOT-Function/AOT-Function.csproj ++++ b/libraries/tests/e2e/functions/core/tracing/AOT-Function/src/AOT-Function/AOT-Function.csproj +@@ -17,7 +17,7 @@ + partial + + +- ++ + + + +diff --git a/libraries/tests/e2e/functions/core/tracing/Function/src/Function/Function.csproj b/libraries/tests/e2e/functions/core/tracing/Function/src/Function/Function.csproj +index c8c00458..e8d65b04 100644 +--- a/libraries/tests/e2e/functions/core/tracing/Function/src/Function/Function.csproj ++++ b/libraries/tests/e2e/functions/core/tracing/Function/src/Function/Function.csproj +@@ -1,6 +1,6 @@ + + +- net6.0;net8.0 ++ net8.0 + enable + enable + true +diff --git a/libraries/tests/e2e/functions/core/tracing/Function/test/Function.Tests/FunctionTests.cs b/libraries/tests/e2e/functions/core/tracing/Function/test/Function.Tests/FunctionTests.cs +index 919fd374..aa1c0b39 100644 +--- a/libraries/tests/e2e/functions/core/tracing/Function/test/Function.Tests/FunctionTests.cs ++++ b/libraries/tests/e2e/functions/core/tracing/Function/test/Function.Tests/FunctionTests.cs +@@ -25,8 +25,8 @@ public class FunctionTests + + [Trait("Category", "AOT")] + [Theory] +- [InlineData("E2ETestLambda_X64_AOT_NET8_tracing_AOT-Function")] +- [InlineData("E2ETestLambda_ARM_AOT_NET8_tracing_AOT-Function")] ++ [InlineData("E2ETestLambda_X64_AOT_NET8_tracing")] ++ [InlineData("E2ETestLambda_ARM_AOT_NET8_tracing")] + public async Task AotFunctionTest(string functionName) + { + await TestFunction(functionName); +diff --git a/libraries/tests/e2e/functions/idempotency/Function/src/Function/Function.csproj b/libraries/tests/e2e/functions/idempotency/Function/src/Function/Function.csproj +index 0dedeaea..82769084 100644 +--- a/libraries/tests/e2e/functions/idempotency/Function/src/Function/Function.csproj ++++ b/libraries/tests/e2e/functions/idempotency/Function/src/Function/Function.csproj +@@ -1,6 +1,6 @@ + + +- net6.0;net8.0 ++ net8.0 + enable + enable + true +diff --git a/libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/FunctionTests.cs b/libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/FunctionTests.cs +index b7bbe28c..3f5c7cc7 100644 +--- a/libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/FunctionTests.cs ++++ b/libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/FunctionTests.cs +@@ -186,7 +186,7 @@ public class FunctionTests + + // Assert DynamoDB + await AssertDynamoDbData( +- $"{key}#24e83361c8bd544887aa99ab26395d54", ++ $"{key}#35973cf447e6cc11008d603c791a232f", + guid1); + } + +@@ -323,7 +323,7 @@ public class FunctionTests + + return await ExecuteRequest(request); + } +- ++ + private async Task<(APIGatewayProxyResponse Response, string Guid)> ExecuteRequest(InvokeRequest request) + { + var response = await _lambdaClient.InvokeAsync(request); +diff --git a/libraries/tests/e2e/functions/payload.json b/libraries/tests/e2e/functions/payload.json +index 65696882..9f23a4b7 100644 +--- a/libraries/tests/e2e/functions/payload.json ++++ b/libraries/tests/e2e/functions/payload.json +@@ -4,12 +4,23 @@ + "path": "/path/to/resource", + "httpMethod": "POST", + "isBase64Encoded": false, ++ "queryStringParameters": { ++ "foo": "bar" ++ }, ++ "headers": { ++ "Accept-Encoding": "gzip, deflate, sdch", ++ "Accept-Language": "en-US,en;q=0.8", ++ "Cache-Control": "max-age=0" ++ }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", ++ "requestTime": "09/Apr/2015:12:34:56 +0000", ++ "requestTimeEpoch": 1428582896000, + "path": "/prod/path/to/resource", ++ "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "apiId": "1234567890", + "protocol": "HTTP/1.1" +diff --git a/libraries/tests/e2e/infra-aot/CoreAotStack.cs b/libraries/tests/e2e/infra-aot/CoreAotStack.cs +index d2ebcc5c..4387892c 100644 +--- a/libraries/tests/e2e/infra-aot/CoreAotStack.cs ++++ b/libraries/tests/e2e/infra-aot/CoreAotStack.cs +@@ -6,18 +6,6 @@ using Architecture = Amazon.CDK.AWS.Lambda.Architecture; + + namespace InfraAot; + +-public class ConstructArgs +-{ +- public Construct Scope { get; set; } +- public string Id { get; set; } +- public Runtime Runtime { get; set; } +- public Architecture Architecture { get; set; } +- public string Name { get; set; } +- public string SourcePath { get; set; } +- public string DistPath { get; set; } +- public string Handler { get; set; } +-} +- + public class CoreAotStack : Stack + { + private readonly Architecture _architecture; +@@ -27,42 +15,31 @@ public class CoreAotStack : Stack + if (props != null) _architecture = props.ArchitectureString == "arm64" ? Architecture.ARM_64 : Architecture.X86_64; + + CreateFunctionConstructs("logging"); +- CreateFunctionConstructs("logging", "AOT-Function-ILogger"); + CreateFunctionConstructs("metrics"); + CreateFunctionConstructs("tracing"); + } + +- private void CreateFunctionConstructs(string utility, string function = "AOT-Function" ) ++ private void CreateFunctionConstructs(string utility) + { +- var baseAotPath = $"../functions/core/{utility}/{function}/src/{function}"; +- var distAotPath = $"../functions/core/{utility}/{function}/dist/{function}"; ++ var baseAotPath = $"../functions/core/{utility}/AOT-Function/src/AOT-Function"; ++ var distAotPath = $"../functions/core/{utility}/AOT-Function/dist"; + var arch = _architecture == Architecture.X86_64 ? "X64" : "ARM"; + +- var construct = new ConstructArgs +- { +- Scope = this, +- Id = $"{utility}_{arch}_aot_net8_{function}", +- Runtime = Runtime.DOTNET_8, +- Architecture = _architecture, +- Name = $"E2ETestLambda_{arch}_AOT_NET8_{utility}_{function}", +- SourcePath = baseAotPath, +- DistPath = distAotPath, +- Handler = function +- }; +- +- CreateFunctionConstruct(construct); ++ CreateFunctionConstruct(this, $"{utility}_{arch}_aot_net8", Runtime.DOTNET_8, _architecture, ++ $"E2ETestLambda_{arch}_AOT_NET8_{utility}", baseAotPath, distAotPath); + } + +- private void CreateFunctionConstruct(ConstructArgs constructArgs) ++ private void CreateFunctionConstruct(Construct scope, string id, Runtime runtime, Architecture architecture, ++ string name, string sourcePath, string distPath) + { +- _ = new FunctionConstruct(constructArgs.Scope, constructArgs.Id, new FunctionConstructProps ++ _ = new FunctionConstruct(scope, id, new FunctionConstructProps + { +- Runtime = constructArgs.Runtime, +- Architecture = constructArgs.Architecture, +- Name = constructArgs.Name, +- Handler = constructArgs.Handler, +- SourcePath = constructArgs.SourcePath, +- DistPath = constructArgs.DistPath, ++ Runtime = runtime, ++ Architecture = architecture, ++ Name = name, ++ Handler = "AOT-Function", ++ SourcePath = sourcePath, ++ DistPath = distPath, + IsAot = true + }); + } +diff --git a/libraries/tests/e2e/infra/CoreStack.cs b/libraries/tests/e2e/infra/CoreStack.cs +index 15f3fd6d..d77c725a 100644 +--- a/libraries/tests/e2e/infra/CoreStack.cs ++++ b/libraries/tests/e2e/infra/CoreStack.cs +@@ -6,28 +6,6 @@ using Architecture = Amazon.CDK.AWS.Lambda.Architecture; + + namespace Infra + { +- public class ConstructArgs +- { +- public ConstructArgs(Construct scope, string id, Runtime runtime, Architecture architecture, string name, string sourcePath, string distPath) +- { +- Scope = scope; +- Id = id; +- Runtime = runtime; +- Architecture = architecture; +- Name = name; +- SourcePath = sourcePath; +- DistPath = distPath; +- } +- +- public Construct Scope { get; private set; } +- public string Id { get; private set; } +- public Runtime Runtime { get; private set; } +- public Architecture Architecture { get; private set; } +- public string Name { get; private set; } +- public string SourcePath { get; private set; } +- public string DistPath { get; private set; } +- } +- + public class CoreStack : Stack + { + internal CoreStack(Construct scope, string id, IStackProps props = null) : base(scope, id, props) +@@ -42,22 +20,27 @@ namespace Infra + var basePath = $"../functions/core/{utility}/Function/src/Function"; + var distPath = $"../functions/core/{utility}/Function/dist"; + +- CreateFunctionConstruct(new ConstructArgs(this, $"{utility}_X64_net8", Runtime.DOTNET_8, Architecture.X86_64, $"E2ETestLambda_X64_NET8_{utility}", basePath, distPath)); +- CreateFunctionConstruct(new ConstructArgs(this, $"{utility}_arm_net8", Runtime.DOTNET_8, Architecture.ARM_64, $"E2ETestLambda_ARM_NET8_{utility}", basePath, distPath)); +- CreateFunctionConstruct(new ConstructArgs(this, $"{utility}_X64_net6", Runtime.DOTNET_6, Architecture.X86_64, $"E2ETestLambda_X64_NET6_{utility}", basePath, distPath)); +- CreateFunctionConstruct(new ConstructArgs(this, $"{utility}_arm_net6", Runtime.DOTNET_6, Architecture.ARM_64, $"E2ETestLambda_ARM_NET6_{utility}", basePath, distPath)); ++ CreateFunctionConstruct(this, $"{utility}_X64_net8", Runtime.DOTNET_8, Architecture.X86_64, ++ $"E2ETestLambda_X64_NET8_{utility}", basePath, distPath); ++ CreateFunctionConstruct(this, $"{utility}_arm_net8", Runtime.DOTNET_8, Architecture.ARM_64, ++ $"E2ETestLambda_ARM_NET8_{utility}", basePath, distPath); ++ CreateFunctionConstruct(this, $"{utility}_X64_net6", Runtime.DOTNET_6, Architecture.X86_64, ++ $"E2ETestLambda_X64_NET6_{utility}", basePath, distPath); ++ CreateFunctionConstruct(this, $"{utility}_arm_net6", Runtime.DOTNET_6, Architecture.ARM_64, ++ $"E2ETestLambda_ARM_NET6_{utility}", basePath, distPath); + } + +- private void CreateFunctionConstruct(ConstructArgs constructArgs) ++ private void CreateFunctionConstruct(Construct scope, string id, Runtime runtime, Architecture architecture, ++ string name, string sourcePath, string distPath) + { +- _ = new FunctionConstruct(constructArgs.Scope, constructArgs.Id, new FunctionConstructProps ++ _ = new FunctionConstruct(scope, id, new FunctionConstructProps + { +- Runtime = constructArgs.Runtime, +- Architecture = constructArgs.Architecture, +- Name = constructArgs.Name, ++ Runtime = runtime, ++ Architecture = architecture, ++ Name = name, + Handler = "Function::Function.Function::FunctionHandler", +- SourcePath = constructArgs.SourcePath, +- DistPath = constructArgs.DistPath, ++ SourcePath = sourcePath, ++ DistPath = distPath, + }); + } + } +diff --git a/mkdocs.yml b/mkdocs.yml +index 81cf8be8..c804a353 100644 +--- a/mkdocs.yml ++++ b/mkdocs.yml +@@ -3,38 +3,25 @@ site_description: Powertools for AWS Lambda (.NET) + site_author: Amazon Web Services + repo_url: https://github.com/aws-powertools/powertools-lambda-dotnet + edit_uri: edit/develop/docs +-site_url: https://docs.powertools.aws.dev/lambda/dotnet/ + + nav: +- - Homepage: +- - index.md +- - References: references.md +- - Changelog: changelog.md +- - Roadmap: roadmap.md +- - We Made This (Community): we_made_this.md +- - Workshop 🆕: https://s12d.com/powertools-for-aws-lambda-workshop" target="_blank +- - Getting started: +- - Logging: +- - getting-started/logger/simple.md +- - getting-started/logger/aspnet.md +- - getting-started/logger/aot.md +- - Features: +- - core/logging.md +- - core/metrics.md +- - core/tracing.md +- - utilities/idempotency.md +- - utilities/batch-processing.md +- - Event Handler: +- - core/event_handler/appsync_events.md +- - core/event_handler/bedrock_agent_function.md +- - utilities/parameters.md +- - utilities/jmespath-functions.md +- - utilities/kafka.md +- - Resources: +- - "llms.txt": ./llms.txt +- - "llms.txt (full version)": ./llms-full.txt ++ - Homepage: index.md ++ - References: references.md ++ - Changelog: changelog.md ++ - Roadmap: roadmap.md + - API Reference: api/" target="_blank +- ++ - We Made This (Community): we_made_this.md ++ - Workshop 🆕: https://s12d.com/powertools-for-aws-lambda-workshop" target="_blank ++ - Core utilities: ++ - core/logging.md ++ - core/metrics.md ++ - core/tracing.md ++ - Utilities: ++ - utilities/parameters.md ++ - utilities/idempotency.md ++ - utilities/batch-processing.md ++ - utilities/jmespath-functions.md ++ + theme: + name: material + font: +@@ -55,16 +42,14 @@ theme: + features: + - header.autohide + - navigation.sections ++ - navigation.expand + - navigation.top +- - navigation.tabs + - navigation.instant + - navigation.indexes + - navigation.tracking + - content.code.annotate +- - content.code.copy + - toc.follow + - announce.dismiss +- - content.tabs.link + icon: + repo: fontawesome/brands/github + logo: media/aws-logo-light.svg +@@ -100,43 +85,13 @@ markdown_extensions: + format: !!python/name:pymdownx.superfences.fence_code_format + - md_in_html + +-copyright: | +- ++copyright: Copyright © 2024 Amazon Web Services + + plugins: + - privacy + - git-revision-date + - search +- - llmstxt: +- markdown_description: Powertools for AWS Lambda (.NET) is a developer toolkit to implement Serverless best practices and increase developer velocity. It provides a suite of utilities for AWS Lambda Functions that makes tracing with AWS X-Ray, structured logging and creating custom metrics asynchronously easier. +- full_output: llms-full.txt +- sections: +- Project Overview: +- - index.md +- - changelog.md +- - roadmap.md +- Core Utilities: +- - core/logging.md +- - core/metrics.md +- - core/tracing.md +- Utilities: +- - utilities/idempotency.md +- - utilities/batch-processing.md +- - utilities/parameters.md +- - utilities/jmespath-functions.md +- - core/event_handler/appsync_events.md +- - core/event_handler/bedrock_agent_function.md +- - utilities/kafka.md +- Getting Started: +- - getting-started/logger/simple.md +- - getting-started/logger/aspnet.md +- - getting-started/logger/aot.md ++ + extra_css: + - stylesheets/extra.css + extra_javascript: +diff --git a/package-lock.json b/package-lock.json +deleted file mode 100644 +index 5ed858e8..00000000 +--- a/package-lock.json ++++ /dev/null +@@ -1,447 +0,0 @@ +-{ +- "name": "powertools-lambda-dotnet", +- "version": "1.0.0", +- "lockfileVersion": 3, +- "requires": true, +- "packages": { +- "": { +- "name": "powertools-lambda-dotnet", +- "version": "1.0.0", +- "license": "MIT", +- "dependencies": { +- "aws-cdk": "^2.1000.2", +- "aws-cdk-lib": "^2.189.1" +- } +- }, +- "node_modules/@aws-cdk/asset-awscli-v1": { +- "version": "2.2.230", +- "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.230.tgz", +- "integrity": "sha512-kUnhKIYu42hqBa6a8x2/7o29ObpJgjYGQy28lZDq9awXyvpR62I2bRxrNKNR3uFUQz3ySuT9JXhGHhuZPdbnFw==", +- "license": "Apache-2.0" +- }, +- "node_modules/@aws-cdk/asset-node-proxy-agent-v6": { +- "version": "2.1.0", +- "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.1.0.tgz", +- "integrity": "sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A==" +- }, +- "node_modules/@aws-cdk/cloud-assembly-schema": { +- "version": "41.2.0", +- "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-41.2.0.tgz", +- "integrity": "sha512-JaulVS6z9y5+u4jNmoWbHZRs9uGOnmn/ktXygNWKNu1k6lF3ad4so3s18eRu15XCbUIomxN9WPYT6Ehh7hzONw==", +- "bundleDependencies": [ +- "jsonschema", +- "semver" +- ], +- "license": "Apache-2.0", +- "dependencies": { +- "jsonschema": "~1.4.1", +- "semver": "^7.7.1" +- }, +- "engines": { +- "node": ">= 14.15.0" +- } +- }, +- "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": { +- "version": "1.4.1", +- "inBundle": true, +- "license": "MIT", +- "engines": { +- "node": "*" +- } +- }, +- "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": { +- "version": "7.7.1", +- "inBundle": true, +- "license": "ISC", +- "bin": { +- "semver": "bin/semver.js" +- }, +- "engines": { +- "node": ">=10" +- } +- }, +- "node_modules/aws-cdk": { +- "version": "2.1000.2", +- "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1000.2.tgz", +- "integrity": "sha512-QsXqJhGWjHNqP7etgE3sHOTiDBXItmSKdFKgsm1qPMBabCMyFfmWZnEeUxfZ4sMaIoxvLpr3sqoWSNeLuUk4sg==", +- "bin": { +- "cdk": "bin/cdk" +- }, +- "engines": { +- "node": ">= 16.0.0" +- }, +- "optionalDependencies": { +- "fsevents": "2.3.2" +- } +- }, +- "node_modules/aws-cdk-lib": { +- "version": "2.189.1", +- "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.189.1.tgz", +- "integrity": "sha512-9JU0yUr2iRTJ1oCPrHyx7hOtBDWyUfyOcdb6arlumJnMcQr2cyAMASY8HuAXHc8Y10ipVp8dRTW+J4/132IIYA==", +- "bundleDependencies": [ +- "@balena/dockerignore", +- "case", +- "fs-extra", +- "ignore", +- "jsonschema", +- "minimatch", +- "punycode", +- "semver", +- "table", +- "yaml", +- "mime-types" +- ], +- "license": "Apache-2.0", +- "dependencies": { +- "@aws-cdk/asset-awscli-v1": "^2.2.229", +- "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.0", +- "@aws-cdk/cloud-assembly-schema": "^41.0.0", +- "@balena/dockerignore": "^1.0.2", +- "case": "1.6.3", +- "fs-extra": "^11.3.0", +- "ignore": "^5.3.2", +- "jsonschema": "^1.5.0", +- "mime-types": "^2.1.35", +- "minimatch": "^3.1.2", +- "punycode": "^2.3.1", +- "semver": "^7.7.1", +- "table": "^6.9.0", +- "yaml": "1.10.2" +- }, +- "engines": { +- "node": ">= 14.15.0" +- }, +- "peerDependencies": { +- "constructs": "^10.0.0" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/@balena/dockerignore": { +- "version": "1.0.2", +- "inBundle": true, +- "license": "Apache-2.0" +- }, +- "node_modules/aws-cdk-lib/node_modules/ajv": { +- "version": "8.17.1", +- "inBundle": true, +- "license": "MIT", +- "dependencies": { +- "fast-deep-equal": "^3.1.3", +- "fast-uri": "^3.0.1", +- "json-schema-traverse": "^1.0.0", +- "require-from-string": "^2.0.2" +- }, +- "funding": { +- "type": "github", +- "url": "https://github.com/sponsors/epoberezkin" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/ansi-regex": { +- "version": "5.0.1", +- "inBundle": true, +- "license": "MIT", +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/ansi-styles": { +- "version": "4.3.0", +- "inBundle": true, +- "license": "MIT", +- "dependencies": { +- "color-convert": "^2.0.1" +- }, +- "engines": { +- "node": ">=8" +- }, +- "funding": { +- "url": "https://github.com/chalk/ansi-styles?sponsor=1" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/astral-regex": { +- "version": "2.0.0", +- "inBundle": true, +- "license": "MIT", +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/balanced-match": { +- "version": "1.0.2", +- "inBundle": true, +- "license": "MIT" +- }, +- "node_modules/aws-cdk-lib/node_modules/brace-expansion": { +- "version": "1.1.11", +- "inBundle": true, +- "license": "MIT", +- "dependencies": { +- "balanced-match": "^1.0.0", +- "concat-map": "0.0.1" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/case": { +- "version": "1.6.3", +- "inBundle": true, +- "license": "(MIT OR GPL-3.0-or-later)", +- "engines": { +- "node": ">= 0.8.0" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/color-convert": { +- "version": "2.0.1", +- "inBundle": true, +- "license": "MIT", +- "dependencies": { +- "color-name": "~1.1.4" +- }, +- "engines": { +- "node": ">=7.0.0" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/color-name": { +- "version": "1.1.4", +- "inBundle": true, +- "license": "MIT" +- }, +- "node_modules/aws-cdk-lib/node_modules/concat-map": { +- "version": "0.0.1", +- "inBundle": true, +- "license": "MIT" +- }, +- "node_modules/aws-cdk-lib/node_modules/emoji-regex": { +- "version": "8.0.0", +- "inBundle": true, +- "license": "MIT" +- }, +- "node_modules/aws-cdk-lib/node_modules/fast-deep-equal": { +- "version": "3.1.3", +- "inBundle": true, +- "license": "MIT" +- }, +- "node_modules/aws-cdk-lib/node_modules/fast-uri": { +- "version": "3.0.6", +- "funding": [ +- { +- "type": "github", +- "url": "https://github.com/sponsors/fastify" +- }, +- { +- "type": "opencollective", +- "url": "https://opencollective.com/fastify" +- } +- ], +- "inBundle": true, +- "license": "BSD-3-Clause" +- }, +- "node_modules/aws-cdk-lib/node_modules/fs-extra": { +- "version": "11.3.0", +- "inBundle": true, +- "license": "MIT", +- "dependencies": { +- "graceful-fs": "^4.2.0", +- "jsonfile": "^6.0.1", +- "universalify": "^2.0.0" +- }, +- "engines": { +- "node": ">=14.14" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/graceful-fs": { +- "version": "4.2.11", +- "inBundle": true, +- "license": "ISC" +- }, +- "node_modules/aws-cdk-lib/node_modules/ignore": { +- "version": "5.3.2", +- "inBundle": true, +- "license": "MIT", +- "engines": { +- "node": ">= 4" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/is-fullwidth-code-point": { +- "version": "3.0.0", +- "inBundle": true, +- "license": "MIT", +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/json-schema-traverse": { +- "version": "1.0.0", +- "inBundle": true, +- "license": "MIT" +- }, +- "node_modules/aws-cdk-lib/node_modules/jsonfile": { +- "version": "6.1.0", +- "inBundle": true, +- "license": "MIT", +- "dependencies": { +- "universalify": "^2.0.0" +- }, +- "optionalDependencies": { +- "graceful-fs": "^4.1.6" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/jsonschema": { +- "version": "1.5.0", +- "inBundle": true, +- "license": "MIT", +- "engines": { +- "node": "*" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/lodash.truncate": { +- "version": "4.4.2", +- "inBundle": true, +- "license": "MIT" +- }, +- "node_modules/aws-cdk-lib/node_modules/mime-db": { +- "version": "1.52.0", +- "inBundle": true, +- "license": "MIT", +- "engines": { +- "node": ">= 0.6" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/mime-types": { +- "version": "2.1.35", +- "inBundle": true, +- "license": "MIT", +- "dependencies": { +- "mime-db": "1.52.0" +- }, +- "engines": { +- "node": ">= 0.6" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/minimatch": { +- "version": "3.1.2", +- "inBundle": true, +- "license": "ISC", +- "dependencies": { +- "brace-expansion": "^1.1.7" +- }, +- "engines": { +- "node": "*" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/punycode": { +- "version": "2.3.1", +- "inBundle": true, +- "license": "MIT", +- "engines": { +- "node": ">=6" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/require-from-string": { +- "version": "2.0.2", +- "inBundle": true, +- "license": "MIT", +- "engines": { +- "node": ">=0.10.0" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/semver": { +- "version": "7.7.1", +- "inBundle": true, +- "license": "ISC", +- "bin": { +- "semver": "bin/semver.js" +- }, +- "engines": { +- "node": ">=10" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/slice-ansi": { +- "version": "4.0.0", +- "inBundle": true, +- "license": "MIT", +- "dependencies": { +- "ansi-styles": "^4.0.0", +- "astral-regex": "^2.0.0", +- "is-fullwidth-code-point": "^3.0.0" +- }, +- "engines": { +- "node": ">=10" +- }, +- "funding": { +- "url": "https://github.com/chalk/slice-ansi?sponsor=1" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/string-width": { +- "version": "4.2.3", +- "inBundle": true, +- "license": "MIT", +- "dependencies": { +- "emoji-regex": "^8.0.0", +- "is-fullwidth-code-point": "^3.0.0", +- "strip-ansi": "^6.0.1" +- }, +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/strip-ansi": { +- "version": "6.0.1", +- "inBundle": true, +- "license": "MIT", +- "dependencies": { +- "ansi-regex": "^5.0.1" +- }, +- "engines": { +- "node": ">=8" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/table": { +- "version": "6.9.0", +- "inBundle": true, +- "license": "BSD-3-Clause", +- "dependencies": { +- "ajv": "^8.0.1", +- "lodash.truncate": "^4.4.2", +- "slice-ansi": "^4.0.0", +- "string-width": "^4.2.3", +- "strip-ansi": "^6.0.1" +- }, +- "engines": { +- "node": ">=10.0.0" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/universalify": { +- "version": "2.0.1", +- "inBundle": true, +- "license": "MIT", +- "engines": { +- "node": ">= 10.0.0" +- } +- }, +- "node_modules/aws-cdk-lib/node_modules/yaml": { +- "version": "1.10.2", +- "inBundle": true, +- "license": "ISC", +- "engines": { +- "node": ">= 6" +- } +- }, +- "node_modules/constructs": { +- "version": "10.4.2", +- "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.4.2.tgz", +- "integrity": "sha512-wsNxBlAott2qg8Zv87q3eYZYgheb9lchtBfjHzzLHtXbttwSrHPs1NNQbBrmbb1YZvYg2+Vh0Dor76w4mFxJkA==", +- "peer": true +- }, +- "node_modules/fsevents": { +- "version": "2.3.2", +- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", +- "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", +- "hasInstallScript": true, +- "optional": true, +- "os": [ +- "darwin" +- ], +- "engines": { +- "node": "^8.16.0 || ^10.6.0 || >=11.0.0" +- } +- } +- } +-} +diff --git a/package.json b/package.json +deleted file mode 100644 +index e402ead5..00000000 +--- a/package.json ++++ /dev/null +@@ -1,19 +0,0 @@ +-{ +- "name": "powertools-lambda-dotnet", +- "version": "1.0.0", +- "description": "Powertools for AWS Lambda (.NET)", +- "main": "index.js", +- "directories": { +- "doc": "docs", +- "example": "examples" +- }, +- "scripts": { +- "test": "echo \"Error: no test specified\" && exit 1" +- }, +- "author": "", +- "license": "MIT", +- "dependencies": { +- "aws-cdk": "^2.1000.2", +- "aws-cdk-lib": "^2.189.1" +- } +-} +diff --git a/poetry.lock b/poetry.lock +index 80a21cfb..b54242f5 100644 +--- a/poetry.lock ++++ b/poetry.lock +@@ -1,144 +1,14 @@ +-# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. +- +-[[package]] +-name = "babel" +-version = "2.17.0" +-description = "Internationalization utilities" +-optional = false +-python-versions = ">=3.8" +-groups = ["main"] +-files = [ +- {file = "babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"}, +- {file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"}, +-] +- +-[package.extras] +-dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata ; sys_platform == \"win32\""] +- +-[[package]] +-name = "certifi" +-version = "2025.1.31" +-description = "Python package for providing Mozilla's CA Bundle." +-optional = false +-python-versions = ">=3.6" +-groups = ["main"] +-files = [ +- {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, +- {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, +-] +- +-[[package]] +-name = "charset-normalizer" +-version = "3.4.1" +-description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +-optional = false +-python-versions = ">=3.7" +-groups = ["main"] +-files = [ +- {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, +- {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, +- {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"}, +- {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"}, +- {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"}, +- {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"}, +- {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"}, +- {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"}, +- {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"}, +- {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"}, +- {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"}, +- {file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"}, +- {file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"}, +- {file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"}, +- {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"}, +- {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"}, +- {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"}, +- {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"}, +- {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"}, +- {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"}, +- {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"}, +- {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"}, +- {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"}, +- {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"}, +- {file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"}, +- {file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"}, +- {file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"}, +- {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"}, +- {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"}, +- {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"}, +- {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"}, +- {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"}, +- {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"}, +- {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"}, +- {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"}, +- {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"}, +- {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"}, +- {file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"}, +- {file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"}, +- {file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"}, +- {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"}, +- {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"}, +- {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"}, +- {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"}, +- {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"}, +- {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"}, +- {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"}, +- {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"}, +- {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"}, +- {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"}, +- {file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"}, +- {file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"}, +- {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089"}, +- {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d"}, +- {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf"}, +- {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e"}, +- {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a"}, +- {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd"}, +- {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534"}, +- {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e"}, +- {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e"}, +- {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa"}, +- {file = "charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487"}, +- {file = "charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d"}, +- {file = "charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c"}, +- {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9"}, +- {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8"}, +- {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6"}, +- {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c"}, +- {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a"}, +- {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd"}, +- {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd"}, +- {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824"}, +- {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca"}, +- {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b"}, +- {file = "charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e"}, +- {file = "charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4"}, +- {file = "charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41"}, +- {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f"}, +- {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2"}, +- {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770"}, +- {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4"}, +- {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537"}, +- {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496"}, +- {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78"}, +- {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7"}, +- {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6"}, +- {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294"}, +- {file = "charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5"}, +- {file = "charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765"}, +- {file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"}, +- {file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"}, +-] ++# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. + + [[package]] + name = "click" +-version = "8.1.8" ++version = "8.1.3" + description = "Composable command line interface toolkit" + optional = false + python-versions = ">=3.7" +-groups = ["main"] + files = [ +- {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, +- {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, ++ {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, ++ {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, + ] + + [package.dependencies] +@@ -150,7 +20,6 @@ version = "0.4.6" + description = "Cross-platform colored terminal text." + optional = false + python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +-groups = ["main"] + files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +@@ -162,7 +31,6 @@ version = "2.1.0" + description = "Copy your docs directly to the gh-pages branch." + optional = false + python-versions = "*" +-groups = ["main"] + files = [ + {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, + {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, +@@ -176,14 +44,13 @@ dev = ["flake8", "markdown", "twine", "wheel"] + + [[package]] + name = "gitdb" +-version = "4.0.12" ++version = "4.0.10" + description = "Git Object Database" + optional = false + python-versions = ">=3.7" +-groups = ["main"] + files = [ +- {file = "gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf"}, +- {file = "gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571"}, ++ {file = "gitdb-4.0.10-py3-none-any.whl", hash = "sha256:c286cf298426064079ed96a9e4a9d39e7f3e9bf15ba60701e95f5492f28415c7"}, ++ {file = "gitdb-4.0.10.tar.gz", hash = "sha256:6eb990b69df4e15bad899ea868dc46572c3f75339735663b81de79b06f17eb9a"}, + ] + + [package.dependencies] +@@ -191,73 +58,49 @@ smmap = ">=3.0.1,<6" + + [[package]] + name = "gitpython" +-version = "3.1.44" ++version = "3.1.41" + description = "GitPython is a Python library used to interact with Git repositories" + optional = false + python-versions = ">=3.7" +-groups = ["main"] + files = [ +- {file = "GitPython-3.1.44-py3-none-any.whl", hash = "sha256:9e0e10cda9bed1ee64bc9a6de50e7e38a9c9943241cd7f585f6df3ed28011110"}, +- {file = "gitpython-3.1.44.tar.gz", hash = "sha256:c87e30b26253bf5418b01b0660f818967f3c503193838337fe5e573331249269"}, ++ {file = "GitPython-3.1.41-py3-none-any.whl", hash = "sha256:c36b6634d069b3f719610175020a9aed919421c87552185b085e04fbbdb10b7c"}, ++ {file = "GitPython-3.1.41.tar.gz", hash = "sha256:ed66e624884f76df22c8e16066d567aaa5a37d5b5fa19db2c6df6f7156db9048"}, + ] + + [package.dependencies] + gitdb = ">=4.0.1,<5" + + [package.extras] +-doc = ["sphinx (>=7.1.2,<7.2)", "sphinx-autodoc-typehints", "sphinx_rtd_theme"] +-test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock ; python_version < \"3.8\"", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "typing-extensions ; python_version < \"3.11\""] +- +-[[package]] +-name = "idna" +-version = "3.10" +-description = "Internationalized Domain Names in Applications (IDNA)" +-optional = false +-python-versions = ">=3.6" +-groups = ["main"] +-files = [ +- {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, +- {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +-] +- +-[package.extras] +-all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] ++test = ["black", "coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "sumtypes"] + + [[package]] + name = "importlib-metadata" +-version = "8.6.1" ++version = "5.2.0" + description = "Read metadata from Python packages" + optional = false +-python-versions = ">=3.9" +-groups = ["main"] +-markers = "python_version < \"3.10\"" ++python-versions = ">=3.7" + files = [ +- {file = "importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e"}, +- {file = "importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580"}, ++ {file = "importlib_metadata-5.2.0-py3-none-any.whl", hash = "sha256:0eafa39ba42bf225fc00e67f701d71f85aead9f878569caf13c3724f704b970f"}, ++ {file = "importlib_metadata-5.2.0.tar.gz", hash = "sha256:404d48d62bba0b7a77ff9d405efd91501bef2e67ff4ace0bed40a0cf28c3c7cd"}, + ] + + [package.dependencies] +-zipp = ">=3.20" ++zipp = ">=0.5" + + [package.extras] +-check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +-cover = ["pytest-cov"] +-doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +-enabler = ["pytest-enabler (>=2.2)"] ++docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] + perf = ["ipython"] +-test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +-type = ["pytest-mypy"] ++testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] + + [[package]] + name = "jinja2" +-version = "3.1.6" ++version = "3.1.5" + description = "A very fast and expressive template engine." + optional = false + python-versions = ">=3.7" +-groups = ["main"] + files = [ +- {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, +- {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, ++ {file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"}, ++ {file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"}, + ] + + [package.dependencies] +@@ -268,92 +111,68 @@ i18n = ["Babel (>=2.7)"] + + [[package]] + name = "markdown" +-version = "3.7" +-description = "Python implementation of John Gruber's Markdown." ++version = "3.3.7" ++description = "Python implementation of Markdown." + optional = false +-python-versions = ">=3.8" +-groups = ["main"] ++python-versions = ">=3.6" + files = [ +- {file = "Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803"}, +- {file = "markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2"}, ++ {file = "Markdown-3.3.7-py3-none-any.whl", hash = "sha256:f5da449a6e1c989a4cea2631aa8ee67caa5a2ef855d551c88f9e309f4634c621"}, ++ {file = "Markdown-3.3.7.tar.gz", hash = "sha256:cbb516f16218e643d8e0a95b309f77eb118cb138d39a4f27851e6a63581db874"}, + ] + + [package.dependencies] + importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} + + [package.extras] +-docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] + testing = ["coverage", "pyyaml"] + + [[package]] + name = "markupsafe" +-version = "3.0.2" ++version = "2.1.1" + description = "Safely add untrusted strings to HTML/XML markup." + optional = false +-python-versions = ">=3.9" +-groups = ["main"] ++python-versions = ">=3.7" + files = [ +- {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, +- {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, +- {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, +- {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, +- {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, +- {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, +- {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, +- {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, +- {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, +- {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, +- {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, +- {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, +- {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, +- {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, +- {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, +- {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, +- {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, +- {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, +- {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, +- {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, +- {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, +- {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, +- {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, +- {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, +- {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, +- {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, +- {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, +- {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, +- {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, +- {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, +- {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, +- {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, +- {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, +- {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, +- {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, +- {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, +- {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, +- {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, +- {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, +- {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, +- {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, +- {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, +- {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, +- {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, +- {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, +- {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, +- {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, +- {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, +- {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, +- {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, +- {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, +- {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, +- {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, +- {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, +- {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, +- {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, +- {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, +- {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, +- {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, +- {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, +- {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, ++ {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, ++ {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, ++ {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, ++ {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, ++ {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, ++ {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, ++ {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, ++ {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, ++ {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, ++ {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, ++ {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, ++ {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, ++ {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, ++ {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, ++ {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, ++ {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, ++ {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, ++ {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, ++ {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, ++ {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, ++ {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, ++ {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, ++ {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, ++ {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, ++ {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, ++ {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, ++ {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, ++ {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, ++ {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, ++ {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, ++ {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, ++ {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, ++ {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, ++ {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, ++ {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, ++ {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, ++ {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, ++ {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, ++ {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, ++ {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, + ] + + [[package]] +@@ -362,7 +181,6 @@ version = "1.3.4" + description = "A deep merge function for 🐍." + optional = false + python-versions = ">=3.6" +-groups = ["main"] + files = [ + {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, + {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, +@@ -374,7 +192,6 @@ version = "1.1.2" + description = "Manage multiple versions of your MkDocs-powered documentation" + optional = false + python-versions = "*" +-groups = ["main"] + files = [ + {file = "mike-1.1.2-py3-none-any.whl", hash = "sha256:4c307c28769834d78df10f834f57f810f04ca27d248f80a75f49c6fa2d1527ca"}, + {file = "mike-1.1.2.tar.gz", hash = "sha256:56c3f1794c2d0b5fdccfa9b9487beb013ca813de2e3ad0744724e9d34d40b77b"}, +@@ -392,53 +209,31 @@ test = ["coverage", "flake8 (>=3.0)", "shtab"] + + [[package]] + name = "mkdocs" +-version = "1.6.1" ++version = "1.4.2" + description = "Project documentation with Markdown." + optional = false +-python-versions = ">=3.8" +-groups = ["main"] ++python-versions = ">=3.7" + files = [ +- {file = "mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e"}, +- {file = "mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2"}, ++ {file = "mkdocs-1.4.2-py3-none-any.whl", hash = "sha256:c8856a832c1e56702577023cd64cc5f84948280c1c0fcc6af4cd39006ea6aa8c"}, ++ {file = "mkdocs-1.4.2.tar.gz", hash = "sha256:8947af423a6d0facf41ea1195b8e1e8c85ad94ac95ae307fe11232e0424b11c5"}, + ] + + [package.dependencies] + click = ">=7.0" + colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} + ghp-import = ">=1.0" +-importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} ++importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""} + jinja2 = ">=2.11.1" +-markdown = ">=3.3.6" +-markupsafe = ">=2.0.1" ++markdown = ">=3.2.1,<3.4" + mergedeep = ">=1.3.4" +-mkdocs-get-deps = ">=0.2.0" + packaging = ">=20.5" +-pathspec = ">=0.11.1" + pyyaml = ">=5.1" + pyyaml-env-tag = ">=0.1" + watchdog = ">=2.0" + + [package.extras] + i18n = ["babel (>=2.9.0)"] +-min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4) ; platform_system == \"Windows\"", "ghp-import (==1.0)", "importlib-metadata (==4.4) ; python_version < \"3.10\"", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"] +- +-[[package]] +-name = "mkdocs-get-deps" +-version = "0.2.0" +-description = "MkDocs extension that lists all dependencies according to a mkdocs.yml file" +-optional = false +-python-versions = ">=3.8" +-groups = ["main"] +-files = [ +- {file = "mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134"}, +- {file = "mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c"}, +-] +- +-[package.dependencies] +-importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""} +-mergedeep = ">=1.3.4" +-platformdirs = ">=2.2.0" +-pyyaml = ">=5.1" ++min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.3)", "jinja2 (==2.11.1)", "markdown (==3.2.1)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "packaging (==20.5)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "typing-extensions (==3.10)", "watchdog (==2.0)"] + + [[package]] + name = "mkdocs-git-revision-date-plugin" +@@ -446,7 +241,6 @@ version = "0.3.2" + description = "MkDocs plugin for setting revision date from git per markdown file." + optional = false + python-versions = ">=3.4" +-groups = ["main"] + files = [ + {file = "mkdocs_git_revision_date_plugin-0.3.2-py3-none-any.whl", hash = "sha256:2e67956cb01823dd2418e2833f3623dee8604cdf223bddd005fe36226a56f6ef"}, + ] +@@ -458,147 +252,83 @@ mkdocs = ">=0.17" + + [[package]] + name = "mkdocs-material" +-version = "9.6.5" +-description = "Documentation that simply works" ++version = "7.3.6" ++description = "A Material Design theme for MkDocs" + optional = false +-python-versions = ">=3.8" +-groups = ["main"] ++python-versions = "*" + files = [ +- {file = "mkdocs_material-9.6.5-py3-none-any.whl", hash = "sha256:aad3e6fb860c20870f75fb2a69ef901f1be727891e41adb60b753efcae19453b"}, +- {file = "mkdocs_material-9.6.5.tar.gz", hash = "sha256:b714679a8c91b0ffe2188e11ed58c44d2523e9c2ae26a29cc652fa7478faa21f"}, ++ {file = "mkdocs-material-7.3.6.tar.gz", hash = "sha256:1b1dbd8ef2508b358d93af55a5c5db3f141c95667fad802301ec621c40c7c217"}, ++ {file = "mkdocs_material-7.3.6-py2.py3-none-any.whl", hash = "sha256:1b6b3e9e09f922c2d7f1160fe15c8f43d4adc0d6fb81aa6ff0cbc7ef5b78ec75"}, + ] + + [package.dependencies] +-babel = ">=2.10,<3.0" +-colorama = ">=0.4,<1.0" +-jinja2 = ">=3.0,<4.0" +-markdown = ">=3.2,<4.0" +-mkdocs = ">=1.6,<2.0" +-mkdocs-material-extensions = ">=1.3,<2.0" +-paginate = ">=0.5,<1.0" +-pygments = ">=2.16,<3.0" +-pymdown-extensions = ">=10.2,<11.0" +-regex = ">=2022.4" +-requests = ">=2.26,<3.0" +- +-[package.extras] +-git = ["mkdocs-git-committers-plugin-2 (>=1.1,<3)", "mkdocs-git-revision-date-localized-plugin (>=1.2.4,<2.0)"] +-imaging = ["cairosvg (>=2.6,<3.0)", "pillow (>=10.2,<11.0)"] +-recommended = ["mkdocs-minify-plugin (>=0.7,<1.0)", "mkdocs-redirects (>=1.2,<2.0)", "mkdocs-rss-plugin (>=1.6,<2.0)"] ++jinja2 = ">=2.11.1" ++markdown = ">=3.2" ++mkdocs = ">=1.2.3" ++mkdocs-material-extensions = ">=1.0" ++pygments = ">=2.10" ++pymdown-extensions = ">=9.0" + + [[package]] + name = "mkdocs-material-extensions" +-version = "1.3.1" ++version = "1.1.1" + description = "Extension pack for Python Markdown and MkDocs Material." + optional = false +-python-versions = ">=3.8" +-groups = ["main"] ++python-versions = ">=3.7" + files = [ +- {file = "mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31"}, +- {file = "mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443"}, ++ {file = "mkdocs_material_extensions-1.1.1-py3-none-any.whl", hash = "sha256:e41d9f38e4798b6617ad98ca8f7f1157b1e4385ac1459ca1e4ea219b556df945"}, ++ {file = "mkdocs_material_extensions-1.1.1.tar.gz", hash = "sha256:9c003da71e2cc2493d910237448c672e00cefc800d3d6ae93d2fc69979e3bd93"}, + ] + + [[package]] + name = "packaging" +-version = "24.2" ++version = "22.0" + description = "Core utilities for Python packages" + optional = false +-python-versions = ">=3.8" +-groups = ["main"] +-files = [ +- {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, +- {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, +-] +- +-[[package]] +-name = "paginate" +-version = "0.5.7" +-description = "Divides large result sets into pages for easier browsing" +-optional = false +-python-versions = "*" +-groups = ["main"] +-files = [ +- {file = "paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591"}, +- {file = "paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945"}, +-] +- +-[package.extras] +-dev = ["pytest", "tox"] +-lint = ["black"] +- +-[[package]] +-name = "pathspec" +-version = "0.12.1" +-description = "Utility library for gitignore style pattern matching of file paths." +-optional = false +-python-versions = ">=3.8" +-groups = ["main"] +-files = [ +- {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, +- {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +-] +- +-[[package]] +-name = "platformdirs" +-version = "4.3.6" +-description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +-optional = false +-python-versions = ">=3.8" +-groups = ["main"] ++python-versions = ">=3.7" + files = [ +- {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, +- {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, ++ {file = "packaging-22.0-py3-none-any.whl", hash = "sha256:957e2148ba0e1a3b282772e791ef1d8083648bc131c8ab0c1feba110ce1146c3"}, ++ {file = "packaging-22.0.tar.gz", hash = "sha256:2198ec20bd4c017b8f9717e00f0c8714076fc2fd93816750ab48e2c41de2cfd3"}, + ] + +-[package.extras] +-docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] +-test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] +-type = ["mypy (>=1.11.2)"] +- + [[package]] + name = "pygments" +-version = "2.19.1" ++version = "2.15.0" + description = "Pygments is a syntax highlighting package written in Python." + optional = false +-python-versions = ">=3.8" +-groups = ["main"] ++python-versions = ">=3.7" + files = [ +- {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, +- {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, ++ {file = "Pygments-2.15.0-py3-none-any.whl", hash = "sha256:77a3299119af881904cd5ecd1ac6a66214b6e9bed1f2db16993b54adede64094"}, ++ {file = "Pygments-2.15.0.tar.gz", hash = "sha256:f7e36cffc4c517fbc252861b9a6e4644ca0e5abadf9a113c72d1358ad09b9500"}, + ] + + [package.extras] +-windows-terminal = ["colorama (>=0.4.6)"] ++plugins = ["importlib-metadata"] + + [[package]] + name = "pymdown-extensions" +-version = "10.14.3" ++version = "10.0" + description = "Extension pack for Python Markdown." + optional = false +-python-versions = ">=3.8" +-groups = ["main"] ++python-versions = ">=3.7" + files = [ +- {file = "pymdown_extensions-10.14.3-py3-none-any.whl", hash = "sha256:05e0bee73d64b9c71a4ae17c72abc2f700e8bc8403755a00580b49a4e9f189e9"}, +- {file = "pymdown_extensions-10.14.3.tar.gz", hash = "sha256:41e576ce3f5d650be59e900e4ceff231e0aed2a88cf30acaee41e02f063a061b"}, ++ {file = "pymdown_extensions-10.0-py3-none-any.whl", hash = "sha256:e6cbe8ace7d8feda30bc4fd6a21a073893a9a0e90c373e92d69ce5b653051f55"}, ++ {file = "pymdown_extensions-10.0.tar.gz", hash = "sha256:9a77955e63528c2ee98073a1fb3207c1a45607bc74a34ef21acd098f46c3aa8a"}, + ] + + [package.dependencies] +-markdown = ">=3.6" ++markdown = ">=3.2" + pyyaml = "*" + +-[package.extras] +-extra = ["pygments (>=2.19.1)"] +- + [[package]] + name = "python-dateutil" +-version = "2.9.0.post0" ++version = "2.8.2" + description = "Extensions to the standard Python datetime module" + optional = false + python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +-groups = ["main"] + files = [ +- {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, +- {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, ++ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, ++ {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, + ] + + [package.dependencies] +@@ -606,65 +336,51 @@ six = ">=1.5" + + [[package]] + name = "pyyaml" +-version = "6.0.2" ++version = "6.0" + description = "YAML parser and emitter for Python" + optional = false +-python-versions = ">=3.8" +-groups = ["main"] ++python-versions = ">=3.6" + files = [ +- {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, +- {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, +- {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, +- {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, +- {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, +- {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, +- {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, +- {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, +- {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, +- {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, +- {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, +- {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, +- {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, +- {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, +- {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, +- {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, +- {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, +- {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, +- {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, +- {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, +- {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, +- {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, +- {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, +- {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, +- {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, +- {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, +- {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, +- {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, +- {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, +- {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, +- {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, +- {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, +- {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, +- {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, +- {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, +- {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, +- {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, +- {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, +- {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, +- {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, +- {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, +- {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, +- {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, +- {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, +- {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, +- {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, +- {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, +- {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, +- {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, +- {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, +- {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, +- {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, +- {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, ++ {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, ++ {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, ++ {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, ++ {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, ++ {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, ++ {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, ++ {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, ++ {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, ++ {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, ++ {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, ++ {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, ++ {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, ++ {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, ++ {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, ++ {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, ++ {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, ++ {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, ++ {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, ++ {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, ++ {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, ++ {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, ++ {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, ++ {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, ++ {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, ++ {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, ++ {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, ++ {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, ++ {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, ++ {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, ++ {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, ++ {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, ++ {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, ++ {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, ++ {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, ++ {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, ++ {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, ++ {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, ++ {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, ++ {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, ++ {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, + ] + + [[package]] +@@ -673,7 +389,6 @@ version = "0.1" + description = "A custom YAML tag for referencing environment variables in YAML files. " + optional = false + python-versions = ">=3.6" +-groups = ["main"] + files = [ + {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, + {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, +@@ -682,181 +397,34 @@ files = [ + [package.dependencies] + pyyaml = "*" + +-[[package]] +-name = "regex" +-version = "2024.11.6" +-description = "Alternative regular expression module, to replace re." +-optional = false +-python-versions = ">=3.8" +-groups = ["main"] +-files = [ +- {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"}, +- {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"}, +- {file = "regex-2024.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e"}, +- {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde"}, +- {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e"}, +- {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2"}, +- {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf"}, +- {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c"}, +- {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86"}, +- {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67"}, +- {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d"}, +- {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2"}, +- {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008"}, +- {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62"}, +- {file = "regex-2024.11.6-cp310-cp310-win32.whl", hash = "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e"}, +- {file = "regex-2024.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519"}, +- {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638"}, +- {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7"}, +- {file = "regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20"}, +- {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114"}, +- {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3"}, +- {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f"}, +- {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0"}, +- {file = "regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55"}, +- {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89"}, +- {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d"}, +- {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34"}, +- {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d"}, +- {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45"}, +- {file = "regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9"}, +- {file = "regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60"}, +- {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a"}, +- {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9"}, +- {file = "regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2"}, +- {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4"}, +- {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577"}, +- {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3"}, +- {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e"}, +- {file = "regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe"}, +- {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e"}, +- {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29"}, +- {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39"}, +- {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51"}, +- {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad"}, +- {file = "regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54"}, +- {file = "regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b"}, +- {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84"}, +- {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4"}, +- {file = "regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0"}, +- {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0"}, +- {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7"}, +- {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7"}, +- {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c"}, +- {file = "regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3"}, +- {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07"}, +- {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e"}, +- {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6"}, +- {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4"}, +- {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d"}, +- {file = "regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff"}, +- {file = "regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a"}, +- {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3a51ccc315653ba012774efca4f23d1d2a8a8f278a6072e29c7147eee7da446b"}, +- {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ad182d02e40de7459b73155deb8996bbd8e96852267879396fb274e8700190e3"}, +- {file = "regex-2024.11.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba9b72e5643641b7d41fa1f6d5abda2c9a263ae835b917348fc3c928182ad467"}, +- {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40291b1b89ca6ad8d3f2b82782cc33807f1406cf68c8d440861da6304d8ffbbd"}, +- {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdf58d0e516ee426a48f7b2c03a332a4114420716d55769ff7108c37a09951bf"}, +- {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a36fdf2af13c2b14738f6e973aba563623cb77d753bbbd8d414d18bfaa3105dd"}, +- {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1cee317bfc014c2419a76bcc87f071405e3966da434e03e13beb45f8aced1a6"}, +- {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50153825ee016b91549962f970d6a4442fa106832e14c918acd1c8e479916c4f"}, +- {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea1bfda2f7162605f6e8178223576856b3d791109f15ea99a9f95c16a7636fb5"}, +- {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:df951c5f4a1b1910f1a99ff42c473ff60f8225baa1cdd3539fe2819d9543e9df"}, +- {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:072623554418a9911446278f16ecb398fb3b540147a7828c06e2011fa531e773"}, +- {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f654882311409afb1d780b940234208a252322c24a93b442ca714d119e68086c"}, +- {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:89d75e7293d2b3e674db7d4d9b1bee7f8f3d1609428e293771d1a962617150cc"}, +- {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:f65557897fc977a44ab205ea871b690adaef6b9da6afda4790a2484b04293a5f"}, +- {file = "regex-2024.11.6-cp38-cp38-win32.whl", hash = "sha256:6f44ec28b1f858c98d3036ad5d7d0bfc568bdd7a74f9c24e25f41ef1ebfd81a4"}, +- {file = "regex-2024.11.6-cp38-cp38-win_amd64.whl", hash = "sha256:bb8f74f2f10dbf13a0be8de623ba4f9491faf58c24064f32b65679b021ed0001"}, +- {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5704e174f8ccab2026bd2f1ab6c510345ae8eac818b613d7d73e785f1310f839"}, +- {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:220902c3c5cc6af55d4fe19ead504de80eb91f786dc102fbd74894b1551f095e"}, +- {file = "regex-2024.11.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7e351589da0850c125f1600a4c4ba3c722efefe16b297de54300f08d734fbf"}, +- {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5056b185ca113c88e18223183aa1a50e66507769c9640a6ff75859619d73957b"}, +- {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e34b51b650b23ed3354b5a07aab37034d9f923db2a40519139af34f485f77d0"}, +- {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5670bce7b200273eee1840ef307bfa07cda90b38ae56e9a6ebcc9f50da9c469b"}, +- {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08986dce1339bc932923e7d1232ce9881499a0e02925f7402fb7c982515419ef"}, +- {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93c0b12d3d3bc25af4ebbf38f9ee780a487e8bf6954c115b9f015822d3bb8e48"}, +- {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:764e71f22ab3b305e7f4c21f1a97e1526a25ebdd22513e251cf376760213da13"}, +- {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f056bf21105c2515c32372bbc057f43eb02aae2fda61052e2f7622c801f0b4e2"}, +- {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:69ab78f848845569401469da20df3e081e6b5a11cb086de3eed1d48f5ed57c95"}, +- {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:86fddba590aad9208e2fa8b43b4c098bb0ec74f15718bb6a704e3c63e2cef3e9"}, +- {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:684d7a212682996d21ca12ef3c17353c021fe9de6049e19ac8481ec35574a70f"}, +- {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a03e02f48cd1abbd9f3b7e3586d97c8f7a9721c436f51a5245b3b9483044480b"}, +- {file = "regex-2024.11.6-cp39-cp39-win32.whl", hash = "sha256:41758407fc32d5c3c5de163888068cfee69cb4c2be844e7ac517a52770f9af57"}, +- {file = "regex-2024.11.6-cp39-cp39-win_amd64.whl", hash = "sha256:b2837718570f95dd41675328e111345f9b7095d821bac435aac173ac80b19983"}, +- {file = "regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519"}, +-] +- +-[[package]] +-name = "requests" +-version = "2.32.4" +-description = "Python HTTP for Humans." +-optional = false +-python-versions = ">=3.8" +-groups = ["main"] +-files = [ +- {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"}, +- {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"}, +-] +- +-[package.dependencies] +-certifi = ">=2017.4.17" +-charset_normalizer = ">=2,<4" +-idna = ">=2.5,<4" +-urllib3 = ">=1.21.1,<3" +- +-[package.extras] +-socks = ["PySocks (>=1.5.6,!=1.5.7)"] +-use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +- + [[package]] + name = "six" +-version = "1.17.0" ++version = "1.16.0" + description = "Python 2 and 3 compatibility utilities" + optional = false +-python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +-groups = ["main"] ++python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + files = [ +- {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, +- {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, ++ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, ++ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, + ] + + [[package]] + name = "smmap" +-version = "5.0.2" ++version = "5.0.0" + description = "A pure Python implementation of a sliding window memory map manager" + optional = false +-python-versions = ">=3.7" +-groups = ["main"] +-files = [ +- {file = "smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e"}, +- {file = "smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5"}, +-] +- +-[[package]] +-name = "urllib3" +-version = "2.5.0" +-description = "HTTP library with thread-safe connection pooling, file post, and more." +-optional = false +-python-versions = ">=3.9" +-groups = ["main"] ++python-versions = ">=3.6" + files = [ +- {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, +- {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, ++ {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, ++ {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, + ] + +-[package.extras] +-brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] +-h2 = ["h2 (>=4,<5)"] +-socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +-zstd = ["zstandard (>=0.18.0)"] +- + [[package]] + name = "verspec" + version = "0.1.0" + description = "Flexible version handling" + optional = false + python-versions = "*" +-groups = ["main"] + files = [ + {file = "verspec-0.1.0-py3-none-any.whl", hash = "sha256:741877d5633cc9464c45a469ae2a31e801e6dbbaa85b9675d481cda100f11c31"}, + {file = "verspec-0.1.0.tar.gz", hash = "sha256:c4504ca697b2056cdb4bfa7121461f5a0e81809255b41c03dda4ba823637c01e"}, +@@ -867,42 +435,39 @@ test = ["coverage", "flake8 (>=3.7)", "mypy", "pretend", "pytest"] + + [[package]] + name = "watchdog" +-version = "6.0.0" ++version = "2.2.0" + description = "Filesystem events monitoring" + optional = false +-python-versions = ">=3.9" +-groups = ["main"] ++python-versions = ">=3.6" + files = [ +- {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26"}, +- {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112"}, +- {file = "watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3"}, +- {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c"}, +- {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2"}, +- {file = "watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c"}, +- {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948"}, +- {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860"}, +- {file = "watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0"}, +- {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c"}, +- {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134"}, +- {file = "watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b"}, +- {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8"}, +- {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a"}, +- {file = "watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c"}, +- {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881"}, +- {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11"}, +- {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa"}, +- {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e"}, +- {file = "watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13"}, +- {file = "watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379"}, +- {file = "watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e"}, +- {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f"}, +- {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26"}, +- {file = "watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c"}, +- {file = "watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2"}, +- {file = "watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a"}, +- {file = "watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680"}, +- {file = "watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f"}, +- {file = "watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282"}, ++ {file = "watchdog-2.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ed91c3ccfc23398e7aa9715abf679d5c163394b8cad994f34f156d57a7c163dc"}, ++ {file = "watchdog-2.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:76a2743402b794629a955d96ea2e240bd0e903aa26e02e93cd2d57b33900962b"}, ++ {file = "watchdog-2.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:920a4bda7daa47545c3201a3292e99300ba81ca26b7569575bd086c865889090"}, ++ {file = "watchdog-2.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ceaa9268d81205876bedb1069f9feab3eccddd4b90d9a45d06a0df592a04cae9"}, ++ {file = "watchdog-2.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1893d425ef4fb4f129ee8ef72226836619c2950dd0559bba022b0818c63a7b60"}, ++ {file = "watchdog-2.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9e99c1713e4436d2563f5828c8910e5ff25abd6ce999e75f15c15d81d41980b6"}, ++ {file = "watchdog-2.2.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a5bd9e8656d07cae89ac464ee4bcb6f1b9cecbedc3bf1334683bed3d5afd39ba"}, ++ {file = "watchdog-2.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3a048865c828389cb06c0bebf8a883cec3ae58ad3e366bcc38c61d8455a3138f"}, ++ {file = "watchdog-2.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e722755d995035dd32177a9c633d158f2ec604f2a358b545bba5bed53ab25bca"}, ++ {file = "watchdog-2.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:af4b5c7ba60206759a1d99811b5938ca666ea9562a1052b410637bb96ff97512"}, ++ {file = "watchdog-2.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:619d63fa5be69f89ff3a93e165e602c08ed8da402ca42b99cd59a8ec115673e1"}, ++ {file = "watchdog-2.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1f2b0665c57358ce9786f06f5475bc083fea9d81ecc0efa4733fd0c320940a37"}, ++ {file = "watchdog-2.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:441024df19253bb108d3a8a5de7a186003d68564084576fecf7333a441271ef7"}, ++ {file = "watchdog-2.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1a410dd4d0adcc86b4c71d1317ba2ea2c92babaf5b83321e4bde2514525544d5"}, ++ {file = "watchdog-2.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:28704c71afdb79c3f215c90231e41c52b056ea880b6be6cee035c6149d658ed1"}, ++ {file = "watchdog-2.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2ac0bd7c206bb6df78ef9e8ad27cc1346f2b41b1fef610395607319cdab89bc1"}, ++ {file = "watchdog-2.2.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:27e49268735b3c27310883012ab3bd86ea0a96dcab90fe3feb682472e30c90f3"}, ++ {file = "watchdog-2.2.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:2af1a29fd14fc0a87fb6ed762d3e1ae5694dcde22372eebba50e9e5be47af03c"}, ++ {file = "watchdog-2.2.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:c7bd98813d34bfa9b464cf8122e7d4bec0a5a427399094d2c17dd5f70d59bc61"}, ++ {file = "watchdog-2.2.0-py3-none-manylinux2014_i686.whl", hash = "sha256:56fb3f40fc3deecf6e518303c7533f5e2a722e377b12507f6de891583f1b48aa"}, ++ {file = "watchdog-2.2.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:74535e955359d79d126885e642d3683616e6d9ab3aae0e7dcccd043bd5a3ff4f"}, ++ {file = "watchdog-2.2.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:cf05e6ff677b9655c6e9511d02e9cc55e730c4e430b7a54af9c28912294605a4"}, ++ {file = "watchdog-2.2.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:d6ae890798a3560688b441ef086bb66e87af6b400a92749a18b856a134fc0318"}, ++ {file = "watchdog-2.2.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:e5aed2a700a18c194c39c266900d41f3db0c1ebe6b8a0834b9995c835d2ca66e"}, ++ {file = "watchdog-2.2.0-py3-none-win32.whl", hash = "sha256:d0fb5f2b513556c2abb578c1066f5f467d729f2eb689bc2db0739daf81c6bb7e"}, ++ {file = "watchdog-2.2.0-py3-none-win_amd64.whl", hash = "sha256:1f8eca9d294a4f194ce9df0d97d19b5598f310950d3ac3dd6e8d25ae456d4c8a"}, ++ {file = "watchdog-2.2.0-py3-none-win_ia64.whl", hash = "sha256:ad0150536469fa4b693531e497ffe220d5b6cd76ad2eda474a5e641ee204bbb6"}, ++ {file = "watchdog-2.2.0.tar.gz", hash = "sha256:83cf8bc60d9c613b66a4c018051873d6273d9e45d040eed06d6a96241bd8ec01"}, + ] + + [package.extras] +@@ -910,26 +475,20 @@ watchmedo = ["PyYAML (>=3.10)"] + + [[package]] + name = "zipp" +-version = "3.21.0" ++version = "3.19.1" + description = "Backport of pathlib-compatible object wrapper for zip files" + optional = false +-python-versions = ">=3.9" +-groups = ["main"] +-markers = "python_version < \"3.10\"" ++python-versions = ">=3.8" + files = [ +- {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, +- {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, ++ {file = "zipp-3.19.1-py3-none-any.whl", hash = "sha256:2828e64edb5386ea6a52e7ba7cdb17bb30a73a858f5eb6eb93d8d36f5ea26091"}, ++ {file = "zipp-3.19.1.tar.gz", hash = "sha256:35427f6d5594f4acf82d25541438348c26736fa9b3afa2754bcd63cdb99d8e8f"}, + ] + + [package.extras] +-check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +-cover = ["pytest-cov"] + doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +-enabler = ["pytest-enabler (>=2.2)"] +-test = ["big-O", "importlib-resources ; python_version < \"3.9\"", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +-type = ["pytest-mypy"] ++test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] + + [metadata] +-lock-version = "2.1" ++lock-version = "2.0" + python-versions = "^3.9" +-content-hash = "7aaff1ad012ab8384fc464aee9558ca91c70ae990fbef27af1a3fb0ce416cfa2" ++content-hash = "f082f3c26f0efb74521bfc86a3242f15f24e7108ee3f1bf455a08f11594cda75" +diff --git a/pyproject.toml b/pyproject.toml +index c1fa378f..da01f922 100644 +--- a/pyproject.toml ++++ b/pyproject.toml +@@ -7,8 +7,8 @@ authors = ["Amazon Web Services"] + [tool.poetry.dependencies] + python = "^3.9" + mike = "^1.0.1" ++mkdocs-material = "^7.2.4" + mkdocs-git-revision-date-plugin = "^0.3.1" +-mkdocs-material = "^9.6.5" + + [tool.poetry.dev-dependencies] + +diff --git a/version.json b/version.json +index c2735039..d52ea67c 100644 +--- a/version.json ++++ b/version.json +@@ -1,18 +1,12 @@ + { +- "Core": { +- "Logging": "2.0.2", +- "Metrics": "2.1.1", +- "Tracing": "1.6.2", +- "Metrics.AspNetCore": "0.1.0" +- }, +- "Utilities": { +- "Parameters": "1.3.1", +- "Idempotency": "1.4.0", +- "BatchProcessing": "1.2.1", +- "EventHandler": "1.0.1", +- "EventHandler.Resolvers.BedrockAgentFunction": "1.0.1", +- "Kafka.Json": "1.0.2", +- "Kafka.Avro": "1.0.2", +- "Kafka.Protobuf": "1.0.2" +- } ++ "Core": { ++ "Logging": "1.6.4", ++ "Metrics": "1.8.0", ++ "Tracing": "1.6.1" ++ }, ++ "Utilities": { ++ "Parameters": "1.3.0", ++ "Idempotency": "1.3.0", ++ "BatchProcessing": "1.2.0" ++ } + } diff --git a/examples/AOT/AOT_Logging/src/AOT_Logging/AOT_Logging.csproj b/examples/AOT/AOT_Logging/src/AOT_Logging/AOT_Logging.csproj index d06a0a531..97cc041c4 100644 --- a/examples/AOT/AOT_Logging/src/AOT_Logging/AOT_Logging.csproj +++ b/examples/AOT/AOT_Logging/src/AOT_Logging/AOT_Logging.csproj @@ -18,8 +18,8 @@ - - - + + + diff --git a/examples/AOT/AOT_Logging/test/AOT_Logging.Tests/AOT_Logging.Tests.csproj b/examples/AOT/AOT_Logging/test/AOT_Logging.Tests/AOT_Logging.Tests.csproj index cba0ba03e..3d996e245 100644 --- a/examples/AOT/AOT_Logging/test/AOT_Logging.Tests/AOT_Logging.Tests.csproj +++ b/examples/AOT/AOT_Logging/test/AOT_Logging.Tests/AOT_Logging.Tests.csproj @@ -6,7 +6,7 @@ true - + diff --git a/examples/AOT/AOT_Metrics/src/AOT_Metrics/AOT_Metrics.csproj b/examples/AOT/AOT_Metrics/src/AOT_Metrics/AOT_Metrics.csproj index 74caf11d7..ec08ac525 100644 --- a/examples/AOT/AOT_Metrics/src/AOT_Metrics/AOT_Metrics.csproj +++ b/examples/AOT/AOT_Metrics/src/AOT_Metrics/AOT_Metrics.csproj @@ -18,8 +18,8 @@ - - - + + + \ No newline at end of file diff --git a/examples/AOT/AOT_Metrics/test/AOT_Metrics.Tests/AOT_Metrics.Tests.csproj b/examples/AOT/AOT_Metrics/test/AOT_Metrics.Tests/AOT_Metrics.Tests.csproj index fb935a9a9..34fa6d4ce 100644 --- a/examples/AOT/AOT_Metrics/test/AOT_Metrics.Tests/AOT_Metrics.Tests.csproj +++ b/examples/AOT/AOT_Metrics/test/AOT_Metrics.Tests/AOT_Metrics.Tests.csproj @@ -6,7 +6,7 @@ true - + diff --git a/examples/AOT/AOT_Tracing/src/AOT_Tracing/AOT_Tracing.csproj b/examples/AOT/AOT_Tracing/src/AOT_Tracing/AOT_Tracing.csproj index 6e92d3312..20100166e 100644 --- a/examples/AOT/AOT_Tracing/src/AOT_Tracing/AOT_Tracing.csproj +++ b/examples/AOT/AOT_Tracing/src/AOT_Tracing/AOT_Tracing.csproj @@ -18,8 +18,8 @@ - - - + + + \ No newline at end of file diff --git a/examples/AOT/AOT_Tracing/test/AOT_Tracing.Tests/AOT_Tracing.Tests.csproj b/examples/AOT/AOT_Tracing/test/AOT_Tracing.Tests/AOT_Tracing.Tests.csproj index b62601e63..2bdc9557b 100644 --- a/examples/AOT/AOT_Tracing/test/AOT_Tracing.Tests/AOT_Tracing.Tests.csproj +++ b/examples/AOT/AOT_Tracing/test/AOT_Tracing.Tests/AOT_Tracing.Tests.csproj @@ -6,7 +6,7 @@ true - + diff --git a/examples/BatchProcessing/events/event.json b/examples/BatchProcessing/events/event.json index f98443d83..6b9c1b910 100644 --- a/examples/BatchProcessing/events/event.json +++ b/examples/BatchProcessing/events/event.json @@ -7,7 +7,7 @@ "attributes": { "ApproximateReceiveCount": "1", "SentTimestamp": "1545082649183", - "SenderId": "SENDER_ID", + "SenderId": "AIDAIENQZJOLO23YVJ4VO", "ApproximateFirstReceiveTimestamp": "1545082649185" }, "messageAttributes": {}, @@ -23,7 +23,7 @@ "attributes": { "ApproximateReceiveCount": "1", "SentTimestamp": "1545082649183", - "SenderId": "SENDER_ID", + "SenderId": "AIDAIENQZJOLO23YVJ4VO", "ApproximateFirstReceiveTimestamp": "1545082649185" }, "messageAttributes": {}, diff --git a/examples/BatchProcessing/events/typed-dynamodb-event.json b/examples/BatchProcessing/events/typed-dynamodb-event.json deleted file mode 100644 index 858454d6e..000000000 --- a/examples/BatchProcessing/events/typed-dynamodb-event.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "Records": [ - { - "eventID": "1", - "eventVersion": "1.0", - "dynamodb": { - "Keys": { - "customerId": { - "S": "CUST-123" - } - }, - "NewImage": { - "customerId": { - "S": "CUST-123" - }, - "name": { - "S": "John Doe" - }, - "email": { - "S": "john.doe@example.com" - }, - "createdAt": { - "S": "2024-01-15T10:30:00Z" - } - }, - "StreamViewType": "NEW_AND_OLD_IMAGES", - "SequenceNumber": "111", - "SizeBytes": 26 - }, - "awsRegion": "us-west-2", - "eventName": "INSERT", - "eventSourceARN": "arn:aws:dynamodb:us-west-2:123456789012:table/customers/stream/2015-06-27T00:48:05.899", - "eventSource": "aws:dynamodb" - }, - { - "eventID": "2", - "eventVersion": "1.0", - "dynamodb": { - "Keys": { - "customerId": { - "S": "CUST-124" - } - }, - "NewImage": { - "customerId": { - "S": "CUST-124" - }, - "name": { - "S": "Jane Smith" - }, - "email": { - "S": "jane.smith@example.com" - }, - "createdAt": { - "S": "2024-01-15T10:35:00Z" - } - }, - "OldImage": { - "customerId": { - "S": "CUST-124" - }, - "name": { - "S": "Jane Doe" - }, - "email": { - "S": "jane.doe@example.com" - }, - "createdAt": { - "S": "2024-01-15T10:35:00Z" - } - }, - "StreamViewType": "NEW_AND_OLD_IMAGES", - "SequenceNumber": "222", - "SizeBytes": 59 - }, - "awsRegion": "us-west-2", - "eventName": "MODIFY", - "eventSourceARN": "arn:aws:dynamodb:us-west-2:123456789012:table/customers/stream/2015-06-27T00:48:05.899", - "eventSource": "aws:dynamodb" - } - ] -} \ No newline at end of file diff --git a/examples/BatchProcessing/events/typed-kinesis-event.json b/examples/BatchProcessing/events/typed-kinesis-event.json deleted file mode 100644 index c9d66d8e9..000000000 --- a/examples/BatchProcessing/events/typed-kinesis-event.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "Records": [ - { - "kinesis": { - "kinesisSchemaVersion": "1.0", - "partitionKey": "order-1", - "sequenceNumber": "49590338271490256608559692538361571095921575989136588898", - "data": "eyJvcmRlcklkIjogIk9SRC0xMjMiLCAib3JkZXJEYXRlIjogIjIwMjQtMDEtMTVUMTA6MzA6MDBaIiwgImN1c3RvbWVySWQiOiAiQ1VTVC0xMjMiLCAiaXRlbXMiOiBbeyJpZCI6IDEsICJuYW1lIjogIkxhcHRvcCIsICJwcmljZSI6IDk5OS45OX0sIHsiaWQiOiAyLCAibmFtZSI6ICJNb3VzZSIsICJwcmljZSI6IDI5Ljk5fV0sICJ0b3RhbEFtb3VudCI6IDEwMjkuOTgsICJzdGF0dXMiOiAiUGVuZGluZyJ9", - "approximateArrivalTimestamp": 1545084650.987 - }, - "eventSource": "aws:kinesis", - "eventVersion": "1.0", - "eventID": "shardId-000000000006:49590338271490256608559692538361571095921575989136588898", - "eventName": "aws:kinesis:record", - "invokeIdentityArn": "arn:aws:iam::123456789012:role/lambda-kinesis-role", - "awsRegion": "us-east-1", - "eventSourceARN": "arn:aws:kinesis:us-east-1:123456789012:stream/lambda-stream" - }, - { - "kinesis": { - "kinesisSchemaVersion": "1.0", - "partitionKey": "order-2", - "sequenceNumber": "49590338271490256608559692538361571095921575989136588899", - "data": "eyJvcmRlcklkIjogIk9SRC0xMjQiLCAib3JkZXJEYXRlIjogIjIwMjQtMDEtMTVUMTA6MzU6MDBaIiwgImN1c3RvbWVySWQiOiAiQ1VTVC0xMjQiLCAiaXRlbXMiOiBbeyJpZCI6IDMsICJuYW1lIjogIktleWJvYXJkIiwgInByaWNlIjogNzkuOTl9XSwgInRvdGFsQW1vdW50IjogNzkuOTksICJzdGF0dXMiOiAiUGVuZGluZyJ9", - "approximateArrivalTimestamp": 1545084651.987 - }, - "eventSource": "aws:kinesis", - "eventVersion": "1.0", - "eventID": "shardId-000000000006:49590338271490256608559692538361571095921575989136588899", - "eventName": "aws:kinesis:record", - "invokeIdentityArn": "arn:aws:iam::123456789012:role/lambda-kinesis-role", - "awsRegion": "us-east-1", - "eventSourceARN": "arn:aws:kinesis:us-east-1:123456789012:stream/lambda-stream" - } - ] -} \ No newline at end of file diff --git a/examples/BatchProcessing/events/typed-sqs-event.json b/examples/BatchProcessing/events/typed-sqs-event.json deleted file mode 100644 index 864180c08..000000000 --- a/examples/BatchProcessing/events/typed-sqs-event.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "Records": [ - { - "messageId": "19dd0b57-b21e-4ac1-bd88-01bbb068cb78", - "receiptHandle": "MessageReceiptHandle", - "body": "{\"id\": 1, \"name\": \"Laptop Computer\", \"price\": 999.99}", - "attributes": { - "ApproximateReceiveCount": "1", - "SentTimestamp": "1523232000000", - "SenderId": "123456789012", - "ApproximateFirstReceiveTimestamp": "1523232000001" - }, - "messageAttributes": {}, - "md5OfBody": "7b270e59b47ff90a553787216d55d91d", - "eventSource": "aws:sqs", - "eventSourceARN": "arn:aws:sqs:us-east-1:123456789012:MyQueue", - "awsRegion": "us-east-1" - }, - { - "messageId": "19dd0b57-b21e-4ac1-bd88-01bbb068cb79", - "receiptHandle": "MessageReceiptHandle2", - "body": "{\"id\": 2, \"name\": \"Wireless Mouse\", \"price\": 29.99}", - "attributes": { - "ApproximateReceiveCount": "1", - "SentTimestamp": "1523232000000", - "SenderId": "123456789012", - "ApproximateFirstReceiveTimestamp": "1523232000001" - }, - "messageAttributes": {}, - "md5OfBody": "7b270e59b47ff90a553787216d55d92e", - "eventSource": "aws:sqs", - "eventSourceARN": "arn:aws:sqs:us-east-1:123456789012:MyQueue", - "awsRegion": "us-east-1" - }, - { - "messageId": "19dd0b57-b21e-4ac1-bd88-01bbb068cb80", - "receiptHandle": "MessageReceiptHandle3", - "body": "{\"id\": 999, \"name\": \"Invalid Product\", \"price\": -10.00}", - "attributes": { - "ApproximateReceiveCount": "1", - "SentTimestamp": "1523232000000", - "SenderId": "123456789012", - "ApproximateFirstReceiveTimestamp": "1523232000001" - }, - "messageAttributes": {}, - "md5OfBody": "7b270e59b47ff90a553787216d55d93f", - "eventSource": "aws:sqs", - "eventSourceARN": "arn:aws:sqs:us-east-1:123456789012:MyQueue", - "awsRegion": "us-east-1" - } - ] -} \ No newline at end of file diff --git a/examples/BatchProcessing/src/HelloWorld/Data/Order.cs b/examples/BatchProcessing/src/HelloWorld/Data/Order.cs deleted file mode 100644 index 89703a92c..000000000 --- a/examples/BatchProcessing/src/HelloWorld/Data/Order.cs +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -using System; -using System.Collections.Generic; -using System.Text.Json.Serialization; - -namespace HelloWorld.Data; - -public class Order -{ - public string? OrderId { get; set; } - public DateTime OrderDate { get; set; } - public string? CustomerId { get; set; } - public List Items { get; set; } = new(); - public decimal TotalAmount { get; set; } - public string? Status { get; set; } -} - -public class Customer -{ - public string? CustomerId { get; set; } - public string? Name { get; set; } - public string? Email { get; set; } - public DateTime CreatedAt { get; set; } -} - -/// -/// JsonSerializerContext for AOT compatibility -/// -[JsonSerializable(typeof(Product))] -[JsonSerializable(typeof(Order))] -[JsonSerializable(typeof(Customer))] -[JsonSerializable(typeof(List))] -[JsonSerializable(typeof(List))] -public partial class ExampleJsonSerializerContext : JsonSerializerContext -{ -} \ No newline at end of file diff --git a/examples/BatchProcessing/src/HelloWorld/Dockerfile b/examples/BatchProcessing/src/HelloWorld/Dockerfile index 0b025f36b..eb6d0e0b1 100644 --- a/examples/BatchProcessing/src/HelloWorld/Dockerfile +++ b/examples/BatchProcessing/src/HelloWorld/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk@sha256:c8fdd06e430de9f4ddd066b475ea350d771f341b77dd5ff4c2fafa748e3f2ef2 AS build-image +FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-image ARG FUNCTION_DIR="/build" ARG SAM_BUILD_MODE="run" @@ -15,7 +15,7 @@ RUN mkdir -p build_artifacts RUN if [ "$SAM_BUILD_MODE" = "debug" ]; then dotnet lambda package --configuration Debug; else dotnet lambda package --configuration Release; fi RUN if [ "$SAM_BUILD_MODE" = "debug" ]; then cp -r /build/bin/Debug/net6.0/publish/* /build/build_artifacts; else cp -r /build/bin/Release/net6.0/publish/* /build/build_artifacts; fi -FROM public.ecr.aws/lambda/dotnet@sha256:ec61a7f638e2a0c86d75204117cc7710bcdc70222ffc777e3fc1458287b09834 +FROM public.ecr.aws/lambda/dotnet:6 COPY --from=build-image /build/build_artifacts/ /var/task/ # Command can be overwritten by providing a different command in the template directly. diff --git a/examples/BatchProcessing/src/HelloWorld/HelloWorld.csproj b/examples/BatchProcessing/src/HelloWorld/HelloWorld.csproj index 7d3263e25..d34e3e63e 100644 --- a/examples/BatchProcessing/src/HelloWorld/HelloWorld.csproj +++ b/examples/BatchProcessing/src/HelloWorld/HelloWorld.csproj @@ -5,10 +5,10 @@ enable - - + + - - + + diff --git a/examples/BatchProcessing/test/HelloWorld.Test/HelloWorld.Tests.csproj b/examples/BatchProcessing/test/HelloWorld.Test/HelloWorld.Tests.csproj index 3990c0112..903aee7db 100644 --- a/examples/BatchProcessing/test/HelloWorld.Test/HelloWorld.Tests.csproj +++ b/examples/BatchProcessing/test/HelloWorld.Test/HelloWorld.Tests.csproj @@ -3,12 +3,12 @@ net8.0 - + - + diff --git a/examples/Event Handler/BedrockAgentFunction/infra/.gitignore b/examples/Event Handler/BedrockAgentFunction/infra/.gitignore deleted file mode 100644 index f60797b6a..000000000 --- a/examples/Event Handler/BedrockAgentFunction/infra/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -*.js -!jest.config.js -*.d.ts -node_modules - -# CDK asset staging directory -.cdk.staging -cdk.out diff --git a/examples/Event Handler/BedrockAgentFunction/infra/.npmignore b/examples/Event Handler/BedrockAgentFunction/infra/.npmignore deleted file mode 100644 index c1d6d45dc..000000000 --- a/examples/Event Handler/BedrockAgentFunction/infra/.npmignore +++ /dev/null @@ -1,6 +0,0 @@ -*.ts -!*.d.ts - -# CDK asset staging directory -.cdk.staging -cdk.out diff --git a/examples/Event Handler/BedrockAgentFunction/infra/cdk.json b/examples/Event Handler/BedrockAgentFunction/infra/cdk.json deleted file mode 100644 index eea31fee9..000000000 --- a/examples/Event Handler/BedrockAgentFunction/infra/cdk.json +++ /dev/null @@ -1,96 +0,0 @@ -{ - "app": "npx ts-node --prefer-ts-exts bin/infra.ts", - "watch": { - "include": [ - "**" - ], - "exclude": [ - "README.md", - "cdk*.json", - "**/*.d.ts", - "**/*.js", - "tsconfig.json", - "package*.json", - "yarn.lock", - "node_modules", - "test" - ] - }, - "context": { - "@aws-cdk/aws-lambda:recognizeLayerVersion": true, - "@aws-cdk/core:checkSecretUsage": true, - "@aws-cdk/core:target-partitions": [ - "aws", - "aws-cn" - ], - "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, - "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, - "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, - "@aws-cdk/aws-iam:minimizePolicies": true, - "@aws-cdk/core:validateSnapshotRemovalPolicy": true, - "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, - "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, - "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, - "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, - "@aws-cdk/core:enablePartitionLiterals": true, - "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, - "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, - "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, - "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, - "@aws-cdk/aws-route53-patters:useCertificate": true, - "@aws-cdk/customresources:installLatestAwsSdkDefault": false, - "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, - "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, - "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, - "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, - "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, - "@aws-cdk/aws-redshift:columnId": true, - "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, - "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, - "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, - "@aws-cdk/aws-kms:aliasNameRef": true, - "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, - "@aws-cdk/core:includePrefixInUniqueNameGeneration": true, - "@aws-cdk/aws-efs:denyAnonymousAccess": true, - "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true, - "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true, - "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true, - "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true, - "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true, - "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true, - "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true, - "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true, - "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true, - "@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true, - "@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true, - "@aws-cdk/aws-eks:nodegroupNameAttribute": true, - "@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true, - "@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true, - "@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false, - "@aws-cdk/aws-s3:keepNotificationInImportedBucket": false, - "@aws-cdk/aws-ecs:enableImdsBlockingDeprecatedFeature": false, - "@aws-cdk/aws-ecs:disableEcsImdsBlocking": true, - "@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": true, - "@aws-cdk/aws-dynamodb:resourcePolicyPerReplica": true, - "@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": true, - "@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": true, - "@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": true, - "@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": true, - "@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": true, - "@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": true, - "@aws-cdk/aws-ec2:bastionHostUseAmazonLinux2023ByDefault": true, - "@aws-cdk/aws-route53-targets:userPoolDomainNameMethodWithoutCustomResource": true, - "@aws-cdk/aws-elasticloadbalancingV2:albDualstackWithoutPublicIpv4SecurityGroupRulesDefault": true, - "@aws-cdk/aws-iam:oidcRejectUnauthorizedConnections": true, - "@aws-cdk/core:enableAdditionalMetadataCollection": true, - "@aws-cdk/aws-lambda:createNewPoliciesWithAddToRolePolicy": false, - "@aws-cdk/aws-s3:setUniqueReplicationRoleName": true, - "@aws-cdk/aws-events:requireEventBusPolicySid": true, - "@aws-cdk/core:aspectPrioritiesMutating": true, - "@aws-cdk/aws-dynamodb:retainTableReplica": true, - "@aws-cdk/aws-stepfunctions:useDistributedMapResultWriterV2": true, - "@aws-cdk/s3-notifications:addS3TrustKeyPolicyForSnsSubscriptions": true, - "@aws-cdk/aws-ec2:requirePrivateSubnetsForEgressOnlyInternetGateway": true, - "@aws-cdk/aws-s3:publicAccessBlockedByDefault": true - } -} diff --git a/examples/Event Handler/BedrockAgentFunction/infra/jest.config.js b/examples/Event Handler/BedrockAgentFunction/infra/jest.config.js deleted file mode 100644 index 08263b895..000000000 --- a/examples/Event Handler/BedrockAgentFunction/infra/jest.config.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - testEnvironment: 'node', - roots: ['/test'], - testMatch: ['**/*.test.ts'], - transform: { - '^.+\\.tsx?$': 'ts-jest' - } -}; diff --git a/examples/Event Handler/BedrockAgentFunction/infra/lib/bedrockagents-stack.ts b/examples/Event Handler/BedrockAgentFunction/infra/lib/bedrockagents-stack.ts deleted file mode 100644 index 001d9912d..000000000 --- a/examples/Event Handler/BedrockAgentFunction/infra/lib/bedrockagents-stack.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { - Stack, - type StackProps, - CfnOutput, - RemovalPolicy, - Arn, - Duration, -} from 'aws-cdk-lib'; -import type { Construct } from 'constructs'; -import { Runtime, Function as LambdaFunction, Code, Architecture } from 'aws-cdk-lib/aws-lambda'; -import { LogGroup, RetentionDays } from 'aws-cdk-lib/aws-logs'; -import { CfnAgent } from 'aws-cdk-lib/aws-bedrock'; -import { - PolicyDocument, - PolicyStatement, - Role, - ServicePrincipal, -} from 'aws-cdk-lib/aws-iam'; - -export class BedrockAgentsStack extends Stack { - constructor(scope: Construct, id: string, props?: StackProps) { - super(scope, id, props); - - const fnName = 'BedrockAgentsFn'; - const logGroup = new LogGroup(this, 'MyLogGroup', { - logGroupName: `/aws/lambda/${fnName}`, - removalPolicy: RemovalPolicy.DESTROY, - retention: RetentionDays.ONE_DAY, - }); - - const fn = new LambdaFunction(this, 'MyFunction', { - functionName: fnName, - logGroup, - timeout: Duration.minutes(3), - runtime: Runtime.DOTNET_8, - handler: 'BedrockAgentFunction', - code: Code.fromAsset('../release/BedrockAgentFunction.zip'), - architecture: Architecture.X86_64, - }); - - const agentRole = new Role(this, 'MyAgentRole', { - assumedBy: new ServicePrincipal('bedrock.amazonaws.com'), - description: 'Role for Bedrock airport agent', - inlinePolicies: { - bedrock: new PolicyDocument({ - statements: [ - new PolicyStatement({ - actions: [ - 'bedrock:*', - ], - resources: [ - Arn.format( - { - service: 'bedrock', - resource: 'foundation-model/*', - region: 'us-*', - account: '', - }, - Stack.of(this) - ), - Arn.format( - { - service: 'bedrock', - resource: 'inference-profile/*', - region: 'us-*', - account: '*', - }, - Stack.of(this) - ), - ], - }), - ], - }), - }, - }); - - const agent = new CfnAgent(this, 'MyCfnAgent', { - agentName: 'airportAgent', - actionGroups: [ - { - actionGroupName: 'airportActionGroup', - actionGroupExecutor: { - lambda: fn.functionArn, - }, - functionSchema: { - functions: [ - { - name: 'getAirportCodeForCity', - description: 'Get airport code and full airport name for a specific city', - parameters: { - city: { - type: 'string', - description: 'The name of the city to get the airport code for', - required: true, - }, - }, - }, - ], - }, - }, - ], - agentResourceRoleArn: agentRole.roleArn, - autoPrepare: true, - description: 'A simple airport agent', - foundationModel: `arn:aws:bedrock:us-west-2:${Stack.of(this).account}:inference-profile/us.amazon.nova-pro-v1:0`, - instruction: - 'You are an airport traffic control agent. You will be given a city name and you will return the airport code and airport full name for that city.', - }); - - fn.addPermission('BedrockAgentInvokePermission', { - principal: new ServicePrincipal('bedrock.amazonaws.com'), - action: 'lambda:InvokeFunction', - sourceAccount: this.account, - sourceArn: `arn:aws:bedrock:${this.region}:${this.account}:agent/${agent.attrAgentId}`, - }); - - new CfnOutput(this, 'FunctionArn', { - value: fn.functionArn, - }); - } -} diff --git a/examples/Event Handler/BedrockAgentFunction/infra/package-lock.json b/examples/Event Handler/BedrockAgentFunction/infra/package-lock.json deleted file mode 100644 index 1cf13f4bd..000000000 --- a/examples/Event Handler/BedrockAgentFunction/infra/package-lock.json +++ /dev/null @@ -1,4453 +0,0 @@ -{ - "name": "infra", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "infra", - "version": "0.1.0", - "dependencies": { - "aws-cdk-lib": "2.198.0", - "constructs": "^10.0.0" - }, - "bin": { - "infra": "bin/infra.js" - }, - "devDependencies": { - "@types/jest": "^29.5.14", - "@types/node": "22.7.9", - "aws-cdk": "2.1017.1", - "jest": "^29.7.0", - "ts-jest": "^29.2.5", - "ts-node": "^10.9.2", - "typescript": "~5.6.3" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@aws-cdk/asset-awscli-v1": { - "version": "2.2.237", - "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.237.tgz", - "integrity": "sha512-OlXylbXI52lboFVJBFLae+WB99qWmI121x/wXQHEMj2RaVNVbWE+OAHcDk2Um1BitUQCaTf9ki57B0Fuqx0Rvw==", - "license": "Apache-2.0" - }, - "node_modules/@aws-cdk/asset-node-proxy-agent-v6": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.1.0.tgz", - "integrity": "sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A==", - "license": "Apache-2.0" - }, - "node_modules/@aws-cdk/cloud-assembly-schema": { - "version": "41.2.0", - "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-41.2.0.tgz", - "integrity": "sha512-JaulVS6z9y5+u4jNmoWbHZRs9uGOnmn/ktXygNWKNu1k6lF3ad4so3s18eRu15XCbUIomxN9WPYT6Ehh7hzONw==", - "bundleDependencies": [ - "jsonschema", - "semver" - ], - "license": "Apache-2.0", - "dependencies": { - "jsonschema": "~1.4.1", - "semver": "^7.7.1" - }, - "engines": { - "node": ">= 14.15.0" - } - }, - "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": { - "version": "1.4.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": { - "version": "7.7.1", - "inBundle": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.3.tgz", - "integrity": "sha512-V42wFfx1ymFte+ecf6iXghnnP8kWTO+ZLXIyZq+1LAXHHvTZdVxicn4yiVYdYMGaCO3tmqub11AorKkv+iodqw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz", - "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.27.3", - "@babel/helpers": "^7.27.4", - "@babel/parser": "^7.27.4", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.27.4", - "@babel/types": "^7.27.3", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.3.tgz", - "integrity": "sha512-xnlJYj5zepml8NXtjkG0WquFUv8RskFqyFcVgTBp5k+NaA/8uw/K+OSVf8AMGw5e9HKP2ETd5xpK5MLZQD6b4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.27.3", - "@babel/types": "^7.27.3", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", - "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.4.tgz", - "integrity": "sha512-Y+bO6U+I7ZKaM5G5rDUZiYfUvQPUibYmAFe7EnKdnKBbVXDZxvp+MWOH5gYciY0EPk4EScsuFMQBbEfpdRKSCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.27.3" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.4.tgz", - "integrity": "sha512-BRmLHGwpUqLFR2jzx9orBuX/ABDkj2jLKOXrHDTN2aOKL+jFDDKaRNo9nyYsIl9h/UE/7lMKdDjKQQyxKKDZ7g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.3" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", - "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", - "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", - "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", - "@babel/parser": "^7.27.4", - "@babel/template": "^7.27.2", - "@babel/types": "^7.27.3", - "debug": "^4.3.1", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.3.tgz", - "integrity": "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", - "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/core": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", - "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/reporters": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.7.0", - "jest-config": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-resolve-dependencies": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "jest-watcher": "^29.7.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/environment": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", - "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "^29.7.0", - "jest-snapshot": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", - "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-get-type": "^29.6.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/fake-timers": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/globals": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", - "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/types": "^29.6.3", - "jest-mock": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/reporters": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", - "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", - "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-result": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", - "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", - "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.0" - } - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", - "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.20.7" - } - }, - "node_modules/@types/graceful-fs": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", - "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jest": { - "version": "29.5.14", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", - "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - } - }, - "node_modules/@types/node": { - "version": "22.7.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.9.tgz", - "integrity": "sha512-jrTfRC7FM6nChvU7X2KqcrgquofrWLFDeYC1hKfwNWomVvrn7JIksqf344WN2X/y8xrgqBd2dJATZV4GbatBfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.19.2" - } - }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "license": "MIT" - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true, - "license": "MIT" - }, - "node_modules/aws-cdk": { - "version": "2.1017.1", - "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1017.1.tgz", - "integrity": "sha512-KtDdkMhfVjDeexjpMrVoSlz2mTYI5BE/KotvJ7iFbZy1G0nkpW1ImZ54TdBefeeFmZ+8DAjU3I6nUFtymyOI1A==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "cdk": "bin/cdk" - }, - "engines": { - "node": ">= 14.15.0" - }, - "optionalDependencies": { - "fsevents": "2.3.2" - } - }, - "node_modules/aws-cdk-lib": { - "version": "2.198.0", - "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.198.0.tgz", - "integrity": "sha512-CyZ+lnRsCsLskzQLPO0EiGl5EVcLluhfa67df3b8/gJfsm+91SHJa75OH+ymdGtUp5Vn/MWUPsujw0EhWMfsIQ==", - "bundleDependencies": [ - "@balena/dockerignore", - "case", - "fs-extra", - "ignore", - "jsonschema", - "minimatch", - "punycode", - "semver", - "table", - "yaml", - "mime-types" - ], - "license": "Apache-2.0", - "dependencies": { - "@aws-cdk/asset-awscli-v1": "2.2.237", - "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.0", - "@aws-cdk/cloud-assembly-schema": "^41.2.0", - "@balena/dockerignore": "^1.0.2", - "case": "1.6.3", - "fs-extra": "^11.3.0", - "ignore": "^5.3.2", - "jsonschema": "^1.5.0", - "mime-types": "^2.1.35", - "minimatch": "^3.1.2", - "punycode": "^2.3.1", - "semver": "^7.7.2", - "table": "^6.9.0", - "yaml": "1.10.2" - }, - "engines": { - "node": ">= 14.15.0" - }, - "peerDependencies": { - "constructs": "^10.0.0" - } - }, - "node_modules/aws-cdk-lib/node_modules/@balena/dockerignore": { - "version": "1.0.2", - "inBundle": true, - "license": "Apache-2.0" - }, - "node_modules/aws-cdk-lib/node_modules/ajv": { - "version": "8.17.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/aws-cdk-lib/node_modules/ansi-regex": { - "version": "5.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/aws-cdk-lib/node_modules/ansi-styles": { - "version": "4.3.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/aws-cdk-lib/node_modules/astral-regex": { - "version": "2.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/aws-cdk-lib/node_modules/balanced-match": { - "version": "1.0.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/aws-cdk-lib/node_modules/brace-expansion": { - "version": "1.1.11", - "inBundle": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/aws-cdk-lib/node_modules/case": { - "version": "1.6.3", - "inBundle": true, - "license": "(MIT OR GPL-3.0-or-later)", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/aws-cdk-lib/node_modules/color-convert": { - "version": "2.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/aws-cdk-lib/node_modules/color-name": { - "version": "1.1.4", - "inBundle": true, - "license": "MIT" - }, - "node_modules/aws-cdk-lib/node_modules/concat-map": { - "version": "0.0.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/aws-cdk-lib/node_modules/emoji-regex": { - "version": "8.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/aws-cdk-lib/node_modules/fast-deep-equal": { - "version": "3.1.3", - "inBundle": true, - "license": "MIT" - }, - "node_modules/aws-cdk-lib/node_modules/fast-uri": { - "version": "3.0.6", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "inBundle": true, - "license": "BSD-3-Clause" - }, - "node_modules/aws-cdk-lib/node_modules/fs-extra": { - "version": "11.3.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/aws-cdk-lib/node_modules/graceful-fs": { - "version": "4.2.11", - "inBundle": true, - "license": "ISC" - }, - "node_modules/aws-cdk-lib/node_modules/ignore": { - "version": "5.3.2", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/aws-cdk-lib/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/aws-cdk-lib/node_modules/json-schema-traverse": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/aws-cdk-lib/node_modules/jsonfile": { - "version": "6.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/aws-cdk-lib/node_modules/jsonschema": { - "version": "1.5.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/aws-cdk-lib/node_modules/lodash.truncate": { - "version": "4.4.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/aws-cdk-lib/node_modules/mime-db": { - "version": "1.52.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/aws-cdk-lib/node_modules/mime-types": { - "version": "2.1.35", - "inBundle": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/aws-cdk-lib/node_modules/minimatch": { - "version": "3.1.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/aws-cdk-lib/node_modules/punycode": { - "version": "2.3.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/aws-cdk-lib/node_modules/require-from-string": { - "version": "2.0.2", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/aws-cdk-lib/node_modules/semver": { - "version": "7.7.2", - "inBundle": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/aws-cdk-lib/node_modules/slice-ansi": { - "version": "4.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/aws-cdk-lib/node_modules/string-width": { - "version": "4.2.3", - "inBundle": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/aws-cdk-lib/node_modules/strip-ansi": { - "version": "6.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/aws-cdk-lib/node_modules/table": { - "version": "6.9.0", - "inBundle": true, - "license": "BSD-3-Clause", - "dependencies": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/aws-cdk-lib/node_modules/universalify": { - "version": "2.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/aws-cdk-lib/node_modules/yaml": { - "version": "1.10.2", - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">= 6" - } - }, - "node_modules/babel-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", - "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/transform": "^29.7.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", - "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", - "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-preset-jest": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", - "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-plugin-jest-hoist": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.25.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", - "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001718", - "electron-to-chromium": "^1.5.160", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bs-logger": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-json-stable-stringify": "2.x" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001720", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001720.tgz", - "integrity": "sha512-Ec/2yV2nNPwb4DnTANEV99ZWwm3ZWfdlfkQbWSDDt+PsXEVYwlhPH8tdMaPunYTKKmz7AnHi2oNEi1GcmKCD8g==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cjs-module-lexer": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", - "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", - "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/constructs": { - "version": "10.4.2", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.4.2.tgz", - "integrity": "sha512-wsNxBlAott2qg8Zv87q3eYZYgheb9lchtBfjHzzLHtXbttwSrHPs1NNQbBrmbb1YZvYg2+Vh0Dor76w4mFxJkA==", - "license": "Apache-2.0" - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/create-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", - "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "prompts": "^2.0.1" - }, - "bin": { - "create-jest": "bin/create-jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/dedent": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", - "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" - }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/ejs": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", - "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "jake": "^10.8.5" - }, - "bin": { - "ejs": "bin/cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.161", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.161.tgz", - "integrity": "sha512-hwtetwfKNZo/UlwHIVBlKZVdy7o8bIZxxKs0Mv/ROPiQQQmDgdm5a+KvKtBsxM8ZjFzTaCeLoodZ8jiBE3o9rA==", - "dev": true, - "license": "ISC" - }, - "node_modules/emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "minimatch": "^5.0.1" - } - }, - "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/import-local": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jake": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", - "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "async": "^3.2.3", - "chalk": "^4.0.2", - "filelist": "^1.0.4", - "minimatch": "^3.1.2" - }, - "bin": { - "jake": "bin/cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", - "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/core": "^29.7.0", - "@jest/types": "^29.6.3", - "import-local": "^3.0.2", - "jest-cli": "^29.7.0" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-changed-files": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", - "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", - "dev": true, - "license": "MIT", - "dependencies": { - "execa": "^5.0.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-circus": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", - "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^1.0.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.7.0", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0", - "pretty-format": "^29.7.0", - "pure-rand": "^6.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-cli": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", - "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/core": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "create-jest": "^29.7.0", - "exit": "^0.1.2", - "import-local": "^3.0.2", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "yargs": "^17.3.1" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-config": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", - "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-jest": "^29.7.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-diff": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", - "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-docblock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", - "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "detect-newline": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-each": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", - "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "jest-util": "^29.7.0", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-environment-node": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", - "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/jest-leak-detector": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", - "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", - "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", - "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-mock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", - "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", - "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", - "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-regex-util": "^29.6.3", - "jest-snapshot": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runner": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", - "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/environment": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-leak-detector": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-resolve": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-util": "^29.7.0", - "jest-watcher": "^29.7.0", - "jest-worker": "^29.7.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runtime": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", - "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/globals": "^29.7.0", - "@jest/source-map": "^29.6.3", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", - "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "natural-compare": "^1.4.0", - "pretty-format": "^29.7.0", - "semver": "^7.5.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", - "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "leven": "^3.1.0", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-watcher": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", - "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "jest-util": "^29.7.0", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT" - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true, - "license": "MIT" - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true, - "license": "ISC" - }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tmpl": "1.0.5" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/pure-rand": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", - "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "license": "MIT" - }, - "node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve.exports": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", - "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true, - "license": "MIT" - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/ts-jest": { - "version": "29.3.4", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.3.4.tgz", - "integrity": "sha512-Iqbrm8IXOmV+ggWHOTEbjwyCf2xZlUMv5npExksXohL+tk8va4Fjhb+X2+Rt9NBmgO7bJ8WpnMLOwih/DnMlFA==", - "dev": true, - "license": "MIT", - "dependencies": { - "bs-logger": "^0.2.6", - "ejs": "^3.1.10", - "fast-json-stable-stringify": "^2.1.0", - "jest-util": "^29.0.0", - "json5": "^2.2.3", - "lodash.memoize": "^4.1.2", - "make-error": "^1.3.6", - "semver": "^7.7.2", - "type-fest": "^4.41.0", - "yargs-parser": "^21.1.1" - }, - "bin": { - "ts-jest": "cli.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/transform": "^29.0.0", - "@jest/types": "^29.0.0", - "babel-jest": "^29.0.0", - "jest": "^29.0.0", - "typescript": ">=4.3 <6" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@jest/transform": { - "optional": true - }, - "@jest/types": { - "optional": true - }, - "babel-jest": { - "optional": true - }, - "esbuild": { - "optional": true - } - } - }, - "node_modules/ts-jest/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ts-jest/node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typescript": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", - "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true, - "license": "MIT" - }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, - "license": "MIT" - }, - "node_modules/v8-to-istanbul": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", - "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", - "dev": true, - "license": "ISC", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "makeerror": "1.0.12" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, - "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/examples/Event Handler/BedrockAgentFunction/infra/package.json b/examples/Event Handler/BedrockAgentFunction/infra/package.json deleted file mode 100644 index eb6545cac..000000000 --- a/examples/Event Handler/BedrockAgentFunction/infra/package.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "infra", - "version": "0.1.0", - "bin": { - "infra": "bin/infra.js" - }, - "scripts": { - "build": "tsc", - "watch": "tsc -w", - "test": "jest", - "cdk": "cdk" - }, - "devDependencies": { - "@types/jest": "^29.5.14", - "@types/node": "22.7.9", - "jest": "^29.7.0", - "ts-jest": "^29.2.5", - "aws-cdk": "2.1017.1", - "ts-node": "^10.9.2", - "typescript": "~5.6.3" - }, - "dependencies": { - "aws-cdk-lib": "2.198.0", - "constructs": "^10.0.0" - } -} diff --git a/examples/Event Handler/BedrockAgentFunction/infra/tsconfig.json b/examples/Event Handler/BedrockAgentFunction/infra/tsconfig.json deleted file mode 100644 index 28bb557fa..000000000 --- a/examples/Event Handler/BedrockAgentFunction/infra/tsconfig.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "NodeNext", - "moduleResolution": "NodeNext", - "lib": [ - "es2022" - ], - "declaration": true, - "strict": true, - "noImplicitAny": true, - "strictNullChecks": true, - "noImplicitThis": true, - "alwaysStrict": true, - "noUnusedLocals": false, - "noUnusedParameters": false, - "noImplicitReturns": true, - "noFallthroughCasesInSwitch": false, - "inlineSourceMap": true, - "inlineSources": true, - "experimentalDecorators": true, - "strictPropertyInitialization": false, - "typeRoots": [ - "./node_modules/@types" - ] - }, - "exclude": [ - "node_modules", - "cdk.out" - ] -} diff --git a/examples/Event Handler/BedrockAgentFunction/src/AirportService.cs b/examples/Event Handler/BedrockAgentFunction/src/AirportService.cs deleted file mode 100644 index aa26e7f9f..000000000 --- a/examples/Event Handler/BedrockAgentFunction/src/AirportService.cs +++ /dev/null @@ -1,222 +0,0 @@ -namespace BedrockAgentFunction; - -public class AirportService -{ - private readonly Dictionary _airportsByCity = new(StringComparer.OrdinalIgnoreCase) - { - { - "New York", - new AirportInfo { City = "New York", Code = "JFK", Name = "John F. Kennedy International Airport" } - }, - { "London", new AirportInfo { City = "London", Code = "LHR", Name = "London Heathrow Airport" } }, - { "Paris", new AirportInfo { City = "Paris", Code = "CDG", Name = "Charles de Gaulle Airport" } }, - { "Tokyo", new AirportInfo { City = "Tokyo", Code = "HND", Name = "Tokyo Haneda Airport" } }, - { "Sydney", new AirportInfo { City = "Sydney", Code = "SYD", Name = "Sydney Airport" } }, - { - "Los Angeles", - new AirportInfo { City = "Los Angeles", Code = "LAX", Name = "Los Angeles International Airport" } - }, - { "Berlin", new AirportInfo { City = "Berlin", Code = "TXL", Name = "Berlin Tegel Airport" } }, - { "Dubai", new AirportInfo { City = "Dubai", Code = "DXB", Name = "Dubai International Airport" } }, - { - "Toronto", - new AirportInfo { City = "Toronto", Code = "YYZ", Name = "Toronto Pearson International Airport" } - }, - { "Singapore", new AirportInfo { City = "Singapore", Code = "SIN", Name = "Singapore Changi Airport" } }, - { "Hong Kong", new AirportInfo { City = "Hong Kong", Code = "HKG", Name = "Hong Kong International Airport" } }, - { "Madrid", new AirportInfo { City = "Madrid", Code = "MAD", Name = "Adolfo Suárez Madrid–Barajas Airport" } }, - { "Rome", new AirportInfo { City = "Rome", Code = "FCO", Name = "Leonardo da Vinci International Airport" } }, - { "Moscow", new AirportInfo { City = "Moscow", Code = "SVO", Name = "Sheremetyevo International Airport" } }, - { - "São Paulo", - new AirportInfo - { - City = "São Paulo", Code = "GRU", - Name = "São Paulo/Guarulhos–Governador André Franco Montoro International Airport" - } - }, - { "Istanbul", new AirportInfo { City = "Istanbul", Code = "IST", Name = "Istanbul Airport" } }, - { "Bangkok", new AirportInfo { City = "Bangkok", Code = "BKK", Name = "Suvarnabhumi Airport" } }, - { - "Mexico City", - new AirportInfo { City = "Mexico City", Code = "MEX", Name = "Mexico City International Airport" } - }, - { "Cairo", new AirportInfo { City = "Cairo", Code = "CAI", Name = "Cairo International Airport" } }, - { - "Buenos Aires", - new AirportInfo { City = "Buenos Aires", Code = "EZE", Name = "Ministro Pistarini International Airport" } - }, - { - "Kuala Lumpur", - new AirportInfo { City = "Kuala Lumpur", Code = "KUL", Name = "Kuala Lumpur International Airport" } - }, - { "Amsterdam", new AirportInfo { City = "Amsterdam", Code = "AMS", Name = "Amsterdam Airport Schiphol" } }, - { "Barcelona", new AirportInfo { City = "Barcelona", Code = "BCN", Name = "Barcelona–El Prat Airport" } }, - { "Lima", new AirportInfo { City = "Lima", Code = "LIM", Name = "Jorge Chávez International Airport" } }, - { "Seoul", new AirportInfo { City = "Seoul", Code = "ICN", Name = "Incheon International Airport" } }, - { - "Rio de Janeiro", - new AirportInfo - { - City = "Rio de Janeiro", Code = "GIG", - Name = "Rio de Janeiro/Galeão–Antonio Carlos Jobim International Airport" - } - }, - { "Dublin", new AirportInfo { City = "Dublin", Code = "DUB", Name = "Dublin Airport" } }, - { "Brussels", new AirportInfo { City = "Brussels", Code = "BRU", Name = "Brussels Airport" } }, - { "Lisbon", new AirportInfo { City = "Lisbon", Code = "LIS", Name = "Lisbon Portela Airport" } }, - { "Athens", new AirportInfo { City = "Athens", Code = "ATH", Name = "Athens International Airport" } }, - { "Oslo", new AirportInfo { City = "Oslo", Code = "OSL", Name = "Oslo Airport, Gardermoen" } }, - { "Stockholm", new AirportInfo { City = "Stockholm", Code = "ARN", Name = "Stockholm Arlanda Airport" } }, - { "Helsinki", new AirportInfo { City = "Helsinki", Code = "HEL", Name = "Helsinki-Vantaa Airport" } }, - { "Prague", new AirportInfo { City = "Prague", Code = "PRG", Name = "Václav Havel Airport Prague" } }, - { "Warsaw", new AirportInfo { City = "Warsaw", Code = "WAW", Name = "Warsaw Chopin Airport" } }, - { "Copenhagen", new AirportInfo { City = "Copenhagen", Code = "CPH", Name = "Copenhagen Airport" } }, - { - "Budapest", - new AirportInfo { City = "Budapest", Code = "BUD", Name = "Budapest Ferenc Liszt International Airport" } - }, - { "Osaka", new AirportInfo { City = "Osaka", Code = "KIX", Name = "Kansai International Airport" } }, - { - "San Francisco", - new AirportInfo { City = "San Francisco", Code = "SFO", Name = "San Francisco International Airport" } - }, - { "Miami", new AirportInfo { City = "Miami", Code = "MIA", Name = "Miami International Airport" } }, - { - "Seattle", new AirportInfo { City = "Seattle", Code = "SEA", Name = "Seattle–Tacoma International Airport" } - }, - { "Vancouver", new AirportInfo { City = "Vancouver", Code = "YVR", Name = "Vancouver International Airport" } }, - { "Melbourne", new AirportInfo { City = "Melbourne", Code = "MEL", Name = "Melbourne Airport" } }, - { "Auckland", new AirportInfo { City = "Auckland", Code = "AKL", Name = "Auckland Airport" } }, - { "Doha", new AirportInfo { City = "Doha", Code = "DOH", Name = "Hamad International Airport" } }, - { - "Kuwait City", new AirportInfo { City = "Kuwait City", Code = "KWI", Name = "Kuwait International Airport" } - }, - { - "Bangalore", new AirportInfo { City = "Bangalore", Code = "BLR", Name = "Kempegowda International Airport" } - }, - { - "Beijing", - new AirportInfo { City = "Beijing", Code = "PEK", Name = "Beijing Capital International Airport" } - }, - { - "Shanghai", - new AirportInfo { City = "Shanghai", Code = "PVG", Name = "Shanghai Pudong International Airport" } - }, - { "Manila", new AirportInfo { City = "Manila", Code = "MNL", Name = "Ninoy Aquino International Airport" } }, - { - "Jakarta", new AirportInfo { City = "Jakarta", Code = "CGK", Name = "Soekarno–Hatta International Airport" } - }, - { - "Santiago", - new AirportInfo - { City = "Santiago", Code = "SCL", Name = "Comodoro Arturo Merino Benítez International Airport" } - }, - { "Lagos", new AirportInfo { City = "Lagos", Code = "LOS", Name = "Murtala Muhammed International Airport" } }, - { "Nairobi", new AirportInfo { City = "Nairobi", Code = "NBO", Name = "Jomo Kenyatta International Airport" } }, - { "Chicago", new AirportInfo { City = "Chicago", Code = "ORD", Name = "O'Hare International Airport" } }, - { - "Atlanta", - new AirportInfo - { City = "Atlanta", Code = "ATL", Name = "Hartsfield–Jackson Atlanta International Airport" } - }, - { - "Dallas", - new AirportInfo { City = "Dallas", Code = "DFW", Name = "Dallas/Fort Worth International Airport" } - }, - { - "Washington, D.C.", - new AirportInfo - { City = "Washington, D.C.", Code = "IAD", Name = "Washington Dulles International Airport" } - }, - { "Boston", new AirportInfo { City = "Boston", Code = "BOS", Name = "Logan International Airport" } }, - { - "Philadelphia", - new AirportInfo { City = "Philadelphia", Code = "PHL", Name = "Philadelphia International Airport" } - }, - { "Orlando", new AirportInfo { City = "Orlando", Code = "MCO", Name = "Orlando International Airport" } }, - { "Denver", new AirportInfo { City = "Denver", Code = "DEN", Name = "Denver International Airport" } }, - { - "Phoenix", - new AirportInfo { City = "Phoenix", Code = "PHX", Name = "Phoenix Sky Harbor International Airport" } - }, - { "Las Vegas", new AirportInfo { City = "Las Vegas", Code = "LAS", Name = "McCarran International Airport" } }, - { - "Houston", new AirportInfo { City = "Houston", Code = "IAH", Name = "George Bush Intercontinental Airport" } - }, - { - "Detroit", - new AirportInfo { City = "Detroit", Code = "DTW", Name = "Detroit Metropolitan Wayne County Airport" } - }, - { - "Charlotte", - new AirportInfo { City = "Charlotte", Code = "CLT", Name = "Charlotte Douglas International Airport" } - }, - { - "Baltimore", - new AirportInfo - { - City = "Baltimore", Code = "BWI", Name = "Baltimore/Washington International Thurgood Marshall Airport" - } - }, - { - "Minneapolis", - new AirportInfo - { City = "Minneapolis", Code = "MSP", Name = "Minneapolis–Saint Paul International Airport" } - }, - { "San Diego", new AirportInfo { City = "San Diego", Code = "SAN", Name = "San Diego International Airport" } }, - { "Portland", new AirportInfo { City = "Portland", Code = "PDX", Name = "Portland International Airport" } }, - { - "Salt Lake City", - new AirportInfo { City = "Salt Lake City", Code = "SLC", Name = "Salt Lake City International Airport" } - }, - { - "Cincinnati", - new AirportInfo - { City = "Cincinnati", Code = "CVG", Name = "Cincinnati/Northern Kentucky International Airport" } - }, - { - "St. Louis", - new AirportInfo { City = "St. Louis", Code = "STL", Name = "St. Louis Lambert International Airport" } - }, - { - "Indianapolis", - new AirportInfo { City = "Indianapolis", Code = "IND", Name = "Indianapolis International Airport" } - }, - { "Tampa", new AirportInfo { City = "Tampa", Code = "TPA", Name = "Tampa International Airport" } }, - { "Milan", new AirportInfo { City = "Milan", Code = "MXP", Name = "Milan Malpensa Airport" } }, - { "Frankfurt", new AirportInfo { City = "Frankfurt", Code = "FRA", Name = "Frankfurt am Main Airport" } }, - { "Munich", new AirportInfo { City = "Munich", Code = "MUC", Name = "Munich Airport" } }, - { - "Mumbai", - new AirportInfo - { City = "Mumbai", Code = "BOM", Name = "Chhatrapati Shivaji Maharaj International Airport" } - }, - { "Cape Town", new AirportInfo { City = "Cape Town", Code = "CPT", Name = "Cape Town International Airport" } }, - { "Zurich", new AirportInfo { City = "Zurich", Code = "ZRH", Name = "Zurich Airport" } }, - { "Vienna", new AirportInfo { City = "Vienna", Code = "VIE", Name = "Vienna International Airport" } } - // Add more airports as needed - }; - - public AirportInfo GetAirportInfoForCity(string city) - { - if (_airportsByCity.TryGetValue(city, out var airportInfo)) - { - return airportInfo; - } - - throw new KeyNotFoundException($"No airport information found for city: {city}"); - } -} - -public class AirportInfo -{ - public string City { get; set; } = string.Empty; - public string Code { get; set; } = string.Empty; - public string Name { get; set; } = string.Empty; - - public override string ToString() - { - return $"{Name} ({Code}) in {City}"; - } -} \ No newline at end of file diff --git a/examples/Event Handler/BedrockAgentFunction/src/BedrockAgentFunction.csproj b/examples/Event Handler/BedrockAgentFunction/src/BedrockAgentFunction.csproj deleted file mode 100644 index bcd2c51cd..000000000 --- a/examples/Event Handler/BedrockAgentFunction/src/BedrockAgentFunction.csproj +++ /dev/null @@ -1,22 +0,0 @@ - - - Exe - net8.0 - enable - enable - true - Lambda - - true - - true - - - - - - - - - - \ No newline at end of file diff --git a/examples/Event Handler/BedrockAgentFunction/src/Function.cs b/examples/Event Handler/BedrockAgentFunction/src/Function.cs deleted file mode 100644 index c4e847ef0..000000000 --- a/examples/Event Handler/BedrockAgentFunction/src/Function.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Amazon.Lambda.Core; -using Amazon.Lambda.RuntimeSupport; -using Amazon.Lambda.Serialization.SystemTextJson; -using AWS.Lambda.Powertools.EventHandler.Resolvers; -using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; -using AWS.Lambda.Powertools.Logging; -using BedrockAgentFunction; -using Microsoft.Extensions.Logging; - - -var logger = LoggerFactory.Create(builder => -{ - builder.AddPowertoolsLogger(config => { config.Service = "AirportService"; }); -}).CreatePowertoolsLogger(); - -var resolver = new BedrockAgentFunctionResolver(); - - -resolver.Tool("getAirportCodeForCity", "Get airport code and full name for a specific city", (string city, ILambdaContext context) => -{ - logger.LogInformation("Getting airport code for city: {City}", city); - var airportService = new AirportService(); - var airportInfo = airportService.GetAirportInfoForCity(city); - - logger.LogInformation("Airport for {City}: {AirportInfoCode} - {AirportInfoName}", city, airportInfo.Code, airportInfo.Name); - - // Note: Best approach is to override the ToString method in the AirportInfo class - return airportInfo; -}); - - -// The function handler that will be called for each Lambda event -var handler = async (BedrockFunctionRequest input, ILambdaContext context) => -{ - return await resolver.ResolveAsync(input, context); -}; - -// Build the Lambda runtime client passing in the handler to call for each -// event and the JSON serializer to use for translating Lambda JSON documents -// to .NET types. -await LambdaBootstrapBuilder.Create(handler, new DefaultLambdaJsonSerializer()) - .Build() - .RunAsync(); - - diff --git a/examples/Event Handler/BedrockAgentFunction/src/aws-lambda-tools-defaults.json b/examples/Event Handler/BedrockAgentFunction/src/aws-lambda-tools-defaults.json deleted file mode 100644 index 1dc447ae8..000000000 --- a/examples/Event Handler/BedrockAgentFunction/src/aws-lambda-tools-defaults.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "Information": [ - "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", - "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", - "dotnet lambda help", - "All the command line options for the Lambda command can be specified in this file." - ], - "profile": "", - "region": "", - "configuration": "Release", - "function-runtime": "dotnet8", - "function-memory-size": 512, - "function-timeout": 30, - "function-handler": "BedrockAgentFunction" -} \ No newline at end of file diff --git a/examples/Idempotency/src/HelloWorld/HelloWorld.csproj b/examples/Idempotency/src/HelloWorld/HelloWorld.csproj index 39615764a..86f103f9e 100644 --- a/examples/Idempotency/src/HelloWorld/HelloWorld.csproj +++ b/examples/Idempotency/src/HelloWorld/HelloWorld.csproj @@ -5,10 +5,10 @@ enable - + - - - + + + diff --git a/examples/Idempotency/test/HelloWorld.Test/HelloWorld.Tests.csproj b/examples/Idempotency/test/HelloWorld.Test/HelloWorld.Tests.csproj index e143aa862..b00a6873d 100644 --- a/examples/Idempotency/test/HelloWorld.Test/HelloWorld.Tests.csproj +++ b/examples/Idempotency/test/HelloWorld.Test/HelloWorld.Tests.csproj @@ -3,9 +3,9 @@ net6.0;net8.0 - + - + diff --git a/examples/Kafka/Avro/src/Avro.csproj b/examples/Kafka/Avro/src/Avro.csproj deleted file mode 100644 index 05314f2fb..000000000 --- a/examples/Kafka/Avro/src/Avro.csproj +++ /dev/null @@ -1,35 +0,0 @@ - - - Exe - net8.0 - enable - enable - true - Lambda - - true - - true - - Avro.Example - - - - - - - - - - - - - - - - - - PreserveNewest - - - \ No newline at end of file diff --git a/examples/Kafka/Avro/src/CustomerProfile.avsc b/examples/Kafka/Avro/src/CustomerProfile.avsc deleted file mode 100644 index bf8cc090c..000000000 --- a/examples/Kafka/Avro/src/CustomerProfile.avsc +++ /dev/null @@ -1,46 +0,0 @@ -{ - "type": "record", - "name": "CustomerProfile", - "namespace": "com.example", - "fields": [ - {"name": "user_id", "type": "string"}, - {"name": "full_name", "type": "string"}, - {"name": "email", "type": { - "type": "record", - "name": "EmailAddress", - "fields": [ - {"name": "address", "type": "string"}, - {"name": "verified", "type": "boolean"}, - {"name": "primary", "type": "boolean"} - ] - }}, - {"name": "age", "type": "int"}, - {"name": "address", "type": { - "type": "record", - "name": "Address", - "fields": [ - {"name": "street", "type": "string"}, - {"name": "city", "type": "string"}, - {"name": "state", "type": "string"}, - {"name": "country", "type": "string"}, - {"name": "zip_code", "type": "string"} - ] - }}, - {"name": "phone_numbers", "type": { - "type": "array", - "items": { - "type": "record", - "name": "PhoneNumber", - "fields": [ - {"name": "number", "type": "string"}, - {"name": "type", "type": {"type": "enum", "name": "PhoneType", "symbols": ["HOME", "WORK", "MOBILE"]}} - ] - } - }}, - {"name": "preferences", "type": { - "type": "map", - "values": "string" - }}, - {"name": "account_status", "type": {"type": "enum", "name": "AccountStatus", "symbols": ["ACTIVE", "INACTIVE", "SUSPENDED"]}} - ] -} \ No newline at end of file diff --git a/examples/Kafka/Avro/src/Function.cs b/examples/Kafka/Avro/src/Function.cs deleted file mode 100644 index 6ca9ebdb5..000000000 --- a/examples/Kafka/Avro/src/Function.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Amazon.Lambda.Core; -using Amazon.Lambda.RuntimeSupport; -using AWS.Lambda.Powertools.Kafka; -using AWS.Lambda.Powertools.Kafka.Avro; -using AWS.Lambda.Powertools.Logging; -using com.example; - -string Handler(ConsumerRecords records, ILambdaContext context) -{ - foreach (var record in records) - { - Logger.LogInformation("Record Value: {@record}", record.Value); - } - - return "Processed " + records.Count() + " records"; -} - -await LambdaBootstrapBuilder.Create((Func, ILambdaContext, string>?)Handler, - new PowertoolsKafkaAvroSerializer()) // Use PowertoolsKafkaAvroSerializer for Avro serialization - .Build() - .RunAsync(); \ No newline at end of file diff --git a/examples/Kafka/Avro/src/Generated/com/example/AccountStatus.cs b/examples/Kafka/Avro/src/Generated/com/example/AccountStatus.cs deleted file mode 100644 index c7809f518..000000000 --- a/examples/Kafka/Avro/src/Generated/com/example/AccountStatus.cs +++ /dev/null @@ -1,23 +0,0 @@ -// ------------------------------------------------------------------------------ -// -// Generated by avrogen, version 1.12.0+8c27801dc8d42ccc00997f25c0b8f45f8d4a233e -// Changes to this file may cause incorrect behavior and will be lost if code -// is regenerated -// -// ------------------------------------------------------------------------------ -namespace com.example -{ - using System; - using System.Collections.Generic; - using System.Text; - using global::Avro; - using global::Avro.Specific; - - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("avrogen", "1.12.0+8c27801dc8d42ccc00997f25c0b8f45f8d4a233e")] - public enum AccountStatus - { - ACTIVE, - INACTIVE, - SUSPENDED, - } -} diff --git a/examples/Kafka/Avro/src/Generated/com/example/Address.cs b/examples/Kafka/Avro/src/Generated/com/example/Address.cs deleted file mode 100644 index e2053e0f2..000000000 --- a/examples/Kafka/Avro/src/Generated/com/example/Address.cs +++ /dev/null @@ -1,115 +0,0 @@ -// ------------------------------------------------------------------------------ -// -// Generated by avrogen, version 1.12.0+8c27801dc8d42ccc00997f25c0b8f45f8d4a233e -// Changes to this file may cause incorrect behavior and will be lost if code -// is regenerated -// -// ------------------------------------------------------------------------------ -namespace com.example -{ - using System; - using System.Collections.Generic; - using System.Text; - using global::Avro; - using global::Avro.Specific; - - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("avrogen", "1.12.0+8c27801dc8d42ccc00997f25c0b8f45f8d4a233e")] - public partial class Address : global::Avro.Specific.ISpecificRecord - { - public static global::Avro.Schema _SCHEMA = global::Avro.Schema.Parse("{\"type\":\"record\",\"name\":\"Address\",\"namespace\":\"com.example\",\"fields\":[{\"name\":\"st" + - "reet\",\"type\":\"string\"},{\"name\":\"city\",\"type\":\"string\"},{\"name\":\"state\",\"type\":\"s" + - "tring\"},{\"name\":\"country\",\"type\":\"string\"},{\"name\":\"zip_code\",\"type\":\"string\"}]}" + - ""); - private string _street; - private string _city; - private string _state; - private string _country; - private string _zip_code; - public virtual global::Avro.Schema Schema - { - get - { - return Address._SCHEMA; - } - } - public string street - { - get - { - return this._street; - } - set - { - this._street = value; - } - } - public string city - { - get - { - return this._city; - } - set - { - this._city = value; - } - } - public string state - { - get - { - return this._state; - } - set - { - this._state = value; - } - } - public string country - { - get - { - return this._country; - } - set - { - this._country = value; - } - } - public string zip_code - { - get - { - return this._zip_code; - } - set - { - this._zip_code = value; - } - } - public virtual object Get(int fieldPos) - { - switch (fieldPos) - { - case 0: return this.street; - case 1: return this.city; - case 2: return this.state; - case 3: return this.country; - case 4: return this.zip_code; - default: throw new global::Avro.AvroRuntimeException("Bad index " + fieldPos + " in Get()"); - }; - } - public virtual void Put(int fieldPos, object fieldValue) - { - switch (fieldPos) - { - case 0: this.street = (System.String)fieldValue; break; - case 1: this.city = (System.String)fieldValue; break; - case 2: this.state = (System.String)fieldValue; break; - case 3: this.country = (System.String)fieldValue; break; - case 4: this.zip_code = (System.String)fieldValue; break; - default: throw new global::Avro.AvroRuntimeException("Bad index " + fieldPos + " in Put()"); - }; - } - } -} diff --git a/examples/Kafka/Avro/src/Generated/com/example/CustomerProfile.cs b/examples/Kafka/Avro/src/Generated/com/example/CustomerProfile.cs deleted file mode 100644 index 15d62095d..000000000 --- a/examples/Kafka/Avro/src/Generated/com/example/CustomerProfile.cs +++ /dev/null @@ -1,154 +0,0 @@ -// ------------------------------------------------------------------------------ -// -// Generated by avrogen, version 1.12.0+8c27801dc8d42ccc00997f25c0b8f45f8d4a233e -// Changes to this file may cause incorrect behavior and will be lost if code -// is regenerated -// -// ------------------------------------------------------------------------------ -namespace com.example -{ - using System; - using System.Collections.Generic; - using System.Text; - using global::Avro; - using global::Avro.Specific; - - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("avrogen", "1.12.0+8c27801dc8d42ccc00997f25c0b8f45f8d4a233e")] - public partial class CustomerProfile : global::Avro.Specific.ISpecificRecord - { - public static global::Avro.Schema _SCHEMA = global::Avro.Schema.Parse(@"{""type"":""record"",""name"":""CustomerProfile"",""namespace"":""com.example"",""fields"":[{""name"":""user_id"",""type"":""string""},{""name"":""full_name"",""type"":""string""},{""name"":""email"",""type"":{""type"":""record"",""name"":""EmailAddress"",""namespace"":""com.example"",""fields"":[{""name"":""address"",""type"":""string""},{""name"":""verified"",""type"":""boolean""},{""name"":""primary"",""type"":""boolean""}]}},{""name"":""age"",""type"":""int""},{""name"":""address"",""type"":{""type"":""record"",""name"":""Address"",""namespace"":""com.example"",""fields"":[{""name"":""street"",""type"":""string""},{""name"":""city"",""type"":""string""},{""name"":""state"",""type"":""string""},{""name"":""country"",""type"":""string""},{""name"":""zip_code"",""type"":""string""}]}},{""name"":""phone_numbers"",""type"":{""type"":""array"",""items"":{""type"":""record"",""name"":""PhoneNumber"",""namespace"":""com.example"",""fields"":[{""name"":""number"",""type"":""string""},{""name"":""type"",""type"":{""type"":""enum"",""name"":""PhoneType"",""namespace"":""com.example"",""symbols"":[""HOME"",""WORK"",""MOBILE""]}}]}}},{""name"":""preferences"",""type"":{""type"":""map"",""values"":""string""}},{""name"":""account_status"",""type"":{""type"":""enum"",""name"":""AccountStatus"",""namespace"":""com.example"",""symbols"":[""ACTIVE"",""INACTIVE"",""SUSPENDED""]}}]}"); - private string _user_id; - private string _full_name; - private com.example.EmailAddress _email; - private int _age; - private com.example.Address _address; - private IList _phone_numbers; - private IDictionary _preferences; - private com.example.AccountStatus _account_status; - public virtual global::Avro.Schema Schema - { - get - { - return CustomerProfile._SCHEMA; - } - } - public string user_id - { - get - { - return this._user_id; - } - set - { - this._user_id = value; - } - } - public string full_name - { - get - { - return this._full_name; - } - set - { - this._full_name = value; - } - } - public com.example.EmailAddress email - { - get - { - return this._email; - } - set - { - this._email = value; - } - } - public int age - { - get - { - return this._age; - } - set - { - this._age = value; - } - } - public com.example.Address address - { - get - { - return this._address; - } - set - { - this._address = value; - } - } - public IList phone_numbers - { - get - { - return this._phone_numbers; - } - set - { - this._phone_numbers = value; - } - } - public IDictionary preferences - { - get - { - return this._preferences; - } - set - { - this._preferences = value; - } - } - public com.example.AccountStatus account_status - { - get - { - return this._account_status; - } - set - { - this._account_status = value; - } - } - public virtual object Get(int fieldPos) - { - switch (fieldPos) - { - case 0: return this.user_id; - case 1: return this.full_name; - case 2: return this.email; - case 3: return this.age; - case 4: return this.address; - case 5: return this.phone_numbers; - case 6: return this.preferences; - case 7: return this.account_status; - default: throw new global::Avro.AvroRuntimeException("Bad index " + fieldPos + " in Get()"); - }; - } - public virtual void Put(int fieldPos, object fieldValue) - { - switch (fieldPos) - { - case 0: this.user_id = (System.String)fieldValue; break; - case 1: this.full_name = (System.String)fieldValue; break; - case 2: this.email = (com.example.EmailAddress)fieldValue; break; - case 3: this.age = (System.Int32)fieldValue; break; - case 4: this.address = (com.example.Address)fieldValue; break; - case 5: this.phone_numbers = (IList)fieldValue; break; - case 6: this.preferences = (IDictionary)fieldValue; break; - case 7: this.account_status = (com.example.AccountStatus)fieldValue; break; - default: throw new global::Avro.AvroRuntimeException("Bad index " + fieldPos + " in Put()"); - }; - } - } -} diff --git a/examples/Kafka/Avro/src/Generated/com/example/EmailAddress.cs b/examples/Kafka/Avro/src/Generated/com/example/EmailAddress.cs deleted file mode 100644 index 4a25a6e0b..000000000 --- a/examples/Kafka/Avro/src/Generated/com/example/EmailAddress.cs +++ /dev/null @@ -1,86 +0,0 @@ -// ------------------------------------------------------------------------------ -// -// Generated by avrogen, version 1.12.0+8c27801dc8d42ccc00997f25c0b8f45f8d4a233e -// Changes to this file may cause incorrect behavior and will be lost if code -// is regenerated -// -// ------------------------------------------------------------------------------ -namespace com.example -{ - using System; - using System.Collections.Generic; - using System.Text; - using global::Avro; - using global::Avro.Specific; - - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("avrogen", "1.12.0+8c27801dc8d42ccc00997f25c0b8f45f8d4a233e")] - public partial class EmailAddress : global::Avro.Specific.ISpecificRecord - { - public static global::Avro.Schema _SCHEMA = global::Avro.Schema.Parse("{\"type\":\"record\",\"name\":\"EmailAddress\",\"namespace\":\"com.example\",\"fields\":[{\"name" + - "\":\"address\",\"type\":\"string\"},{\"name\":\"verified\",\"type\":\"boolean\"},{\"name\":\"prima" + - "ry\",\"type\":\"boolean\"}]}"); - private string _address; - private bool _verified; - private bool _primary; - public virtual global::Avro.Schema Schema - { - get - { - return EmailAddress._SCHEMA; - } - } - public string address - { - get - { - return this._address; - } - set - { - this._address = value; - } - } - public bool verified - { - get - { - return this._verified; - } - set - { - this._verified = value; - } - } - public bool primary - { - get - { - return this._primary; - } - set - { - this._primary = value; - } - } - public virtual object Get(int fieldPos) - { - switch (fieldPos) - { - case 0: return this.address; - case 1: return this.verified; - case 2: return this.primary; - default: throw new global::Avro.AvroRuntimeException("Bad index " + fieldPos + " in Get()"); - }; - } - public virtual void Put(int fieldPos, object fieldValue) - { - switch (fieldPos) - { - case 0: this.address = (System.String)fieldValue; break; - case 1: this.verified = (System.Boolean)fieldValue; break; - case 2: this.primary = (System.Boolean)fieldValue; break; - default: throw new global::Avro.AvroRuntimeException("Bad index " + fieldPos + " in Put()"); - }; - } - } -} diff --git a/examples/Kafka/Avro/src/Generated/com/example/PhoneNumber.cs b/examples/Kafka/Avro/src/Generated/com/example/PhoneNumber.cs deleted file mode 100644 index ea3d2b8ed..000000000 --- a/examples/Kafka/Avro/src/Generated/com/example/PhoneNumber.cs +++ /dev/null @@ -1,72 +0,0 @@ -// ------------------------------------------------------------------------------ -// -// Generated by avrogen, version 1.12.0+8c27801dc8d42ccc00997f25c0b8f45f8d4a233e -// Changes to this file may cause incorrect behavior and will be lost if code -// is regenerated -// -// ------------------------------------------------------------------------------ -namespace com.example -{ - using System; - using System.Collections.Generic; - using System.Text; - using global::Avro; - using global::Avro.Specific; - - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("avrogen", "1.12.0+8c27801dc8d42ccc00997f25c0b8f45f8d4a233e")] - public partial class PhoneNumber : global::Avro.Specific.ISpecificRecord - { - public static global::Avro.Schema _SCHEMA = global::Avro.Schema.Parse("{\"type\":\"record\",\"name\":\"PhoneNumber\",\"namespace\":\"com.example\",\"fields\":[{\"name\"" + - ":\"number\",\"type\":\"string\"},{\"name\":\"type\",\"type\":{\"type\":\"enum\",\"name\":\"PhoneTyp" + - "e\",\"namespace\":\"com.example\",\"symbols\":[\"HOME\",\"WORK\",\"MOBILE\"]}}]}"); - private string _number; - private com.example.PhoneType _type; - public virtual global::Avro.Schema Schema - { - get - { - return PhoneNumber._SCHEMA; - } - } - public string number - { - get - { - return this._number; - } - set - { - this._number = value; - } - } - public com.example.PhoneType type - { - get - { - return this._type; - } - set - { - this._type = value; - } - } - public virtual object Get(int fieldPos) - { - switch (fieldPos) - { - case 0: return this.number; - case 1: return this.type; - default: throw new global::Avro.AvroRuntimeException("Bad index " + fieldPos + " in Get()"); - }; - } - public virtual void Put(int fieldPos, object fieldValue) - { - switch (fieldPos) - { - case 0: this.number = (System.String)fieldValue; break; - case 1: this.type = (com.example.PhoneType)fieldValue; break; - default: throw new global::Avro.AvroRuntimeException("Bad index " + fieldPos + " in Put()"); - }; - } - } -} diff --git a/examples/Kafka/Avro/src/Generated/com/example/PhoneType.cs b/examples/Kafka/Avro/src/Generated/com/example/PhoneType.cs deleted file mode 100644 index f592d8692..000000000 --- a/examples/Kafka/Avro/src/Generated/com/example/PhoneType.cs +++ /dev/null @@ -1,23 +0,0 @@ -// ------------------------------------------------------------------------------ -// -// Generated by avrogen, version 1.12.0+8c27801dc8d42ccc00997f25c0b8f45f8d4a233e -// Changes to this file may cause incorrect behavior and will be lost if code -// is regenerated -// -// ------------------------------------------------------------------------------ -namespace com.example -{ - using System; - using System.Collections.Generic; - using System.Text; - using global::Avro; - using global::Avro.Specific; - - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("avrogen", "1.12.0+8c27801dc8d42ccc00997f25c0b8f45f8d4a233e")] - public enum PhoneType - { - HOME, - WORK, - MOBILE, - } -} diff --git a/examples/Kafka/Avro/src/aws-lambda-tools-defaults.json b/examples/Kafka/Avro/src/aws-lambda-tools-defaults.json deleted file mode 100644 index cd93437eb..000000000 --- a/examples/Kafka/Avro/src/aws-lambda-tools-defaults.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "Information": [ - "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", - "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", - "dotnet lambda help", - "All the command line options for the Lambda command can be specified in this file." - ], - "profile": "", - "region": "", - "configuration": "Release", - "function-runtime": "dotnet8", - "function-memory-size": 512, - "function-timeout": 30, - "function-handler": "Avro.Example" -} \ No newline at end of file diff --git a/examples/Kafka/Avro/src/kafka-avro-event.json b/examples/Kafka/Avro/src/kafka-avro-event.json deleted file mode 100644 index 6f5e045e3..000000000 --- a/examples/Kafka/Avro/src/kafka-avro-event.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "eventSource": "aws:kafka", - "eventSourceArn": "arn:aws:kafka:us-east-1:0123456789019:cluster/CustomerCluster/abcd1234-abcd-cafe-abab-9876543210ab-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": { - "customer-topic-0": [ - { - "topic": "customer-topic", - "partition": 0, - "offset": 15, - "timestamp": 1545084650987, - "timestampType": "CREATE_TIME", - "key": "dXNlcl85NzU0", - "value": "EnVzZXJfOTc1NBxVc2VyIHVzZXJfOTc1NCh1c2VyXzk3NTRAaWNsb3VkLmNvbQABahg5MzQwIE1haW4gU3QQU2FuIEpvc2UEQ0EGVVNBCjM5NTk2AhgyNDQtNDA3LTg4NzECAAYQdGltZXpvbmUOZW5hYmxlZBBsYW5ndWFnZRBkaXNhYmxlZBpub3RpZmljYXRpb25zCGRhcmsABA==", - "headers": [ - { - "headerKey": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] - } - ] - } - ] - } -} \ No newline at end of file diff --git a/examples/Kafka/Avro/src/template.yaml b/examples/Kafka/Avro/src/template.yaml deleted file mode 100644 index a08325be2..000000000 --- a/examples/Kafka/Avro/src/template.yaml +++ /dev/null @@ -1,27 +0,0 @@ -AWSTemplateFormatVersion: '2010-09-09' -Transform: AWS::Serverless-2016-10-31 -Description: > - kafka - - Sample SAM Template for kafka - -# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst -Globals: - Function: - Timeout: 15 - MemorySize: 512 - Runtime: dotnet8 - -Resources: - AvroDeserializationFunction: - Type: AWS::Serverless::Function - Properties: - Handler: Avro.Example - Architectures: - - x86_64 - Tracing: Active - Environment: # Powertools env vars: https://awslabs.github.io/aws-lambda-powertools-python/#environment-variables - Variables: - POWERTOOLS_SERVICE_NAME: PowertoolsHelloWorld - POWERTOOLS_LOG_LEVEL: Info - POWERTOOLS_LOGGER_CASE: PascalCase # Allowed values are: CamelCase, PascalCase and SnakeCase (Default) \ No newline at end of file diff --git a/examples/Kafka/Json/src/Function.cs b/examples/Kafka/Json/src/Function.cs deleted file mode 100644 index d7d96bfca..000000000 --- a/examples/Kafka/Json/src/Function.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Amazon.Lambda.Core; -using Amazon.Lambda.RuntimeSupport; -using AWS.Lambda.Powertools.Kafka; -using AWS.Lambda.Powertools.Kafka.Json; -using AWS.Lambda.Powertools.Logging; -using Json.Models; - -string Handler(ConsumerRecords records, ILambdaContext context) -{ - foreach (var record in records) - { - Logger.LogInformation("Record Value: {@record}", record.Value); - } - - return "Processed " + records.Count() + " records"; -} - -await LambdaBootstrapBuilder.Create((Func, ILambdaContext, string>?)Handler, - new PowertoolsKafkaJsonSerializer()) // Use PowertoolsKafkaJsonSerializer for Json serialization - .Build() - .RunAsync(); \ No newline at end of file diff --git a/examples/Kafka/Json/src/Json.csproj b/examples/Kafka/Json/src/Json.csproj deleted file mode 100644 index aba6cde89..000000000 --- a/examples/Kafka/Json/src/Json.csproj +++ /dev/null @@ -1,30 +0,0 @@ - - - Exe - net8.0 - enable - enable - true - Lambda - - true - - true - - - - - - - - - - - - - - PreserveNewest - - - - \ No newline at end of file diff --git a/examples/Kafka/Json/src/Models/Address.cs b/examples/Kafka/Json/src/Models/Address.cs deleted file mode 100644 index a011b3cee..000000000 --- a/examples/Kafka/Json/src/Models/Address.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Json.Models; - -public partial class Address -{ - [JsonPropertyName("street")] public string Street { get; set; } - - [JsonPropertyName("city")] public string City { get; set; } - - [JsonPropertyName("state")] public string State { get; set; } - - [JsonPropertyName("country")] public string Country { get; set; } - - [JsonPropertyName("zip_code")] public string ZipCode { get; set; } -} \ No newline at end of file diff --git a/examples/Kafka/Json/src/Models/CustomerProfile.cs b/examples/Kafka/Json/src/Models/CustomerProfile.cs deleted file mode 100644 index 1e7ab62b6..000000000 --- a/examples/Kafka/Json/src/Models/CustomerProfile.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Json.Models; - -public partial class CustomerProfile -{ - [JsonPropertyName("user_id")] public string UserId { get; set; } - - [JsonPropertyName("full_name")] public string FullName { get; set; } - - [JsonPropertyName("email")] public Email Email { get; set; } - - [JsonPropertyName("age")] public long Age { get; set; } - - [JsonPropertyName("address")] public Address Address { get; set; } - - [JsonPropertyName("phone_numbers")] public List PhoneNumbers { get; set; } - - [JsonPropertyName("preferences")] public Preferences Preferences { get; set; } - - [JsonPropertyName("account_status")] public string AccountStatus { get; set; } -} \ No newline at end of file diff --git a/examples/Kafka/Json/src/Models/Email.cs b/examples/Kafka/Json/src/Models/Email.cs deleted file mode 100644 index 045118baf..000000000 --- a/examples/Kafka/Json/src/Models/Email.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Json.Models; - -public partial class Email -{ - [JsonPropertyName("address")] public string Address { get; set; } - - [JsonPropertyName("verified")] public bool Verified { get; set; } - - [JsonPropertyName("primary")] public bool Primary { get; set; } -} \ No newline at end of file diff --git a/examples/Kafka/Json/src/Models/PhoneNumber.cs b/examples/Kafka/Json/src/Models/PhoneNumber.cs deleted file mode 100644 index 7681265d1..000000000 --- a/examples/Kafka/Json/src/Models/PhoneNumber.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Json.Models; - -public partial class PhoneNumber -{ - [JsonPropertyName("number")] public string Number { get; set; } - - [JsonPropertyName("type")] public string Type { get; set; } -} \ No newline at end of file diff --git a/examples/Kafka/Json/src/Models/Preferences.cs b/examples/Kafka/Json/src/Models/Preferences.cs deleted file mode 100644 index 5dd84aa99..000000000 --- a/examples/Kafka/Json/src/Models/Preferences.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Json.Models; - -public partial class Preferences -{ - [JsonPropertyName("language")] public string Language { get; set; } - - [JsonPropertyName("notifications")] public string Notifications { get; set; } - - [JsonPropertyName("timezone")] public string Timezone { get; set; } -} \ No newline at end of file diff --git a/examples/Kafka/Json/src/aws-lambda-tools-defaults.json b/examples/Kafka/Json/src/aws-lambda-tools-defaults.json deleted file mode 100644 index fb3240903..000000000 --- a/examples/Kafka/Json/src/aws-lambda-tools-defaults.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "Information": [ - "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", - "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", - "dotnet lambda help", - "All the command line options for the Lambda command can be specified in this file." - ], - "profile": "", - "region": "", - "configuration": "Release", - "function-runtime": "dotnet8", - "function-memory-size": 512, - "function-timeout": 30, - "function-handler": "Json" -} \ No newline at end of file diff --git a/examples/Kafka/Json/src/kafka-json-event.json b/examples/Kafka/Json/src/kafka-json-event.json deleted file mode 100644 index 66dc2ab5a..000000000 --- a/examples/Kafka/Json/src/kafka-json-event.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "eventSource": "aws:kafka", - "eventSourceArn": "arn:aws:kafka:us-east-1:0123456789019:cluster/CustomerCluster/abcd1234-abcd-cafe-abab-9876543210ab-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": { - "customer-topic-0": [ - { - "topic": "customer-topic", - "partition": 0, - "offset": 15, - "timestamp": 1545084650987, - "timestampType": "CREATE_TIME", - "key": "dXNlcl85NzU0", - "value": "eyJwaG9uZV9udW1iZXJzIjpbeyJudW1iZXIiOiIyNDQtNDA3LTg4NzEiLCJ0eXBlIjoiV09SSyJ9XSwicHJlZmVyZW5jZXMiOnsidGltZXpvbmUiOiJlbmFibGVkIiwibGFuZ3VhZ2UiOiJkaXNhYmxlZCIsIm5vdGlmaWNhdGlvbnMiOiJkYXJrIn0sImZ1bGxfbmFtZSI6IlVzZXIgdXNlcl85NzU0IiwiYWRkcmVzcyI6eyJjb3VudHJ5IjoiVVNBIiwiY2l0eSI6IlNhbiBKb3NlIiwic3RyZWV0IjoiOTM0MCBNYWluIFN0Iiwic3RhdGUiOiJDQSIsInppcF9jb2RlIjoiMzk1OTYifSwidXNlcl9pZCI6InVzZXJfOTc1NCIsImFjY291bnRfc3RhdHVzIjoiU1VTUEVOREVEIiwiYWdlIjo1MywiZW1haWwiOnsiYWRkcmVzcyI6InVzZXJfOTc1NEBpY2xvdWQuY29tIiwidmVyaWZpZWQiOmZhbHNlLCJwcmltYXJ5Ijp0cnVlfX0=", - "headers": [ - { - "headerKey": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] - } - ] - } - ] - } -} \ No newline at end of file diff --git a/examples/Kafka/Json/src/template.yaml b/examples/Kafka/Json/src/template.yaml deleted file mode 100644 index dd4bfb9ff..000000000 --- a/examples/Kafka/Json/src/template.yaml +++ /dev/null @@ -1,27 +0,0 @@ -AWSTemplateFormatVersion: '2010-09-09' -Transform: AWS::Serverless-2016-10-31 -Description: > - kafka - - Sample SAM Template for kafka - -# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst -Globals: - Function: - Timeout: 15 - MemorySize: 512 - Runtime: dotnet8 - -Resources: - JsonDeserializationFunction: - Type: AWS::Serverless::Function - Properties: - Handler: Json - Architectures: - - x86_64 - Tracing: Active - Environment: # Powertools env vars: https://awslabs.github.io/aws-lambda-powertools-python/#environment-variables - Variables: - POWERTOOLS_SERVICE_NAME: PowertoolsHelloWorld - POWERTOOLS_LOG_LEVEL: Info - POWERTOOLS_LOGGER_CASE: PascalCase # Allowed values are: CamelCase, PascalCase and SnakeCase (Default) \ No newline at end of file diff --git a/examples/Kafka/JsonClassLibrary/src/CustomerProfile.proto b/examples/Kafka/JsonClassLibrary/src/CustomerProfile.proto deleted file mode 100644 index 9c69b1c41..000000000 --- a/examples/Kafka/JsonClassLibrary/src/CustomerProfile.proto +++ /dev/null @@ -1,49 +0,0 @@ -syntax = "proto3"; - -package com.example; - -enum PhoneType { - HOME = 0; - WORK = 1; - MOBILE = 2; -} - -enum AccountStatus { - ACTIVE = 0; - INACTIVE = 1; - SUSPENDED = 2; -} - -// EmailAddress message -message EmailAddress { - string address = 1; - bool verified = 2; - bool primary = 3; -} - -// Address message -message Address { - string street = 1; - string city = 2; - string state = 3; - string country = 4; - string zip_code = 5; -} - -// PhoneNumber message -message PhoneNumber { - string number = 1; - PhoneType type = 2; -} - -// CustomerProfile message -message CustomerProfile { - string user_id = 1; - string full_name = 2; - EmailAddress email = 3; - int32 age = 4; - Address address = 5; - repeated PhoneNumber phone_numbers = 6; - map preferences = 7; - AccountStatus account_status = 8; -} \ No newline at end of file diff --git a/examples/Kafka/JsonClassLibrary/src/Function.cs b/examples/Kafka/JsonClassLibrary/src/Function.cs deleted file mode 100644 index 98795029e..000000000 --- a/examples/Kafka/JsonClassLibrary/src/Function.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Amazon.Lambda.Core; -using AWS.Lambda.Powertools.Kafka; -using AWS.Lambda.Powertools.Kafka.Protobuf; -using AWS.Lambda.Powertools.Logging; -using Com.Example; - -// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class. -[assembly: LambdaSerializer(typeof(PowertoolsKafkaProtobufSerializer))] - -namespace ProtoBufClassLibrary; - -public class Function -{ - public string FunctionHandler(ConsumerRecords records, ILambdaContext context) - { - foreach (var record in records) - { - Logger.LogInformation("Processing messagem from topic: {topic}", record.Topic); - Logger.LogInformation("Partition: {partition}, Offset: {offset}", record.Partition, record.Offset); - Logger.LogInformation("Produced at: {timestamp}", record.Timestamp); - - foreach (var header in record.Headers.DecodedValues()) - { - Logger.LogInformation($"{header.Key}: {header.Value}"); - } - - Logger.LogInformation("Processing order for: {fullName}", record.Value.FullName); - } - - return "Processed " + records.Count() + " records"; - } -} \ No newline at end of file diff --git a/examples/Kafka/JsonClassLibrary/src/ProtoBufClassLibrary.csproj b/examples/Kafka/JsonClassLibrary/src/ProtoBufClassLibrary.csproj deleted file mode 100644 index a28e1a2f8..000000000 --- a/examples/Kafka/JsonClassLibrary/src/ProtoBufClassLibrary.csproj +++ /dev/null @@ -1,42 +0,0 @@ - - - net8.0 - enable - enable - true - Lambda - - true - - true - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - PreserveNewest - - - - - Client - Public - True - True - obj/Debug/net8.0/ - MSBuild:Compile - PreserveNewest - - - - \ No newline at end of file diff --git a/examples/Kafka/JsonClassLibrary/src/aws-lambda-tools-defaults.json b/examples/Kafka/JsonClassLibrary/src/aws-lambda-tools-defaults.json deleted file mode 100644 index d4ec43f14..000000000 --- a/examples/Kafka/JsonClassLibrary/src/aws-lambda-tools-defaults.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "Information": [ - "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", - "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", - "dotnet lambda help", - "All the command line options for the Lambda command can be specified in this file." - ], - "profile": "", - "region": "", - "configuration": "Release", - "function-architecture": "x86_64", - "function-runtime": "dotnet8", - "function-memory-size": 512, - "function-timeout": 30, - "function-handler": "ProtoBufClassLibrary::ProtoBufClassLibrary.Function::FunctionHandler" -} \ No newline at end of file diff --git a/examples/Kafka/JsonClassLibrary/src/kafka-protobuf-event.json b/examples/Kafka/JsonClassLibrary/src/kafka-protobuf-event.json deleted file mode 100644 index 6731ceb40..000000000 --- a/examples/Kafka/JsonClassLibrary/src/kafka-protobuf-event.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "eventSource": "aws:kafka", - "eventSourceArn": "arn:aws:kafka:us-east-1:0123456789019:cluster/CustomerCluster/abcd1234-abcd-cafe-abab-9876543210ab-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": { - "customer-topic-0": [ - { - "topic": "customer-topic", - "partition": 0, - "offset": 15, - "timestamp": 1545084650987, - "timestampType": "CREATE_TIME", - "key": "dXNlcl85NzU0", - "value": "Cgl1c2VyXzk3NTQSDlVzZXIgdXNlcl85NzU0GhgKFHVzZXJfOTc1NEBpY2xvdWQuY29tGAEgNSooCgw5MzQwIE1haW4gU3QSCFNhbiBKb3NlGgJDQSIDVVNBKgUzOTU5NjIQCgwyNDQtNDA3LTg4NzEQAToUCghsYW5ndWFnZRIIZGlzYWJsZWQ6FQoNbm90aWZpY2F0aW9ucxIEZGFyazoTCgh0aW1lem9uZRIHZW5hYmxlZEAC", - "headers": [ - { - "headerKey": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] - } - ] - } - ] - } -} \ No newline at end of file diff --git a/examples/Kafka/JsonClassLibrary/src/template.yaml b/examples/Kafka/JsonClassLibrary/src/template.yaml deleted file mode 100644 index 0df5feaa2..000000000 --- a/examples/Kafka/JsonClassLibrary/src/template.yaml +++ /dev/null @@ -1,27 +0,0 @@ -AWSTemplateFormatVersion: '2010-09-09' -Transform: AWS::Serverless-2016-10-31 -Description: > - kafka - - Sample SAM Template for kafka - -# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst -Globals: - Function: - Timeout: 15 - MemorySize: 512 - Runtime: dotnet8 - -Resources: - ProtobufClassLibraryDeserializationFunction: - Type: AWS::Serverless::Function - Properties: - Handler: ProtoBufClassLibrary::ProtoBufClassLibrary.Function::FunctionHandler - Architectures: - - x86_64 - Tracing: Active - Environment: # Powertools env vars: https://awslabs.github.io/aws-lambda-powertools-python/#environment-variables - Variables: - POWERTOOLS_SERVICE_NAME: PowertoolsHelloWorld - POWERTOOLS_LOG_LEVEL: Info - POWERTOOLS_LOGGER_CASE: PascalCase # Allowed values are: CamelCase, PascalCase and SnakeCase (Default) \ No newline at end of file diff --git a/examples/Kafka/Protobuf/src/CustomerProfile.proto b/examples/Kafka/Protobuf/src/CustomerProfile.proto deleted file mode 100644 index 9c69b1c41..000000000 --- a/examples/Kafka/Protobuf/src/CustomerProfile.proto +++ /dev/null @@ -1,49 +0,0 @@ -syntax = "proto3"; - -package com.example; - -enum PhoneType { - HOME = 0; - WORK = 1; - MOBILE = 2; -} - -enum AccountStatus { - ACTIVE = 0; - INACTIVE = 1; - SUSPENDED = 2; -} - -// EmailAddress message -message EmailAddress { - string address = 1; - bool verified = 2; - bool primary = 3; -} - -// Address message -message Address { - string street = 1; - string city = 2; - string state = 3; - string country = 4; - string zip_code = 5; -} - -// PhoneNumber message -message PhoneNumber { - string number = 1; - PhoneType type = 2; -} - -// CustomerProfile message -message CustomerProfile { - string user_id = 1; - string full_name = 2; - EmailAddress email = 3; - int32 age = 4; - Address address = 5; - repeated PhoneNumber phone_numbers = 6; - map preferences = 7; - AccountStatus account_status = 8; -} \ No newline at end of file diff --git a/examples/Kafka/Protobuf/src/Function.cs b/examples/Kafka/Protobuf/src/Function.cs deleted file mode 100644 index 446328696..000000000 --- a/examples/Kafka/Protobuf/src/Function.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Amazon.Lambda.Core; -using Amazon.Lambda.RuntimeSupport; -using AWS.Lambda.Powertools.Kafka; -using AWS.Lambda.Powertools.Kafka.Protobuf; -using AWS.Lambda.Powertools.Logging; -using Com.Example; - -string Handler(ConsumerRecords records, ILambdaContext context) -{ - foreach (var record in records) - { - Logger.LogInformation("Record Value: {@record}", record.Value); - } - - return "Processed " + records.Count() + " records"; -} - -await LambdaBootstrapBuilder.Create((Func, ILambdaContext, string>?)Handler, - new PowertoolsKafkaProtobufSerializer()) // Use PowertoolsKafkaProtobufSerializer for Protobuf serialization - .Build() - .RunAsync(); - diff --git a/examples/Kafka/Protobuf/src/Protobuf.csproj b/examples/Kafka/Protobuf/src/Protobuf.csproj deleted file mode 100644 index 858ccfb49..000000000 --- a/examples/Kafka/Protobuf/src/Protobuf.csproj +++ /dev/null @@ -1,44 +0,0 @@ - - - Exe - net8.0 - enable - enable - true - Lambda - - true - - true - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - PreserveNewest - - - - - Client - Public - True - - True - obj\Debug/net8.0/ - MSBuild:Compile - PreserveNewest - - - - - - \ No newline at end of file diff --git a/examples/Kafka/Protobuf/src/aws-lambda-tools-defaults.json b/examples/Kafka/Protobuf/src/aws-lambda-tools-defaults.json deleted file mode 100644 index 1a1c5de1d..000000000 --- a/examples/Kafka/Protobuf/src/aws-lambda-tools-defaults.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "Information": [ - "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", - "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", - "dotnet lambda help", - "All the command line options for the Lambda command can be specified in this file." - ], - "profile": "", - "region": "", - "configuration": "Release", - "function-runtime": "dotnet8", - "function-memory-size": 512, - "function-timeout": 30, - "function-handler": "Protobuf" -} \ No newline at end of file diff --git a/examples/Kafka/Protobuf/src/kafka-protobuf-event.json b/examples/Kafka/Protobuf/src/kafka-protobuf-event.json deleted file mode 100644 index 6731ceb40..000000000 --- a/examples/Kafka/Protobuf/src/kafka-protobuf-event.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "eventSource": "aws:kafka", - "eventSourceArn": "arn:aws:kafka:us-east-1:0123456789019:cluster/CustomerCluster/abcd1234-abcd-cafe-abab-9876543210ab-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": { - "customer-topic-0": [ - { - "topic": "customer-topic", - "partition": 0, - "offset": 15, - "timestamp": 1545084650987, - "timestampType": "CREATE_TIME", - "key": "dXNlcl85NzU0", - "value": "Cgl1c2VyXzk3NTQSDlVzZXIgdXNlcl85NzU0GhgKFHVzZXJfOTc1NEBpY2xvdWQuY29tGAEgNSooCgw5MzQwIE1haW4gU3QSCFNhbiBKb3NlGgJDQSIDVVNBKgUzOTU5NjIQCgwyNDQtNDA3LTg4NzEQAToUCghsYW5ndWFnZRIIZGlzYWJsZWQ6FQoNbm90aWZpY2F0aW9ucxIEZGFyazoTCgh0aW1lem9uZRIHZW5hYmxlZEAC", - "headers": [ - { - "headerKey": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] - } - ] - } - ] - } -} \ No newline at end of file diff --git a/examples/Kafka/Protobuf/src/template.yaml b/examples/Kafka/Protobuf/src/template.yaml deleted file mode 100644 index b8f7df6a5..000000000 --- a/examples/Kafka/Protobuf/src/template.yaml +++ /dev/null @@ -1,27 +0,0 @@ -AWSTemplateFormatVersion: '2010-09-09' -Transform: AWS::Serverless-2016-10-31 -Description: > - kafka - - Sample SAM Template for kafka - -# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst -Globals: - Function: - Timeout: 15 - MemorySize: 512 - Runtime: dotnet8 - -Resources: - ProtobufDeserializationFunction: - Type: AWS::Serverless::Function - Properties: - Handler: Protobuf - Architectures: - - x86_64 - Tracing: Active - Environment: # Powertools env vars: https://awslabs.github.io/aws-lambda-powertools-python/#environment-variables - Variables: - POWERTOOLS_SERVICE_NAME: PowertoolsHelloWorld - POWERTOOLS_LOG_LEVEL: Info - POWERTOOLS_LOGGER_CASE: PascalCase # Allowed values are: CamelCase, PascalCase and SnakeCase (Default) \ No newline at end of file diff --git a/examples/Logging/src/HelloWorld/Dockerfile b/examples/Logging/src/HelloWorld/Dockerfile index 0b025f36b..eb6d0e0b1 100644 --- a/examples/Logging/src/HelloWorld/Dockerfile +++ b/examples/Logging/src/HelloWorld/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk@sha256:c8fdd06e430de9f4ddd066b475ea350d771f341b77dd5ff4c2fafa748e3f2ef2 AS build-image +FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-image ARG FUNCTION_DIR="/build" ARG SAM_BUILD_MODE="run" @@ -15,7 +15,7 @@ RUN mkdir -p build_artifacts RUN if [ "$SAM_BUILD_MODE" = "debug" ]; then dotnet lambda package --configuration Debug; else dotnet lambda package --configuration Release; fi RUN if [ "$SAM_BUILD_MODE" = "debug" ]; then cp -r /build/bin/Debug/net6.0/publish/* /build/build_artifacts; else cp -r /build/bin/Release/net6.0/publish/* /build/build_artifacts; fi -FROM public.ecr.aws/lambda/dotnet@sha256:ec61a7f638e2a0c86d75204117cc7710bcdc70222ffc777e3fc1458287b09834 +FROM public.ecr.aws/lambda/dotnet:6 COPY --from=build-image /build/build_artifacts/ /var/task/ # Command can be overwritten by providing a different command in the template directly. diff --git a/examples/Logging/src/HelloWorld/HelloWorld.csproj b/examples/Logging/src/HelloWorld/HelloWorld.csproj index 36e8ed0df..2fa0c42da 100644 --- a/examples/Logging/src/HelloWorld/HelloWorld.csproj +++ b/examples/Logging/src/HelloWorld/HelloWorld.csproj @@ -5,10 +5,10 @@ enable - + - - + + diff --git a/examples/Logging/test/HelloWorld.Test/HelloWorld.Tests.csproj b/examples/Logging/test/HelloWorld.Test/HelloWorld.Tests.csproj index 14917e4cb..446d7f284 100644 --- a/examples/Logging/test/HelloWorld.Test/HelloWorld.Tests.csproj +++ b/examples/Logging/test/HelloWorld.Test/HelloWorld.Tests.csproj @@ -3,9 +3,9 @@ net6.0;net8.0 - + - + diff --git a/examples/Metrics/src/HelloWorld/Dockerfile b/examples/Metrics/src/HelloWorld/Dockerfile index 932a2740c..8cf3b466d 100644 --- a/examples/Metrics/src/HelloWorld/Dockerfile +++ b/examples/Metrics/src/HelloWorld/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk@sha256:c8fdd06e430de9f4ddd066b475ea350d771f341b77dd5ff4c2fafa748e3f2ef2 AS build-image +FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-image ARG FUNCTION_DIR="/build" ARG SAM_BUILD_MODE="run" @@ -16,7 +16,7 @@ RUN mkdir -p build_artifacts RUN if [ "$SAM_BUILD_MODE" = "debug" ]; then dotnet lambda package --configuration Debug; else dotnet lambda package --configuration Release; fi RUN if [ "$SAM_BUILD_MODE" = "debug" ]; then cp -r /build/bin/Debug/net6.0/publish/* /build/build_artifacts; else cp -r /build/bin/Release/net6.0/publish/* /build/build_artifacts; fi -FROM public.ecr.aws/lambda/dotnet@sha256:ec61a7f638e2a0c86d75204117cc7710bcdc70222ffc777e3fc1458287b09834 +FROM public.ecr.aws/lambda/dotnet:6 COPY --from=build-image /build/build_artifacts/ /var/task/ # Command can be overwritten by providing a different command in the template directly. diff --git a/examples/Metrics/src/HelloWorld/Function.cs b/examples/Metrics/src/HelloWorld/Function.cs index 9c797f6e5..f99cf3955 100644 --- a/examples/Metrics/src/HelloWorld/Function.cs +++ b/examples/Metrics/src/HelloWorld/Function.cs @@ -82,11 +82,11 @@ public async Task FunctionHandler(APIGatewayProxyReques // Add Metric to capture the amount of time Metrics.PushSingleMetric( - name: "CallingIP", + metricName: "CallingIP", value: 1, unit: MetricUnit.Count, service: "lambda-powertools-metrics-example", - dimensions: new Dictionary + defaultDimensions: new Dictionary { { "Metric Type", "Single" } }); @@ -104,11 +104,11 @@ public async Task FunctionHandler(APIGatewayProxyReques try { Metrics.PushSingleMetric( - name: "RecordsSaved", + metricName: "RecordsSaved", value: 1, unit: MetricUnit.Count, service: "lambda-powertools-metrics-example", - dimensions: new Dictionary + defaultDimensions: new Dictionary { { "Metric Type", "Single" } }); diff --git a/examples/Metrics/src/HelloWorld/HelloWorld.csproj b/examples/Metrics/src/HelloWorld/HelloWorld.csproj index dc82111b9..14d90df29 100644 --- a/examples/Metrics/src/HelloWorld/HelloWorld.csproj +++ b/examples/Metrics/src/HelloWorld/HelloWorld.csproj @@ -5,11 +5,11 @@ enable - + - - - + + + diff --git a/examples/Metrics/test/HelloWorld.Test/HelloWorld.Tests.csproj b/examples/Metrics/test/HelloWorld.Test/HelloWorld.Tests.csproj index 14917e4cb..446d7f284 100644 --- a/examples/Metrics/test/HelloWorld.Test/HelloWorld.Tests.csproj +++ b/examples/Metrics/test/HelloWorld.Test/HelloWorld.Tests.csproj @@ -3,9 +3,9 @@ net6.0;net8.0 - + - + diff --git a/examples/Parameters/cfn/HelloWorld.Cfn/HelloWorld.Cfn.csproj b/examples/Parameters/cfn/HelloWorld.Cfn/HelloWorld.Cfn.csproj index cf97597d8..713914f28 100644 --- a/examples/Parameters/cfn/HelloWorld.Cfn/HelloWorld.Cfn.csproj +++ b/examples/Parameters/cfn/HelloWorld.Cfn/HelloWorld.Cfn.csproj @@ -6,8 +6,8 @@ HelloWorld.Cfn - - + + diff --git a/examples/Parameters/src/HelloWorld/HelloWorld.csproj b/examples/Parameters/src/HelloWorld/HelloWorld.csproj index 99b13a66e..6b29f4253 100644 --- a/examples/Parameters/src/HelloWorld/HelloWorld.csproj +++ b/examples/Parameters/src/HelloWorld/HelloWorld.csproj @@ -5,9 +5,9 @@ enable - + - + diff --git a/examples/Parameters/test/HelloWorld.Test/HelloWorld.Tests.csproj b/examples/Parameters/test/HelloWorld.Test/HelloWorld.Tests.csproj index 589c8306c..9b17d57f0 100644 --- a/examples/Parameters/test/HelloWorld.Test/HelloWorld.Tests.csproj +++ b/examples/Parameters/test/HelloWorld.Test/HelloWorld.Tests.csproj @@ -3,9 +3,9 @@ net6.0;net8.0 - + - + diff --git a/examples/ServerlessApi/src/LambdaPowertoolsAPI/LambdaPowertoolsAPI.csproj b/examples/ServerlessApi/src/LambdaPowertoolsAPI/LambdaPowertoolsAPI.csproj index fd91c9de9..f529937d1 100644 --- a/examples/ServerlessApi/src/LambdaPowertoolsAPI/LambdaPowertoolsAPI.csproj +++ b/examples/ServerlessApi/src/LambdaPowertoolsAPI/LambdaPowertoolsAPI.csproj @@ -13,8 +13,8 @@ - - - + + + diff --git a/examples/ServerlessApi/test/LambdaPowertoolsAPI.Tests/LambdaPowertoolsAPI.Tests.csproj b/examples/ServerlessApi/test/LambdaPowertoolsAPI.Tests/LambdaPowertoolsAPI.Tests.csproj index edfda0a56..d9cdaef49 100644 --- a/examples/ServerlessApi/test/LambdaPowertoolsAPI.Tests/LambdaPowertoolsAPI.Tests.csproj +++ b/examples/ServerlessApi/test/LambdaPowertoolsAPI.Tests/LambdaPowertoolsAPI.Tests.csproj @@ -16,7 +16,7 @@ - + diff --git a/examples/Tracing/src/HelloWorld/Dockerfile b/examples/Tracing/src/HelloWorld/Dockerfile index 932a2740c..8cf3b466d 100644 --- a/examples/Tracing/src/HelloWorld/Dockerfile +++ b/examples/Tracing/src/HelloWorld/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk@sha256:c8fdd06e430de9f4ddd066b475ea350d771f341b77dd5ff4c2fafa748e3f2ef2 AS build-image +FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-image ARG FUNCTION_DIR="/build" ARG SAM_BUILD_MODE="run" @@ -16,7 +16,7 @@ RUN mkdir -p build_artifacts RUN if [ "$SAM_BUILD_MODE" = "debug" ]; then dotnet lambda package --configuration Debug; else dotnet lambda package --configuration Release; fi RUN if [ "$SAM_BUILD_MODE" = "debug" ]; then cp -r /build/bin/Debug/net6.0/publish/* /build/build_artifacts; else cp -r /build/bin/Release/net6.0/publish/* /build/build_artifacts; fi -FROM public.ecr.aws/lambda/dotnet@sha256:ec61a7f638e2a0c86d75204117cc7710bcdc70222ffc777e3fc1458287b09834 +FROM public.ecr.aws/lambda/dotnet:6 COPY --from=build-image /build/build_artifacts/ /var/task/ # Command can be overwritten by providing a different command in the template directly. diff --git a/examples/Tracing/src/HelloWorld/HelloWorld.csproj b/examples/Tracing/src/HelloWorld/HelloWorld.csproj index f6c4873c8..0f61b8f45 100644 --- a/examples/Tracing/src/HelloWorld/HelloWorld.csproj +++ b/examples/Tracing/src/HelloWorld/HelloWorld.csproj @@ -5,11 +5,11 @@ enable - + - - - + + + diff --git a/examples/Tracing/test/HelloWorld.Test/HelloWorld.Tests.csproj b/examples/Tracing/test/HelloWorld.Test/HelloWorld.Tests.csproj index 14917e4cb..446d7f284 100644 --- a/examples/Tracing/test/HelloWorld.Test/HelloWorld.Tests.csproj +++ b/examples/Tracing/test/HelloWorld.Test/HelloWorld.Tests.csproj @@ -3,9 +3,9 @@ net6.0;net8.0 - + - + diff --git a/examples/examples.sln b/examples/examples.sln index 6b9fa877a..10ec48509 100644 --- a/examples/examples.sln +++ b/examples/examples.sln @@ -109,16 +109,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AOT_Logging", "AOT\AOT_Logg EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AOT_Logging.Tests", "AOT\AOT_Logging\test\AOT_Logging.Tests\AOT_Logging.Tests.csproj", "{FC010A0E-64A9-4440-97FE-DEDA8CEE0BE5}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Kafka", "Kafka", "{71027B81-CA39-498C-9A50-ADDAFA2AC2F5}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Json", "Kafka\Json\src\Json.csproj", "{58EC305E-353A-4996-A541-3CF7FC0EDD80}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Protobuf", "Kafka\Protobuf\src\Protobuf.csproj", "{853F6FE9-1762-4BA3-BAF4-2FCD605B81CF}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avro", "Kafka\Avro\src\Avro.csproj", "{B03F22B2-315C-429B-9CC0-C15BE94CBF77}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtoBufClassLibrary", "Kafka\JsonClassLibrary\src\ProtoBufClassLibrary.csproj", "{B6B3136D-B739-4917-AD3D-30F19FE12D3F}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -212,22 +202,6 @@ Global {FC010A0E-64A9-4440-97FE-DEDA8CEE0BE5}.Debug|Any CPU.Build.0 = Debug|Any CPU {FC010A0E-64A9-4440-97FE-DEDA8CEE0BE5}.Release|Any CPU.ActiveCfg = Release|Any CPU {FC010A0E-64A9-4440-97FE-DEDA8CEE0BE5}.Release|Any CPU.Build.0 = Release|Any CPU - {58EC305E-353A-4996-A541-3CF7FC0EDD80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {58EC305E-353A-4996-A541-3CF7FC0EDD80}.Debug|Any CPU.Build.0 = Debug|Any CPU - {58EC305E-353A-4996-A541-3CF7FC0EDD80}.Release|Any CPU.ActiveCfg = Release|Any CPU - {58EC305E-353A-4996-A541-3CF7FC0EDD80}.Release|Any CPU.Build.0 = Release|Any CPU - {853F6FE9-1762-4BA3-BAF4-2FCD605B81CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {853F6FE9-1762-4BA3-BAF4-2FCD605B81CF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {853F6FE9-1762-4BA3-BAF4-2FCD605B81CF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {853F6FE9-1762-4BA3-BAF4-2FCD605B81CF}.Release|Any CPU.Build.0 = Release|Any CPU - {B03F22B2-315C-429B-9CC0-C15BE94CBF77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B03F22B2-315C-429B-9CC0-C15BE94CBF77}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B03F22B2-315C-429B-9CC0-C15BE94CBF77}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B03F22B2-315C-429B-9CC0-C15BE94CBF77}.Release|Any CPU.Build.0 = Release|Any CPU - {B6B3136D-B739-4917-AD3D-30F19FE12D3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B6B3136D-B739-4917-AD3D-30F19FE12D3F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B6B3136D-B739-4917-AD3D-30F19FE12D3F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B6B3136D-B739-4917-AD3D-30F19FE12D3F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {0CC66DBC-C1DF-4AF6-8EEB-FFED6C578BF4} = {526F1EF7-5A9C-4BFF-ABAE-75992ACD8F78} @@ -275,9 +249,5 @@ Global {343CF6B9-C006-43F8-924C-BF5BF5B6D051} = {FE1CAA26-87E9-4B71-800E-81D2997A7B53} {FC02CF45-DE15-4413-958A-D86808B99146} = {FEE72EAB-494F-403B-A75A-825E713C3D43} {FC010A0E-64A9-4440-97FE-DEDA8CEE0BE5} = {F3480212-EE7F-46FE-9ED5-24ACAB5B681D} - {58EC305E-353A-4996-A541-3CF7FC0EDD80} = {71027B81-CA39-498C-9A50-ADDAFA2AC2F5} - {853F6FE9-1762-4BA3-BAF4-2FCD605B81CF} = {71027B81-CA39-498C-9A50-ADDAFA2AC2F5} - {B03F22B2-315C-429B-9CC0-C15BE94CBF77} = {71027B81-CA39-498C-9A50-ADDAFA2AC2F5} - {B6B3136D-B739-4917-AD3D-30F19FE12D3F} = {71027B81-CA39-498C-9A50-ADDAFA2AC2F5} EndGlobalSection EndGlobal diff --git a/libraries/AWS.Lambda.Powertools.sln b/libraries/AWS.Lambda.Powertools.sln index 325c683e0..72aea9672 100644 --- a/libraries/AWS.Lambda.Powertools.sln +++ b/libraries/AWS.Lambda.Powertools.sln @@ -97,32 +97,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AOT-FunctionHandlerTest", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AOT-FunctionMethodAttributeTest", "tests\e2e\functions\idempotency\AOT-Function\src\AOT-FunctionMethodAttributeTest\AOT-FunctionMethodAttributeTest.csproj", "{CC8CFF43-DC72-464C-A42D-55E023DE8500}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.Lambda.Powertools.Metrics.AspNetCore", "src\AWS.Lambda.Powertools.Metrics.AspNetCore\AWS.Lambda.Powertools.Metrics.AspNetCore.csproj", "{A2AD98B1-2BED-4864-B573-77BE7B52FED2}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.Lambda.Powertools.Metrics.AspNetCore.Tests", "tests\AWS.Lambda.Powertools.Metrics.AspNetCore.Tests\AWS.Lambda.Powertools.Metrics.AspNetCore.Tests.csproj", "{F8F80477-1EAD-4C5C-A329-CBC0A60C7CAB}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Metrics", "Metrics", "{A566F2D7-F8FE-466A-8306-85F266B7E656}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AOT-Function-ILogger", "tests\e2e\functions\core\logging\AOT-Function-ILogger\src\AOT-Function-ILogger\AOT-Function-ILogger.csproj", "{7FC6DD65-0352-4139-8D08-B25C0A0403E3}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.Lambda.Powertools.EventHandler.Tests", "tests\AWS.Lambda.Powertools.EventHandler.Tests\AWS.Lambda.Powertools.EventHandler.Tests.csproj", "{61374D8E-F77C-4A31-AE07-35DAF1847369}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.Lambda.Powertools.EventHandler", "src\AWS.Lambda.Powertools.EventHandler\AWS.Lambda.Powertools.EventHandler.csproj", "{F4B8D5AF-D3CA-4910-A14D-E5BAEF0FD1DE}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction", "src\AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction\AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.csproj", "{281F7EB5-ACE5-458F-BC88-46A8899DF3BA}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore", "src\AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore\AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore.csproj", "{8A22F22E-D10A-4897-A89A-DC76C267F6BB}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.Lambda.Powertools.Kafka", "src\AWS.Lambda.Powertools.Kafka\AWS.Lambda.Powertools.Kafka.csproj", "{5B0DDE6F-ED16-452F-90D3-F0B6086D51B3}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.Lambda.Powertools.Kafka.Tests", "tests\AWS.Lambda.Powertools.Kafka.Tests\AWS.Lambda.Powertools.Kafka.Tests.csproj", "{FDBDB9F8-B3E2-4ACA-9FC6-E12FF3D95645}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.Lambda.Powertools.Kafka.Avro", "src\AWS.Lambda.Powertools.Kafka.Avro\AWS.Lambda.Powertools.Kafka.Avro.csproj", "{25F0929B-2E04-4ED6-A0ED-5379A0A755B0}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.Lambda.Powertools.Kafka.Json", "src\AWS.Lambda.Powertools.Kafka.Json\AWS.Lambda.Powertools.Kafka.Json.csproj", "{9E2B8160-3E76-4B33-86AB-DE35A5FCDB1E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AWS.Lambda.Powertools.Kafka.Protobuf", "src\AWS.Lambda.Powertools.Kafka.Protobuf\AWS.Lambda.Powertools.Kafka.Protobuf.csproj", "{B640DB80-C982-407B-A2EC-CD29AC77DDB8}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -544,150 +518,6 @@ Global {CC8CFF43-DC72-464C-A42D-55E023DE8500}.Release|x64.Build.0 = Release|Any CPU {CC8CFF43-DC72-464C-A42D-55E023DE8500}.Release|x86.ActiveCfg = Release|Any CPU {CC8CFF43-DC72-464C-A42D-55E023DE8500}.Release|x86.Build.0 = Release|Any CPU - {A2AD98B1-2BED-4864-B573-77BE7B52FED2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A2AD98B1-2BED-4864-B573-77BE7B52FED2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A2AD98B1-2BED-4864-B573-77BE7B52FED2}.Debug|x64.ActiveCfg = Debug|Any CPU - {A2AD98B1-2BED-4864-B573-77BE7B52FED2}.Debug|x64.Build.0 = Debug|Any CPU - {A2AD98B1-2BED-4864-B573-77BE7B52FED2}.Debug|x86.ActiveCfg = Debug|Any CPU - {A2AD98B1-2BED-4864-B573-77BE7B52FED2}.Debug|x86.Build.0 = Debug|Any CPU - {A2AD98B1-2BED-4864-B573-77BE7B52FED2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A2AD98B1-2BED-4864-B573-77BE7B52FED2}.Release|Any CPU.Build.0 = Release|Any CPU - {A2AD98B1-2BED-4864-B573-77BE7B52FED2}.Release|x64.ActiveCfg = Release|Any CPU - {A2AD98B1-2BED-4864-B573-77BE7B52FED2}.Release|x64.Build.0 = Release|Any CPU - {A2AD98B1-2BED-4864-B573-77BE7B52FED2}.Release|x86.ActiveCfg = Release|Any CPU - {A2AD98B1-2BED-4864-B573-77BE7B52FED2}.Release|x86.Build.0 = Release|Any CPU - {F8F80477-1EAD-4C5C-A329-CBC0A60C7CAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F8F80477-1EAD-4C5C-A329-CBC0A60C7CAB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F8F80477-1EAD-4C5C-A329-CBC0A60C7CAB}.Debug|x64.ActiveCfg = Debug|Any CPU - {F8F80477-1EAD-4C5C-A329-CBC0A60C7CAB}.Debug|x64.Build.0 = Debug|Any CPU - {F8F80477-1EAD-4C5C-A329-CBC0A60C7CAB}.Debug|x86.ActiveCfg = Debug|Any CPU - {F8F80477-1EAD-4C5C-A329-CBC0A60C7CAB}.Debug|x86.Build.0 = Debug|Any CPU - {F8F80477-1EAD-4C5C-A329-CBC0A60C7CAB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F8F80477-1EAD-4C5C-A329-CBC0A60C7CAB}.Release|Any CPU.Build.0 = Release|Any CPU - {F8F80477-1EAD-4C5C-A329-CBC0A60C7CAB}.Release|x64.ActiveCfg = Release|Any CPU - {F8F80477-1EAD-4C5C-A329-CBC0A60C7CAB}.Release|x64.Build.0 = Release|Any CPU - {F8F80477-1EAD-4C5C-A329-CBC0A60C7CAB}.Release|x86.ActiveCfg = Release|Any CPU - {F8F80477-1EAD-4C5C-A329-CBC0A60C7CAB}.Release|x86.Build.0 = Release|Any CPU - {7FC6DD65-0352-4139-8D08-B25C0A0403E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7FC6DD65-0352-4139-8D08-B25C0A0403E3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7FC6DD65-0352-4139-8D08-B25C0A0403E3}.Debug|x64.ActiveCfg = Debug|Any CPU - {7FC6DD65-0352-4139-8D08-B25C0A0403E3}.Debug|x64.Build.0 = Debug|Any CPU - {7FC6DD65-0352-4139-8D08-B25C0A0403E3}.Debug|x86.ActiveCfg = Debug|Any CPU - {7FC6DD65-0352-4139-8D08-B25C0A0403E3}.Debug|x86.Build.0 = Debug|Any CPU - {7FC6DD65-0352-4139-8D08-B25C0A0403E3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7FC6DD65-0352-4139-8D08-B25C0A0403E3}.Release|Any CPU.Build.0 = Release|Any CPU - {7FC6DD65-0352-4139-8D08-B25C0A0403E3}.Release|x64.ActiveCfg = Release|Any CPU - {7FC6DD65-0352-4139-8D08-B25C0A0403E3}.Release|x64.Build.0 = Release|Any CPU - {7FC6DD65-0352-4139-8D08-B25C0A0403E3}.Release|x86.ActiveCfg = Release|Any CPU - {7FC6DD65-0352-4139-8D08-B25C0A0403E3}.Release|x86.Build.0 = Release|Any CPU - {61374D8E-F77C-4A31-AE07-35DAF1847369}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {61374D8E-F77C-4A31-AE07-35DAF1847369}.Debug|Any CPU.Build.0 = Debug|Any CPU - {61374D8E-F77C-4A31-AE07-35DAF1847369}.Debug|x64.ActiveCfg = Debug|Any CPU - {61374D8E-F77C-4A31-AE07-35DAF1847369}.Debug|x64.Build.0 = Debug|Any CPU - {61374D8E-F77C-4A31-AE07-35DAF1847369}.Debug|x86.ActiveCfg = Debug|Any CPU - {61374D8E-F77C-4A31-AE07-35DAF1847369}.Debug|x86.Build.0 = Debug|Any CPU - {61374D8E-F77C-4A31-AE07-35DAF1847369}.Release|Any CPU.ActiveCfg = Release|Any CPU - {61374D8E-F77C-4A31-AE07-35DAF1847369}.Release|Any CPU.Build.0 = Release|Any CPU - {61374D8E-F77C-4A31-AE07-35DAF1847369}.Release|x64.ActiveCfg = Release|Any CPU - {61374D8E-F77C-4A31-AE07-35DAF1847369}.Release|x64.Build.0 = Release|Any CPU - {61374D8E-F77C-4A31-AE07-35DAF1847369}.Release|x86.ActiveCfg = Release|Any CPU - {61374D8E-F77C-4A31-AE07-35DAF1847369}.Release|x86.Build.0 = Release|Any CPU - {F4B8D5AF-D3CA-4910-A14D-E5BAEF0FD1DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F4B8D5AF-D3CA-4910-A14D-E5BAEF0FD1DE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F4B8D5AF-D3CA-4910-A14D-E5BAEF0FD1DE}.Debug|x64.ActiveCfg = Debug|Any CPU - {F4B8D5AF-D3CA-4910-A14D-E5BAEF0FD1DE}.Debug|x64.Build.0 = Debug|Any CPU - {F4B8D5AF-D3CA-4910-A14D-E5BAEF0FD1DE}.Debug|x86.ActiveCfg = Debug|Any CPU - {F4B8D5AF-D3CA-4910-A14D-E5BAEF0FD1DE}.Debug|x86.Build.0 = Debug|Any CPU - {F4B8D5AF-D3CA-4910-A14D-E5BAEF0FD1DE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F4B8D5AF-D3CA-4910-A14D-E5BAEF0FD1DE}.Release|Any CPU.Build.0 = Release|Any CPU - {F4B8D5AF-D3CA-4910-A14D-E5BAEF0FD1DE}.Release|x64.ActiveCfg = Release|Any CPU - {F4B8D5AF-D3CA-4910-A14D-E5BAEF0FD1DE}.Release|x64.Build.0 = Release|Any CPU - {F4B8D5AF-D3CA-4910-A14D-E5BAEF0FD1DE}.Release|x86.ActiveCfg = Release|Any CPU - {F4B8D5AF-D3CA-4910-A14D-E5BAEF0FD1DE}.Release|x86.Build.0 = Release|Any CPU - {281F7EB5-ACE5-458F-BC88-46A8899DF3BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {281F7EB5-ACE5-458F-BC88-46A8899DF3BA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {281F7EB5-ACE5-458F-BC88-46A8899DF3BA}.Debug|x64.ActiveCfg = Debug|Any CPU - {281F7EB5-ACE5-458F-BC88-46A8899DF3BA}.Debug|x64.Build.0 = Debug|Any CPU - {281F7EB5-ACE5-458F-BC88-46A8899DF3BA}.Debug|x86.ActiveCfg = Debug|Any CPU - {281F7EB5-ACE5-458F-BC88-46A8899DF3BA}.Debug|x86.Build.0 = Debug|Any CPU - {281F7EB5-ACE5-458F-BC88-46A8899DF3BA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {281F7EB5-ACE5-458F-BC88-46A8899DF3BA}.Release|Any CPU.Build.0 = Release|Any CPU - {281F7EB5-ACE5-458F-BC88-46A8899DF3BA}.Release|x64.ActiveCfg = Release|Any CPU - {281F7EB5-ACE5-458F-BC88-46A8899DF3BA}.Release|x64.Build.0 = Release|Any CPU - {281F7EB5-ACE5-458F-BC88-46A8899DF3BA}.Release|x86.ActiveCfg = Release|Any CPU - {281F7EB5-ACE5-458F-BC88-46A8899DF3BA}.Release|x86.Build.0 = Release|Any CPU - {8A22F22E-D10A-4897-A89A-DC76C267F6BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8A22F22E-D10A-4897-A89A-DC76C267F6BB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8A22F22E-D10A-4897-A89A-DC76C267F6BB}.Debug|x64.ActiveCfg = Debug|Any CPU - {8A22F22E-D10A-4897-A89A-DC76C267F6BB}.Debug|x64.Build.0 = Debug|Any CPU - {8A22F22E-D10A-4897-A89A-DC76C267F6BB}.Debug|x86.ActiveCfg = Debug|Any CPU - {8A22F22E-D10A-4897-A89A-DC76C267F6BB}.Debug|x86.Build.0 = Debug|Any CPU - {8A22F22E-D10A-4897-A89A-DC76C267F6BB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8A22F22E-D10A-4897-A89A-DC76C267F6BB}.Release|Any CPU.Build.0 = Release|Any CPU - {8A22F22E-D10A-4897-A89A-DC76C267F6BB}.Release|x64.ActiveCfg = Release|Any CPU - {8A22F22E-D10A-4897-A89A-DC76C267F6BB}.Release|x64.Build.0 = Release|Any CPU - {8A22F22E-D10A-4897-A89A-DC76C267F6BB}.Release|x86.ActiveCfg = Release|Any CPU - {8A22F22E-D10A-4897-A89A-DC76C267F6BB}.Release|x86.Build.0 = Release|Any CPU - {5B0DDE6F-ED16-452F-90D3-F0B6086D51B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5B0DDE6F-ED16-452F-90D3-F0B6086D51B3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5B0DDE6F-ED16-452F-90D3-F0B6086D51B3}.Debug|x64.ActiveCfg = Debug|Any CPU - {5B0DDE6F-ED16-452F-90D3-F0B6086D51B3}.Debug|x64.Build.0 = Debug|Any CPU - {5B0DDE6F-ED16-452F-90D3-F0B6086D51B3}.Debug|x86.ActiveCfg = Debug|Any CPU - {5B0DDE6F-ED16-452F-90D3-F0B6086D51B3}.Debug|x86.Build.0 = Debug|Any CPU - {5B0DDE6F-ED16-452F-90D3-F0B6086D51B3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5B0DDE6F-ED16-452F-90D3-F0B6086D51B3}.Release|Any CPU.Build.0 = Release|Any CPU - {5B0DDE6F-ED16-452F-90D3-F0B6086D51B3}.Release|x64.ActiveCfg = Release|Any CPU - {5B0DDE6F-ED16-452F-90D3-F0B6086D51B3}.Release|x64.Build.0 = Release|Any CPU - {5B0DDE6F-ED16-452F-90D3-F0B6086D51B3}.Release|x86.ActiveCfg = Release|Any CPU - {5B0DDE6F-ED16-452F-90D3-F0B6086D51B3}.Release|x86.Build.0 = Release|Any CPU - {FDBDB9F8-B3E2-4ACA-9FC6-E12FF3D95645}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FDBDB9F8-B3E2-4ACA-9FC6-E12FF3D95645}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FDBDB9F8-B3E2-4ACA-9FC6-E12FF3D95645}.Debug|x64.ActiveCfg = Debug|Any CPU - {FDBDB9F8-B3E2-4ACA-9FC6-E12FF3D95645}.Debug|x64.Build.0 = Debug|Any CPU - {FDBDB9F8-B3E2-4ACA-9FC6-E12FF3D95645}.Debug|x86.ActiveCfg = Debug|Any CPU - {FDBDB9F8-B3E2-4ACA-9FC6-E12FF3D95645}.Debug|x86.Build.0 = Debug|Any CPU - {FDBDB9F8-B3E2-4ACA-9FC6-E12FF3D95645}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FDBDB9F8-B3E2-4ACA-9FC6-E12FF3D95645}.Release|Any CPU.Build.0 = Release|Any CPU - {FDBDB9F8-B3E2-4ACA-9FC6-E12FF3D95645}.Release|x64.ActiveCfg = Release|Any CPU - {FDBDB9F8-B3E2-4ACA-9FC6-E12FF3D95645}.Release|x64.Build.0 = Release|Any CPU - {FDBDB9F8-B3E2-4ACA-9FC6-E12FF3D95645}.Release|x86.ActiveCfg = Release|Any CPU - {FDBDB9F8-B3E2-4ACA-9FC6-E12FF3D95645}.Release|x86.Build.0 = Release|Any CPU - {25F0929B-2E04-4ED6-A0ED-5379A0A755B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {25F0929B-2E04-4ED6-A0ED-5379A0A755B0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {25F0929B-2E04-4ED6-A0ED-5379A0A755B0}.Debug|x64.ActiveCfg = Debug|Any CPU - {25F0929B-2E04-4ED6-A0ED-5379A0A755B0}.Debug|x64.Build.0 = Debug|Any CPU - {25F0929B-2E04-4ED6-A0ED-5379A0A755B0}.Debug|x86.ActiveCfg = Debug|Any CPU - {25F0929B-2E04-4ED6-A0ED-5379A0A755B0}.Debug|x86.Build.0 = Debug|Any CPU - {25F0929B-2E04-4ED6-A0ED-5379A0A755B0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {25F0929B-2E04-4ED6-A0ED-5379A0A755B0}.Release|Any CPU.Build.0 = Release|Any CPU - {25F0929B-2E04-4ED6-A0ED-5379A0A755B0}.Release|x64.ActiveCfg = Release|Any CPU - {25F0929B-2E04-4ED6-A0ED-5379A0A755B0}.Release|x64.Build.0 = Release|Any CPU - {25F0929B-2E04-4ED6-A0ED-5379A0A755B0}.Release|x86.ActiveCfg = Release|Any CPU - {25F0929B-2E04-4ED6-A0ED-5379A0A755B0}.Release|x86.Build.0 = Release|Any CPU - {9E2B8160-3E76-4B33-86AB-DE35A5FCDB1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9E2B8160-3E76-4B33-86AB-DE35A5FCDB1E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9E2B8160-3E76-4B33-86AB-DE35A5FCDB1E}.Debug|x64.ActiveCfg = Debug|Any CPU - {9E2B8160-3E76-4B33-86AB-DE35A5FCDB1E}.Debug|x64.Build.0 = Debug|Any CPU - {9E2B8160-3E76-4B33-86AB-DE35A5FCDB1E}.Debug|x86.ActiveCfg = Debug|Any CPU - {9E2B8160-3E76-4B33-86AB-DE35A5FCDB1E}.Debug|x86.Build.0 = Debug|Any CPU - {9E2B8160-3E76-4B33-86AB-DE35A5FCDB1E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9E2B8160-3E76-4B33-86AB-DE35A5FCDB1E}.Release|Any CPU.Build.0 = Release|Any CPU - {9E2B8160-3E76-4B33-86AB-DE35A5FCDB1E}.Release|x64.ActiveCfg = Release|Any CPU - {9E2B8160-3E76-4B33-86AB-DE35A5FCDB1E}.Release|x64.Build.0 = Release|Any CPU - {9E2B8160-3E76-4B33-86AB-DE35A5FCDB1E}.Release|x86.ActiveCfg = Release|Any CPU - {9E2B8160-3E76-4B33-86AB-DE35A5FCDB1E}.Release|x86.Build.0 = Release|Any CPU - {B640DB80-C982-407B-A2EC-CD29AC77DDB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B640DB80-C982-407B-A2EC-CD29AC77DDB8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B640DB80-C982-407B-A2EC-CD29AC77DDB8}.Debug|x64.ActiveCfg = Debug|Any CPU - {B640DB80-C982-407B-A2EC-CD29AC77DDB8}.Debug|x64.Build.0 = Debug|Any CPU - {B640DB80-C982-407B-A2EC-CD29AC77DDB8}.Debug|x86.ActiveCfg = Debug|Any CPU - {B640DB80-C982-407B-A2EC-CD29AC77DDB8}.Debug|x86.Build.0 = Debug|Any CPU - {B640DB80-C982-407B-A2EC-CD29AC77DDB8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B640DB80-C982-407B-A2EC-CD29AC77DDB8}.Release|Any CPU.Build.0 = Release|Any CPU - {B640DB80-C982-407B-A2EC-CD29AC77DDB8}.Release|x64.ActiveCfg = Release|Any CPU - {B640DB80-C982-407B-A2EC-CD29AC77DDB8}.Release|x64.Build.0 = Release|Any CPU - {B640DB80-C982-407B-A2EC-CD29AC77DDB8}.Release|x86.ActiveCfg = Release|Any CPU - {B640DB80-C982-407B-A2EC-CD29AC77DDB8}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution @@ -696,6 +526,7 @@ Global {3BA6251D-DE4E-4547-AAA9-25F4BA04C636} = {73C9B1E5-3893-47E8-B373-17E5F5D7E6F5} {1A3AC28C-3AEE-40FE-B229-9E38BB609547} = {73C9B1E5-3893-47E8-B373-17E5F5D7E6F5} {B68A0D0A-4785-48CB-864F-29E3A8ACA526} = {1CFF5568-8486-475F-81F6-06105C437528} + {A422C742-2CF9-409D-BDAE-15825AB62113} = {1CFF5568-8486-475F-81F6-06105C437528} {4EC48E6A-45B5-4E25-ABBD-C23FE2BD6E1E} = {1CFF5568-8486-475F-81F6-06105C437528} {A040AED5-BBB8-4BFA-B2A5-BBD82817B8A5} = {1CFF5568-8486-475F-81F6-06105C437528} {1ECB31E8-2EF0-41E2-8C71-CB9876D207F0} = {73C9B1E5-3893-47E8-B373-17E5F5D7E6F5} @@ -732,19 +563,5 @@ Global {ACA789EA-BD38-490B-A7F8-6A3A86985025} = {FB2C7DA3-6FCE-429D-86F9-5775D0231EC6} {E71C48D2-AD56-4177-BBD7-6BB859A40C92} = {FB2C7DA3-6FCE-429D-86F9-5775D0231EC6} {CC8CFF43-DC72-464C-A42D-55E023DE8500} = {FB2C7DA3-6FCE-429D-86F9-5775D0231EC6} - {A2AD98B1-2BED-4864-B573-77BE7B52FED2} = {73C9B1E5-3893-47E8-B373-17E5F5D7E6F5} - {A566F2D7-F8FE-466A-8306-85F266B7E656} = {1CFF5568-8486-475F-81F6-06105C437528} - {F8F80477-1EAD-4C5C-A329-CBC0A60C7CAB} = {A566F2D7-F8FE-466A-8306-85F266B7E656} - {A422C742-2CF9-409D-BDAE-15825AB62113} = {A566F2D7-F8FE-466A-8306-85F266B7E656} - {7FC6DD65-0352-4139-8D08-B25C0A0403E3} = {4EAB66F9-C9CB-4E8A-BEE6-A14CD7FDE02F} - {61374D8E-F77C-4A31-AE07-35DAF1847369} = {1CFF5568-8486-475F-81F6-06105C437528} - {F4B8D5AF-D3CA-4910-A14D-E5BAEF0FD1DE} = {73C9B1E5-3893-47E8-B373-17E5F5D7E6F5} - {281F7EB5-ACE5-458F-BC88-46A8899DF3BA} = {73C9B1E5-3893-47E8-B373-17E5F5D7E6F5} - {8A22F22E-D10A-4897-A89A-DC76C267F6BB} = {73C9B1E5-3893-47E8-B373-17E5F5D7E6F5} - {5B0DDE6F-ED16-452F-90D3-F0B6086D51B3} = {73C9B1E5-3893-47E8-B373-17E5F5D7E6F5} - {FDBDB9F8-B3E2-4ACA-9FC6-E12FF3D95645} = {1CFF5568-8486-475F-81F6-06105C437528} - {25F0929B-2E04-4ED6-A0ED-5379A0A755B0} = {73C9B1E5-3893-47E8-B373-17E5F5D7E6F5} - {9E2B8160-3E76-4B33-86AB-DE35A5FCDB1E} = {73C9B1E5-3893-47E8-B373-17E5F5D7E6F5} - {B640DB80-C982-407B-A2EC-CD29AC77DDB8} = {73C9B1E5-3893-47E8-B373-17E5F5D7E6F5} EndGlobalSection EndGlobal diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/AWS.Lambda.Powertools.BatchProcessing.csproj b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/AWS.Lambda.Powertools.BatchProcessing.csproj index e4bb98ea9..54af16708 100644 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/AWS.Lambda.Powertools.BatchProcessing.csproj +++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/AWS.Lambda.Powertools.BatchProcessing.csproj @@ -10,7 +10,6 @@ - diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/BatchItemFailuresResponse.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/BatchItemFailuresResponse.cs index 162487188..d7ef6bfa5 100644 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/BatchItemFailuresResponse.cs +++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/BatchItemFailuresResponse.cs @@ -1,4 +1,17 @@ - +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ using System.Collections.Generic; using System.Runtime.Serialization; diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/BatchProcessor.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/BatchProcessor.cs index 6afeebfa6..ba3c5f3fc 100644 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/BatchProcessor.cs +++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/BatchProcessor.cs @@ -1,4 +1,19 @@ -using System; +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/BatchProcessorAttribute.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/BatchProcessorAttribute.cs index f28f7f3be..d693d4ec7 100644 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/BatchProcessorAttribute.cs +++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/BatchProcessorAttribute.cs @@ -1,10 +1,21 @@ - +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; using Amazon.Lambda.DynamoDBEvents; @@ -130,62 +141,23 @@ public class BatchProcessorAttribute : UniversalWrapperAttribute /// /// Type of batch processor. /// - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] public Type BatchProcessor { get; set; } /// /// Type of batch processor provider. /// - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] public Type BatchProcessorProvider { get; set; } /// /// Type of record handler. /// - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] public Type RecordHandler { get; set; } /// /// Type of record handler provider. /// - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] public Type RecordHandlerProvider { get; set; } - /// - /// Type of typed record handler for strongly-typed processing. - /// - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] - public Type TypedRecordHandler { get; set; } - - /// - /// Type of typed record handler provider for strongly-typed processing. - /// - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] - public Type TypedRecordHandlerProvider { get; set; } - - /// - /// Type of typed record handler with context for strongly-typed processing. - /// - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] - public Type TypedRecordHandlerWithContext { get; set; } - - /// - /// Type of typed record handler with context provider for strongly-typed processing. - /// - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] - public Type TypedRecordHandlerWithContextProvider { get; set; } - - /// - /// JsonSerializerContext type for AOT-compatible deserialization. - /// - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] - public Type JsonSerializerContext { get; set; } - - /// - /// Policy for handling deserialization errors. - /// - public DeserializationErrorPolicy DeserializationErrorPolicy { get; set; } = DeserializationErrorPolicy.FailRecord; - /// /// Error handling policy. /// @@ -238,12 +210,6 @@ public class BatchProcessorAttribute : UniversalWrapperAttribute {BatchEventType.KinesisDataStream, typeof(IRecordHandlerProvider)}, {BatchEventType.Sqs, typeof(IRecordHandlerProvider)} }; - private static readonly Dictionary TypedBatchProcessorTypes = new() - { - {BatchEventType.DynamoDbStream, typeof(ITypedBatchProcessor)}, - {BatchEventType.KinesisDataStream, typeof(ITypedBatchProcessor)}, - {BatchEventType.Sqs, typeof(ITypedBatchProcessor)} - }; /// protected internal override T WrapSync(Func target, object[] args, AspectEventArgs eventArgs) @@ -264,7 +230,7 @@ protected internal override async Task WrapAsync(Func> t return await target(args); } - internal IBatchProcessingAspectHandler CreateAspectHandler(IReadOnlyList args) + private IBatchProcessingAspectHandler CreateAspectHandler(IReadOnlyList args) { // Try get event type if (args == null || args.Count == 0 || !EventTypes.TryGetValue(args[0].GetType(), out var eventType)) @@ -296,15 +262,6 @@ internal IBatchProcessingAspectHandler CreateAspectHandler(IReadOnlyList throw new ArgumentException($"The provided record handler provider must implement: '{RecordHandlerProviderTypes[eventType]}'.", nameof(RecordHandlerProvider)); } - // Validate typed handler configurations - ValidateTypedHandlerConfiguration(); - - // Check if typed handlers are configured (not yet fully supported in attributes) - if (IsTypedHandlerConfigured()) - { - throw new NotSupportedException("Typed record handlers are not yet fully supported with BatchProcessorAttribute. Please use direct typed batch processor calls for typed processing."); - } - // Create aspect handler return eventType switch { @@ -394,40 +351,4 @@ private BatchProcessingAspectHandler CreateBatchProcessingAspec ThrowOnFullBatchFailure = ThrowOnFullBatchFailure }); } - - private void ValidateTypedHandlerConfiguration() - { - // Ensure only one type of handler is configured - var handlerCount = 0; - if (RecordHandler != null) handlerCount++; - if (RecordHandlerProvider != null) handlerCount++; - if (TypedRecordHandler != null) handlerCount++; - if (TypedRecordHandlerProvider != null) handlerCount++; - if (TypedRecordHandlerWithContext != null) handlerCount++; - if (TypedRecordHandlerWithContextProvider != null) handlerCount++; - - if (handlerCount == 0) - { - throw new InvalidOperationException("A record handler, record handler provider, typed record handler, or typed record handler provider is required."); - } - - if (handlerCount > 1) - { - throw new InvalidOperationException("Only one type of handler (traditional or typed) can be configured at a time."); - } - - // Validate JsonSerializerContext type if provided - if (JsonSerializerContext != null && !JsonSerializerContext.IsAssignableTo(typeof(JsonSerializerContext))) - { - throw new InvalidOperationException($"The provided JsonSerializerContext must inherit from: '{typeof(JsonSerializerContext)}'."); - } - } - - private bool IsTypedHandlerConfigured() - { - return TypedRecordHandler != null || - TypedRecordHandlerProvider != null || - TypedRecordHandlerWithContext != null || - TypedRecordHandlerWithContextProvider != null; - } } diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/BatchProcessorErrorHandlingPolicy.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/BatchProcessorErrorHandlingPolicy.cs index 87401c137..13ab9b12d 100644 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/BatchProcessorErrorHandlingPolicy.cs +++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/BatchProcessorErrorHandlingPolicy.cs @@ -1,4 +1,17 @@ - +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ namespace AWS.Lambda.Powertools.BatchProcessing; diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DeserializationErrorPolicy.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DeserializationErrorPolicy.cs deleted file mode 100644 index bad1f34d8..000000000 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DeserializationErrorPolicy.cs +++ /dev/null @@ -1,27 +0,0 @@ - - -namespace AWS.Lambda.Powertools.BatchProcessing; - -/// -/// Defines how deserialization errors should be handled during batch processing. -/// -public enum DeserializationErrorPolicy -{ - /// - /// Mark the record as failed when deserialization fails (default behavior). - /// The record will be included in the batch failure response. - /// - FailRecord, - - /// - /// Skip records that fail deserialization and continue processing other records. - /// Failed records will not be included in the batch failure response. - /// - IgnoreRecord, - - /// - /// Use a custom error handler to process deserialization failures. - /// The custom handler determines how to handle the failed record. - /// - CustomHandler -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DeserializationOptions.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DeserializationOptions.cs deleted file mode 100644 index 780eadaa5..000000000 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DeserializationOptions.cs +++ /dev/null @@ -1,64 +0,0 @@ - - -using System; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace AWS.Lambda.Powertools.BatchProcessing; - -/// -/// Configuration options for deserialization operations. -/// -public class DeserializationOptions -{ - /// - /// Gets or sets the JsonSerializerContext to use for AOT-compatible deserialization. - /// When provided, this takes precedence over JsonSerializerOptions. - /// - public JsonSerializerContext JsonSerializerContext { get; set; } - - /// - /// Gets or sets the JsonSerializerOptions to use for deserialization. - /// This is ignored if JsonSerializerContext is provided. - /// - public JsonSerializerOptions JsonSerializerOptions { get; set; } - - /// - /// Gets or sets a value indicating whether deserialization errors should be ignored. - /// When true, failed deserialization attempts will not throw exceptions but will return default values. - /// Default is false. - /// - [Obsolete("Use ErrorPolicy property instead. This property will be removed in a future version.")] - public bool IgnoreDeserializationErrors { get; set; } = false; - - /// - /// Gets or sets the policy for handling deserialization errors. - /// Default is FailRecord. - /// - public DeserializationErrorPolicy ErrorPolicy { get; set; } = DeserializationErrorPolicy.FailRecord; - - /// - /// Creates a new instance of DeserializationOptions with default settings. - /// - public DeserializationOptions() - { - } - - /// - /// Creates a new instance of DeserializationOptions with the specified JsonSerializerContext. - /// - /// The JsonSerializerContext to use for AOT-compatible deserialization. - public DeserializationOptions(JsonSerializerContext jsonSerializerContext) - { - JsonSerializerContext = jsonSerializerContext; - } - - /// - /// Creates a new instance of DeserializationOptions with the specified JsonSerializerOptions. - /// - /// The JsonSerializerOptions to use for deserialization. - public DeserializationOptions(JsonSerializerOptions jsonSerializerOptions) - { - JsonSerializerOptions = jsonSerializerOptions; - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DynamoDb/DynamoDbRecordDataExtractor.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DynamoDb/DynamoDbRecordDataExtractor.cs deleted file mode 100644 index b39b967af..000000000 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DynamoDb/DynamoDbRecordDataExtractor.cs +++ /dev/null @@ -1,43 +0,0 @@ - - -using System.Text.Json; -using Amazon.Lambda.DynamoDBEvents; - -namespace AWS.Lambda.Powertools.BatchProcessing.DynamoDb; - -/// -/// Extracts data from DynamoDB stream records for deserialization. -/// -public class DynamoDbRecordDataExtractor : IRecordDataExtractor -{ - /// - /// The singleton instance of the DynamoDB record data extractor. - /// - public static readonly DynamoDbRecordDataExtractor Instance = new(); - - /// - /// Extracts the data from a DynamoDB stream record by serializing the entire DynamoDB record. - /// For INSERT and MODIFY events, this includes the NewImage. For REMOVE events, this includes the OldImage. - /// - /// The DynamoDB stream record. - /// The serialized DynamoDB record data. - public string ExtractData(DynamoDBEvent.DynamodbStreamRecord record) - { - if (record?.Dynamodb == null) - return string.Empty; - - // Create a simplified representation of the DynamoDB record for deserialization - var recordData = new - { - record.EventName, - record.Dynamodb.Keys, - record.Dynamodb.NewImage, - record.Dynamodb.OldImage, - record.Dynamodb.SequenceNumber, - record.Dynamodb.SizeBytes, - record.Dynamodb.StreamViewType - }; - - return JsonSerializer.Serialize(recordData); - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DynamoDb/DynamoDbStreamBatchProcessor.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DynamoDb/DynamoDbStreamBatchProcessor.cs index f17d0e711..c19b4a44f 100644 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DynamoDb/DynamoDbStreamBatchProcessor.cs +++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DynamoDb/DynamoDbStreamBatchProcessor.cs @@ -1,4 +1,17 @@ - +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ using System.Collections.Generic; using Amazon.Lambda.DynamoDBEvents; diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DynamoDb/IDynamoDbStreamBatchProcessor.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DynamoDb/IDynamoDbStreamBatchProcessor.cs index 7a6aa721e..2641338af 100644 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DynamoDb/IDynamoDbStreamBatchProcessor.cs +++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DynamoDb/IDynamoDbStreamBatchProcessor.cs @@ -1,4 +1,17 @@ - +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ using Amazon.Lambda.DynamoDBEvents; diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DynamoDb/IDynamoDbStreamRecordHandler.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DynamoDb/IDynamoDbStreamRecordHandler.cs index 1d910daaa..ed24545d4 100644 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DynamoDb/IDynamoDbStreamRecordHandler.cs +++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DynamoDb/IDynamoDbStreamRecordHandler.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using Amazon.Lambda.DynamoDBEvents; namespace AWS.Lambda.Powertools.BatchProcessing.DynamoDb; diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DynamoDb/TypedDynamoDbStreamBatchProcessor.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DynamoDb/TypedDynamoDbStreamBatchProcessor.cs deleted file mode 100644 index a5f259757..000000000 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/DynamoDb/TypedDynamoDbStreamBatchProcessor.cs +++ /dev/null @@ -1,216 +0,0 @@ - - -using System; -using System.Threading; -using System.Threading.Tasks; -using Amazon.Lambda.Core; -using Amazon.Lambda.DynamoDBEvents; -using AWS.Lambda.Powertools.BatchProcessing.Exceptions; -using AWS.Lambda.Powertools.BatchProcessing.Internal; -using AWS.Lambda.Powertools.Common; - -namespace AWS.Lambda.Powertools.BatchProcessing.DynamoDb; - -/// -/// Typed batch processor for DynamoDB stream events that supports automatic deserialization of record data. -/// -public class TypedDynamoDbStreamBatchProcessor : DynamoDbStreamBatchProcessor, ITypedBatchProcessor -{ - private readonly IDeserializationService _deserializationService; - private readonly IRecordDataExtractor _recordDataExtractor; - - - - /// - /// Initializes a new instance of the TypedDynamoDbStreamBatchProcessor class. - /// - /// The Powertools configurations. - /// The deserialization service. If null, uses JsonDeserializationService.Instance. - /// The record data extractor. If null, uses DynamoDbRecordDataExtractor.Instance. - public TypedDynamoDbStreamBatchProcessor( - IPowertoolsConfigurations powertoolsConfigurations, - IDeserializationService deserializationService = null, - IRecordDataExtractor recordDataExtractor = null) - : base(powertoolsConfigurations) - { - _deserializationService = deserializationService ?? JsonDeserializationService.Instance; - _recordDataExtractor = recordDataExtractor ?? DynamoDbRecordDataExtractor.Instance; - } - - /// - /// Default constructor for when consumers create a custom typed batch processor. - /// - protected TypedDynamoDbStreamBatchProcessor() : this(PowertoolsConfigurations.Instance) - { - } - - /// - public async Task> ProcessAsync( - DynamoDBEvent @event, - ITypedRecordHandler recordHandler) - { - return await ProcessAsync(@event, recordHandler, null, CancellationToken.None); - } - - /// - public async Task> ProcessAsync( - DynamoDBEvent @event, - ITypedRecordHandler recordHandler, - DeserializationOptions deserializationOptions) - { - return await ProcessAsync(@event, recordHandler, deserializationOptions, CancellationToken.None); - } - - /// - public async Task> ProcessAsync( - DynamoDBEvent @event, - ITypedRecordHandler recordHandler, - CancellationToken cancellationToken) - { - return await ProcessAsync(@event, recordHandler, null, cancellationToken); - } - - /// - public async Task> ProcessAsync( - DynamoDBEvent @event, - ITypedRecordHandler recordHandler, - DeserializationOptions deserializationOptions, - CancellationToken cancellationToken) - { - var processingOptions = new ProcessingOptions - { - CancellationToken = cancellationToken - }; - return await ProcessAsync(@event, recordHandler, deserializationOptions, processingOptions); - } - - /// - public async Task> ProcessAsync( - DynamoDBEvent @event, - ITypedRecordHandler recordHandler, - DeserializationOptions deserializationOptions, - ProcessingOptions processingOptions) - { - // Validate AOT compatibility before processing - AotCompatibilityHelper.ValidateAotCompatibility(deserializationOptions); - - var wrappedHandler = new TypedRecordHandlerWrapper(recordHandler, _deserializationService, _recordDataExtractor, deserializationOptions); - return await ProcessAsync(@event, wrappedHandler, processingOptions); - } - - /// - public async Task> ProcessAsync( - DynamoDBEvent @event, - ITypedRecordHandlerWithContext recordHandler, - ILambdaContext context) - { - return await ProcessAsync(@event, recordHandler, context, null, CancellationToken.None); - } - - /// - public async Task> ProcessAsync( - DynamoDBEvent @event, - ITypedRecordHandlerWithContext recordHandler, - ILambdaContext context, - DeserializationOptions deserializationOptions) - { - return await ProcessAsync(@event, recordHandler, context, deserializationOptions, CancellationToken.None); - } - - /// - public async Task> ProcessAsync( - DynamoDBEvent @event, - ITypedRecordHandlerWithContext recordHandler, - ILambdaContext context, - CancellationToken cancellationToken) - { - return await ProcessAsync(@event, recordHandler, context, null, cancellationToken); - } - - /// - public async Task> ProcessAsync( - DynamoDBEvent @event, - ITypedRecordHandlerWithContext recordHandler, - ILambdaContext context, - DeserializationOptions deserializationOptions, - CancellationToken cancellationToken) - { - var processingOptions = new ProcessingOptions - { - CancellationToken = cancellationToken - }; - return await ProcessAsync(@event, recordHandler, context, deserializationOptions, processingOptions); - } - - /// - public async Task> ProcessAsync( - DynamoDBEvent @event, - ITypedRecordHandlerWithContext recordHandler, - ILambdaContext context, - DeserializationOptions deserializationOptions, - ProcessingOptions processingOptions) - { - // Validate AOT compatibility before processing - AotCompatibilityHelper.ValidateAotCompatibility(deserializationOptions); - - var wrappedHandler = new TypedRecordHandlerWithContextWrapper(recordHandler, context, _deserializationService, _recordDataExtractor, deserializationOptions); - return await ProcessAsync(@event, wrappedHandler, processingOptions); - } - - /// - /// Wrapper class that adapts ITypedRecordHandler to IRecordHandler. - /// - private sealed class TypedRecordHandlerWrapper : TypedRecordHandlerWrapperBase - { - private readonly ITypedRecordHandler _typedHandler; - - public TypedRecordHandlerWrapper( - ITypedRecordHandler typedHandler, - IDeserializationService deserializationService, - IRecordDataExtractor recordDataExtractor, - DeserializationOptions deserializationOptions) - : base(deserializationService, recordDataExtractor, deserializationOptions) - { - _typedHandler = typedHandler ?? throw new ArgumentNullException(nameof(typedHandler)); - } - - protected override async Task HandleTypedRecordAsync(T deserializedData, CancellationToken cancellationToken) - { - return await _typedHandler.HandleAsync(deserializedData, cancellationToken); - } - - protected override string GetDeserializationErrorMessage(DynamoDBEvent.DynamodbStreamRecord record, DeserializationException ex) - { - return $"Failed to deserialize DynamoDB stream record '{record.Dynamodb.SequenceNumber}' to type '{typeof(T).Name}'. See inner exception for details."; - } - } - - /// - /// Wrapper class that adapts ITypedRecordHandlerWithContext to IRecordHandler. - /// - private sealed class TypedRecordHandlerWithContextWrapper : TypedRecordHandlerWithContextWrapperBase - { - private readonly ITypedRecordHandlerWithContext _typedHandler; - - public TypedRecordHandlerWithContextWrapper( - ITypedRecordHandlerWithContext typedHandler, - ILambdaContext context, - IDeserializationService deserializationService, - IRecordDataExtractor recordDataExtractor, - DeserializationOptions deserializationOptions) - : base(context, deserializationService, recordDataExtractor, deserializationOptions) - { - _typedHandler = typedHandler ?? throw new ArgumentNullException(nameof(typedHandler)); - } - - protected override async Task HandleTypedRecordWithContextAsync(T deserializedData, ILambdaContext context, CancellationToken cancellationToken) - { - return await _typedHandler.HandleAsync(deserializedData, context, cancellationToken); - } - - protected override string GetDeserializationErrorMessage(DynamoDBEvent.DynamodbStreamRecord record, DeserializationException ex) - { - return $"Failed to deserialize DynamoDB stream record '{record.Dynamodb.SequenceNumber}' to type '{typeof(T).Name}'. See inner exception for details."; - } - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/AotCompatibilityException.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/AotCompatibilityException.cs deleted file mode 100644 index b75711c3d..000000000 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/AotCompatibilityException.cs +++ /dev/null @@ -1,39 +0,0 @@ - - -using System; - -namespace AWS.Lambda.Powertools.BatchProcessing.Exceptions; - -/// -/// Exception thrown when AOT (Ahead-of-Time) compilation compatibility requirements are not met. -/// -public class AotCompatibilityException : Exception -{ - /// - /// Gets the type that caused the AOT compatibility issue. - /// - public Type TargetType { get; } - - /// - /// Initializes a new instance of the AotCompatibilityException class. - /// - /// The type that caused the AOT compatibility issue. - /// The error message. - public AotCompatibilityException(Type targetType, string message) - : base(message) - { - TargetType = targetType; - } - - /// - /// Initializes a new instance of the AotCompatibilityException class. - /// - /// The type that caused the AOT compatibility issue. - /// The error message. - /// The inner exception. - public AotCompatibilityException(Type targetType, string message, Exception innerException) - : base(message, innerException) - { - TargetType = targetType; - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/AotTypeValidationException.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/AotTypeValidationException.cs deleted file mode 100644 index 472c158bc..000000000 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/AotTypeValidationException.cs +++ /dev/null @@ -1,39 +0,0 @@ - - -using System; - -namespace AWS.Lambda.Powertools.BatchProcessing.Exceptions; - -/// -/// Exception thrown when type validation fails in AOT scenarios. -/// -public class AotTypeValidationException : Exception -{ - /// - /// Gets the type that failed validation. - /// - public Type TargetType { get; } - - /// - /// Initializes a new instance of the AotTypeValidationException class. - /// - /// The type that failed validation. - /// The error message. - public AotTypeValidationException(Type targetType, string message) - : base(message) - { - TargetType = targetType; - } - - /// - /// Initializes a new instance of the AotTypeValidationException class. - /// - /// The type that failed validation. - /// The error message. - /// The inner exception. - public AotTypeValidationException(Type targetType, string message, Exception innerException) - : base(message, innerException) - { - TargetType = targetType; - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/BatchProcessingException.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/BatchProcessingException.cs index a4f8669cd..73160630c 100644 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/BatchProcessingException.cs +++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/BatchProcessingException.cs @@ -1,4 +1,17 @@ - +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ using System; using System.Collections.Generic; diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/CircuitBreakerException.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/CircuitBreakerException.cs index 63e9c91f4..a7aee255f 100644 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/CircuitBreakerException.cs +++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/CircuitBreakerException.cs @@ -1,4 +1,17 @@ - +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ using System; diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/DeserializationException.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/DeserializationException.cs deleted file mode 100644 index 0b36e1014..000000000 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/DeserializationException.cs +++ /dev/null @@ -1,71 +0,0 @@ - - -using System; - -namespace AWS.Lambda.Powertools.BatchProcessing.Exceptions; - -/// -/// Exception thrown when deserialization of record data fails. -/// -public class DeserializationException : Exception -{ - /// - /// Gets the raw record data that failed to deserialize. - /// - public string RecordData { get; } - - /// - /// Gets the target type that the record data was being deserialized to. - /// - public Type TargetType { get; } - - /// - /// Gets the record identifier, if available. - /// - public string RecordId { get; } - - /// - /// Initializes a new instance of the DeserializationException class. - /// - /// The raw record data that failed to deserialize. - /// The target type that the record data was being deserialized to. - /// The record identifier, if available. - /// The exception that caused the deserialization failure. - public DeserializationException(string recordData, Type targetType, string recordId, Exception innerException) - : base($"Failed to deserialize record '{recordId}' to type '{targetType?.Name ?? "Unknown"}'. See inner exception for details.", innerException) - { - RecordData = recordData; - TargetType = targetType; - RecordId = recordId; - } - - /// - /// Initializes a new instance of the DeserializationException class. - /// - /// The raw record data that failed to deserialize. - /// The target type that the record data was being deserialized to. - /// The exception that caused the deserialization failure. - public DeserializationException(string recordData, Type targetType, Exception innerException) - : this(recordData, targetType, "Unknown", innerException) - { - } - - /// - /// Initializes a new instance of the DeserializationException class. - /// - /// The error message that explains the reason for the exception. - /// The exception that caused the deserialization failure. - public DeserializationException(string message, Exception innerException) - : base(message, innerException) - { - } - - /// - /// Initializes a new instance of the DeserializationException class. - /// - /// The error message that explains the reason for the exception. - public DeserializationException(string message) - : base(message) - { - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/RecordProcessingException.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/RecordProcessingException.cs index 102aa9f61..4b2a87c44 100644 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/RecordProcessingException.cs +++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/RecordProcessingException.cs @@ -1,4 +1,17 @@ - +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ using System; diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/UnprocessedRecordException.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/UnprocessedRecordException.cs index 88e633b51..543d71f9c 100644 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/UnprocessedRecordException.cs +++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Exceptions/UnprocessedRecordException.cs @@ -1,4 +1,17 @@ - +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ using System; diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IBatchProcessor.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IBatchProcessor.cs index 61dae7380..dbe52df1f 100644 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IBatchProcessor.cs +++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IBatchProcessor.cs @@ -1,4 +1,17 @@ - +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ using System.Threading; using System.Threading.Tasks; diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IBatchProcessorProvider.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IBatchProcessorProvider.cs index f3bee954b..ab191f990 100644 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IBatchProcessorProvider.cs +++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IBatchProcessorProvider.cs @@ -1,4 +1,17 @@ - +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ namespace AWS.Lambda.Powertools.BatchProcessing; diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IDeserializationErrorHandler.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IDeserializationErrorHandler.cs deleted file mode 100644 index cb26e423c..000000000 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IDeserializationErrorHandler.cs +++ /dev/null @@ -1,23 +0,0 @@ - - -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace AWS.Lambda.Powertools.BatchProcessing; - -/// -/// Interface for handling deserialization errors during batch processing. -/// -/// The type of the record being processed. -public interface IDeserializationErrorHandler -{ - /// - /// Handles a deserialization error for a specific record. - /// - /// The record that failed to deserialize. - /// The exception that occurred during deserialization. - /// The cancellation token. - /// A task that represents the asynchronous operation. The task result contains the record handler result. - Task HandleDeserializationError(TRecord record, Exception exception, CancellationToken cancellationToken); -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IDeserializationService.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IDeserializationService.cs deleted file mode 100644 index 1b49c7bf6..000000000 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IDeserializationService.cs +++ /dev/null @@ -1,42 +0,0 @@ - - -using System; - -namespace AWS.Lambda.Powertools.BatchProcessing; - -/// -/// Service for deserializing record data into strongly-typed objects. -/// -public interface IDeserializationService -{ - /// - /// Deserializes the provided data string into the specified type. - /// - /// The target type to deserialize to. - /// The data string to deserialize. - /// Optional deserialization options. - /// The deserialized object of type T. - /// Thrown when deserialization fails. - T Deserialize(string data, DeserializationOptions options = null); - - /// - /// Attempts to deserialize the provided data string into the specified type. - /// - /// The target type to deserialize to. - /// The data string to deserialize. - /// When this method returns, contains the deserialized object if successful, or the default value if unsuccessful. - /// Optional deserialization options. - /// true if deserialization was successful; otherwise, false. - bool TryDeserialize(string data, out T result, DeserializationOptions options = null); - - /// - /// Attempts to deserialize the provided data string into the specified type, capturing any exception that occurs. - /// - /// The target type to deserialize to. - /// The data string to deserialize. - /// When this method returns, contains the deserialized object if successful, or the default value if unsuccessful. - /// When this method returns, contains the exception that occurred during deserialization if unsuccessful, or null if successful. - /// Optional deserialization options. - /// true if deserialization was successful; otherwise, false. - bool TryDeserialize(string data, out T result, out Exception exception, DeserializationOptions options = null); -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IRecordDataExtractor.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IRecordDataExtractor.cs deleted file mode 100644 index 6eb49f977..000000000 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IRecordDataExtractor.cs +++ /dev/null @@ -1,17 +0,0 @@ - - -namespace AWS.Lambda.Powertools.BatchProcessing; - -/// -/// Interface for extracting data from event records for deserialization. -/// -/// The type of the event record. -public interface IRecordDataExtractor -{ - /// - /// Extracts the data string from the event record that should be deserialized. - /// - /// The event record to extract data from. - /// The data string to be deserialized. - string ExtractData(TRecord record); -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IRecordHandler.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IRecordHandler.cs index 4cb068db9..975c0b36c 100644 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IRecordHandler.cs +++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IRecordHandler.cs @@ -1,4 +1,17 @@ - +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ using System.Threading; using System.Threading.Tasks; diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IRecordHandlerProvider.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IRecordHandlerProvider.cs index b73e0454b..79394228c 100644 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IRecordHandlerProvider.cs +++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/IRecordHandlerProvider.cs @@ -1,4 +1,17 @@ - +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ namespace AWS.Lambda.Powertools.BatchProcessing; diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ITypedBatchProcessor.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ITypedBatchProcessor.cs deleted file mode 100644 index 83cdc18fb..000000000 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ITypedBatchProcessor.cs +++ /dev/null @@ -1,95 +0,0 @@ - - -using System.Threading; -using System.Threading.Tasks; -using Amazon.Lambda.Core; - -namespace AWS.Lambda.Powertools.BatchProcessing; - -/// -/// The interface for strongly-typed batch processing. -/// -/// Type of batch event. -/// Type of batch record. -public interface ITypedBatchProcessor -{ - /// - /// The of the latest batch processing run. This includes a object with the identifiers of the batch items that failed processing. - /// - ProcessingResult ProcessingResult { get; } - - /// - /// - /// - Task> ProcessAsync(TEvent @event, ITypedRecordHandler recordHandler); - - /// - /// - /// - /// Options for controlling deserialization behavior. - Task> ProcessAsync(TEvent @event, ITypedRecordHandler recordHandler, DeserializationOptions deserializationOptions); - - /// - /// - /// - /// The cancellation token to monitor. - Task> ProcessAsync(TEvent @event, ITypedRecordHandler recordHandler, CancellationToken cancellationToken); - - /// - /// - /// - /// Options for controlling deserialization behavior. - /// The cancellation token to monitor. - Task> ProcessAsync(TEvent @event, ITypedRecordHandler recordHandler, DeserializationOptions deserializationOptions, CancellationToken cancellationToken); - - /// - /// Processes a batch event with strongly-typed record handling. - /// - /// The type to deserialize record data to. - /// The event to process. - /// The typed record handler containing the per-record processing logic. - /// Options for controlling deserialization behavior. - /// Processing options to control settings such as cancellation, error handling policy and parallelism. - /// A of the latest batch processing run. This includes a object with the identifiers of the batch items that failed processing. - Task> ProcessAsync(TEvent @event, ITypedRecordHandler recordHandler, DeserializationOptions deserializationOptions, ProcessingOptions processingOptions); - - /// - /// - /// - /// - Task> ProcessAsync(TEvent @event, ITypedRecordHandlerWithContext recordHandler, ILambdaContext context); - - /// - /// - /// - /// - /// Options for controlling deserialization behavior. - Task> ProcessAsync(TEvent @event, ITypedRecordHandlerWithContext recordHandler, ILambdaContext context, DeserializationOptions deserializationOptions); - - /// - /// - /// - /// - /// The cancellation token to monitor. - Task> ProcessAsync(TEvent @event, ITypedRecordHandlerWithContext recordHandler, ILambdaContext context, CancellationToken cancellationToken); - - /// - /// - /// - /// - /// Options for controlling deserialization behavior. - /// The cancellation token to monitor. - Task> ProcessAsync(TEvent @event, ITypedRecordHandlerWithContext recordHandler, ILambdaContext context, DeserializationOptions deserializationOptions, CancellationToken cancellationToken); - - /// - /// Processes a batch event with strongly-typed record handling and Lambda context. - /// - /// The type to deserialize record data to. - /// The event to process. - /// The typed record handler with context containing the per-record processing logic. - /// The Lambda context for the current invocation. - /// Options for controlling deserialization behavior. - /// Processing options to control settings such as cancellation, error handling policy and parallelism. - /// A of the latest batch processing run. This includes a object with the identifiers of the batch items that failed processing. - Task> ProcessAsync(TEvent @event, ITypedRecordHandlerWithContext recordHandler, ILambdaContext context, DeserializationOptions deserializationOptions, ProcessingOptions processingOptions); -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ITypedRecordHandler.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ITypedRecordHandler.cs deleted file mode 100644 index 4c00df2d3..000000000 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ITypedRecordHandler.cs +++ /dev/null @@ -1,21 +0,0 @@ - - -using System.Threading; -using System.Threading.Tasks; - -namespace AWS.Lambda.Powertools.BatchProcessing; - -/// -/// The interface for strongly-typed record handling. -/// -/// Type of the deserialized data from the batch record. -public interface ITypedRecordHandler -{ - /// - /// Handles processing of a given batch record with strongly-typed data. - /// - /// The deserialized data from the record to process. - /// The cancellation token to monitor. - /// An awaitable with a . - Task HandleAsync(T data, CancellationToken cancellationToken); -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ITypedRecordHandlerProvider.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ITypedRecordHandlerProvider.cs deleted file mode 100644 index 757a84724..000000000 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ITypedRecordHandlerProvider.cs +++ /dev/null @@ -1,16 +0,0 @@ - - -namespace AWS.Lambda.Powertools.BatchProcessing; - -/// -/// The interface for creating strongly-typed record handlers. -/// -/// Type of the deserialized data from the batch record. -public interface ITypedRecordHandlerProvider -{ - /// - /// Creates a typed record handler. - /// - /// The created typed record handler. - ITypedRecordHandler Create(); -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ITypedRecordHandlerWithContext.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ITypedRecordHandlerWithContext.cs deleted file mode 100644 index 31d38e763..000000000 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ITypedRecordHandlerWithContext.cs +++ /dev/null @@ -1,23 +0,0 @@ - - -using System.Threading; -using System.Threading.Tasks; -using Amazon.Lambda.Core; - -namespace AWS.Lambda.Powertools.BatchProcessing; - -/// -/// The interface for strongly-typed record handling with Lambda context. -/// -/// Type of the deserialized data from the batch record. -public interface ITypedRecordHandlerWithContext -{ - /// - /// Handles processing of a given batch record with strongly-typed data and Lambda context. - /// - /// The deserialized data from the record to process. - /// The Lambda context for the current invocation. - /// The cancellation token to monitor. - /// An awaitable with a . - Task HandleAsync(T data, ILambdaContext context, CancellationToken cancellationToken); -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ITypedRecordHandlerWithContextProvider.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ITypedRecordHandlerWithContextProvider.cs deleted file mode 100644 index 07eb0e5af..000000000 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ITypedRecordHandlerWithContextProvider.cs +++ /dev/null @@ -1,16 +0,0 @@ - - -namespace AWS.Lambda.Powertools.BatchProcessing; - -/// -/// The interface for creating strongly-typed record handlers with Lambda context. -/// -/// Type of the deserialized data from the batch record. -public interface ITypedRecordHandlerWithContextProvider -{ - /// - /// Creates a typed record handler with context. - /// - /// The created typed record handler with context. - ITypedRecordHandlerWithContext Create(); -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/AotCompatibilityHelper.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/AotCompatibilityHelper.cs deleted file mode 100644 index a4020432c..000000000 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/AotCompatibilityHelper.cs +++ /dev/null @@ -1,134 +0,0 @@ - - -using System; -using System.Runtime.CompilerServices; -using System.Text.Json; -using System.Text.Json.Serialization; -using AWS.Lambda.Powertools.BatchProcessing.Exceptions; - -namespace AWS.Lambda.Powertools.BatchProcessing.Internal; - -/// -/// Helper class for AOT (Ahead-of-Time) compilation compatibility features. -/// -internal static class AotCompatibilityHelper -{ - /// - /// Determines if the current runtime environment is AOT compiled. - /// - /// True if running in AOT mode, false otherwise. - public static bool IsAotMode() - { - // In AOT mode, RuntimeFeature.IsDynamicCodeSupported returns false - return !RuntimeFeature.IsDynamicCodeSupported; - } - - /// - /// Validates that the JsonSerializerContext contains type information for the specified type. - /// - /// The type to validate. - /// The JsonSerializerContext to validate. - /// Whether to throw an exception if type information is missing. - /// True if type information is available, false otherwise. - /// Thrown when type information is missing and throwOnMissing is true. - public static bool ValidateTypeInContext(JsonSerializerContext context, bool throwOnMissing = true) - { - if (context == null) - { - if (throwOnMissing) - { - throw new AotTypeValidationException(typeof(T), "JsonSerializerContext is null. AOT compilation requires a JsonSerializerContext with type information."); - } - return false; - } - - try - { - var typeInfo = context.GetTypeInfo(typeof(T)); - if (typeInfo == null) - { - if (throwOnMissing) - { - throw new AotTypeValidationException(typeof(T), - $"Type '{typeof(T).FullName}' is not registered in the provided JsonSerializerContext. " + - $"Add [JsonSerializable(typeof({typeof(T).Name}))] to your JsonSerializerContext class."); - } - return false; - } - return true; - } - catch (NotSupportedException ex) - { - if (throwOnMissing) - { - throw new AotTypeValidationException(typeof(T), - $"Type '{typeof(T).FullName}' is not supported by the provided JsonSerializerContext. " + - $"Ensure the type is properly registered with [JsonSerializable(typeof({typeof(T).Name}))].", ex); - } - return false; - } - } - - /// - /// Provides a fallback deserialization strategy when JsonSerializerContext is not available in AOT mode. - /// - /// The type to deserialize. - /// The JSON data to deserialize. - /// The deserialization options. - /// The deserialized object. - /// Thrown when AOT mode requires JsonSerializerContext but none is provided. - - public static T FallbackDeserialize(string data, DeserializationOptions options) - { - if (IsAotMode() && options?.JsonSerializerContext == null) - { - throw new AotCompatibilityException(typeof(T), - "AOT compilation detected but no JsonSerializerContext provided. " + - "For AOT compatibility, provide a JsonSerializerContext with type information using DeserializationOptions."); - } - - // Use reflection-based deserialization as fallback - return JsonSerializer.Deserialize(data, options?.JsonSerializerOptions); - } - - /// - /// Gets a user-friendly error message for AOT compatibility issues. - /// - /// The type that caused the issue. - /// Whether a JsonSerializerContext was provided. - /// A descriptive error message with guidance. - public static string GetAotCompatibilityErrorMessage(Type targetType, bool contextProvided) - { - if (!contextProvided) - { - return $"AOT compilation requires a JsonSerializerContext for type '{targetType.FullName}'. " + - $"Create a JsonSerializerContext with [JsonSerializable(typeof({targetType.Name}))] and provide it via DeserializationOptions."; - } - - return $"The provided JsonSerializerContext does not contain type information for '{targetType.FullName}'. " + - $"Add [JsonSerializable(typeof({targetType.Name}))] to your JsonSerializerContext class."; - } - - /// - /// Validates AOT compatibility for the given type and options. - /// - /// The type to validate. - /// The deserialization options. - /// Thrown when AOT requirements are not met. - /// Thrown when type is not registered in JsonSerializerContext. - public static void ValidateAotCompatibility(DeserializationOptions options) - { - // If we're in AOT mode and no context is provided, that's an error - if (IsAotMode() && options?.JsonSerializerContext == null) - { - throw new AotCompatibilityException(typeof(T), GetAotCompatibilityErrorMessage(typeof(T), false)); - } - - // If a JsonSerializerContext is provided, always validate the type is registered - // This provides early validation regardless of runtime mode - if (options?.JsonSerializerContext != null) - { - ValidateTypeInContext(options.JsonSerializerContext); - } - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/BatchEventType.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/BatchEventType.cs index 3f196d053..df11ade79 100644 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/BatchEventType.cs +++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/BatchEventType.cs @@ -1,4 +1,17 @@ - +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ namespace AWS.Lambda.Powertools.BatchProcessing.Internal; diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/BatchProcessingAspectHandler.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/BatchProcessingAspectHandler.cs index e7c71ad81..c0037357f 100644 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/BatchProcessingAspectHandler.cs +++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/BatchProcessingAspectHandler.cs @@ -1,4 +1,17 @@ - +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ using System; using System.Linq; diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/IBatchProcessingAspectHandler.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/IBatchProcessingAspectHandler.cs index 2dcfed5e7..c43ec06b9 100644 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/IBatchProcessingAspectHandler.cs +++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/IBatchProcessingAspectHandler.cs @@ -1,4 +1,17 @@ - +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ using System.Threading.Tasks; diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/TypedRecordHandlerWithContextWrapperBase.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/TypedRecordHandlerWithContextWrapperBase.cs deleted file mode 100644 index aac7faae2..000000000 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/TypedRecordHandlerWithContextWrapperBase.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using Amazon.Lambda.Core; -using AWS.Lambda.Powertools.BatchProcessing.Exceptions; - -namespace AWS.Lambda.Powertools.BatchProcessing.Internal; - -/// -/// Base wrapper class that adapts ITypedRecordHandlerWithContext to IRecordHandler with common deserialization logic. -/// -/// The type of the record being processed. -/// The type to deserialize the record data to. -internal abstract class TypedRecordHandlerWithContextWrapperBase : IRecordHandler -{ - protected readonly ILambdaContext Context; - protected readonly IDeserializationService DeserializationService; - protected readonly IRecordDataExtractor RecordDataExtractor; - protected readonly DeserializationOptions DeserializationOptions; - - protected TypedRecordHandlerWithContextWrapperBase( - ILambdaContext context, - IDeserializationService deserializationService, - IRecordDataExtractor recordDataExtractor, - DeserializationOptions deserializationOptions) - { - Context = context; // Context can be null - DeserializationService = deserializationService ?? throw new ArgumentNullException(nameof(deserializationService)); - RecordDataExtractor = recordDataExtractor ?? throw new ArgumentNullException(nameof(recordDataExtractor)); - DeserializationOptions = deserializationOptions; - } - - public async Task HandleAsync(TRecord record, CancellationToken cancellationToken) - { - try - { - var recordData = RecordDataExtractor.ExtractData(record); - - // Use TryDeserialize to check if deserialization was successful - if (DeserializationOptions?.ErrorPolicy == DeserializationErrorPolicy.IgnoreRecord || - DeserializationOptions?.IgnoreDeserializationErrors == true) - { - if (!DeserializationService.TryDeserialize(recordData, out var deserializedData, out _, DeserializationOptions)) - { - // Deserialization failed and we're ignoring errors, don't call the handler - return RecordHandlerResult.None; - } - return await HandleTypedRecordWithContextAsync(deserializedData, Context, cancellationToken); - } - else - { - // Use regular deserialize which will throw on errors - var deserializedData = DeserializationService.Deserialize(recordData, DeserializationOptions); - return await HandleTypedRecordWithContextAsync(deserializedData, Context, cancellationToken); - } - } - catch (DeserializationException ex) - { - // Handle deserialization errors based on policy - if (DeserializationOptions?.ErrorPolicy == DeserializationErrorPolicy.IgnoreRecord) - { - return RecordHandlerResult.None; - } - - // For FailRecord policy or default, re-throw the exception - throw new RecordProcessingException(GetDeserializationErrorMessage(record, ex), ex); - } - } - - /// - /// Handles the typed record with context after successful deserialization. - /// - /// The deserialized data. - /// The Lambda context. - /// The cancellation token. - /// The result of handling the record. - protected abstract Task HandleTypedRecordWithContextAsync(T deserializedData, ILambdaContext context, CancellationToken cancellationToken); - - /// - /// Gets the error message for deserialization failures. - /// - /// The record that failed to deserialize. - /// The deserialization exception. - /// The error message. - protected abstract string GetDeserializationErrorMessage(TRecord record, DeserializationException ex); -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/TypedRecordHandlerWrapperBase.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/TypedRecordHandlerWrapperBase.cs deleted file mode 100644 index f9e51e70c..000000000 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Internal/TypedRecordHandlerWrapperBase.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; -using AWS.Lambda.Powertools.BatchProcessing.Exceptions; - -namespace AWS.Lambda.Powertools.BatchProcessing.Internal; - -/// -/// Base wrapper class that adapts ITypedRecordHandler to IRecordHandler with common deserialization logic. -/// -/// The type of the record being processed. -/// The type to deserialize the record data to. -internal abstract class TypedRecordHandlerWrapperBase : IRecordHandler -{ - protected readonly IDeserializationService DeserializationService; - protected readonly IRecordDataExtractor RecordDataExtractor; - protected readonly DeserializationOptions DeserializationOptions; - - protected TypedRecordHandlerWrapperBase( - IDeserializationService deserializationService, - IRecordDataExtractor recordDataExtractor, - DeserializationOptions deserializationOptions) - { - DeserializationService = deserializationService ?? throw new ArgumentNullException(nameof(deserializationService)); - RecordDataExtractor = recordDataExtractor ?? throw new ArgumentNullException(nameof(recordDataExtractor)); - DeserializationOptions = deserializationOptions; - } - - public async Task HandleAsync(TRecord record, CancellationToken cancellationToken) - { - try - { - var recordData = RecordDataExtractor.ExtractData(record); - - // Use TryDeserialize to check if deserialization was successful - if (DeserializationOptions?.ErrorPolicy == DeserializationErrorPolicy.IgnoreRecord || - DeserializationOptions?.IgnoreDeserializationErrors == true) - { - if (!DeserializationService.TryDeserialize(recordData, out var deserializedData, out _, DeserializationOptions)) - { - // Deserialization failed and we're ignoring errors, don't call the handler - return RecordHandlerResult.None; - } - return await HandleTypedRecordAsync(deserializedData, cancellationToken); - } - else - { - // Use regular deserialize which will throw on errors - var deserializedData = DeserializationService.Deserialize(recordData, DeserializationOptions); - return await HandleTypedRecordAsync(deserializedData, cancellationToken); - } - } - catch (DeserializationException ex) - { - // Handle deserialization errors based on policy - if (DeserializationOptions?.ErrorPolicy == DeserializationErrorPolicy.IgnoreRecord) - { - return RecordHandlerResult.None; - } - - // For FailRecord policy or default, re-throw the exception - throw new RecordProcessingException(GetDeserializationErrorMessage(record, ex), ex); - } - } - - /// - /// Handles the typed record after successful deserialization. - /// - /// The deserialized data. - /// The cancellation token. - /// The result of handling the record. - protected abstract Task HandleTypedRecordAsync(T deserializedData, CancellationToken cancellationToken); - - /// - /// Gets the error message for deserialization failures. - /// - /// The record that failed to deserialize. - /// The deserialization exception. - /// The error message. - protected abstract string GetDeserializationErrorMessage(TRecord record, DeserializationException ex); -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/InternalsVisibleTo.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/InternalsVisibleTo.cs index 1fbd2680a..0a7bb3e22 100644 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/InternalsVisibleTo.cs +++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/InternalsVisibleTo.cs @@ -1,4 +1,17 @@ - +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ using System.Runtime.CompilerServices; diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/JsonDeserializationService.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/JsonDeserializationService.cs deleted file mode 100644 index 99ef35a35..000000000 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/JsonDeserializationService.cs +++ /dev/null @@ -1,120 +0,0 @@ - - -using System; -using System.Text.Json; -using AWS.Lambda.Powertools.BatchProcessing.Exceptions; -using AWS.Lambda.Powertools.BatchProcessing.Internal; - -namespace AWS.Lambda.Powertools.BatchProcessing; - -/// -/// JSON-based implementation of IDeserializationService using System.Text.Json. -/// -public class JsonDeserializationService : IDeserializationService -{ - /// - /// Gets the singleton instance of JsonDeserializationService. - /// - public static JsonDeserializationService Instance { get; } = new(); - - /// - /// Initializes a new instance of the JsonDeserializationService class. - /// - public JsonDeserializationService() - { - } - - /// - /// Performs deserialization with AOT compatibility validation and fallback behavior. - /// - /// The type to deserialize to. - /// The JSON data to deserialize. - /// The deserialization options. - /// The deserialized object. - - private static T DeserializeWithFallback(string data, DeserializationOptions options) - { - // Check if we're in AOT mode and provide appropriate guidance - if (AotCompatibilityHelper.IsAotMode()) - { - throw new AotCompatibilityException(typeof(T), - AotCompatibilityHelper.GetAotCompatibilityErrorMessage(typeof(T), false)); - } - - // Use reflection-based deserialization as fallback for non-AOT scenarios - return JsonSerializer.Deserialize(data, options?.JsonSerializerOptions); - } - - /// - public T Deserialize(string data, DeserializationOptions options = null) - { - if (string.IsNullOrWhiteSpace(data)) - { - throw new DeserializationException("Data cannot be null or empty.", new ArgumentException("Data cannot be null or empty.", nameof(data))); - } - - try - { - if (options?.JsonSerializerContext != null) - { - // Validate AOT compatibility when JsonSerializerContext is provided - AotCompatibilityHelper.ValidateTypeInContext(options.JsonSerializerContext); - return (T)JsonSerializer.Deserialize(data, typeof(T), options.JsonSerializerContext); - } - - // Use fallback deserialization with AOT validation - return DeserializeWithFallback(data, options); - } - catch (Exception ex) when (ex is JsonException || ex is NotSupportedException || ex is ArgumentException) - { - if (options?.IgnoreDeserializationErrors == true || options?.ErrorPolicy == DeserializationErrorPolicy.IgnoreRecord) - { - return default(T); - } - - throw new DeserializationException(data, typeof(T), ex); - } - } - - /// - public bool TryDeserialize(string data, out T result, DeserializationOptions options = null) - { - return TryDeserialize(data, out result, out _, options); - } - - /// - public bool TryDeserialize(string data, out T result, out Exception exception, DeserializationOptions options = null) - { - result = default(T); - exception = null; - - if (string.IsNullOrWhiteSpace(data)) - { - exception = new ArgumentException("Data cannot be null or empty.", nameof(data)); - return false; - } - - try - { - if (options?.JsonSerializerContext != null) - { - // Validate AOT compatibility when JsonSerializerContext is provided - AotCompatibilityHelper.ValidateTypeInContext(options.JsonSerializerContext); - result = (T)JsonSerializer.Deserialize(data, typeof(T), options.JsonSerializerContext); - } - else - { - // Use fallback deserialization with AOT validation - result = DeserializeWithFallback(data, options); - } - - return true; - } - catch (Exception ex) when (ex is JsonException || ex is NotSupportedException || ex is ArgumentException || - ex is AotCompatibilityException || ex is AotTypeValidationException) - { - exception = ex; - return false; - } - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Kinesis/IKinesisEventBatchProcessor.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Kinesis/IKinesisEventBatchProcessor.cs index d911e6e37..097a1fcaf 100644 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Kinesis/IKinesisEventBatchProcessor.cs +++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Kinesis/IKinesisEventBatchProcessor.cs @@ -1,4 +1,19 @@ -using Amazon.Lambda.KinesisEvents; +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using Amazon.Lambda.KinesisEvents; namespace AWS.Lambda.Powertools.BatchProcessing.Kinesis; diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Kinesis/IKinesisEventRecordHandler.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Kinesis/IKinesisEventRecordHandler.cs index 37def333d..5a21afc43 100644 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Kinesis/IKinesisEventRecordHandler.cs +++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Kinesis/IKinesisEventRecordHandler.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using Amazon.Lambda.KinesisEvents; namespace AWS.Lambda.Powertools.BatchProcessing.Kinesis; diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Kinesis/KinesisEventBatchProcessor.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Kinesis/KinesisEventBatchProcessor.cs index 6c3323080..1ea01041c 100644 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Kinesis/KinesisEventBatchProcessor.cs +++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Kinesis/KinesisEventBatchProcessor.cs @@ -1,4 +1,19 @@ -using System.Collections.Generic; +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System.Collections.Generic; using Amazon.Lambda.KinesisEvents; using AWS.Lambda.Powertools.Common; diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Kinesis/KinesisRecordDataExtractor.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Kinesis/KinesisRecordDataExtractor.cs deleted file mode 100644 index 1ca62a3eb..000000000 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Kinesis/KinesisRecordDataExtractor.cs +++ /dev/null @@ -1,45 +0,0 @@ - - -using System; -using System.IO; -using System.Text; -using Amazon.Lambda.KinesisEvents; - -namespace AWS.Lambda.Powertools.BatchProcessing.Kinesis; - -/// -/// Extracts data from Kinesis event records for deserialization. -/// -public class KinesisRecordDataExtractor : IRecordDataExtractor -{ - /// - /// The singleton instance of the Kinesis record data extractor. - /// - public static readonly KinesisRecordDataExtractor Instance = new(); - - /// - /// Extracts the data from a Kinesis event record by reading from the MemoryStream. - /// - /// The Kinesis event record. - /// The decoded data string. - public string ExtractData(KinesisEvent.KinesisEventRecord record) - { - if (record?.Kinesis?.Data == null) - return string.Empty; - - try - { - // Reset the stream position to the beginning - record.Kinesis.Data.Position = 0; - - // Read the data from the MemoryStream - using var reader = new StreamReader(record.Kinesis.Data, Encoding.UTF8, leaveOpen: true); - return reader.ReadToEnd(); - } - catch (Exception) - { - // If reading fails, return empty string - return string.Empty; - } - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Kinesis/TypedKinesisEventBatchProcessor.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Kinesis/TypedKinesisEventBatchProcessor.cs deleted file mode 100644 index 0311fa7c4..000000000 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Kinesis/TypedKinesisEventBatchProcessor.cs +++ /dev/null @@ -1,216 +0,0 @@ - - -using System; -using System.Threading; -using System.Threading.Tasks; -using Amazon.Lambda.Core; -using Amazon.Lambda.KinesisEvents; -using AWS.Lambda.Powertools.BatchProcessing.Exceptions; -using AWS.Lambda.Powertools.BatchProcessing.Internal; -using AWS.Lambda.Powertools.Common; - -namespace AWS.Lambda.Powertools.BatchProcessing.Kinesis; - -/// -/// Typed batch processor for Kinesis events that supports automatic deserialization of record data. -/// -public class TypedKinesisEventBatchProcessor : KinesisEventBatchProcessor, ITypedBatchProcessor -{ - private readonly IDeserializationService _deserializationService; - private readonly IRecordDataExtractor _recordDataExtractor; - - - - /// - /// Initializes a new instance of the TypedKinesisEventBatchProcessor class. - /// - /// The Powertools configurations. - /// The deserialization service. If null, uses JsonDeserializationService.Instance. - /// The record data extractor. If null, uses KinesisRecordDataExtractor.Instance. - public TypedKinesisEventBatchProcessor( - IPowertoolsConfigurations powertoolsConfigurations, - IDeserializationService deserializationService = null, - IRecordDataExtractor recordDataExtractor = null) - : base(powertoolsConfigurations) - { - _deserializationService = deserializationService ?? JsonDeserializationService.Instance; - _recordDataExtractor = recordDataExtractor ?? KinesisRecordDataExtractor.Instance; - } - - /// - /// Default constructor for when consumers create a custom typed batch processor. - /// - protected TypedKinesisEventBatchProcessor() : this(PowertoolsConfigurations.Instance) - { - } - - /// - public async Task> ProcessAsync( - KinesisEvent @event, - ITypedRecordHandler recordHandler) - { - return await ProcessAsync(@event, recordHandler, null, CancellationToken.None); - } - - /// - public async Task> ProcessAsync( - KinesisEvent @event, - ITypedRecordHandler recordHandler, - DeserializationOptions deserializationOptions) - { - return await ProcessAsync(@event, recordHandler, deserializationOptions, CancellationToken.None); - } - - /// - public async Task> ProcessAsync( - KinesisEvent @event, - ITypedRecordHandler recordHandler, - CancellationToken cancellationToken) - { - return await ProcessAsync(@event, recordHandler, null, cancellationToken); - } - - /// - public async Task> ProcessAsync( - KinesisEvent @event, - ITypedRecordHandler recordHandler, - DeserializationOptions deserializationOptions, - CancellationToken cancellationToken) - { - var processingOptions = new ProcessingOptions - { - CancellationToken = cancellationToken - }; - return await ProcessAsync(@event, recordHandler, deserializationOptions, processingOptions); - } - - /// - public async Task> ProcessAsync( - KinesisEvent @event, - ITypedRecordHandler recordHandler, - DeserializationOptions deserializationOptions, - ProcessingOptions processingOptions) - { - // Validate AOT compatibility before processing - AotCompatibilityHelper.ValidateAotCompatibility(deserializationOptions); - - var wrappedHandler = new TypedRecordHandlerWrapper(recordHandler, _deserializationService, _recordDataExtractor, deserializationOptions); - return await ProcessAsync(@event, wrappedHandler, processingOptions); - } - - /// - public async Task> ProcessAsync( - KinesisEvent @event, - ITypedRecordHandlerWithContext recordHandler, - ILambdaContext context) - { - return await ProcessAsync(@event, recordHandler, context, null, CancellationToken.None); - } - - /// - public async Task> ProcessAsync( - KinesisEvent @event, - ITypedRecordHandlerWithContext recordHandler, - ILambdaContext context, - DeserializationOptions deserializationOptions) - { - return await ProcessAsync(@event, recordHandler, context, deserializationOptions, CancellationToken.None); - } - - /// - public async Task> ProcessAsync( - KinesisEvent @event, - ITypedRecordHandlerWithContext recordHandler, - ILambdaContext context, - CancellationToken cancellationToken) - { - return await ProcessAsync(@event, recordHandler, context, null, cancellationToken); - } - - /// - public async Task> ProcessAsync( - KinesisEvent @event, - ITypedRecordHandlerWithContext recordHandler, - ILambdaContext context, - DeserializationOptions deserializationOptions, - CancellationToken cancellationToken) - { - var processingOptions = new ProcessingOptions - { - CancellationToken = cancellationToken - }; - return await ProcessAsync(@event, recordHandler, context, deserializationOptions, processingOptions); - } - - /// - public async Task> ProcessAsync( - KinesisEvent @event, - ITypedRecordHandlerWithContext recordHandler, - ILambdaContext context, - DeserializationOptions deserializationOptions, - ProcessingOptions processingOptions) - { - // Validate AOT compatibility before processing - AotCompatibilityHelper.ValidateAotCompatibility(deserializationOptions); - - var wrappedHandler = new TypedRecordHandlerWithContextWrapper(recordHandler, context, _deserializationService, _recordDataExtractor, deserializationOptions); - return await ProcessAsync(@event, wrappedHandler, processingOptions); - } - - /// - /// Wrapper class that adapts ITypedRecordHandler to IRecordHandler. - /// - private sealed class TypedRecordHandlerWrapper : TypedRecordHandlerWrapperBase - { - private readonly ITypedRecordHandler _typedHandler; - - public TypedRecordHandlerWrapper( - ITypedRecordHandler typedHandler, - IDeserializationService deserializationService, - IRecordDataExtractor recordDataExtractor, - DeserializationOptions deserializationOptions) - : base(deserializationService, recordDataExtractor, deserializationOptions) - { - _typedHandler = typedHandler ?? throw new ArgumentNullException(nameof(typedHandler)); - } - - protected override async Task HandleTypedRecordAsync(T deserializedData, CancellationToken cancellationToken) - { - return await _typedHandler.HandleAsync(deserializedData, cancellationToken); - } - - protected override string GetDeserializationErrorMessage(KinesisEvent.KinesisEventRecord record, DeserializationException ex) - { - return $"Failed to deserialize Kinesis record '{record.Kinesis.SequenceNumber}' to type '{typeof(T).Name}'. See inner exception for details."; - } - } - - /// - /// Wrapper class that adapts ITypedRecordHandlerWithContext to IRecordHandler. - /// - private sealed class TypedRecordHandlerWithContextWrapper : TypedRecordHandlerWithContextWrapperBase - { - private readonly ITypedRecordHandlerWithContext _typedHandler; - - public TypedRecordHandlerWithContextWrapper( - ITypedRecordHandlerWithContext typedHandler, - ILambdaContext context, - IDeserializationService deserializationService, - IRecordDataExtractor recordDataExtractor, - DeserializationOptions deserializationOptions) - : base(context, deserializationService, recordDataExtractor, deserializationOptions) - { - _typedHandler = typedHandler ?? throw new ArgumentNullException(nameof(typedHandler)); - } - - protected override async Task HandleTypedRecordWithContextAsync(T deserializedData, ILambdaContext context, CancellationToken cancellationToken) - { - return await _typedHandler.HandleAsync(deserializedData, context, cancellationToken); - } - - protected override string GetDeserializationErrorMessage(KinesisEvent.KinesisEventRecord record, DeserializationException ex) - { - return $"Failed to deserialize Kinesis record '{record.Kinesis.SequenceNumber}' to type '{typeof(T).Name}'. See inner exception for details."; - } - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ProcessingOptions.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ProcessingOptions.cs index 410491386..8fc7701fb 100644 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ProcessingOptions.cs +++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ProcessingOptions.cs @@ -1,4 +1,17 @@ - +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ using System; using System.Threading; diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ProcessingResult.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ProcessingResult.cs index 8deda4d70..088111df3 100644 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ProcessingResult.cs +++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/ProcessingResult.cs @@ -1,4 +1,17 @@ - +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ using System.Collections.Generic; diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/RecordFailure.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/RecordFailure.cs index a6bf20517..60cf1bcd3 100644 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/RecordFailure.cs +++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/RecordFailure.cs @@ -1,4 +1,17 @@ - +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ using System; diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/RecordHandler.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/RecordHandler.cs index 7768916ff..e3df2512b 100644 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/RecordHandler.cs +++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/RecordHandler.cs @@ -1,4 +1,17 @@ - +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ using System; using System.Threading; diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/RecordHandlerResult.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/RecordHandlerResult.cs index 84212e246..01419d89d 100644 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/RecordHandlerResult.cs +++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/RecordHandlerResult.cs @@ -1,4 +1,17 @@ - +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ namespace AWS.Lambda.Powertools.BatchProcessing; diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/RecordSuccess.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/RecordSuccess.cs index b22635c0c..d244ae724 100644 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/RecordSuccess.cs +++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/RecordSuccess.cs @@ -1,4 +1,17 @@ - +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ namespace AWS.Lambda.Powertools.BatchProcessing; diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Sqs/ISqsBatchProcessor.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Sqs/ISqsBatchProcessor.cs index 8fb6021be..72e933af1 100644 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Sqs/ISqsBatchProcessor.cs +++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Sqs/ISqsBatchProcessor.cs @@ -1,4 +1,19 @@ -using Amazon.Lambda.SQSEvents; +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using Amazon.Lambda.SQSEvents; namespace AWS.Lambda.Powertools.BatchProcessing.Sqs; diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Sqs/ISqsRecordHandler.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Sqs/ISqsRecordHandler.cs index 232c7ff84..67213a158 100644 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Sqs/ISqsRecordHandler.cs +++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Sqs/ISqsRecordHandler.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using Amazon.Lambda.SQSEvents; namespace AWS.Lambda.Powertools.BatchProcessing.Sqs; diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Sqs/SqsBatchProcessor.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Sqs/SqsBatchProcessor.cs index f7741e522..bf191f9c9 100644 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Sqs/SqsBatchProcessor.cs +++ b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Sqs/SqsBatchProcessor.cs @@ -1,4 +1,19 @@ -using System; +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System; using System.Collections.Generic; using System.Linq; using Amazon.Lambda.SQSEvents; diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Sqs/SqsRecordDataExtractor.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Sqs/SqsRecordDataExtractor.cs deleted file mode 100644 index 9fc80b449..000000000 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Sqs/SqsRecordDataExtractor.cs +++ /dev/null @@ -1,26 +0,0 @@ - - -using Amazon.Lambda.SQSEvents; - -namespace AWS.Lambda.Powertools.BatchProcessing.Sqs; - -/// -/// Extracts data from SQS message records for deserialization. -/// -public class SqsRecordDataExtractor : IRecordDataExtractor -{ - /// - /// The singleton instance of the SQS record data extractor. - /// - public static readonly SqsRecordDataExtractor Instance = new(); - - /// - /// Extracts the message body from an SQS message record. - /// - /// The SQS message record. - /// The message body string. - public string ExtractData(SQSEvent.SQSMessage record) - { - return record?.Body ?? string.Empty; - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Sqs/TypedSqsBatchProcessor.cs b/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Sqs/TypedSqsBatchProcessor.cs deleted file mode 100644 index a723f4936..000000000 --- a/libraries/src/AWS.Lambda.Powertools.BatchProcessing/Sqs/TypedSqsBatchProcessor.cs +++ /dev/null @@ -1,220 +0,0 @@ - - -using System; -using System.Threading; -using System.Threading.Tasks; -using Amazon.Lambda.Core; -using Amazon.Lambda.SQSEvents; -using AWS.Lambda.Powertools.BatchProcessing.Exceptions; -using AWS.Lambda.Powertools.BatchProcessing.Internal; -using AWS.Lambda.Powertools.Common; - -namespace AWS.Lambda.Powertools.BatchProcessing.Sqs; - -/// -/// Typed batch processor for SQS events that supports automatic deserialization of message bodies. -/// -public class TypedSqsBatchProcessor : SqsBatchProcessor, ITypedBatchProcessor -{ - private readonly IDeserializationService _deserializationService; - private readonly IRecordDataExtractor _recordDataExtractor; - - - - /// - /// Initializes a new instance of the TypedSqsBatchProcessor class. - /// - /// The Powertools configurations. - /// The deserialization service. If null, uses JsonDeserializationService.Instance. - /// The record data extractor. If null, uses SqsRecordDataExtractor.Instance. - public TypedSqsBatchProcessor( - IPowertoolsConfigurations powertoolsConfigurations, - IDeserializationService deserializationService = null, - IRecordDataExtractor recordDataExtractor = null) - : base(powertoolsConfigurations) - { - _deserializationService = deserializationService ?? JsonDeserializationService.Instance; - _recordDataExtractor = recordDataExtractor ?? SqsRecordDataExtractor.Instance; - } - - /// - /// Default constructor for when consumers create a custom typed batch processor. - /// - protected TypedSqsBatchProcessor() : this(PowertoolsConfigurations.Instance) - { - } - - /// - public async Task> ProcessAsync( - SQSEvent @event, - ITypedRecordHandler recordHandler) - { - return await ProcessAsync(@event, recordHandler, null, CancellationToken.None); - } - - /// - public async Task> ProcessAsync( - SQSEvent @event, - ITypedRecordHandler recordHandler, - DeserializationOptions deserializationOptions) - { - return await ProcessAsync(@event, recordHandler, deserializationOptions, CancellationToken.None); - } - - /// - public async Task> ProcessAsync( - SQSEvent @event, - ITypedRecordHandler recordHandler, - CancellationToken cancellationToken) - { - return await ProcessAsync(@event, recordHandler, null, cancellationToken); - } - - /// - public async Task> ProcessAsync( - SQSEvent @event, - ITypedRecordHandler recordHandler, - DeserializationOptions deserializationOptions, - CancellationToken cancellationToken) - { - var processingOptions = new ProcessingOptions - { - CancellationToken = cancellationToken - }; - return await ProcessAsync(@event, recordHandler, deserializationOptions, processingOptions); - } - - /// - public async Task> ProcessAsync( - SQSEvent @event, - ITypedRecordHandler recordHandler, - DeserializationOptions deserializationOptions, - ProcessingOptions processingOptions) - { - // Validate AOT compatibility before processing - AotCompatibilityHelper.ValidateAotCompatibility(deserializationOptions); - - var wrappedHandler = new TypedRecordHandlerWrapper(recordHandler, _deserializationService, _recordDataExtractor, deserializationOptions); - return await ProcessAsync(@event, wrappedHandler, processingOptions); - } - - /// - public async Task> ProcessAsync( - SQSEvent @event, - ITypedRecordHandlerWithContext recordHandler, - ILambdaContext context) - { - return await ProcessAsync(@event, recordHandler, context, null, CancellationToken.None); - } - - /// - public async Task> ProcessAsync( - SQSEvent @event, - ITypedRecordHandlerWithContext recordHandler, - ILambdaContext context, - DeserializationOptions deserializationOptions) - { - return await ProcessAsync(@event, recordHandler, context, deserializationOptions, CancellationToken.None); - } - - /// - public async Task> ProcessAsync( - SQSEvent @event, - ITypedRecordHandlerWithContext recordHandler, - ILambdaContext context, - CancellationToken cancellationToken) - { - return await ProcessAsync(@event, recordHandler, context, null, cancellationToken); - } - - /// - public async Task> ProcessAsync( - SQSEvent @event, - ITypedRecordHandlerWithContext recordHandler, - ILambdaContext context, - DeserializationOptions deserializationOptions, - CancellationToken cancellationToken) - { - var processingOptions = new ProcessingOptions - { - CancellationToken = cancellationToken - }; - return await ProcessAsync(@event, recordHandler, context, deserializationOptions, processingOptions); - } - - /// - public async Task> ProcessAsync( - SQSEvent @event, - ITypedRecordHandlerWithContext recordHandler, - ILambdaContext context, - DeserializationOptions deserializationOptions, - ProcessingOptions processingOptions) - { - // Validate AOT compatibility before processing - AotCompatibilityHelper.ValidateAotCompatibility(deserializationOptions); - - var wrappedHandler = new TypedRecordHandlerWithContextWrapper(recordHandler, context, _deserializationService, _recordDataExtractor, deserializationOptions); - return await ProcessAsync(@event, wrappedHandler, processingOptions); - } - - - - /// - /// Wrapper class that adapts ITypedRecordHandler to IRecordHandler. - /// - private sealed class TypedRecordHandlerWrapper : TypedRecordHandlerWrapperBase - { - private readonly ITypedRecordHandler _typedHandler; - - public TypedRecordHandlerWrapper( - ITypedRecordHandler typedHandler, - IDeserializationService deserializationService, - IRecordDataExtractor recordDataExtractor, - DeserializationOptions deserializationOptions) - : base(deserializationService, recordDataExtractor, deserializationOptions) - { - _typedHandler = typedHandler ?? throw new ArgumentNullException(nameof(typedHandler)); - } - - protected override async Task HandleTypedRecordAsync(T deserializedData, CancellationToken cancellationToken) - { - return await _typedHandler.HandleAsync(deserializedData, cancellationToken); - } - - protected override string GetDeserializationErrorMessage(SQSEvent.SQSMessage record, DeserializationException ex) - { - return $"Failed to deserialize SQS message '{record.MessageId}' to type '{typeof(T).Name}'. See inner exception for details."; - } - } - - - - /// - /// Wrapper class that adapts ITypedRecordHandlerWithContext to IRecordHandler. - /// - private sealed class TypedRecordHandlerWithContextWrapper : TypedRecordHandlerWithContextWrapperBase - { - private readonly ITypedRecordHandlerWithContext _typedHandler; - - public TypedRecordHandlerWithContextWrapper( - ITypedRecordHandlerWithContext typedHandler, - ILambdaContext context, - IDeserializationService deserializationService, - IRecordDataExtractor recordDataExtractor, - DeserializationOptions deserializationOptions) - : base(context, deserializationService, recordDataExtractor, deserializationOptions) - { - _typedHandler = typedHandler ?? throw new ArgumentNullException(nameof(typedHandler)); - } - - protected override async Task HandleTypedRecordWithContextAsync(T deserializedData, ILambdaContext context, CancellationToken cancellationToken) - { - return await _typedHandler.HandleAsync(deserializedData, context, cancellationToken); - } - - protected override string GetDeserializationErrorMessage(SQSEvent.SQSMessage record, DeserializationException ex) - { - return $"Failed to deserialize SQS message '{record.MessageId}' to type '{typeof(T).Name}'. See inner exception for details."; - } - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Aspects/UniversalWrapperAspect.cs b/libraries/src/AWS.Lambda.Powertools.Common/Aspects/UniversalWrapperAspect.cs index 0656a8bd3..c4b014682 100644 --- a/libraries/src/AWS.Lambda.Powertools.Common/Aspects/UniversalWrapperAspect.cs +++ b/libraries/src/AWS.Lambda.Powertools.Common/Aspects/UniversalWrapperAspect.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/ConsoleWrapper.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/ConsoleWrapper.cs deleted file mode 100644 index 4a124d94d..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Common/Core/ConsoleWrapper.cs +++ /dev/null @@ -1,160 +0,0 @@ -using System; -using System.IO; - -namespace AWS.Lambda.Powertools.Common; - -/// -public class ConsoleWrapper : IConsoleWrapper -{ - private static bool _override; - private static TextWriter _testOutputStream; - private static bool _inTestMode = false; - - /// - public void WriteLine(string message) - { - if (_inTestMode && _testOutputStream != null) - { - _testOutputStream.WriteLine(message); - } - else - { - EnsureConsoleOutput(); - Console.WriteLine(message); - } - } - - /// - public void Debug(string message) - { - if (_inTestMode && _testOutputStream != null) - { - _testOutputStream.WriteLine(message); - } - else - { - EnsureConsoleOutput(); - System.Diagnostics.Debug.WriteLine(message); - } - } - - /// - public void Error(string message) - { - if (_inTestMode && _testOutputStream != null) - { - _testOutputStream.WriteLine(message); - } - else - { - if (!_override) - { - var errorOutput = new StreamWriter(Console.OpenStandardError()); - errorOutput.AutoFlush = true; - Console.SetError(errorOutput); - } - Console.Error.WriteLine(message); - } - } - - /// - /// Set the ConsoleWrapper to use a different TextWriter - /// This is useful for unit tests where you want to capture the output - /// - public static void SetOut(TextWriter consoleOut) - { - _testOutputStream = consoleOut; - _inTestMode = true; - _override = true; - Console.SetOut(consoleOut); - } - - private static void EnsureConsoleOutput() - { - // Check if we need to override console output for Lambda environment - if (ShouldOverrideConsole()) - { - OverrideLambdaLogger(); - } - } - - private static bool ShouldOverrideConsole() - { - // Don't override if we're in test mode - if (_inTestMode) return false; - - // Always override in Lambda environment to prevent Lambda's log wrapping - var isLambda = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("AWS_LAMBDA_FUNCTION_NAME")); - - return isLambda && (!_override || HasLambdaReInterceptedConsole()); - } - - internal static bool HasLambdaReInterceptedConsole() - { - return HasLambdaReInterceptedConsole(() => Console.Out); - } - - internal static bool HasLambdaReInterceptedConsole(Func consoleOutAccessor) - { - // Lambda might re-intercept console between init and handler execution - try - { - var currentOut = consoleOutAccessor(); - // Check if current output stream looks like it might be Lambda's wrapper - var typeName = currentOut.GetType().FullName ?? ""; - return typeName.Contains("Lambda") || typeName == "System.IO.TextWriter+SyncTextWriter"; - } - catch - { - return true; // Assume re-interception if we can't determine - } - } - - internal static void OverrideLambdaLogger() - { - OverrideLambdaLogger(() => Console.OpenStandardOutput()); - } - - internal static void OverrideLambdaLogger(Func standardOutputOpener) - { - try - { - // Force override of LambdaLogger - var standardOutput = new StreamWriter(standardOutputOpener()) - { - AutoFlush = true - }; - Console.SetOut(standardOutput); - _override = true; - } - catch (Exception) - { - // Log the failure but don't throw - degraded functionality is better than crash - _override = false; - } - } - - internal static void WriteLine(string logLevel, string message) - { - Console.WriteLine($"{DateTime.UtcNow:yyyy-MM-ddTHH:mm:ss.fffZ}\t{logLevel}\t{message}"); - } - - /// - /// Reset the ConsoleWrapper to its original state - /// - public static void ResetForTest() - { - _override = false; - _inTestMode = false; - _testOutputStream = null; - } - - /// - /// Clear the output reset flag - /// - public static void ClearOutputResetFlag() - { - // This method is kept for backward compatibility but no longer needed - // since we removed the _outputResetPerformed flag - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/Constants.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/Constants.cs index 456a50921..912196daf 100644 --- a/libraries/src/AWS.Lambda.Powertools.Common/Core/Constants.cs +++ b/libraries/src/AWS.Lambda.Powertools.Common/Core/Constants.cs @@ -20,14 +20,6 @@ namespace AWS.Lambda.Powertools.Common; /// internal static class Constants { - /// - /// Constant for AWS_LAMBDA_INITIALIZATION_TYPE environment variable - /// This is used to determine if the Lambda function is running in provisioned concurrency mode - /// or not. If the value is "provisioned-concurrency", it indicates that the function is running in provisioned - /// concurrency mode. Otherwise, it is running in standard mode. - /// - internal const string AWSInitializationTypeEnv = "AWS_LAMBDA_INITIALIZATION_TYPE"; - /// /// Constant for POWERTOOLS_SERVICE_NAME environment variable /// @@ -138,9 +130,4 @@ internal static class Constants /// Constant for POWERTOOLS_BATCH_THROW_ON_FULL_BATCH_FAILURE environment variable /// internal const string BatchThrowOnFullBatchFailureEnv = "POWERTOOLS_BATCH_THROW_ON_FULL_BATCH_FAILURE"; - - /// - /// Constant for POWERTOOLS_METRICS_DISABLED environment variable - /// - internal const string PowertoolsMetricsDisabledEnv = "POWERTOOLS_METRICS_DISABLED"; } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/IConsoleWrapper.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/IConsoleWrapper.cs deleted file mode 100644 index 9c4f1db14..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Common/Core/IConsoleWrapper.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace AWS.Lambda.Powertools.Common; - -/// -/// Wrapper for console operations to facilitate testing by abstracting system console interactions. -/// -public interface IConsoleWrapper -{ - /// - /// Writes the specified message followed by a line terminator to the standard output stream. - /// - /// The message to write. - void WriteLine(string message); - - /// - /// Writes a debug message to the trace listeners in the Debug.Listeners collection. - /// - /// The debug message to write. - void Debug(string message); - - /// - /// Writes the specified error message followed by a line terminator to the standard error stream. - /// - /// The error message to write. - void Error(string message); -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/IPowertoolsConfigurations.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/IPowertoolsConfigurations.cs index 755d33ef7..ff2c56644 100644 --- a/libraries/src/AWS.Lambda.Powertools.Common/Core/IPowertoolsConfigurations.cs +++ b/libraries/src/AWS.Lambda.Powertools.Common/Core/IPowertoolsConfigurations.cs @@ -155,27 +155,11 @@ public interface IPowertoolsConfigurations /// Gets the maximum degree of parallelism to apply during batch processing. /// /// Defaults to 1 (no parallelism). Specify -1 to automatically use the value of ProcessorCount. - int BatchProcessingMaxDegreeOfParallelism { get; } - + int BatchProcessingMaxDegreeOfParallelism { get; } + /// /// Gets a value indicating whether Batch processing will throw an exception on full batch failure. /// /// Defaults to true bool BatchThrowOnFullBatchFailureEnabled { get; } - - /// - /// Gets a value indicating whether Metrics are disabled. - /// - bool MetricsDisabled { get; } - - /// - /// Indicates if the current execution is a cold start. - /// - bool IsColdStart { get; } - - /// - /// AWS Lambda initialization type. - /// This is set to "on-demand" for on-demand Lambda functions and "provisioned-concurrency" for provisioned concurrency. - /// - string AwsInitializationType { get; } } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/IPowertoolsEnvironment.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/IPowertoolsEnvironment.cs index 6f57aabb3..059cfb7e0 100644 --- a/libraries/src/AWS.Lambda.Powertools.Common/Core/IPowertoolsEnvironment.cs +++ b/libraries/src/AWS.Lambda.Powertools.Common/Core/IPowertoolsEnvironment.cs @@ -34,10 +34,4 @@ public interface IPowertoolsEnvironment /// /// Assembly Version in the Major.Minor.Build format string GetAssemblyVersion(T type); - - /// - /// Sets the execution Environment Variable (AWS_EXECUTION_ENV) - /// - /// - void SetExecutionEnvironment(T type); } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/ISystemWrapper.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/ISystemWrapper.cs new file mode 100644 index 000000000..8a0359843 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.Common/Core/ISystemWrapper.cs @@ -0,0 +1,73 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System.IO; + +namespace AWS.Lambda.Powertools.Common; + +/// +/// Interface ISystemWrapper +/// +public interface ISystemWrapper +{ + /// + /// Gets the environment variable. + /// + /// The variable. + /// System.String. + string GetEnvironmentVariable(string variable); + + /// + /// Logs the specified value. + /// + /// The value. + void Log(string value); + + /// + /// Logs the line. + /// + /// The value. + void LogLine(string value); + + /// + /// Gets random number + /// + /// System.Double. + double GetRandom(); + + /// + /// Sets the environment variable. + /// + /// The variable. + /// + void SetEnvironmentVariable(string variable, string value); + + /// + /// Sets the execution Environment Variable (AWS_EXECUTION_ENV) + /// + /// + void SetExecutionEnvironment(T type); + + /// + /// Sets console output + /// Useful for testing and checking the console output + /// + /// var consoleOut = new StringWriter(); + /// SystemWrapper.Instance.SetOut(consoleOut); + /// + /// + /// The TextWriter instance where to write to + void SetOut(TextWriter writeTo); +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/LambdaLifecycleTracker.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/LambdaLifecycleTracker.cs deleted file mode 100644 index c802bd21c..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Common/Core/LambdaLifecycleTracker.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Threading; - -namespace AWS.Lambda.Powertools.Common.Core; - -/// -/// Tracks Lambda lifecycle state including cold starts -/// -internal static class LambdaLifecycleTracker -{ - // Static flag that's true only for the first Lambda container initialization - private static bool _isFirstContainer = true; - - // Store the cold start state for the current invocation - private static readonly AsyncLocal CurrentInvocationColdStart = new AsyncLocal(); - - private static string _lambdaInitType; - private static string LambdaInitType => _lambdaInitType ?? Environment.GetEnvironmentVariable(Constants.AWSInitializationTypeEnv); - - /// - /// Returns true if the current Lambda invocation is a cold start - /// - public static bool IsColdStart - { - get - { - if(LambdaInitType == "provisioned-concurrency") - { - // If the Lambda is provisioned concurrency, it is not a cold start - return false; - } - - // Initialize the cold start state for this invocation if not already set - if (!CurrentInvocationColdStart.Value.HasValue) - { - // Capture the container's cold start state for this entire invocation - CurrentInvocationColdStart.Value = _isFirstContainer; - - // After detecting the first invocation, mark future ones as warm - if (_isFirstContainer) - { - _isFirstContainer = false; - } - } - - // Return the cold start state for this invocation (cannot change during the invocation) - return CurrentInvocationColdStart.Value ?? false; - } - } - - - - /// - /// Resets the cold start state for testing - /// - /// Whether to reset the container state (defaults to true) - internal static void Reset(bool resetContainer = true) - { - if (resetContainer) - { - _isFirstContainer = true; - } - CurrentInvocationColdStart.Value = null; - _lambdaInitType = null; - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsConfigurations.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsConfigurations.cs index e6b6f6446..cf316389f 100644 --- a/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsConfigurations.cs +++ b/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsConfigurations.cs @@ -1,5 +1,19 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System.Globalization; -using AWS.Lambda.Powertools.Common.Core; namespace AWS.Lambda.Powertools.Common; @@ -10,12 +24,10 @@ namespace AWS.Lambda.Powertools.Common; /// public class PowertoolsConfigurations : IPowertoolsConfigurations { - private readonly IPowertoolsEnvironment _powertoolsEnvironment; - /// /// The maximum dimensions /// - public const int MaxDimensions = 29; + public const int MaxDimensions = 9; /// /// The maximum metrics @@ -27,13 +39,18 @@ public class PowertoolsConfigurations : IPowertoolsConfigurations /// private static IPowertoolsConfigurations _instance; + /// + /// The system wrapper + /// + private readonly ISystemWrapper _systemWrapper; + /// /// Initializes a new instance of the class. /// - /// - internal PowertoolsConfigurations(IPowertoolsEnvironment powertoolsEnvironment) + /// The system wrapper. + internal PowertoolsConfigurations(ISystemWrapper systemWrapper) { - _powertoolsEnvironment = powertoolsEnvironment; + _systemWrapper = systemWrapper; } /// @@ -41,7 +58,7 @@ internal PowertoolsConfigurations(IPowertoolsEnvironment powertoolsEnvironment) /// /// The instance. public static IPowertoolsConfigurations Instance => - _instance ??= new PowertoolsConfigurations(PowertoolsEnvironment.Instance); + _instance ??= new PowertoolsConfigurations(SystemWrapper.Instance); /// /// Gets the environment variable. @@ -50,7 +67,7 @@ internal PowertoolsConfigurations(IPowertoolsEnvironment powertoolsEnvironment) /// System.String. public string GetEnvironmentVariable(string variable) { - return _powertoolsEnvironment.GetEnvironmentVariable(variable); + return _systemWrapper.GetEnvironmentVariable(variable); } /// @@ -61,7 +78,7 @@ public string GetEnvironmentVariable(string variable) /// System.String. public string GetEnvironmentVariableOrDefault(string variable, string defaultValue) { - var result = _powertoolsEnvironment.GetEnvironmentVariable(variable); + var result = _systemWrapper.GetEnvironmentVariable(variable); return string.IsNullOrWhiteSpace(result) ? defaultValue : result; } @@ -73,7 +90,7 @@ public string GetEnvironmentVariableOrDefault(string variable, string defaultVal /// System.Int32. public int GetEnvironmentVariableOrDefault(string variable, int defaultValue) { - var result = _powertoolsEnvironment.GetEnvironmentVariable(variable); + var result = _systemWrapper.GetEnvironmentVariable(variable); return int.TryParse(result, out var parsedValue) ? parsedValue : defaultValue; } @@ -85,7 +102,7 @@ public int GetEnvironmentVariableOrDefault(string variable, int defaultValue) /// true if XXXX, false otherwise. public bool GetEnvironmentVariableOrDefault(string variable, bool defaultValue) { - return bool.TryParse(_powertoolsEnvironment.GetEnvironmentVariable(variable), out var result) + return bool.TryParse(_systemWrapper.GetEnvironmentVariable(variable), out var result) ? result : defaultValue; } @@ -143,8 +160,7 @@ public bool GetEnvironmentVariableOrDefault(string variable, bool defaultValue) /// /// The logger sample rate. public double LoggerSampleRate => - double.TryParse(_powertoolsEnvironment.GetEnvironmentVariable(Constants.LoggerSampleRateNameEnv), - NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var result) + double.TryParse(_systemWrapper.GetEnvironmentVariable(Constants.LoggerSampleRateNameEnv), NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var result) ? result : 0; @@ -174,7 +190,7 @@ public bool GetEnvironmentVariableOrDefault(string variable, bool defaultValue) /// /// true if this instance is Lambda; otherwise, false. public bool IsLambdaEnvironment => GetEnvironmentVariable(Constants.LambdaTaskRoot) is not null; - + /// /// Gets a value indicating whether [tracing is disabled]. /// @@ -185,7 +201,7 @@ public bool GetEnvironmentVariableOrDefault(string variable, bool defaultValue) /// public void SetExecutionEnvironment(T type) { - _powertoolsEnvironment.SetExecutionEnvironment(type); + _systemWrapper.SetExecutionEnvironment(type); } /// @@ -193,28 +209,14 @@ public void SetExecutionEnvironment(T type) GetEnvironmentVariableOrDefault(Constants.IdempotencyDisabledEnv, false); /// - public string BatchProcessingErrorHandlingPolicy => - GetEnvironmentVariableOrDefault(Constants.BatchErrorHandlingPolicyEnv, "DeriveFromEvent"); + public string BatchProcessingErrorHandlingPolicy => GetEnvironmentVariableOrDefault(Constants.BatchErrorHandlingPolicyEnv, "DeriveFromEvent"); /// - public bool BatchParallelProcessingEnabled => - GetEnvironmentVariableOrDefault(Constants.BatchParallelProcessingEnabled, false); + public bool BatchParallelProcessingEnabled => GetEnvironmentVariableOrDefault(Constants.BatchParallelProcessingEnabled, false); /// - public int BatchProcessingMaxDegreeOfParallelism => - GetEnvironmentVariableOrDefault(Constants.BatchMaxDegreeOfParallelismEnv, 1); + public int BatchProcessingMaxDegreeOfParallelism => GetEnvironmentVariableOrDefault(Constants.BatchMaxDegreeOfParallelismEnv, 1); /// - public bool BatchThrowOnFullBatchFailureEnabled => - GetEnvironmentVariableOrDefault(Constants.BatchThrowOnFullBatchFailureEnv, true); - - /// - public bool MetricsDisabled => GetEnvironmentVariableOrDefault(Constants.PowertoolsMetricsDisabledEnv, false); - - /// - public bool IsColdStart => LambdaLifecycleTracker.IsColdStart; - - /// - public string AwsInitializationType => - GetEnvironmentVariable(Constants.AWSInitializationTypeEnv); + public bool BatchThrowOnFullBatchFailureEnabled => GetEnvironmentVariableOrDefault(Constants.BatchThrowOnFullBatchFailureEnv, true); } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsEnvironment.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsEnvironment.cs index afc796b6a..3ad5317c6 100644 --- a/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsEnvironment.cs +++ b/libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsEnvironment.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Concurrent; -using System.Text; namespace AWS.Lambda.Powertools.Common; @@ -12,16 +10,6 @@ public class PowertoolsEnvironment : IPowertoolsEnvironment /// private static IPowertoolsEnvironment _instance; - /// - /// Cached runtime environment string - /// - private static readonly string CachedRuntimeEnvironment = $"PTENV/AWS_LAMBDA_DOTNET{Environment.Version.Major}"; - - /// - /// Cache for parsed assembly names to avoid repeated string operations - /// - private static readonly ConcurrentDictionary ParsedAssemblyNameCache = new(); - /// /// Gets the instance. /// @@ -43,100 +31,13 @@ public void SetEnvironmentVariable(string variableName, string value) /// public string GetAssemblyName(T type) { - if (type is Type typeObject) - { - return typeObject.Assembly.GetName().Name; - } - return type.GetType().Assembly.GetName().Name; } /// public string GetAssemblyVersion(T type) { - Version version; - - if (type is Type typeObject) - { - version = typeObject.Assembly.GetName().Version; - } - else - { - version = type.GetType().Assembly.GetName().Version; - } - + var version = type.GetType().Assembly.GetName().Version; return version != null ? $"{version.Major}.{version.Minor}.{version.Build}" : string.Empty; } - - /// - public void SetExecutionEnvironment(T type) - { - const string envName = Constants.AwsExecutionEnvironmentVariableName; - var currentEnvValue = GetEnvironmentVariable(envName); - var assemblyName = ParseAssemblyName(GetAssemblyName(type)); - - // Check for duplication early - if (!string.IsNullOrEmpty(currentEnvValue) && currentEnvValue.Contains(assemblyName)) - { - return; - } - - var assemblyVersion = GetAssemblyVersion(type); - var newEntry = $"{assemblyName}/{assemblyVersion}"; - - string finalValue; - - if (string.IsNullOrEmpty(currentEnvValue)) - { - // First entry: "PT/Assembly/1.0.0 PTENV/AWS_LAMBDA_DOTNET8" - finalValue = $"{newEntry} {CachedRuntimeEnvironment}"; - } - else - { - // Check if PTENV already exists in one pass - var containsPtenv = currentEnvValue.Contains("PTENV/"); - - if (containsPtenv) - { - // Just append the new entry: "existing PT/Assembly/1.0.0" - finalValue = $"{currentEnvValue} {newEntry}"; - } - else - { - // Append new entry + PTENV: "existing PT/Assembly/1.0.0 PTENV/AWS_LAMBDA_DOTNET8" - finalValue = $"{currentEnvValue} {newEntry} {CachedRuntimeEnvironment}"; - } - } - - SetEnvironmentVariable(envName, finalValue); - } - - /// - /// Parsing the name to conform with the required naming convention for the UserAgent header (PTFeature/Name/Version) - /// Fallback to Assembly Name on exception - /// - /// - /// - internal static string ParseAssemblyName(string assemblyName) - { - // Use cache to avoid repeated string operations - try - { - return ParsedAssemblyNameCache.GetOrAdd(assemblyName, name => - { - var lastDotIndex = name.LastIndexOf('.'); - if (lastDotIndex >= 0 && lastDotIndex < name.Length - 1) - { - var parsedName = name.Substring(lastDotIndex + 1); - return $"{Constants.FeatureContextIdentifier}/{parsedName}"; - } - - return $"{Constants.FeatureContextIdentifier}/{name}"; - }); - } - catch - { - return string.Empty; - } - } -} +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Core/SystemWrapper.cs b/libraries/src/AWS.Lambda.Powertools.Common/Core/SystemWrapper.cs new file mode 100644 index 000000000..8f42bda4c --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.Common/Core/SystemWrapper.cs @@ -0,0 +1,155 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System; +using System.IO; +using System.Text; + +namespace AWS.Lambda.Powertools.Common; + +/// +/// Class SystemWrapper. +/// Implements the +/// +/// +public class SystemWrapper : ISystemWrapper +{ + private static IPowertoolsEnvironment _powertoolsEnvironment; + + /// + /// The instance + /// + private static ISystemWrapper _instance; + + /// + /// Prevents a default instance of the class from being created. + /// + public SystemWrapper(IPowertoolsEnvironment powertoolsEnvironment) + { + _powertoolsEnvironment = powertoolsEnvironment; + _instance ??= this; + + // Clear AWS SDK Console injected parameters StdOut and StdErr + var standardOutput = new StreamWriter(Console.OpenStandardOutput()); + standardOutput.AutoFlush = true; + Console.SetOut(standardOutput); + var errordOutput = new StreamWriter(Console.OpenStandardError()); + errordOutput.AutoFlush = true; + Console.SetError(errordOutput); + } + + /// + /// Gets the instance. + /// + /// The instance. + public static ISystemWrapper Instance => _instance ??= new SystemWrapper(PowertoolsEnvironment.Instance); + + /// + /// Gets the environment variable. + /// + /// The variable. + /// System.String. + public string GetEnvironmentVariable(string variable) + { + return _powertoolsEnvironment.GetEnvironmentVariable(variable); + } + + /// + /// Logs the specified value. + /// + /// The value. + public void Log(string value) + { + Console.Write(value); + } + + /// + /// Logs the line. + /// + /// The value. + public void LogLine(string value) + { + Console.WriteLine(value); + } + + /// + /// Gets random number + /// + /// System.Double. + public double GetRandom() + { + return new Random().NextDouble(); + } + + /// + public void SetEnvironmentVariable(string variable, string value) + { + _powertoolsEnvironment.SetEnvironmentVariable(variable, value); + } + + /// + public void SetExecutionEnvironment(T type) + { + const string envName = Constants.AwsExecutionEnvironmentVariableName; + var envValue = new StringBuilder(); + var currentEnvValue = GetEnvironmentVariable(envName); + var assemblyName = ParseAssemblyName(_powertoolsEnvironment.GetAssemblyName(type)); + + // If there is an existing execution environment variable add the annotations package as a suffix. + if (!string.IsNullOrEmpty(currentEnvValue)) + { + // Avoid duplication - should not happen since the calling Instances are Singletons - defensive purposes + if (currentEnvValue.Contains(assemblyName)) + { + return; + } + + envValue.Append($"{currentEnvValue} "); + } + + var assemblyVersion = _powertoolsEnvironment.GetAssemblyVersion(type); + + envValue.Append($"{assemblyName}/{assemblyVersion}"); + + SetEnvironmentVariable(envName, envValue.ToString()); + } + + /// + public void SetOut(TextWriter writeTo) + { + Console.SetOut(writeTo); + } + + /// + /// Parsing the name to conform with the required naming convention for the UserAgent header (PTFeature/Name/Version) + /// Fallback to Assembly Name on exception + /// + /// + /// + private string ParseAssemblyName(string assemblyName) + { + try + { + var parsedName = assemblyName.Substring(assemblyName.LastIndexOf(".", StringComparison.Ordinal) + 1); + return $"{Constants.FeatureContextIdentifier}/{parsedName}"; + } + catch + { + //NOOP + } + + return $"{Constants.FeatureContextIdentifier}/{assemblyName}"; + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Common/InternalsVisibleTo.cs b/libraries/src/AWS.Lambda.Powertools.Common/InternalsVisibleTo.cs index 641de17cb..575d005f5 100644 --- a/libraries/src/AWS.Lambda.Powertools.Common/InternalsVisibleTo.cs +++ b/libraries/src/AWS.Lambda.Powertools.Common/InternalsVisibleTo.cs @@ -17,7 +17,6 @@ [assembly: InternalsVisibleTo("AWS.Lambda.Powertools.Logging")] [assembly: InternalsVisibleTo("AWS.Lambda.Powertools.Metrics")] -[assembly: InternalsVisibleTo("AWS.Lambda.Powertools.Tracing")] [assembly: InternalsVisibleTo("AWS.Lambda.Powertools.Idempotency")] [assembly: InternalsVisibleTo("AWS.Lambda.Powertools.Common.Tests")] [assembly: InternalsVisibleTo("AWS.Lambda.Powertools.Tracing.Tests")] diff --git a/libraries/src/AWS.Lambda.Powertools.Common/Tests/TestLoggerOutput.cs b/libraries/src/AWS.Lambda.Powertools.Common/Tests/TestLoggerOutput.cs deleted file mode 100644 index b5dded35c..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Common/Tests/TestLoggerOutput.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Text; - -namespace AWS.Lambda.Powertools.Common.Tests; - -/// -/// Test logger output -/// -public class TestLoggerOutput : IConsoleWrapper -{ - /// - /// Buffer for all the log messages written to the logger. - /// - private readonly StringBuilder _outputBuffer = new(); - - /// - /// Cleasr the output buffer. - /// - public void Clear() - { - _outputBuffer.Clear(); - } - - /// - /// Output the contents of the buffer. - /// - /// - public override string ToString() - { - return _outputBuffer.ToString(); - } - - /// - public void WriteLine(string message) - { - _outputBuffer.AppendLine(message); - } - - /// - public void Debug(string message) - { - _outputBuffer.AppendLine(message); - } - - /// - public void Error(string message) - { - _outputBuffer.AppendLine(message); - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore.csproj b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore.csproj deleted file mode 100644 index 5e5c66660..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore.csproj +++ /dev/null @@ -1,26 +0,0 @@ - - - - - AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore - Powertools for AWS Lambda (.NET) - Event Handler Bedrock Agent Function Resolver AspNetCore package. - AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore - AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore - net8.0 - false - enable - enable - - - - - - - - - - - - - - diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore/BedrockFunctionRegistration.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore/BedrockFunctionRegistration.cs deleted file mode 100644 index 7bc17dbdb..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore/BedrockFunctionRegistration.cs +++ /dev/null @@ -1,41 +0,0 @@ -namespace AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore; - -/// -/// Helper class for function registration with fluent API pattern. -/// -internal class BedrockFunctionRegistration -{ - private readonly BedrockAgentFunctionResolver _resolver; - - /// - /// Initializes a new instance of the class. - /// - /// The Bedrock agent function resolver. - public BedrockFunctionRegistration(BedrockAgentFunctionResolver resolver) - { - _resolver = resolver; - } - - /// - /// Adds a function to the Bedrock resolver. - /// - /// The name of the function. - /// The delegate handler. - /// Optional description of the function. - /// The function registration instance for method chaining. - /// - /// - /// app.MapBedrockFunction("GetWeather", (string city, int month) => - /// $"Weather forecast for {city} in month {month}: Warm and sunny"); - /// - /// app.MapBedrockFunction("Calculate", (int x, int y) => - /// $"Result: {x + y}"); - /// ); - /// - /// - public BedrockFunctionRegistration Add(string name, Delegate handler, string description = "") - { - _resolver.Tool(name, description, handler); - return this; - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore/BedrockMinimalApiExtensions.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore/BedrockMinimalApiExtensions.cs deleted file mode 100644 index ca9fd9ece..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore/BedrockMinimalApiExtensions.cs +++ /dev/null @@ -1,158 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Text.Json; -using System.Text.Json.Serialization; -using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; - -namespace AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.AspNetCore; - -// Source generation for JSON serialization -[JsonSerializable(typeof(BedrockFunctionRequest))] -internal partial class BedrockJsonContext : JsonSerializerContext -{ -} - -/// -/// Extension methods for registering Bedrock Agent Functions in ASP.NET Core Minimal API. -/// -public static class BedrockMinimalApiExtensions -{ - // Static flag to track if handler is mapped (thread-safe with volatile) - private static volatile bool _bedrockRequestHandlerMapped; - - // JSON options with case insensitivity - private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true - }; - - /// - /// Maps an individual Bedrock Agent function that will be called directly from the root endpoint. - /// The function name is extracted from the incoming request payload. - /// - /// The web application to configure. - /// The name of the function to register. - /// The delegate handler that implements the function. - /// Optional description of the function. - /// The web application instance. - /// - /// - /// // Register individual functions - /// app.MapBedrockFunction("GetWeather", (string city, int month) => - /// $"Weather forecast for {city} in month {month}: Warm and sunny"); - /// - /// app.MapBedrockFunction("Calculate", (int x, int y) => - /// $"Result: {x + y}"); - /// - /// - public static WebApplication MapBedrockFunction( - this WebApplication app, - string functionName, - Delegate handler, - string description = "") - { - // Get or create the resolver from services - var resolver = app.Services.GetService() - ?? new BedrockAgentFunctionResolver(); - - // Register the function with the resolver - resolver.Tool(functionName, description, handler); - - // Ensure we have a global handler for Bedrock requests - EnsureBedrockRequestHandler(app, resolver); - - return app; - } - - [UnconditionalSuppressMessage("AOT", "IL3050:RequiresDynamicCode", - Justification = "The handler implementation is controlled and AOT-compatible")] - [UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode", - Justification = "The handler implementation is controlled and trim-compatible")] - private static void EnsureBedrockRequestHandler(WebApplication app, BedrockAgentFunctionResolver resolver) - { - // Check if we've already mapped the handler (we only need to do this once) - if (_bedrockRequestHandlerMapped) - return; - - // Map the root endpoint to handle all Bedrock Agent Function requests - app.MapPost("/", [UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Handler is AOT-friendly")] - [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Handler is trim-friendly")] - async (HttpContext context) => - { - try - { - // Read the request body - string requestBody; - using (var reader = new StreamReader(context.Request.Body)) - { - requestBody = await reader.ReadToEndAsync(); - } - - // Use source-generated serialization for the request - var bedrockRequest = JsonSerializer.Deserialize(requestBody, - BedrockJsonContext.Default.BedrockFunctionRequest); - - if (bedrockRequest == null) - return Results.BadRequest("Invalid request format"); - - // Process the request through the resolver - var result = await resolver.ResolveAsync(bedrockRequest); - - // For the response, use the standard serializer with suppressed warnings - // This is more compatible with different response types - context.Response.ContentType = "application/json"; - await context.Response.WriteAsJsonAsync(result, JsonOptions); - return Results.Empty; - } - catch (Exception ex) - { - return Results.Problem($"Error processing Bedrock Agent request: {ex.Message}"); - } - }); - - // Mark that we've set up the handler - _bedrockRequestHandlerMapped = true; - } - - /// - /// Registers all methods from a class marked with BedrockFunctionTypeAttribute. - /// - /// The type containing tool methods marked with BedrockFunctionToolAttribute - /// The web application to configure. - /// The web application instance. - /// - /// - /// // Define your tool class - /// [BedrockFunctionType] - /// public class WeatherTools - /// { - /// [BedrockFunctionTool(Name = "GetWeather", Description = "Gets weather forecast")] - /// public static string GetWeather(string location, int days) - /// { - /// return $"Weather forecast for {location} for the next {days} days"; - /// } - /// } - /// - /// // Register all tools from the class - /// app.MapBedrockToolClass<WeatherTools>(); - /// - /// - public static WebApplication MapBedrockToolType<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T>( - this WebApplication app) - where T : class - { - // Get or create the resolver from services - var resolver = app.Services.GetService() - ?? new BedrockAgentFunctionResolver(); - - // Register the tool class - resolver.RegisterTool(); - - // Ensure we have a global handler for Bedrock requests - EnsureBedrockRequestHandler(app, resolver); - - return app; - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.csproj b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.csproj deleted file mode 100644 index b0a7db73a..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.csproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - - AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction - Powertools for AWS Lambda (.NET) - Event Handler Bedrock Agent Function Resolver package. - net8.0 - false - enable - enable - true - true - - - - - - - - - \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/BedrockAgentFunctionResolver.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/BedrockAgentFunctionResolver.cs deleted file mode 100644 index 4107a1b9d..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/BedrockAgentFunctionResolver.cs +++ /dev/null @@ -1,364 +0,0 @@ -using System.Text.Json.Serialization.Metadata; -using Amazon.Lambda.Core; -using AWS.Lambda.Powertools.Common; -using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; -using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Helpers; - -// ReSharper disable once CheckNamespace -namespace AWS.Lambda.Powertools.EventHandler.Resolvers -{ - /// - /// A resolver for Bedrock Agent functions that allows registering handlers for tool functions. - /// - /// - /// Basic usage: - /// - /// var resolver = new BedrockAgentFunctionResolver(); - /// resolver.Tool("GetWeather", (string city) => $"Weather in {city} is sunny"); - /// - /// // Lambda handler - /// public BedrockFunctionResponse FunctionHandler(BedrockFunctionRequest input, ILambdaContext context) - /// { - /// return resolver.Resolve(input, context); - /// } - /// - /// - public class BedrockAgentFunctionResolver - { - private readonly - Dictionary> - _handlers = new(); - - private readonly ParameterTypeValidator _parameterValidator = new(); - private readonly ResultConverter _resultConverter = new(); - private readonly ParameterMapper _parameterMapper; - - /// - /// Initializes a new instance of the class. - /// Optionally accepts a type resolver for JSON serialization. - /// - public BedrockAgentFunctionResolver(IJsonTypeInfoResolver? typeResolver = null) - { - _parameterMapper = new ParameterMapper(typeResolver); - PowertoolsEnvironment.Instance.SetExecutionEnvironment(this); - } - - /// - /// Checks if another tool can be registered, and logs a warning if the maximum limit is reached - /// or if a tool with the same name is already registered - /// - /// The name of the tool being registered - /// True if the tool can be registered, false if the maximum limit is reached - private bool CanRegisterTool(string name) - { - if (_handlers.ContainsKey(name)) - { - Console.WriteLine($"WARNING: Tool {name} already registered. Overwriting with new definition."); - } - - return true; - } - - /// - /// Registers a handler that directly accepts BedrockFunctionRequest and returns BedrockFunctionResponse - /// - /// The name of the tool function - /// The handler function that accepts input and context and returns output - /// Optional description of the tool function - /// The resolver instance for method chaining - public BedrockAgentFunctionResolver Tool( - string name, - Func handler, - string description = "") - { - ArgumentNullException.ThrowIfNull(handler); - - if (!CanRegisterTool(name)) - return this; - - _handlers[name] = handler; - return this; - } - - /// - /// Registers a handler that directly accepts BedrockFunctionRequest and returns BedrockFunctionResponse - /// - /// The name of the tool function - /// The handler function that accepts input and returns output - /// Optional description of the tool function - /// The resolver instance for method chaining - public BedrockAgentFunctionResolver Tool( - string name, - Func handler, - string description = "") - { - ArgumentNullException.ThrowIfNull(handler); - - if (!CanRegisterTool(name)) - return this; - - _handlers[name] = (input, _) => handler(input); - return this; - } - - /// - /// Registers a parameter-less handler that returns BedrockFunctionResponse - /// - /// The name of the tool function - /// The handler function that returns output - /// Optional description of the tool function - /// The resolver instance for method chaining - public BedrockAgentFunctionResolver Tool( - string name, - Func handler, - string description = "") - { - ArgumentNullException.ThrowIfNull(handler); - - if (!CanRegisterTool(name)) - return this; - - _handlers[name] = (_, _) => handler(); - return this; - } - - /// - /// Registers a parameter-less handler with automatic string conversion - /// - /// The name of the tool function - /// The handler function that returns a string - /// Optional description of the tool function - /// The resolver instance for method chaining - public BedrockAgentFunctionResolver Tool( - string name, - Func handler, - string description = "") - { - ArgumentNullException.ThrowIfNull(handler); - - if (!CanRegisterTool(name)) - return this; - - _handlers[name] = (input, _) => BedrockFunctionResponse.WithText( - handler(), - input.ActionGroup, - name, - input.SessionAttributes, - input.PromptSessionAttributes, - new Dictionary()); - return this; - } - - /// - /// Registers a parameter-less handler with automatic object conversion - /// - /// The name of the tool function - /// The handler function that returns an object - /// Optional description of the tool function - /// The resolver instance for method chaining - public BedrockAgentFunctionResolver Tool( - string name, - Func handler, - string description = "") - { - ArgumentNullException.ThrowIfNull(handler); - - if (!CanRegisterTool(name)) - return this; - - _handlers[name] = (input, _) => - { - var result = handler(); - return _resultConverter.ConvertToOutput(result, input); - }; - return this; - } - - /// - /// Registers a handler for a tool function with automatically converted return type (no description). - /// - /// The name of the tool function - /// The delegate handler function - /// The resolver instance for method chaining - public BedrockAgentFunctionResolver Tool( - string name, - Delegate handler) - { - return Tool(name, "", handler); - } - - /// - /// Registers a handler for a tool function with description and automatically converted return type. - /// - /// The name of the tool function - /// Description of the tool function - /// The delegate handler function - /// The resolver instance for method chaining - public BedrockAgentFunctionResolver Tool( - string name, - string description, - Delegate handler) - { - return Tool(name, description, handler); - } - - /// - /// Registers a handler for a tool function with typed return value (no description). - /// - /// The return type of the handler - /// The name of the tool function - /// The delegate handler function - /// The resolver instance for method chaining - public BedrockAgentFunctionResolver Tool( - string name, - Delegate handler) - { - return Tool(name, "", handler); - } - - /// - /// Registers a handler for a tool function with description and typed return value. - /// - /// The return type of the handler - /// The name of the tool function - /// Description of the tool function - /// The delegate handler function - /// The resolver instance for method chaining - public BedrockAgentFunctionResolver Tool( - string name, - string description, - Delegate handler) - { - ArgumentNullException.ThrowIfNull(handler); - - if (!CanRegisterTool(name)) - return this; - - _handlers[name] = RegisterToolHandler(handler, name); - return this; - } - - private Func RegisterToolHandler( - Delegate handler, string functionName) - { - return (input, context) => - { - try - { - // Map parameters from Bedrock input and DI - var serviceProvider = (this as DiBedrockAgentFunctionResolver)?.ServiceProvider; - var args = _parameterMapper.MapParameters(handler.Method, input, context, serviceProvider); - - // Execute the handler and process result - return ExecuteHandlerAndProcessResult(handler, args, input, context, functionName); - } - catch (Exception ex) - { - context?.Logger.LogError(ex.ToString()); - var innerException = ex.InnerException ?? ex; - return BedrockFunctionResponse.WithText( - $"Error when invoking tool: {innerException.Message}", - input.ActionGroup, - functionName, - input.SessionAttributes, - input.PromptSessionAttributes, - new Dictionary()); - } - }; - } - - private BedrockFunctionResponse ExecuteHandlerAndProcessResult( - Delegate handler, - object?[] args, - BedrockFunctionRequest input, - ILambdaContext? context, - string functionName) - { - try - { - // Execute the handler - var result = handler.DynamicInvoke(args); - - // Process various result types - return _resultConverter.ProcessResult(result, input, functionName, context); - } - catch (Exception ex) - { - context?.Logger.LogError(ex.ToString()); - var innerException = ex.InnerException ?? ex; - return BedrockFunctionResponse.WithText( - $"Error when invoking tool: {innerException.Message}", - input.ActionGroup, - functionName, - input.SessionAttributes, - input.PromptSessionAttributes, - new Dictionary()); - } - } - - /// - /// Resolves and processes a Bedrock Agent function invocation. - /// - /// The Bedrock Agent input containing the function name and parameters - /// Optional Lambda context - /// The output from the function execution - public BedrockFunctionResponse Resolve(BedrockFunctionRequest input, ILambdaContext? context = null) - { - return ResolveAsync(input, context).GetAwaiter().GetResult(); - } - - /// - /// Asynchronously resolves and processes a Bedrock Agent function invocation. - /// - /// The Bedrock Agent input containing the function name and parameters - /// Optional Lambda context - /// A task that completes with the output from the function execution - public async Task ResolveAsync(BedrockFunctionRequest input, - ILambdaContext? context = null) - { - return await Task.FromResult(HandleEvent(input, context)); - } - - private BedrockFunctionResponse HandleEvent(BedrockFunctionRequest input, ILambdaContext? context) - { - if (string.IsNullOrEmpty(input.Function)) - { - return BedrockFunctionResponse.WithText( - "No tool specified in the request", - input.ActionGroup, - "", - input.SessionAttributes, - input.PromptSessionAttributes, - new Dictionary()); - } - - if (_handlers.TryGetValue(input.Function, out var handler)) - { - try - { - return handler(input, context); - } - catch (Exception ex) - { - context?.Logger.LogError(ex.ToString()); - return BedrockFunctionResponse.WithText( - $"Error when invoking tool: {ex.Message}", - input.ActionGroup, - input.Function, - input.SessionAttributes, - input.PromptSessionAttributes, - new Dictionary()); - } - } - - context?.Logger.LogWarning($"Tool {input.Function} has not been registered."); - return BedrockFunctionResponse.WithText( - $"Error: Tool {input.Function} has not been registered in handler", - input.ActionGroup, - input.Function, - input.SessionAttributes, - input.PromptSessionAttributes, - new Dictionary()); - } - } -} diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/BedrockAgentFunctionResolverExtensions.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/BedrockAgentFunctionResolverExtensions.cs deleted file mode 100644 index a00b95459..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/BedrockAgentFunctionResolverExtensions.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; -using System.Reflection; -using System.Text.Json.Serialization.Metadata; -using Microsoft.Extensions.DependencyInjection; - -// ReSharper disable once CheckNamespace -namespace AWS.Lambda.Powertools.EventHandler.Resolvers -{ - /// - /// Extension methods for Bedrock Agent Function Resolver. - /// - public static class BedrockResolverExtensions - { - /// - /// Registers a Bedrock Agent Function Resolver with dependency injection support. - /// - /// The service collection to add the resolver to. - /// - /// The updated service collection. - /// - /// - /// public void ConfigureServices(IServiceCollection services) - /// { - /// services.AddBedrockResolver(); - /// - /// // Now you can inject BedrockAgentFunctionResolver into your services - /// } - /// - /// - public static IServiceCollection AddBedrockResolver( - this IServiceCollection services, - IJsonTypeInfoResolver? typeResolver = null) - { - services.AddSingleton(sp => - new DiBedrockAgentFunctionResolver(sp, typeResolver)); - return services; - } - - /// - /// Registers tools from a type marked with BedrockFunctionTypeAttribute. - /// - /// The type containing tool methods marked with BedrockFunctionToolAttribute - /// The resolver to register tools with - /// The resolver for method chaining - /// - /// - /// // Define your tool class - /// [BedrockFunctionType] - /// public class WeatherTools - /// { - /// [BedrockFunctionTool(Name = "GetWeather", Description = "Gets weather forecast")] - /// public static string GetWeather(string location, int days) - /// { - /// return $"Weather forecast for {location} for the next {days} days"; - /// } - /// } - /// - /// // Register the tools - /// var resolver = new BedrockAgentFunctionResolver(); - /// resolver.RegisterTool<WeatherTools>(); - /// - /// - public static BedrockAgentFunctionResolver RegisterTool< - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T>( - this BedrockAgentFunctionResolver resolver) - where T : class - { - var type = typeof(T); - - // Check if class has the BedrockFunctionType attribute - if (!type.IsDefined(typeof(BedrockFunctionTypeAttribute), false)) - return resolver; - - // Look at all static methods with the tool attribute - foreach (var method in type.GetMethods(BindingFlags.Static | BindingFlags.Public)) - { - var attr = method.GetCustomAttribute(); - if (attr == null) continue; - - string toolName = attr.Name ?? method.Name; - string description = attr.Description ?? - string.Empty; - - // Create delegate from the static method - var del = Delegate.CreateDelegate( - GetDelegateType(method), - method); - - // Call the Tool method directly instead of using reflection - resolver.Tool(toolName, description, del); - } - - return resolver; - } - - private static Type GetDelegateType(MethodInfo method) - { - var parameters = method.GetParameters(); - var parameterTypes = parameters.Select(p => p.ParameterType).ToList(); - parameterTypes.Add(method.ReturnType); - - return Expression.GetDelegateType(parameterTypes.ToArray()); - } - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/BedrockFunctionResolverContext.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/BedrockFunctionResolverContext.cs deleted file mode 100644 index 0c36c1d1a..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/BedrockFunctionResolverContext.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Text.Json.Serialization; - -// ReSharper disable once CheckNamespace -namespace AWS.Lambda.Powertools.EventHandler.Resolvers; - -[JsonSerializable(typeof(string[]))] -[JsonSerializable(typeof(int[]))] -[JsonSerializable(typeof(long[]))] -[JsonSerializable(typeof(double[]))] -[JsonSerializable(typeof(bool[]))] -[JsonSerializable(typeof(decimal[]))] -internal partial class BedrockFunctionResolverContext : JsonSerializerContext -{ -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/BedrockFunctionToolAttribute.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/BedrockFunctionToolAttribute.cs deleted file mode 100644 index daf90f21d..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/BedrockFunctionToolAttribute.cs +++ /dev/null @@ -1,45 +0,0 @@ -// ReSharper disable once CheckNamespace -namespace AWS.Lambda.Powertools.EventHandler.Resolvers; - -/// -/// Marks a method as a Bedrock Agent function tool. -/// -/// -/// -/// [BedrockFunctionTool(Name = "GetWeather", Description = "Gets the weather for a location")] -/// public static string GetWeather(string location, int days) -/// { -/// return $"Weather forecast for {location} for the next {days} days"; -/// } -/// -/// -[AttributeUsage(AttributeTargets.Method)] -public class BedrockFunctionToolAttribute : Attribute -{ - /// - /// The name of the tool. If not specified, the method name will be used. - /// - public string? Name { get; set; } - - /// - /// The description of the tool. Used to provide context about the tool's functionality. - /// - public string? Description { get; set; } -} - -/// -/// Marks a class as containing Bedrock Agent function tools. -/// -/// -/// -/// [BedrockFunctionType] -/// public class WeatherTools -/// { -/// // Methods that can be registered as tools -/// } -/// -/// -[AttributeUsage(AttributeTargets.Class)] -public class BedrockFunctionTypeAttribute : Attribute -{ -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/DiBedrockAgentFunctionResolver.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/DiBedrockAgentFunctionResolver.cs deleted file mode 100644 index 82064d433..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/DiBedrockAgentFunctionResolver.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Text.Json.Serialization.Metadata; - -namespace AWS.Lambda.Powertools.EventHandler.Resolvers; - -/// -/// Extended Bedrock Agent Function Resolver with dependency injection support. -/// -internal class DiBedrockAgentFunctionResolver : BedrockAgentFunctionResolver -{ - /// - /// Gets the service provider used for dependency injection. - /// - public IServiceProvider ServiceProvider { get; } - - /// - /// Initializes a new instance of the class. - /// - /// The service provider for dependency injection. - /// - public DiBedrockAgentFunctionResolver(IServiceProvider serviceProvider, IJsonTypeInfoResolver? typeResolver = null) - : base(typeResolver) - { - ServiceProvider = serviceProvider; - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Helpers/ParameterAccessor.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Helpers/ParameterAccessor.cs deleted file mode 100644 index abcd22238..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Helpers/ParameterAccessor.cs +++ /dev/null @@ -1,139 +0,0 @@ -using System.Globalization; - -namespace AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Helpers; - -/// -/// Provides strongly-typed access to the parameters of an agent function call. -/// -internal class ParameterAccessor -{ - private readonly List _parameters; - - internal ParameterAccessor(List? parameters) - { - _parameters = parameters ?? new List(); - } - - /// - /// Gets a parameter value by name with type conversion. - /// - public T Get(string name) - { - var parameter = _parameters.FirstOrDefault(p => string.Equals(p.Name, name, StringComparison.OrdinalIgnoreCase)); - if (parameter == null || parameter.Value == null) - { - return default!; - } - - return ConvertParameter(parameter); - } - - /// - /// Gets a parameter value by index with type conversion. - /// - public T GetAt(int index) - { - if (index < 0 || index >= _parameters.Count) - { - return default!; - } - - var parameter = _parameters[index]; - if (parameter.Value == null) - { - return default!; - } - - return ConvertParameter(parameter); - } - - /// - /// Gets a parameter value by name with fallback to a default value. - /// - public T GetOrDefault(string name, T defaultValue) - { - var parameter = _parameters.FirstOrDefault(p => string.Equals(p.Name, name, StringComparison.OrdinalIgnoreCase)); - if (parameter == null || parameter.Value == null) - { - return defaultValue; - } - - try - { - var result = ConvertParameter(parameter); - // If conversion returns default value but we have a non-null parameter, - // that means conversion failed, so return the provided default value - if (EqualityComparer.Default.Equals(result, default) && parameter.Value != null) - { - return defaultValue; - } - return result; - } - catch - { - return defaultValue; - } - } - - private static T ConvertParameter(Parameter? parameter) - { - if (parameter == null || parameter.Value == null) - { - return default!; - } - - // Handle different types explicitly for AOT compatibility - if (typeof(T) == typeof(string)) - { - return (T)(object)parameter.Value; - } - - if (typeof(T) == typeof(int) || typeof(T) == typeof(int?)) - { - if (int.TryParse(parameter.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out int result)) - { - return (T)(object)result; - } - return default!; - } - - if (typeof(T) == typeof(double) || typeof(T) == typeof(double?)) - { - if (double.TryParse(parameter.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out double result)) - { - return (T)(object)result; - } - return default!; - } - - if (typeof(T) == typeof(bool) || typeof(T) == typeof(bool?)) - { - if (bool.TryParse(parameter.Value, out bool result)) - { - return (T)(object)result; - } - return default!; - } - - if (typeof(T) == typeof(long) || typeof(T) == typeof(long?)) - { - if (long.TryParse(parameter.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out long result)) - { - return (T)(object)result; - } - return default!; - } - - if (typeof(T) == typeof(decimal) || typeof(T) == typeof(decimal?)) - { - if (decimal.TryParse(parameter.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out decimal result)) - { - return (T)(object)result; - } - return default!; - } - - // Return default for array and complex types - return default!; - } -} diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Helpers/ParameterMapper.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Helpers/ParameterMapper.cs deleted file mode 100644 index d5eb9da02..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Helpers/ParameterMapper.cs +++ /dev/null @@ -1,194 +0,0 @@ -using System.Reflection; -using System.Text.Json; -using System.Text.Json.Serialization.Metadata; -using Amazon.Lambda.Core; -using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; - -namespace AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Helpers -{ - /// - /// Maps parameters for Bedrock Agent function handlers - /// - internal class ParameterMapper - { - private readonly ParameterTypeValidator _validator = new(); - private readonly IJsonTypeInfoResolver? _typeResolver; - - public ParameterMapper(IJsonTypeInfoResolver? typeResolver = null) - { - _typeResolver = typeResolver; - } - - /// - /// Maps parameters for a handler method from a Bedrock function request - /// - /// The handler method - /// The Bedrock function request - /// The Lambda context - /// Optional service provider for dependency injection - /// Array of arguments to pass to the handler - public object?[] MapParameters( - MethodInfo methodInfo, - BedrockFunctionRequest input, - ILambdaContext? context, - IServiceProvider? serviceProvider) - { - var parameters = methodInfo.GetParameters(); - var args = new object?[parameters.Length]; - var accessor = new ParameterAccessor(input.Parameters); - - for (var i = 0; i < parameters.Length; i++) - { - var parameter = parameters[i]; - var paramType = parameter.ParameterType; - - if (paramType == typeof(ILambdaContext)) - { - args[i] = context; - continue; // Skip further processing for this parameter - } - else if (paramType == typeof(BedrockFunctionRequest)) - { - args[i] = input; - continue; // Skip further processing for this parameter - } - - // Try to deserialize custom complex type from InputText - if (!string.IsNullOrEmpty(input.InputText) && - !paramType.IsPrimitive && - paramType != typeof(string) && - !paramType.IsEnum) - { - try - { - var options = new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true - }; - - if (_typeResolver != null) - { - options.TypeInfoResolver = _typeResolver; - - // Get the JsonTypeInfo for the parameter type - var jsonTypeInfo = _typeResolver.GetTypeInfo(paramType, options); - if (jsonTypeInfo != null) - { - // Use the AOT-friendly overload with JsonTypeInfo - args[i] = JsonSerializer.Deserialize(input.InputText, jsonTypeInfo); - - if (args[i] != null) - { - continue; - } - } - } - else - { - // Fallback to non-AOT deserialization with warning -#pragma warning disable IL2026, IL3050 - args[i] = JsonSerializer.Deserialize(input.InputText, paramType, options); -#pragma warning restore IL2026, IL3050 - - if (args[i] != null) - { - continue; - } - } - } - catch - { - // Deserialization failed, continue to regular parameter mapping - } - } - - if (_validator.IsBedrockParameter(paramType)) - { - args[i] = MapBedrockParameter(paramType, parameter.Name ?? $"arg{i}", accessor); - } - else if (serviceProvider != null) - { - // Resolve from DI - args[i] = serviceProvider.GetService(paramType); - } - } - - return args; - } - - private object? MapBedrockParameter(Type paramType, string paramName, ParameterAccessor accessor) - { - // Array parameter handling - if (paramType.IsArray) - { - return MapArrayParameter(paramType, paramName, accessor); - } - - // Scalar parameter handling - return MapScalarParameter(paramType, paramName, accessor); - } - - private object? MapArrayParameter(Type paramType, string paramName, ParameterAccessor accessor) - { - var jsonArrayStr = accessor.Get(paramName); - - if (string.IsNullOrEmpty(jsonArrayStr)) - { - return null; - } - - try - { - // AOT-compatible deserialization using source generation - if (paramType == typeof(string[])) - return JsonSerializer.Deserialize(jsonArrayStr, BedrockFunctionResolverContext.Default.StringArray); - if (paramType == typeof(int[])) - return JsonSerializer.Deserialize(jsonArrayStr, BedrockFunctionResolverContext.Default.Int32Array); - if (paramType == typeof(long[])) - return JsonSerializer.Deserialize(jsonArrayStr, BedrockFunctionResolverContext.Default.Int64Array); - if (paramType == typeof(double[])) - return JsonSerializer.Deserialize(jsonArrayStr, BedrockFunctionResolverContext.Default.DoubleArray); - if (paramType == typeof(bool[])) - return JsonSerializer.Deserialize(jsonArrayStr, - BedrockFunctionResolverContext.Default.BooleanArray); - if (paramType == typeof(decimal[])) - return JsonSerializer.Deserialize(jsonArrayStr, - BedrockFunctionResolverContext.Default.DecimalArray); - } - catch (JsonException) - { - // Return null on error - } - - return null; - } - - private object? MapScalarParameter(Type paramType, string paramName, ParameterAccessor accessor) - { - if (paramType == typeof(string)) - return accessor.Get(paramName); - if (paramType == typeof(int)) - return accessor.Get(paramName); - if (paramType == typeof(long)) - return accessor.Get(paramName); - if (paramType == typeof(double)) - return accessor.Get(paramName); - if (paramType == typeof(bool)) - return accessor.Get(paramName); - if (paramType == typeof(decimal)) - return accessor.Get(paramName); - if (paramType == typeof(DateTime)) - return accessor.Get(paramName); - if (paramType == typeof(Guid)) - return accessor.Get(paramName); - if (paramType.IsEnum) - { - // For enums, get as string and parse - var strValue = accessor.Get(paramName); - return !string.IsNullOrEmpty(strValue) ? Enum.Parse(paramType, strValue) : null; - } - - return null; - } - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Helpers/ParameterTypeValidator.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Helpers/ParameterTypeValidator.cs deleted file mode 100644 index 064aa7d65..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Helpers/ParameterTypeValidator.cs +++ /dev/null @@ -1,35 +0,0 @@ -namespace AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Helpers -{ - /// - /// Validates parameter types for Bedrock Agent functions - /// - internal class ParameterTypeValidator - { - private static readonly HashSet BedrockParameterTypes = new() - { - typeof(string), - typeof(int), - typeof(long), - typeof(double), - typeof(bool), - typeof(decimal), - typeof(DateTime), - typeof(Guid), - typeof(string[]), - typeof(int[]), - typeof(long[]), - typeof(double[]), - typeof(bool[]), - typeof(decimal[]) - }; - - /// - /// Checks if a type is a valid Bedrock parameter type - /// - /// The type to check - /// True if the type is valid for Bedrock parameters - public bool IsBedrockParameter(Type type) => - BedrockParameterTypes.Contains(type) || type.IsEnum || - (type.IsArray && BedrockParameterTypes.Contains(type.GetElementType()!)); - } -} diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Helpers/ResultConverter.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Helpers/ResultConverter.cs deleted file mode 100644 index 4a68f97e1..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Helpers/ResultConverter.cs +++ /dev/null @@ -1,222 +0,0 @@ -using System.Globalization; -using Amazon.Lambda.Core; -using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; - -namespace AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Helpers -{ - /// - /// Converts handler results to BedrockFunctionResponse - /// - internal class ResultConverter - { - /// - /// Processes results from handler functions and converts to BedrockFunctionResponse - /// - public BedrockFunctionResponse ProcessResult( - object? result, - BedrockFunctionRequest input, - string functionName, - ILambdaContext? context) - { - // Direct return for BedrockFunctionResponse - if (result is BedrockFunctionResponse output) - return EnsureResponseMetadata(output, input, functionName); - - // Handle async results with specific type checks (AOT-compatible) - if (result is Task outputTask) - return EnsureResponseMetadata(outputTask.Result, input, functionName); - - // Handle various Task types - if (result is Task task) - { - return HandleTaskResult(task, input); - } - - // Handle regular (non-task) results - return ConvertToOutput(result, input); - } - - private BedrockFunctionResponse HandleTaskResult(Task task, BedrockFunctionRequest input) - { - // For Task - if (task is Task stringTask) - return ConvertToOutput((TResult)(object)stringTask.Result, input); - - // For Task - if (task is Task intTask) - return ConvertToOutput((TResult)(object)intTask.Result, input); - - // For Task - if (task is Task boolTask) - return ConvertToOutput((TResult)(object)boolTask.Result, input); - - // For Task - if (task is Task doubleTask) - return ConvertToOutput((TResult)(object)doubleTask.Result, input); - - // For Task - if (task is Task longTask) - return ConvertToOutput((TResult)(object)longTask.Result, input); - - // For Task - if (task is Task decimalTask) - return ConvertToOutput((TResult)(object)decimalTask.Result, input); - - // For Task - if (task is Task dateTimeTask) - return ConvertToOutput((TResult)(object)dateTimeTask.Result, input); - - // For Task - if (task is Task guidTask) - return ConvertToOutput((TResult)(object)guidTask.Result, input); - - // For Task - if (task is Task objectTask) - return ConvertToOutput((TResult)objectTask.Result, input); - - // For regular Task with no result - task.GetAwaiter().GetResult(); - return BedrockFunctionResponse.WithText( - string.Empty, - input.ActionGroup, - input.Function, - input.SessionAttributes, - input.PromptSessionAttributes, - new Dictionary()); - } - - /// - /// Converts a result to a BedrockFunctionResponse - /// - public BedrockFunctionResponse ConvertToOutput(T result, BedrockFunctionRequest input) - { - var function = input.Function; - - if (EqualityComparer.Default.Equals(result, default(T))) - { - return CreateEmptyResponse(input); - } - - // If result is already a BedrockFunctionResponse, ensure metadata is set - if (result is BedrockFunctionResponse output) - { - return EnsureResponseMetadata(output, input, function); - } - - // Handle primitive types - return ConvertPrimitiveToOutput(result, input); - } - - private BedrockFunctionResponse ConvertPrimitiveToOutput(T result, BedrockFunctionRequest input) - { - var actionGroup = input.ActionGroup; - var function = input.Function; - - // For primitive types and strings, convert to string - if (result is string str) - { - return BedrockFunctionResponse.WithText( - str, - actionGroup, - function, - input.SessionAttributes, - input.PromptSessionAttributes, - new Dictionary()); - } - - if (result is int intVal) - { - return BedrockFunctionResponse.WithText( - intVal.ToString(CultureInfo.InvariantCulture), - actionGroup, - function, - input.SessionAttributes, - input.PromptSessionAttributes, - new Dictionary()); - } - - if (result is double doubleVal) - { - return BedrockFunctionResponse.WithText( - doubleVal.ToString(CultureInfo.InvariantCulture), - actionGroup, - function, - input.SessionAttributes, - input.PromptSessionAttributes, - new Dictionary()); - } - - if (result is bool boolVal) - { - return BedrockFunctionResponse.WithText( - boolVal.ToString(), - actionGroup, - function, - input.SessionAttributes, - input.PromptSessionAttributes, - new Dictionary()); - } - - if (result is long longVal) - { - return BedrockFunctionResponse.WithText( - longVal.ToString(CultureInfo.InvariantCulture), - actionGroup, - function, - input.SessionAttributes, - input.PromptSessionAttributes, - new Dictionary()); - } - - if (result is decimal decimalVal) - { - return BedrockFunctionResponse.WithText( - decimalVal.ToString(CultureInfo.InvariantCulture), - actionGroup, - function, - input.SessionAttributes, - input.PromptSessionAttributes, - new Dictionary()); - } - - // For any other type, use ToString() - return BedrockFunctionResponse.WithText( - result?.ToString() ?? string.Empty, - actionGroup, - function, - input.SessionAttributes, - input.PromptSessionAttributes, - new Dictionary()); - } - - private BedrockFunctionResponse CreateEmptyResponse(BedrockFunctionRequest input) - { - return BedrockFunctionResponse.WithText( - string.Empty, - input.ActionGroup, - input.Function, - input.SessionAttributes, - input.PromptSessionAttributes, - new Dictionary()); - } - - private BedrockFunctionResponse EnsureResponseMetadata( - BedrockFunctionResponse response, - BedrockFunctionRequest input, - string functionName) - { - // If the action group or function are not set in the output, use the provided values - if (string.IsNullOrEmpty(response.Response.ActionGroup)) - { - response.Response.ActionGroup = input.ActionGroup; - } - - if (string.IsNullOrEmpty(response.Response.Function)) - { - response.Response.Function = functionName; - } - - return response; - } - } -} diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/InternalsVisibleTo.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/InternalsVisibleTo.cs deleted file mode 100644 index a4ee0e7a5..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/InternalsVisibleTo.cs +++ /dev/null @@ -1,3 +0,0 @@ -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("AWS.Lambda.Powertools.EventHandler.Tests")] \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/Agent.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/Agent.cs deleted file mode 100644 index eb0a3eb49..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/Agent.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Text.Json.Serialization; - -namespace AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; - -/// -/// Contains information about the name, ID, alias, and version of the agent that the action group belongs to. -/// -public class Agent -{ - /// - /// Gets or sets the name of the agent. - /// - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; - - /// - /// Gets or sets the version of the agent. - /// - [JsonPropertyName("version")] - public string Version { get; set; } = string.Empty; - - /// - /// Gets or sets the ID of the agent. - /// - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; - - /// - /// Gets or sets the alias of the agent. - /// - [JsonPropertyName("alias")] - public string Alias { get; set; } = string.Empty; -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/BedrockFunctionRequest.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/BedrockFunctionRequest.cs deleted file mode 100644 index 7f7e8ae7a..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/BedrockFunctionRequest.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System.Text.Json.Serialization; - -namespace AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; - -/// -/// Represents the input for a Bedrock Agent function. -/// -public class BedrockFunctionRequest -{ - /// - /// The version of the message that identifies the format of the event data going into the Lambda function and the expected format of the response from a Lambda function. Amazon Bedrock only supports version 1.0. - /// - [JsonPropertyName("messageVersion")] - public string MessageVersion { get; set; } = "1.0"; - - /// - /// The name of the function as defined in the function details for the action group. - /// - [JsonPropertyName("function")] - public string Function { get; set; } = string.Empty; - - /// - /// Contains a list of objects. Each object contains the name, type, and value of a parameter in the API operation, as defined in the OpenAPI schema, or in the function. - /// - [JsonPropertyName("parameters")] - public List Parameters { get; set; } = new List(); - - /// - /// The unique identifier of the agent session. - /// - [JsonPropertyName("sessionId")] - public string SessionId { get; set; } = string.Empty; - - /// - /// Contains information about the name, ID, alias, and version of the agent that the action group belongs to. - /// - [JsonPropertyName("agent")] - public Agent? Agent { get; set; } - - /// - /// The name of the action group. - /// - [JsonPropertyName("actionGroup")] - public string ActionGroup { get; set; } = string.Empty; - - /// - /// Contains session attributes and their values. These attributes are stored over a session and provide context for the agent. - /// For more information, see Session and prompt session attributes. - /// - [JsonPropertyName("sessionAttributes")] - public Dictionary SessionAttributes { get; set; } = new Dictionary(); - - /// - /// Contains prompt session attributes and their values. These attributes are stored over a turn and provide context for the agent. - /// - [JsonPropertyName("promptSessionAttributes")] - public Dictionary PromptSessionAttributes { get; set; } = new Dictionary(); - - /// - /// The user input for the conversation turn. - /// - [JsonPropertyName("inputText")] - public string InputText { get; set; } = string.Empty; -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/BedrockFunctionResponse.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/BedrockFunctionResponse.cs deleted file mode 100644 index e606839c6..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/BedrockFunctionResponse.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System.Text.Json.Serialization; - -namespace AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; - -/// -/// The version of the message that identifies the format of the event data going into the Lambda function and the expected format of the response from a Lambda function. Amazon Bedrock only supports version 1.0. -/// -public class BedrockFunctionResponse -{ - /// - /// Gets or sets the message version. - /// - [JsonPropertyName("messageVersion")] - public string MessageVersion { get; } = "1.0"; - - /// - /// Gets or sets the response. - /// - [JsonPropertyName("response")] - public Response Response { get; set; } = new Response(); - - /// - /// Contains session attributes and their values. For more information, Session and prompt session attributes. - /// - [JsonPropertyName("sessionAttributes")] - public Dictionary SessionAttributes { get; set; } = new Dictionary(); - - /// - /// Contains prompt attributes and their values. For more information, Session and prompt session attributes. - /// - [JsonPropertyName("promptSessionAttributes")] - public Dictionary PromptSessionAttributes { get; set; } = new Dictionary(); - - /// - /// Contains a list of query configurations for knowledge bases attached to the agent. For more information, Knowledge base retrieval configurations. - /// - [JsonPropertyName("knowledgeBasesConfiguration")] - public Dictionary KnowledgeBasesConfiguration { get; set; } = new Dictionary(); - - - /// - /// Creates a new instance of BedrockFunctionResponse with the specified text. - /// - public static BedrockFunctionResponse WithText( - string? text, - string actionGroup = "", - string function = "", - Dictionary? sessionAttributes = null, - Dictionary? promptSessionAttributes = null, - Dictionary? knowledgeBasesConfiguration = null) - { - return new BedrockFunctionResponse - { - Response = new Response - { - ActionGroup = actionGroup, - Function = function, - FunctionResponse = new FunctionResponse - { - ResponseBody = new ResponseBody - { - Text = new TextBody { Body = text ?? string.Empty } - } - } - }, - SessionAttributes = sessionAttributes ?? new Dictionary(), - PromptSessionAttributes = promptSessionAttributes ?? new Dictionary(), - KnowledgeBasesConfiguration = knowledgeBasesConfiguration ?? new Dictionary() - }; - } -} diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/FunctionResponse.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/FunctionResponse.cs deleted file mode 100644 index 5fd2a5d33..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/FunctionResponse.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Text.Json.Serialization; -// ReSharper disable InconsistentNaming -#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member - -namespace AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; - -/// -/// Represents the function response part of a Response. -/// -public class FunctionResponse -{ - /// - /// Contains an object that defines the response from execution of the function. The key is the content type (currently only TEXT is supported) and the value is an object containing the body of the response. - /// - [JsonPropertyName("responseBody")] - public ResponseBody ResponseBody { get; set; } = new ResponseBody(); - - /// - /// (Optional) – Set to one of the following states to define the agent's behavior after processing the action: - /// - /// FAILURE – The agent throws a DependencyFailedException for the current session. Applies when the function execution fails because of a dependency failure. - /// REPROMPT – The agent passes a response string to the model to reprompt it. Applies when the function execution fails because of invalid input. - /// - [JsonPropertyName("responseState")] - [JsonConverter(typeof(JsonStringEnumConverter))] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public ResponseState? ResponseState { get; set; } -} - -/// -/// Represents the response state of a function response. -/// -public enum ResponseState -{ - FAILURE, - REPROMPT -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/Parameter.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/Parameter.cs deleted file mode 100644 index 5e5a65ee7..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/Parameter.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Text.Json.Serialization; - -// ReSharper disable once CheckNamespace -namespace AWS.Lambda.Powertools.EventHandler.Resolvers -{ - /// - /// Represents a parameter for a Bedrock Agent function. - /// - public class Parameter - { - /// - /// Gets or sets the name of the parameter. - /// - [JsonPropertyName("name")] - public string Name { get; set; } = string.Empty; - - /// - /// Gets or sets the type of the parameter. - /// - [JsonPropertyName("type")] - public string Type { get; set; } = string.Empty; - - /// - /// Gets or sets the value of the parameter. - /// - [JsonPropertyName("value")] - public string Value { get; set; } = string.Empty; - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/Response.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/Response.cs deleted file mode 100644 index 5d2e76a7a..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/Response.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Text.Json.Serialization; - -namespace AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; - -/// -/// Represents the response part of an BedrockFunctionResponse. -/// -public class Response -{ - /// - /// Gets or sets the action group. - /// - [JsonPropertyName("actionGroup")] - public string ActionGroup { get; internal set; } = string.Empty; - - /// - /// Gets or sets the function. - /// - [JsonPropertyName("function")] - public string Function { get; internal set; } = string.Empty; - - /// - /// Gets or sets the function response. - /// - [JsonPropertyName("functionResponse")] - public FunctionResponse FunctionResponse { get; set; } = new FunctionResponse(); -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/ResponseBody.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/ResponseBody.cs deleted file mode 100644 index 20bc59c2d..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/ResponseBody.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Text.Json.Serialization; - -namespace AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; - -/// -/// Represents the response body part of a FunctionResponse. -/// -public class ResponseBody -{ - /// - /// Gets or sets the text body. - /// - [JsonPropertyName("TEXT")] - public TextBody Text { get; set; } = new TextBody(); -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/TextBody.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/TextBody.cs deleted file mode 100644 index 8e9a41c76..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction/Models/TextBody.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Text.Json.Serialization; - -namespace AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; - -/// -/// Represents the text body part of a ResponseBody. -/// -public class TextBody -{ - /// - /// Gets or sets the body text. - /// - [JsonPropertyName("body")] - public string Body { get; set; } = string.Empty; -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler/AWS.Lambda.Powertools.EventHandler.csproj b/libraries/src/AWS.Lambda.Powertools.EventHandler/AWS.Lambda.Powertools.EventHandler.csproj deleted file mode 100644 index 04f632feb..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler/AWS.Lambda.Powertools.EventHandler.csproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - - AWS.Lambda.Powertools.EventHandler - Powertools for AWS Lambda (.NET) - Event Handler package. - AWS.Lambda.Powertools.EventHandler - AWS.Lambda.Powertools.EventHandler - net8.0 - false - enable - enable - true - - - - - - - - diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncCognitoIdentity.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncCognitoIdentity.cs deleted file mode 100644 index e59f79496..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncCognitoIdentity.cs +++ /dev/null @@ -1,42 +0,0 @@ -namespace AWS.Lambda.Powertools.EventHandler.AppSyncEvents; - -/// -/// Represents Amazon Cognito User Pools authorization identity for AppSync -/// -public class AppSyncCognitoIdentity -{ - /// - /// The source IP address of the caller received by AWS AppSync - /// - public List? SourceIp { get; set; } - - /// - /// The username of the authenticated user - /// - public string? Username { get; set; } - - /// - /// The UUID of the authenticated user - /// - public string? Sub { get; set; } - - /// - /// The claims that the user has - /// - public Dictionary? Claims { get; set; } - - /// - /// The default authorization strategy for this caller (ALLOW or DENY) - /// - public string? DefaultAuthStrategy { get; set; } - - /// - /// List of OIDC groups - /// - public List? Groups { get; set; } - - /// - /// The token issuer - /// - public string? Issuer { get; set; } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncEvent.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncEvent.cs deleted file mode 100644 index 9ed03423a..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncEvent.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Text.Json.Serialization; - -namespace AWS.Lambda.Powertools.EventHandler.AppSyncEvents; - -/// -/// Represents an event from AWS AppSync. -/// -public class AppSyncEvent -{ - /// - /// Payload data when operation succeeds - /// - [JsonPropertyName("payload")] - public Dictionary? Payload { get; set; } - - /// - /// Error message when operation fails - /// - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] - [JsonPropertyName("error")] - public string? Error { get; set; } - - /// - /// Unique identifier for the event - /// This Id is provided by AppSync and needs to be preserved. - /// - [JsonPropertyName("id")] - public string? Id { get; set; } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncEventsOperation.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncEventsOperation.cs deleted file mode 100644 index ffb970dde..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncEventsOperation.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Text.Json.Serialization; - -namespace AWS.Lambda.Powertools.EventHandler.AppSyncEvents; - -/// -/// Represents the operation type for AppSync events. -/// -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum AppSyncEventsOperation -{ - /// - /// Represents a subscription operation. - /// - Subscribe, - - /// - /// Represents a publish operation. - /// - Publish -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncEventsRequest.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncEventsRequest.cs deleted file mode 100644 index 46097d44a..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncEventsRequest.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System.Text.Json.Serialization; - -namespace AWS.Lambda.Powertools.EventHandler.AppSyncEvents; - -/// -/// Represents the event payload received from AWS AppSync. -/// -public class AppSyncEventsRequest -{ - /// - /// An object that contains information about the caller. - /// Returns null for API_KEY authorization. - /// Returns AppSyncIamIdentity for AWS_IAM authorization. - /// Returns AppSyncCognitoIdentity for AMAZON_COGNITO_USER_POOLS authorization. - /// For AWS_LAMBDA authorization, returns the object returned by your Lambda authorizer function. - /// - /// - /// The Identity object type depends on the authorization mode: - /// - For API_KEY: null - /// - For AWS_IAM: - /// - For AMAZON_COGNITO_USER_POOLS: - /// - For AWS_LAMBDA: - /// - For OPENID_CONNECT: - /// - public object? Identity { get; set; } - - /// - /// Gets or sets information about the data source that originated the event. - /// - [JsonPropertyName("source")] - public object? Source { get; set; } - - /// - /// Gets or sets information about the HTTP request that triggered the event. - /// - [JsonPropertyName("request")] - public RequestContext? Request { get; set; } - - /// - /// Gets or sets information about the previous state of the data before the operation was executed. - /// - [JsonPropertyName("prev")] - public object? Prev { get; set; } - - /// - /// Gets or sets information about the GraphQL operation being executed. - /// - [JsonPropertyName("info")] - public Information? Info { get; set; } - - /// - /// Gets or sets additional information that can be passed between Lambda functions during an AppSync pipeline. - /// - [JsonPropertyName("stash")] - public Dictionary? Stash { get; set; } - - /// - /// The error message when the operation fails. - /// - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] - [JsonPropertyName("error")] - public string? Error { get; set; } - - /// - /// The list of error message when the operation fails. - /// - public object[]? OutErrors { get; set; } - - /// - /// The list of events sent. - /// - [JsonPropertyName("events")] - public AppSyncEvent[]? Events { get; set; } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncEventsResolver.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncEventsResolver.cs deleted file mode 100644 index 09356b648..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncEventsResolver.cs +++ /dev/null @@ -1,552 +0,0 @@ -using Amazon.Lambda.Core; -using AWS.Lambda.Powertools.Common; -using AWS.Lambda.Powertools.EventHandler.Internal; - -namespace AWS.Lambda.Powertools.EventHandler.AppSyncEvents; - -/// -/// Resolver for AWS AppSync Events APIs. -/// Handles onPublish and onSubscribe events from AppSync Events APIs, -/// routing them to appropriate handlers based on path. -/// -public class AppSyncEventsResolver -{ - private readonly RouteHandlerRegistry _publishRoutes; - private readonly RouteHandlerRegistry _subscribeRoutes; - - /// - /// Initializes a new instance of the class. - /// - public AppSyncEventsResolver() - { - _publishRoutes = new RouteHandlerRegistry(); - _subscribeRoutes = new RouteHandlerRegistry(); - PowertoolsEnvironment.Instance.SetExecutionEnvironment(this); - } - - #region OnPublish Methods - - - /// - /// Registers a sync handler for publish events on a specific channel path. - /// - /// The channel path to handle - /// Sync handler without context - public AppSyncEventsResolver OnPublish(string path, Func, object> handler) - { - RegisterPublishHandler(path, handler, false); - return this; - } - - /// - /// Registers a sync handler with Lambda context for publish events on a specific channel path. - /// - /// The channel path to handle - /// Sync handler with context - public AppSyncEventsResolver OnPublish(string path, Func, ILambdaContext, object> handler) - { - RegisterPublishHandler(path, handler, false); - return this; - } - - #endregion - - #region OnPublishAsync Methods - - /// - /// Explicitly registers an async handler for publish events on a specific channel path. - /// Use this method when you want to clearly indicate that your handler is asynchronous. - /// - /// The channel path to handle - /// Async handler without context - public AppSyncEventsResolver OnPublishAsync(string path, Func, Task> handler) - { - RegisterPublishHandler(path, handler, false); - return this; - } - - /// - /// Explicitly registers an async handler with Lambda context for publish events on a specific channel path. - /// Use this method when you want to clearly indicate that your handler is asynchronous. - /// - /// The channel path to handle - /// Async handler with context - public AppSyncEventsResolver OnPublishAsync(string path, Func, ILambdaContext, Task> handler) - { - RegisterPublishHandler(path, handler, false); - return this; - } - - #endregion - - #region OnPublishAggregate Methods - - /// - /// Registers a sync aggregate handler for publish events on a specific channel path. - /// - /// The channel path to handle - /// Sync aggregate handler without context - public AppSyncEventsResolver OnPublishAggregate(string path, Func handler) - { - RegisterAggregateHandler(path, handler); - return this; - } - - /// - /// Registers a sync aggregate handler with Lambda context for publish events on a specific channel path. - /// - /// The channel path to handle - /// Sync aggregate handler with context - public AppSyncEventsResolver OnPublishAggregate(string path, Func handler) - { - RegisterAggregateHandler(path, handler); - return this; - } - - #endregion - - #region OnPublishAggregateAsync Methods - - /// - /// Explicitly registers an async aggregate handler for publish events on a specific channel path. - /// Use this method when you want to clearly indicate that your handler is asynchronous. - /// - /// The channel path to handle - /// Async aggregate handler without context - public AppSyncEventsResolver OnPublishAggregateAsync(string path, Func> handler) - { - RegisterAggregateHandler(path, handler); - return this; - } - - /// - /// Explicitly registers an async aggregate handler with Lambda context for publish events on a specific channel path. - /// Use this method when you want to clearly indicate that your handler is asynchronous. - /// - /// The channel path to handle - /// Async aggregate handler with context - public AppSyncEventsResolver OnPublishAggregateAsync(string path, Func> handler) - { - RegisterAggregateHandler(path, handler); - return this; - } - - #endregion - - #region OnSubscribe Methods - - /// - /// Registers a sync handler for subscription events on a specific channel path. - /// - /// The channel path to handle - /// Sync subscription handler without context - public AppSyncEventsResolver OnSubscribe(string path, Func handler) - { - RegisterSubscribeHandler(path, handler); - return this; - } - - /// - /// Registers a sync handler with Lambda context for subscription events on a specific channel path. - /// - /// The channel path to handle - /// Sync subscription handler with context - public AppSyncEventsResolver OnSubscribe(string path, Func handler) - { - RegisterSubscribeHandler(path, handler); - return this; - } - - #endregion - - #region OnSubscribeAsync Methods - - /// - /// Explicitly registers an async handler for subscription events on a specific channel path. - /// Use this method when you want to clearly indicate that your handler is asynchronous. - /// - /// The channel path to handle - /// Async subscription handler without context - public AppSyncEventsResolver OnSubscribeAsync(string path, Func> handler) - { - RegisterSubscribeHandler(path, handler); - return this; - } - - /// - /// Explicitly registers an async handler with Lambda context for subscription events on a specific channel path. - /// Use this method when you want to clearly indicate that your handler is asynchronous. - /// - /// The channel path to handle - /// Async subscription handler with context - public AppSyncEventsResolver OnSubscribeAsync(string path, Func> handler) - { - RegisterSubscribeHandler(path, handler); - return this; - } - - #endregion - - #region Handler Registration Methods - - private void RegisterPublishHandler(string path, Func, Task> handler, bool aggregate) - { - _publishRoutes.Register(new RouteHandlerOptions - { - Path = path, - Handler = async (evt, _) => - { - var payload = evt.Events?.FirstOrDefault()?.Payload; - return await handler(payload ?? new Dictionary()); - }, - Aggregate = aggregate - }); - } - - private void RegisterPublishHandler(string path, Func, ILambdaContext, Task> handler, bool aggregate) - { - _publishRoutes.Register(new RouteHandlerOptions - { - Path = path, - Handler = async (evt, ctx) => - { - var payload = evt.Events?.FirstOrDefault()?.Payload; - return await handler(payload ?? new Dictionary(), ctx); - }, - Aggregate = aggregate - }); - } - - private void RegisterPublishHandler(string path, Func, object> handler, bool aggregate) - { - _publishRoutes.Register(new RouteHandlerOptions - { - Path = path, - Handler = (evt, _) => - { - var payload = evt.Events?.FirstOrDefault()?.Payload; - return Task.FromResult(handler(payload ?? new Dictionary())); - }, - Aggregate = aggregate - }); - } - - private void RegisterPublishHandler(string path, Func, ILambdaContext, object> handler, bool aggregate) - { - _publishRoutes.Register(new RouteHandlerOptions - { - Path = path, - Handler = (evt, ctx) => - { - var payload = evt.Events?.FirstOrDefault()?.Payload; - return Task.FromResult(handler(payload ?? new Dictionary(), ctx)); - }, - Aggregate = aggregate - }); - } - - private void RegisterAggregateHandler(string path, Func> handler) - { - _publishRoutes.Register(new RouteHandlerOptions - { - Path = path, - Handler = async (evt, _) => await handler(evt), - Aggregate = true - }); - } - - private void RegisterAggregateHandler(string path, Func> handler) - { - _publishRoutes.Register(new RouteHandlerOptions - { - Path = path, - Handler = async (evt, ctx) => await handler(evt, ctx), - Aggregate = true - }); - } - - private void RegisterAggregateHandler(string path, Func handler) - { - _publishRoutes.Register(new RouteHandlerOptions - { - Path = path, - Handler = (evt, _) => Task.FromResult((object)handler(evt)), - Aggregate = true - }); - } - - private void RegisterAggregateHandler(string path, Func handler) - { - _publishRoutes.Register(new RouteHandlerOptions - { - Path = path, - Handler = (evt, ctx) => Task.FromResult((object)handler(evt, ctx)), - Aggregate = true - }); - } - - private void RegisterSubscribeHandler(string path, Func> handler) - { - _subscribeRoutes.Register(new RouteHandlerOptions - { - Path = path, - Handler = async (evt, _) => await handler(evt) - }); - } - - private void RegisterSubscribeHandler(string path, Func> handler) - { - _subscribeRoutes.Register(new RouteHandlerOptions - { - Path = path, - Handler = async (evt, ctx) => await handler(evt, ctx) - }); - } - - private void RegisterSubscribeHandler(string path, Func handler) - { - _subscribeRoutes.Register(new RouteHandlerOptions - { - Path = path, - Handler = (evt, _) => Task.FromResult(handler(evt)) - }); - } - - private void RegisterSubscribeHandler(string path, Func handler) - { - _subscribeRoutes.Register(new RouteHandlerOptions - { - Path = path, - Handler = (evt, ctx) => Task.FromResult(handler(evt, ctx)) - }); - } - - #endregion - - /// - /// Resolves and processes an AppSync event through the registered handlers. - /// - /// The AppSync event to process - /// Lambda execution context - /// Response containing processed events or error information - public AppSyncEventsResponse Resolve(AppSyncEventsRequest appsyncEvent, ILambdaContext context) - { - return ResolveAsync(appsyncEvent, context).GetAwaiter().GetResult(); - } - - /// - /// Resolves and processes an AppSync event through the registered handlers. - /// - /// The AppSync event to process - /// Lambda execution context - /// Response containing processed events or error information - public async Task ResolveAsync(AppSyncEventsRequest appsyncEvent, ILambdaContext context) - { - if (IsPublishEvent(appsyncEvent)) - { - return await HandlePublishEvent(appsyncEvent, context); - } - - if (IsSubscribeEvent(appsyncEvent)) - { - return (await HandleSubscribeEvent(appsyncEvent, context))!; - } - - throw new InvalidOperationException("Unknown event type"); - } - - private async Task HandlePublishEvent(AppSyncEventsRequest appsyncEvent, - ILambdaContext context) - { - var channelPath = appsyncEvent.Info?.Channel?.Path; - var handlerOptions = _publishRoutes.ResolveFirst(channelPath); - - context.Logger.LogInformation($"Resolving publish event for path: {channelPath}"); - - if (handlerOptions == null) - { - // Return unchanged events if no handler found - var events = appsyncEvent.Events? - .Select(e => new AppSyncEvent - { - Id = e.Id, - Payload = e.Payload - }) - .ToList(); - return new AppSyncEventsResponse { Events = events }; - } - - var results = new List(); - - if (handlerOptions.Aggregate) - { - try - { - // Process entire event in one call - var handlerResult = await handlerOptions.Handler(appsyncEvent, context); - if (handlerResult is AppSyncEventsResponse { Events: not null } result) - { - return result; - } - - // Handle unexpected return type - return new AppSyncEventsResponse - { - Error = "Handler returned an invalid response type" - }; - } - catch (UnauthorizedException) - { - throw; - } - catch (Exception ex) - { - return new AppSyncEventsResponse - { - Error = $"{ex.GetType().Name} - {ex.Message}" - }; - } - } - else - { - // Process each event individually - if (appsyncEvent.Events == null) return new AppSyncEventsResponse { Events = results }; - foreach (var eventItem in appsyncEvent.Events) - { - try - { - var result = await handlerOptions.Handler( - new AppSyncEventsRequest - { - Info = appsyncEvent.Info, - Events = [eventItem] - }, context); - - var payload = ConvertToPayload(result, out var error); - if (error != null) - { - results.Add(new AppSyncEvent - { - Id = eventItem.Id, - Error = error - }); - } - else - { - results.Add(new AppSyncEvent - { - Id = eventItem.Id, - Payload = payload - }); - } - } - catch (UnauthorizedException) - { - throw; - } - catch (Exception ex) - { - results.Add(FormatErrorResponse(ex, eventItem.Id!)); - } - } - } - - return new AppSyncEventsResponse { Events = results }; - } - - /// - /// Handles subscription events. - /// Returns null on success, error response on failure. - /// - private async Task HandleSubscribeEvent(AppSyncEventsRequest appsyncEvent, - ILambdaContext context) - { - var channelPath = appsyncEvent.Info?.Channel?.Path; - var channelBase = $"/{appsyncEvent.Info?.Channel?.Segments?[0]}"; - - // Find matching subscribe handler - var subscribeHandler = _subscribeRoutes.ResolveFirst(channelPath); - if (subscribeHandler == null) - { - return null; - } - - // Check if there's ANY publish handler for the base channel namespace - bool hasAnyPublishHandler = _publishRoutes.GetAllHandlers() - .Any(h => h.Path.StartsWith(channelBase)); - - if (!hasAnyPublishHandler) - { - return null; - } - - try - { - var result = await subscribeHandler.Handler(appsyncEvent, context); - return !result ? new AppSyncEventsResponse { Error = "Subscription failed" } : null; - } - catch (UnauthorizedException) - { - throw; - } - catch (Exception ex) - { - context.Logger.LogLine($"Error in subscribe handler: {ex.Message}"); - return new AppSyncEventsResponse { Error = ex.Message }; - } - } - - private Dictionary? ConvertToPayload(object result, out string? error) - { - error = null; - - // Check if this is an error result from ProcessSingleEvent - if (result is Dictionary dict && dict.ContainsKey("error")) - { - error = dict["error"].ToString(); - return null; // No payload when there's an error - } - - // Regular payload handling - if (result is Dictionary payload) - { - return payload; - } - - return new Dictionary { ["data"] = result }; - } - - private AppSyncEvent FormatErrorResponse(Exception ex, string id) - { - return new AppSyncEvent - { - Id = id, - Error = $"{ex.GetType().Name} - {ex.Message}" - }; - } - - private bool IsPublishEvent(AppSyncEventsRequest appsyncEvent) - { - return appsyncEvent.Info?.Operation == AppSyncEventsOperation.Publish; - } - - private bool IsSubscribeEvent(AppSyncEventsRequest appsyncEvent) - { - return appsyncEvent.Info?.Operation == AppSyncEventsOperation.Subscribe; - } -} - -/// -/// Exception thrown when subscription validation fails. -/// This exception causes the Lambda invocation to fail, returning an error to AppSync. -/// -public class UnauthorizedException : Exception -{ - /// - /// Initializes a new instance of the class. - /// - /// The error message - public UnauthorizedException(string message) : base(message) - { - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncEventsResponse.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncEventsResponse.cs deleted file mode 100644 index 7069cba5f..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncEventsResponse.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Text.Json.Serialization; - -namespace AWS.Lambda.Powertools.EventHandler.AppSyncEvents; - -/// -/// Represents the response for AppSync events. -/// -public class AppSyncEventsResponse -{ - /// - /// Collection of event results - /// - [JsonPropertyName("events")] - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] - public List? Events { get; set; } - - /// - /// When operation fails, this will contain the error message - /// - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] - [JsonPropertyName("error")] - public string? Error { get; set; } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncIamIdentity.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncIamIdentity.cs deleted file mode 100644 index f2cf0a173..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncIamIdentity.cs +++ /dev/null @@ -1,47 +0,0 @@ -namespace AWS.Lambda.Powertools.EventHandler.AppSyncEvents; - -/// -/// Represents AWS IAM authorization identity for AppSync -/// -public class AppSyncIamIdentity -{ - /// - /// The source IP address of the caller received by AWS AppSync - /// - public List? SourceIp { get; set; } - - /// - /// The username of the authenticated user (IAM user principal) - /// - public string? Username { get; set; } - - /// - /// The AWS account ID of the caller - /// - public string? AccountId { get; set; } - - /// - /// The Amazon Cognito identity pool ID associated with the caller - /// - public string? CognitoIdentityPoolId { get; set; } - - /// - /// The Amazon Cognito identity ID of the caller - /// - public string? CognitoIdentityId { get; set; } - - /// - /// The ARN of the IAM user - /// - public string? UserArn { get; set; } - - /// - /// Either authenticated or unauthenticated based on the identity type - /// - public string? CognitoIdentityAuthType { get; set; } - - /// - /// A comma separated list of external identity provider information used in obtaining the credentials used to sign the request - /// - public string? CognitoIdentityAuthProvider { get; set; } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncLambdaIdentity.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncLambdaIdentity.cs deleted file mode 100644 index 996aa9a01..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncLambdaIdentity.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace AWS.Lambda.Powertools.EventHandler.AppSyncEvents; - -/// -/// Represents AWS Lambda authorization identity for AppSync -/// -public class AppSyncLambdaIdentity -{ - /// - /// Optional context information that will be passed to subsequent resolvers - /// Can contain user information, claims, or any other contextual data - /// - public Dictionary? ResolverContext { get; set; } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncOidcIdentity.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncOidcIdentity.cs deleted file mode 100644 index 8d06db2ed..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncOidcIdentity.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace AWS.Lambda.Powertools.EventHandler.AppSyncEvents; - -/// -/// Represents OpenID Connect authorization identity for AppSync -/// -public class AppSyncOidcIdentity -{ - /// - /// Claims from the OIDC token as key-value pairs - /// - public Dictionary? Claims { get; set; } - - /// - /// The issuer of the OIDC token - /// - public string? Issuer { get; set; } - - /// - /// The UUID of the authenticated user - /// - public string? Sub { get; set; } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncRequestContext.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncRequestContext.cs deleted file mode 100644 index 3fe5681d8..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/AppSyncRequestContext.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace AWS.Lambda.Powertools.EventHandler.AppSyncEvents; - -/// -/// Contains contextual information about the AppSync request being authorized. -/// This class provides details about the API, account, and GraphQL operation. -/// -public class AppSyncRequestContext -{ - /// - /// Gets or sets the unique identifier of the AppSync API. - /// - public string? ApiId { get; set; } - - /// - /// Gets or sets the AWS account ID where the AppSync API is deployed. - /// - public string? AccountId { get; set; } - - /// - /// Gets or sets the unique identifier for this specific request. - /// - public string? RequestId { get; set; } - - /// - /// Gets or sets the GraphQL query string containing the operation to be executed. - /// - public string? QueryString { get; set; } - - /// - /// Gets or sets the name of the GraphQL operation to be executed. - /// This corresponds to the operation name in the GraphQL query. - /// - public string? OperationName { get; set; } - - /// - /// Gets or sets the variables passed to the GraphQL operation. - /// Contains key-value pairs of variable names and their values. - /// - public Dictionary? Variables { get; set; } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/Channel.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/Channel.cs deleted file mode 100644 index 156c736ac..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/Channel.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Text.Json.Serialization; - -namespace AWS.Lambda.Powertools.EventHandler.AppSyncEvents; - -/// -/// Channel details including path and segments -/// -public class Channel -{ - /// - /// Provides direct access to the 'Path' attribute within the 'Channel' object. - /// - [JsonPropertyName("path")] - public string? Path { get; set; } - - /// - /// Provides direct access to the 'Segments' attribute within the 'Channel' object. - /// - [JsonPropertyName("segments")] - public string[]? Segments { get; set; } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/ChannelNamespace.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/ChannelNamespace.cs deleted file mode 100644 index 9bcc5e6e8..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/ChannelNamespace.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Text.Json.Serialization; - -namespace AWS.Lambda.Powertools.EventHandler.AppSyncEvents; - -/// -/// Namespace configuration for the channel -/// -public class ChannelNamespace -{ - /// - /// Name of the channel namespace - /// - [JsonPropertyName("name")] - public string? Name { get; set; } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/Information.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/Information.cs deleted file mode 100644 index 79c62a054..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/Information.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Text.Json.Serialization; - -namespace AWS.Lambda.Powertools.EventHandler.AppSyncEvents; - -/// -/// Represents information about the AppSync event. -/// -public class Information -{ - /// - /// The channel being used for the operation - /// - [JsonPropertyName("channel")] - public Channel? Channel { get; set; } - - /// - /// The namespace of the channel - /// - public ChannelNamespace? ChannelNamespace { get; set; } - - /// - /// The operation being performed (e.g., Publish, Subscribe) - /// - [JsonPropertyName("operation")] - public AppSyncEventsOperation Operation { get; set; } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/RequestContext.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/RequestContext.cs deleted file mode 100644 index 1c2893548..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler/AppSyncEvents/RequestContext.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace AWS.Lambda.Powertools.EventHandler.AppSyncEvents; - -/// -/// Represents information about the HTTP request that triggered the event. -/// -public class RequestContext -{ - /// - /// Gets or sets the headers of the HTTP request. - /// - public Dictionary Headers { get; set; } = new(); - - /// - /// Gets or sets the domain name associated with the request. - /// - public string? DomainName { get; set; } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler/Internal/LRUCache.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler/Internal/LRUCache.cs deleted file mode 100644 index 37fa46634..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler/Internal/LRUCache.cs +++ /dev/null @@ -1,74 +0,0 @@ -namespace AWS.Lambda.Powertools.EventHandler.Internal; - -/// -/// Basic LRU cache implementation -/// -/// -/// Simple LRU cache implementation for caching route resolutions -/// -internal class LruCache where TKey : notnull -{ - private readonly int _capacity; - private readonly Dictionary> _cache; - private readonly LinkedList _lruList; - - internal class CacheItem - { - public TKey Key { get; } - public TValue Value { get; } - - public CacheItem(TKey key, TValue value) - { - Key = key; - Value = value; - } - } - - public LruCache(int capacity) - { - _capacity = capacity; - _cache = new Dictionary>(); - _lruList = new LinkedList(); - } - - public bool TryGet(TKey key, out TValue? value) - { - if (_cache.TryGetValue(key, out var node)) - { - // Move to the front of the list (most recently used) - _lruList.Remove(node); - _lruList.AddFirst(node); - value = node.Value.Value; - return true; - } - - value = default; - return false; - } - - public void Set(TKey key, TValue value) - { - if (_cache.TryGetValue(key, out var existingNode)) - { - _lruList.Remove(existingNode); - _cache.Remove(key); - } - else if (_cache.Count >= _capacity) - { - // Remove least recently used item - var lastNode = _lruList.Last; - _lruList.RemoveLast(); - if (lastNode != null) _cache.Remove(lastNode.Value.Key); - } - - var newNode = new LinkedListNode(new CacheItem(key, value)); - _lruList.AddFirst(newNode); - _cache[key] = newNode; - } - - public void Clear() - { - _cache.Clear(); - _lruList.Clear(); - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler/Internal/RouteHandlerOptions.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler/Internal/RouteHandlerOptions.cs deleted file mode 100644 index 06cb2a2ac..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler/Internal/RouteHandlerOptions.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Amazon.Lambda.Core; - -namespace AWS.Lambda.Powertools.EventHandler.Internal; - -/// -/// Options for registering a route handler -/// -internal class RouteHandlerOptions -{ - /// - /// The path pattern to match against (e.g., "/default/*") - /// - public string Path { get; set; } = "/default/*"; - - /// - /// The handler function to execute when path matches - /// - public required Func> Handler { get; set; } - - /// - /// Whether to aggregate all events into a single handler call - /// - public bool Aggregate { get; set; } = false; -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler/Internal/RouteHandlerRegistry.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler/Internal/RouteHandlerRegistry.cs deleted file mode 100644 index 78c8ffe29..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler/Internal/RouteHandlerRegistry.cs +++ /dev/null @@ -1,141 +0,0 @@ -namespace AWS.Lambda.Powertools.EventHandler.Internal; - -/// -/// Registry for storing route handlers for path-based routing operations. -/// Handles path matching, caching, and handler resolution. -/// -internal class RouteHandlerRegistry -{ - /// - /// Dictionary of registered handlers - /// - private readonly Dictionary> _resolvers = new(); - - /// - /// Cache for resolved routes to improve performance - /// - private readonly LruCache> _resolverCache; - - /// - /// Set to track already logged warnings - /// - private readonly HashSet _warnedPaths = new(); - - /// - /// Initialize a new registry for route handlers - /// - /// Max size of LRU cache (default 100) - public RouteHandlerRegistry(int cacheSize = 100) - { - _resolverCache = new LruCache>(cacheSize); - } - - /// - /// Register a handler for a specific path pattern. - /// - /// Options for the route handler - public void Register(RouteHandlerOptions options) - { - if (!IsValidPath(options.Path)) - { - LogWarning($"The path \"{options.Path}\" is not valid and will be skipped. " + - "Wildcards are allowed only at the end of the path."); - return; - } - - // Clear cache when registering new handlers - _resolverCache.Clear(); - _resolvers[options.Path] = options; - } - - /// - /// Find the most specific handler for a given path. - /// - /// The path to match against registered routes - /// Most specific matching handler or null if no match - public RouteHandlerOptions? ResolveFirst(string? path) - { - if (path != null && _resolverCache.TryGet(path, out var cachedHandler)) - { - return cachedHandler; - } - - // First try for exact match - if (path != null && _resolvers.TryGetValue(path, out var exactMatch)) - { - _resolverCache.Set(path, exactMatch); - return exactMatch; - } - - // Then try wildcard matches, sorted by specificity (most segments first) - var wildcardMatches = _resolvers.Keys - .Where(pattern => path != null && IsWildcardMatch(pattern, path)) - .OrderByDescending(pattern => pattern.Count(c => c == '/')) - .ThenByDescending(pattern => pattern.Length); - - var bestMatch = wildcardMatches.FirstOrDefault(); - - if (bestMatch != null) - { - var handler = _resolvers[bestMatch]; - if (path != null) _resolverCache.Set(path, handler); - return handler; - } - - return null; - } - - /// - /// Get all registered handlers - /// - public IEnumerable> GetAllHandlers() - { - return _resolvers.Values; - } - - /// - /// Check if a path pattern is valid according to routing rules. - /// - private static bool IsValidPath(string path) - { - if (string.IsNullOrWhiteSpace(path) || !path.StartsWith('/')) - return false; - - // Check for invalid wildcard usage - return !path.Contains("*/"); - } - - /// - /// Check if a wildcard pattern matches the given path - /// - private bool IsWildcardMatch(string pattern, string path) - { - if (!pattern.Contains('*')) - return pattern == path; - - var patternSegments = pattern.Split('/'); - var pathSegments = path.Split('/'); - - if (patternSegments.Length > pathSegments.Length) - return false; - - for (var i = 0; i < patternSegments.Length; i++) - { - // If we've reached the wildcard segment, it matches the rest - if (patternSegments[i] == "*") - return true; - - // Otherwise, segments must match exactly - if (patternSegments[i] != pathSegments[i]) - return false; - } - - return patternSegments.Length == pathSegments.Length; - } - - private void LogWarning(string message) - { - if (!_warnedPaths.Add(message)) return; - Console.WriteLine($"Warning: {message}"); - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.EventHandler/InternalsVisibleTo.cs b/libraries/src/AWS.Lambda.Powertools.EventHandler/InternalsVisibleTo.cs deleted file mode 100644 index a4ee0e7a5..000000000 --- a/libraries/src/AWS.Lambda.Powertools.EventHandler/InternalsVisibleTo.cs +++ /dev/null @@ -1,3 +0,0 @@ -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("AWS.Lambda.Powertools.EventHandler.Tests")] \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Idempotency/Exceptions/IdempotencyItemAlreadyExistsException.cs b/libraries/src/AWS.Lambda.Powertools.Idempotency/Exceptions/IdempotencyItemAlreadyExistsException.cs index 55144bffe..1603bba7c 100644 --- a/libraries/src/AWS.Lambda.Powertools.Idempotency/Exceptions/IdempotencyItemAlreadyExistsException.cs +++ b/libraries/src/AWS.Lambda.Powertools.Idempotency/Exceptions/IdempotencyItemAlreadyExistsException.cs @@ -14,7 +14,6 @@ */ using System; -using AWS.Lambda.Powertools.Idempotency.Persistence; namespace AWS.Lambda.Powertools.Idempotency.Exceptions; @@ -23,11 +22,6 @@ namespace AWS.Lambda.Powertools.Idempotency.Exceptions; /// public class IdempotencyItemAlreadyExistsException : Exception { - /// - /// The record that already exists in the persistence layer. - /// - public DataRecord Record { get; set; } - /// /// Creates a new IdempotencyItemAlreadyExistsException /// diff --git a/libraries/src/AWS.Lambda.Powertools.Idempotency/Idempotency.cs b/libraries/src/AWS.Lambda.Powertools.Idempotency/Idempotency.cs index 3e953e73e..2e626fb34 100644 --- a/libraries/src/AWS.Lambda.Powertools.Idempotency/Idempotency.cs +++ b/libraries/src/AWS.Lambda.Powertools.Idempotency/Idempotency.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.Text.Json.Serialization; using Amazon.Lambda.Core; @@ -166,7 +181,6 @@ public IdempotencyBuilder WithOptions(IdempotencyOptions options) return this; } -#if NET8_0_OR_GREATER /// /// Set Customer JsonSerializerContext to append to IdempotencySerializationContext /// @@ -177,6 +191,5 @@ public IdempotencyBuilder WithJsonSerializationContext(JsonSerializerContext con IdempotencySerializer.AddTypeInfoResolver(context); return this; } -#endif } } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Idempotency/IdempotencyOptions.cs b/libraries/src/AWS.Lambda.Powertools.Idempotency/IdempotencyOptions.cs index df9645d9a..ed1b4a826 100644 --- a/libraries/src/AWS.Lambda.Powertools.Idempotency/IdempotencyOptions.cs +++ b/libraries/src/AWS.Lambda.Powertools.Idempotency/IdempotencyOptions.cs @@ -13,8 +13,6 @@ * permissions and limitations under the License. */ -using System; - namespace AWS.Lambda.Powertools.Idempotency; /// @@ -59,24 +57,32 @@ public class IdempotencyOptions /// as supported by (eg. SHA1, SHA-256, ...) /// public string HashFunction { get; } - /// - /// Delegate for manipulating idempotent responses. - /// - public Func ResponseHook { get; } /// /// Constructor of . /// - /// The builder containing the configuration values - internal IdempotencyOptions(IdempotencyOptionsBuilder builder) + /// + /// + /// + /// + /// + /// + /// + internal IdempotencyOptions( + string eventKeyJmesPath, + string payloadValidationJmesPath, + bool throwOnNoIdempotencyKey, + bool useLocalCache, + int localCacheMaxItems, + long expirationInSeconds, + string hashFunction) { - EventKeyJmesPath = builder.EventKeyJmesPath; - PayloadValidationJmesPath = builder.PayloadValidationJmesPath; - ThrowOnNoIdempotencyKey = builder.ThrowOnNoIdempotencyKey; - UseLocalCache = builder.UseLocalCache; - LocalCacheMaxItems = builder.LocalCacheMaxItems; - ExpirationInSeconds = builder.ExpirationInSeconds; - HashFunction = builder.HashFunction; - ResponseHook = builder.ResponseHook; + EventKeyJmesPath = eventKeyJmesPath; + PayloadValidationJmesPath = payloadValidationJmesPath; + ThrowOnNoIdempotencyKey = throwOnNoIdempotencyKey; + UseLocalCache = useLocalCache; + LocalCacheMaxItems = localCacheMaxItems; + ExpirationInSeconds = expirationInSeconds; + HashFunction = hashFunction; } } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Idempotency/IdempotencyOptionsBuilder.cs b/libraries/src/AWS.Lambda.Powertools.Idempotency/IdempotencyOptionsBuilder.cs index 55b3141b0..3c437d337 100644 --- a/libraries/src/AWS.Lambda.Powertools.Idempotency/IdempotencyOptionsBuilder.cs +++ b/libraries/src/AWS.Lambda.Powertools.Idempotency/IdempotencyOptionsBuilder.cs @@ -43,59 +43,21 @@ public class IdempotencyOptionsBuilder private string _hashFunction = "MD5"; /// - /// Response hook function - /// - private Func _responseHook; - - /// - /// Gets the event key JMESPath expression. - /// - internal string EventKeyJmesPath => _eventKeyJmesPath; - - /// - /// Gets the payload validation JMESPath expression. - /// - internal string PayloadValidationJmesPath => _payloadValidationJmesPath; - - /// - /// Gets whether to throw exception if no idempotency key is found. - /// - internal bool ThrowOnNoIdempotencyKey => _throwOnNoIdempotencyKey; - - /// - /// Gets whether local cache is enabled. - /// - internal bool UseLocalCache => _useLocalCache; - - /// - /// Gets the maximum number of items in the local cache. - /// - internal int LocalCacheMaxItems => _localCacheMaxItems; - - /// - /// Gets the expiration in seconds. - /// - internal long ExpirationInSeconds => _expirationInSeconds; - - /// - /// Gets the hash function. - /// - internal string HashFunction => _hashFunction; - - /// - /// Gets the response hook function. - /// - internal Func ResponseHook => _responseHook; - - /// - /// Initialize and return an instance of IdempotencyOptions. + /// Initialize and return an instance of IdempotencyConfig. /// Example: - /// new IdempotencyOptionsBuilder().WithUseLocalCache().Build(); - /// This instance can then be passed to Idempotency.Configure: - /// Idempotency.Configure(builder => builder.WithOptions(options)); + /// IdempotencyConfig.Builder().WithUseLocalCache().Build(); + /// This instance must then be passed to the Idempotency.Config: + /// Idempotency.Config().WithConfig(config).Configure(); /// - /// an instance of IdempotencyOptions - public IdempotencyOptions Build() => new(this); + /// an instance of IdempotencyConfig + public IdempotencyOptions Build() => + new(_eventKeyJmesPath, + _payloadValidationJmesPath, + _throwOnNoIdempotencyKey, + _useLocalCache, + _localCacheMaxItems, + _expirationInSeconds, + _hashFunction); /// /// A JMESPath expression to extract the idempotency key from the event record. @@ -160,26 +122,9 @@ public IdempotencyOptionsBuilder WithExpiration(TimeSpan duration) /// /// Can be any algorithm supported by HashAlgorithm.Create /// the instance of the builder (to chain operations) -#if NET8_0_OR_GREATER [Obsolete("Idempotency uses MD5 and does not support other hash algorithms.")] -#endif public IdempotencyOptionsBuilder WithHashFunction(string hashFunction) { -#if NET6_0 - // for backward compability keep this code in .net 6 - _hashFunction = hashFunction; -#endif - return this; - } - - /// - /// Set a response hook function, to be called with the response and the data record. - /// - /// The response hook function - /// the instance of the builder (to chain operations) - public IdempotencyOptionsBuilder WithResponseHook(Func hook) - { - _responseHook = hook; return this; } } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/IdempotencyAspectHandler.cs b/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/IdempotencyAspectHandler.cs index e8b674750..a8d7da731 100644 --- a/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/IdempotencyAspectHandler.cs +++ b/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/IdempotencyAspectHandler.cs @@ -106,25 +106,9 @@ private async Task ProcessIdempotency() // already exists. If it succeeds, there's no need to call getRecord. await _persistenceStore.SaveInProgress(_data, DateTimeOffset.UtcNow, GetRemainingTimeInMillis()); } - catch (IdempotencyItemAlreadyExistsException ex) + catch (IdempotencyItemAlreadyExistsException) { - DataRecord record; - - if(ex.Record != null) - { - // If the error includes the existing record, we can use it to validate - // the record being processed and cache it in memory. - var existingRecord = _persistenceStore.ProcessExistingRecord(ex.Record, _data); - record = existingRecord; - } - else - { - // If the error doesn't include the existing record, we need to fetch - // it from the persistence layer. In doing so, we also call the processExistingRecord - // method to validate the record and cache it in memory. - record = await GetIdempotencyRecord(); - } - + var record = await GetIdempotencyRecord(); return await HandleForStatus(record); } catch (IdempotencyKeyException) @@ -208,24 +192,6 @@ private Task HandleForStatus(DataRecord record) { throw new IdempotencyPersistenceLayerException("Unable to cast function response as " + typeof(T).Name); } - // Response hook logic - var responseHook = Idempotency.Instance.IdempotencyOptions?.ResponseHook; - if (responseHook != null) - { - try - { - var hooked = responseHook(result, record); - if (hooked is T typedHooked) - { - return Task.FromResult(typedHooked); - } - // If hook returns wrong type, fallback to original result - } - catch (Exception) - { - // If hook throws, fallback to original result - } - } return Task.FromResult(result); } catch (Exception e) diff --git a/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/Serializers/IdempotencySerializationContext.cs b/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/Serializers/IdempotencySerializationContext.cs index 6f77b9cfb..4b96bb649 100644 --- a/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/Serializers/IdempotencySerializationContext.cs +++ b/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/Serializers/IdempotencySerializationContext.cs @@ -17,8 +17,6 @@ namespace AWS.Lambda.Powertools.Idempotency.Internal.Serializers; -#if NET8_0_OR_GREATER - /// /// The source generated JsonSerializerContext to be used to Serialize Idempotency types @@ -28,5 +26,4 @@ namespace AWS.Lambda.Powertools.Idempotency.Internal.Serializers; public partial class IdempotencySerializationContext : JsonSerializerContext { -} -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/Serializers/IdempotencySerializer.cs b/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/Serializers/IdempotencySerializer.cs index 975a47f4b..b7f984193 100644 --- a/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/Serializers/IdempotencySerializer.cs +++ b/libraries/src/AWS.Lambda.Powertools.Idempotency/Internal/Serializers/IdempotencySerializer.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.Diagnostics.CodeAnalysis; using System.Runtime.Serialization; @@ -29,22 +44,18 @@ private static void BuildDefaultOptions() { PropertyNameCaseInsensitive = true }; -#if NET8_0_OR_GREATER if (!RuntimeFeatureWrapper.IsDynamicCodeSupported) { _jsonOptions.TypeInfoResolverChain.Add(IdempotencySerializationContext.Default); } -#endif } -#if NET8_0_OR_GREATER - /// /// Adds a JsonTypeInfoResolver to the JsonSerializerOptions. /// /// The JsonTypeInfoResolver to add. /// - /// This method is only available in .NET 8.0 and later versions. + /// This method is available in .NET 8.0 and later versions. /// internal static void AddTypeInfoResolver(JsonSerializerContext context) { @@ -73,7 +84,6 @@ internal static void SetJsonOptions(JsonSerializerOptions options) { _jsonOptions = options; } -#endif /// /// Serializes the specified object to a JSON string. @@ -83,9 +93,6 @@ internal static void SetJsonOptions(JsonSerializerOptions options) /// A JSON string representation of the object. internal static string Serialize(object value, Type inputType) { -#if NET6_0 - return JsonSerializer.Serialize(value, _jsonOptions); -#else if (RuntimeFeatureWrapper.IsDynamicCodeSupported) { #pragma warning disable @@ -100,7 +107,6 @@ internal static string Serialize(object value, Type inputType) } return JsonSerializer.Serialize(value, typeInfo); -#endif } /// @@ -113,15 +119,11 @@ internal static string Serialize(object value, Type inputType) [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "False positive")] internal static T Deserialize(string value) { -#if NET6_0 - return JsonSerializer.Deserialize(value,_jsonOptions); -#else if (RuntimeFeatureWrapper.IsDynamicCodeSupported) { return JsonSerializer.Deserialize(value, _jsonOptions); } return (T)JsonSerializer.Deserialize(value, GetTypeInfo(typeof(T))); -#endif } } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Idempotency/Persistence/BasePersistenceStore.cs b/libraries/src/AWS.Lambda.Powertools.Idempotency/Persistence/BasePersistenceStore.cs index 07a199131..e2006b0cb 100644 --- a/libraries/src/AWS.Lambda.Powertools.Idempotency/Persistence/BasePersistenceStore.cs +++ b/libraries/src/AWS.Lambda.Powertools.Idempotency/Persistence/BasePersistenceStore.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.Security.Cryptography; using System.Text; @@ -324,12 +339,8 @@ private static bool IsMissingIdempotencyKey(JsonElement data) /// internal string GenerateHash(JsonElement data) { -#if NET8_0_OR_GREATER // starting .NET 8 no option to change hash algorithm using var hashAlgorithm = MD5.Create(); -#else - using var hashAlgorithm = HashAlgorithm.Create(_idempotencyOptions.HashFunction); -#endif if (hashAlgorithm == null) { throw new ArgumentException("Invalid HashAlgorithm"); @@ -378,20 +389,4 @@ private static string GetHash(HashAlgorithm hashAlgorithm, string input) /// public abstract Task DeleteRecord(string idempotencyKey); - - /// - /// Validates an existing record against the data payload being processed. - /// If the payload does not match the stored record, an `IdempotencyValidationError` error is thrown. - /// Whenever a record is retrieved from the persistence layer, it should be validated against the data payload - /// being processed. This is to ensure that the data payload being processed is the same as the one that was - /// used to create the record in the first place. - /// - /// The record is also saved to the local cache if local caching is enabled. - /// - public virtual DataRecord ProcessExistingRecord(DataRecord exRecord, JsonDocument data) - { - ValidatePayload(data, exRecord); - SaveToCache(exRecord); - return exRecord; - } } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Idempotency/Persistence/DynamoDBPersistenceStore.cs b/libraries/src/AWS.Lambda.Powertools.Idempotency/Persistence/DynamoDBPersistenceStore.cs index 34860ea82..9b8cf5006 100644 --- a/libraries/src/AWS.Lambda.Powertools.Idempotency/Persistence/DynamoDBPersistenceStore.cs +++ b/libraries/src/AWS.Lambda.Powertools.Idempotency/Persistence/DynamoDBPersistenceStore.cs @@ -191,7 +191,6 @@ public override async Task PutRecord(DataRecord record, DateTimeOffset now) Item = item, ConditionExpression = "attribute_not_exists(#id) OR #expiry < :now OR (attribute_exists(#in_progress_expiry) AND #in_progress_expiry < :now_milliseconds AND #status = :inprogress)", ExpressionAttributeNames = expressionAttributeNames, - ReturnValuesOnConditionCheckFailure = ReturnValuesOnConditionCheckFailure.ALL_OLD, ExpressionAttributeValues = new Dictionary { {":now", new AttributeValue {N = now.ToUnixTimeSeconds().ToString()}}, @@ -203,20 +202,8 @@ public override async Task PutRecord(DataRecord record, DateTimeOffset now) } catch (ConditionalCheckFailedException e) { - var ex = new IdempotencyItemAlreadyExistsException( + throw new IdempotencyItemAlreadyExistsException( "Failed to put record for already existing idempotency key: " + record.IdempotencyKey, e); - - if (e.Item != null) - { - ex.Record = new DataRecord(e.Item[_keyAttr].S, - Enum.Parse(e.Item[_statusAttr].S), - long.Parse(e.Item[_expiryAttr].N), - e.Item.TryGetValue(_dataAttr, out var data) ? data?.S : null, - e.Item.TryGetValue(_validationAttr, out var validation) ? validation?.S : null, - e.Item.TryGetValue(_inProgressExpiryAttr, out var inProgExp) ? long.Parse(inProgExp.N) : null); - } - - throw ex; } } @@ -370,9 +357,9 @@ public class DynamoDBPersistenceStoreBuilder private AmazonDynamoDBClient _dynamoDbClient; /// - /// Initialize and return a new instance of . + /// Initialize and return a new instance of {@link DynamoDBPersistenceStore}. /// Example: - /// new DynamoDBPersistenceStoreBuilder().WithTableName("idempotency_store").Build(); + /// DynamoDBPersistenceStore.builder().withTableName("idempotency_store").build(); /// /// /// diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Serializers/JMESPathSerializationContext.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Serializers/JMESPathSerializationContext.cs index ecd26686d..cb158f88a 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Serializers/JMESPathSerializationContext.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Serializers/JMESPathSerializationContext.cs @@ -1,10 +1,23 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System.Text.Json; using System.Text.Json.Serialization; namespace AWS.Lambda.Powertools.JMESPath.Serializers; -#if NET8_0_OR_GREATER - /// /// The source generated JsonSerializerContext to be used to Serialize JMESPath types /// @@ -17,5 +30,3 @@ namespace AWS.Lambda.Powertools.JMESPath.Serializers; public partial class JmesPathSerializationContext : JsonSerializerContext { } - -#endif \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.JMESPath/Serializers/JMESPathSerializer.cs b/libraries/src/AWS.Lambda.Powertools.JMESPath/Serializers/JMESPathSerializer.cs index 79f1b903c..3886a54ca 100644 --- a/libraries/src/AWS.Lambda.Powertools.JMESPath/Serializers/JMESPathSerializer.cs +++ b/libraries/src/AWS.Lambda.Powertools.JMESPath/Serializers/JMESPathSerializer.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.Text.Json; @@ -16,12 +31,7 @@ internal static class JmesPathSerializer /// System.String. internal static string Serialize(object value, Type inputType) { -#if NET6_0 - return JsonSerializer.Serialize(value); -#else - return JsonSerializer.Serialize(value, inputType, JmesPathSerializationContext.Default); -#endif } /// @@ -32,11 +42,6 @@ internal static string Serialize(object value, Type inputType) /// T. internal static T Deserialize(string value) { -#if NET6_0 - return JsonSerializer.Deserialize(value); -#else - return (T)JsonSerializer.Deserialize(value, typeof(T), JmesPathSerializationContext.Default); -#endif } } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Kafka.Avro/AWS.Lambda.Powertools.Kafka.Avro.csproj b/libraries/src/AWS.Lambda.Powertools.Kafka.Avro/AWS.Lambda.Powertools.Kafka.Avro.csproj deleted file mode 100644 index bb0741616..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Kafka.Avro/AWS.Lambda.Powertools.Kafka.Avro.csproj +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - AWS.Lambda.Powertools.Kafka.Avro - Powertools for AWS Lambda (.NET) - Kafka Avro consumer package. - AWS.Lambda.Powertools.Kafka.Avro - AWS.Lambda.Powertools.Kafka.Avro - net8.0 - false - enable - enable - - - - - true - $(DefineConstants);KAFKA_AVRO - - - - - - - - - - - - \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Kafka.Avro/PowertoolsKafkaAvroSerializer.cs b/libraries/src/AWS.Lambda.Powertools.Kafka.Avro/PowertoolsKafkaAvroSerializer.cs deleted file mode 100644 index 6c2b2aead..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Kafka.Avro/PowertoolsKafkaAvroSerializer.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Reflection; -using System.Text.Json; -using System.Text.Json.Serialization; -using Avro; -using Avro.IO; -using Avro.Specific; - -namespace AWS.Lambda.Powertools.Kafka.Avro; - -/// -/// A Lambda serializer for Kafka events that handles Avro-formatted data. -/// This serializer automatically deserializes the Avro binary format from base64-encoded strings -/// in Kafka records and converts them to strongly-typed objects. -/// -/// -/// -/// [assembly: LambdaSerializer(typeof(PowertoolsKafkaAvroSerializer))] -/// -/// // Your Lambda handler will receive properly deserialized objects -/// public class Function -/// { -/// public void Handler(ConsumerRecords<string, Customer> records, ILambdaContext context) -/// { -/// foreach (var record in records) -/// { -/// Customer customer = record.Value; -/// context.Logger.LogInformation($"Processed customer {customer.Name}, age {customer.Age}"); -/// } -/// } -/// } -/// -/// -public class PowertoolsKafkaAvroSerializer : PowertoolsKafkaSerializerBase -{ - /// - /// Initializes a new instance of the class - /// with default JSON serialization options. - /// - public PowertoolsKafkaAvroSerializer() : base() - { - } - - /// - /// Initializes a new instance of the class - /// with custom JSON serialization options. - /// - /// Custom JSON serializer options to use during deserialization. - public PowertoolsKafkaAvroSerializer(JsonSerializerOptions jsonOptions) : base(jsonOptions) - { - } - - /// - /// Initializes a new instance of the class - /// with a JSON serializer context for AOT-compatible serialization. - /// - /// JSON serializer context for AOT compatibility. - public PowertoolsKafkaAvroSerializer(JsonSerializerContext serializerContext) : base(serializerContext) - { - } - - /// - /// Deserializes complex (non-primitive) types using Avro format. - /// Requires types to have a public static _SCHEMA field. - /// - [RequiresDynamicCode("Avro deserialization might require runtime code generation.")] - [RequiresUnreferencedCode("Avro deserialization might require types that cannot be statically analyzed.")] - protected override object? DeserializeComplexTypeFormat(byte[] data, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] - Type targetType, bool isKey, SchemaMetadata? schemaMetadata = null) - { - var schema = GetAvroSchema(targetType); - if (schema == null) - { - throw new InvalidOperationException( - $"Unsupported type for Avro deserialization: {targetType.Name}. " + - "Avro deserialization requires a type with a static _SCHEMA field. " + - "Consider using an alternative Deserializer."); - } - - using var stream = new MemoryStream(data); - var decoder = new BinaryDecoder(stream); - var reader = new SpecificDatumReader(schema, schema); - return reader.Read(null!, decoder); - } - - /// - /// Gets the Avro schema for the specified type from its static _SCHEMA field. - /// - [RequiresDynamicCode("Avro schema access requires reflection.")] - [RequiresUnreferencedCode("Avro schema access requires reflection.")] - private Schema? GetAvroSchema( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] Type payloadType) - { - var schemaField = payloadType.GetField("_SCHEMA", BindingFlags.Public | BindingFlags.Static); - return schemaField?.GetValue(null) as Schema; - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Kafka.Json/AWS.Lambda.Powertools.Kafka.Json.csproj b/libraries/src/AWS.Lambda.Powertools.Kafka.Json/AWS.Lambda.Powertools.Kafka.Json.csproj deleted file mode 100644 index 3c5ec81c4..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Kafka.Json/AWS.Lambda.Powertools.Kafka.Json.csproj +++ /dev/null @@ -1,26 +0,0 @@ - - - - - AWS.Lambda.Powertools.Kafka.Json - Powertools for AWS Lambda (.NET) - Kafka Json consumer package. - AWS.Lambda.Powertools.Kafka.Json - AWS.Lambda.Powertools.Kafka.Json - net8.0 - false - enable - enable - - - - - true - $(DefineConstants);KAFKA_JSON - - - - - - - - diff --git a/libraries/src/AWS.Lambda.Powertools.Kafka.Json/PowertoolsKafkaJsonSerializer.cs b/libraries/src/AWS.Lambda.Powertools.Kafka.Json/PowertoolsKafkaJsonSerializer.cs deleted file mode 100644 index 3e3979ad9..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Kafka.Json/PowertoolsKafkaJsonSerializer.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Text; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace AWS.Lambda.Powertools.Kafka.Json; - -/// -/// A Lambda serializer for Kafka events that handles JSON-formatted data. -/// This serializer automatically deserializes the JSON format from base64-encoded strings -/// in Kafka records and converts them to strongly-typed objects. -/// -public class PowertoolsKafkaJsonSerializer : PowertoolsKafkaSerializerBase -{ - /// - /// Initializes a new instance of the class - /// with default JSON serialization options. - /// - public PowertoolsKafkaJsonSerializer() : base() - { - } - - /// - /// Initializes a new instance of the class - /// with custom JSON serialization options. - /// - /// Custom JSON serializer options to use during deserialization. - public PowertoolsKafkaJsonSerializer(JsonSerializerOptions jsonOptions) : base(jsonOptions) - { - } - - /// - /// Initializes a new instance of the class - /// with a JSON serializer context for AOT-compatible serialization. - /// - /// JSON serializer context for AOT compatibility. - public PowertoolsKafkaJsonSerializer(JsonSerializerContext serializerContext) : base(serializerContext) - { - } - - /// - /// Deserializes complex (non-primitive) types using JSON format. - /// - [RequiresDynamicCode("JSON deserialization might require runtime code generation.")] - [RequiresUnreferencedCode("JSON deserialization might require types that cannot be statically analyzed.")] - protected override object? DeserializeComplexTypeFormat(byte[] data, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | - DynamicallyAccessedMemberTypes.PublicFields)] - Type targetType, bool isKey, SchemaMetadata? schemaMetadata = null) - { - if (data == null || data.Length == 0) - { - return targetType.IsValueType ? Activator.CreateInstance(targetType) : null; - } - - var jsonStr = Encoding.UTF8.GetString(data); - - // Try context-based deserialization first - if (SerializerContext != null) - { - var typeInfo = SerializerContext.GetTypeInfo(targetType); - if (typeInfo != null) - { - return JsonSerializer.Deserialize(jsonStr, typeInfo); - } - } - - // Fallback to regular deserialization -#pragma warning disable IL2026, IL3050 - return JsonSerializer.Deserialize(jsonStr, targetType, JsonOptions); -#pragma warning restore IL2026, IL3050 - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Kafka.Protobuf/AWS.Lambda.Powertools.Kafka.Protobuf.csproj b/libraries/src/AWS.Lambda.Powertools.Kafka.Protobuf/AWS.Lambda.Powertools.Kafka.Protobuf.csproj deleted file mode 100644 index eef178732..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Kafka.Protobuf/AWS.Lambda.Powertools.Kafka.Protobuf.csproj +++ /dev/null @@ -1,31 +0,0 @@ - - - - - AWS.Lambda.Powertools.Kafka.Protobuf - Powertools for AWS Lambda (.NET) - Kafka Protobuf consumer package. - AWS.Lambda.Powertools.Kafka.Protobuf - AWS.Lambda.Powertools.Kafka.Protobuf - net8.0 - false - enable - enable - - - - - true - $(DefineConstants);KAFKA_PROTOBUF - - - - - - - - - - - - - diff --git a/libraries/src/AWS.Lambda.Powertools.Kafka.Protobuf/PowertoolsKafkaProtobufSerializer.cs b/libraries/src/AWS.Lambda.Powertools.Kafka.Protobuf/PowertoolsKafkaProtobufSerializer.cs deleted file mode 100644 index 2cd7f759c..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Kafka.Protobuf/PowertoolsKafkaProtobufSerializer.cs +++ /dev/null @@ -1,168 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using System.Reflection; -using System.Text.Json; -using System.Text.Json.Serialization; -using Google.Protobuf; - - -namespace AWS.Lambda.Powertools.Kafka.Protobuf; - -/// -/// A Lambda serializer for Kafka events that handles Protobuf-formatted data. -/// This serializer automatically deserializes the Protobuf binary format from base64-encoded strings -/// in Kafka records and converts them to strongly-typed objects. -/// -/// -/// -/// [assembly: LambdaSerializer(typeof(PowertoolsKafkaProtobufSerializer))] -/// -/// // Your Lambda handler will receive properly deserialized objects -/// public class Function -/// { -/// public void Handler(ConsumerRecords<string, Customer> records, ILambdaContext context) -/// { -/// foreach (var record in records) -/// { -/// Customer customer = record.Value; -/// context.Logger.LogInformation($"Processed customer {customer.Name}"); -/// } -/// } -/// } -/// -/// -public class PowertoolsKafkaProtobufSerializer : PowertoolsKafkaSerializerBase -{ - /// - /// Initializes a new instance of the class - /// with default JSON serialization options. - /// - public PowertoolsKafkaProtobufSerializer() : base() - { - } - - /// - /// Initializes a new instance of the class - /// with custom JSON serialization options. - /// - /// Custom JSON serializer options to use during deserialization. - public PowertoolsKafkaProtobufSerializer(JsonSerializerOptions jsonOptions) : base(jsonOptions) - { - } - - /// - /// Initializes a new instance of the class - /// with a JSON serializer context for AOT-compatible serialization. - /// - /// JSON serializer context for AOT compatibility. - public PowertoolsKafkaProtobufSerializer(JsonSerializerContext serializerContext) : base(serializerContext) - { - } - - /// - /// Deserializes complex (non-primitive) types using Protobuf format. - /// Handles different parsing strategies based on schema metadata: - /// - No schema ID: Pure Protobuf deserialization - /// - UUID schema ID (16+ chars): Glue format - removes magic uint32 - /// - Short schema ID (≤10 chars): Confluent format - removes message indexes - /// - [RequiresDynamicCode("Protobuf deserialization might require runtime code generation.")] - [RequiresUnreferencedCode("Protobuf deserialization might require types that cannot be statically analyzed.")] - protected override object? DeserializeComplexTypeFormat(byte[] data, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | - DynamicallyAccessedMemberTypes.PublicFields)] - Type targetType, bool isKey, SchemaMetadata? schemaMetadata = null) - { - if (!typeof(IMessage).IsAssignableFrom(targetType)) - { - throw new InvalidOperationException( - $"Unsupported type for Protobuf deserialization: {targetType.Name}. " + - "Protobuf deserialization requires a type that implements IMessage. " + - "Consider using an alternative Deserializer."); - } - - var parser = GetProtobufParser(targetType); - if (parser == null) - { - throw new InvalidOperationException($"Could not find Protobuf parser for type {targetType.Name}"); - } - - return DeserializeByStrategy(data, parser, schemaMetadata); - } - - /// - /// Deserializes protobuf data using the appropriate strategy based on schema metadata. - /// - private IMessage DeserializeByStrategy(byte[] data, MessageParser parser, SchemaMetadata? schemaMetadata) - { - var schemaId = schemaMetadata?.SchemaId; - - if (string.IsNullOrEmpty(schemaId)) - { - // Pure protobuf - no preprocessing needed - return parser.ParseFrom(data); - } - - if (schemaId.Length > 10) - { - // Glue Schema Registry - remove magic uint32 - return DeserializeGlueFormat(data, parser); - } - - // Confluent Schema Registry - remove message indexes - return DeserializeConfluentFormat(data, parser); - } - - /// - /// Deserializes Glue Schema Registry format by removing the magic uint32. - /// - private IMessage DeserializeGlueFormat(byte[] data, MessageParser parser) - { - using var inputStream = new MemoryStream(data); - using var codedInput = new CodedInputStream(inputStream); - - codedInput.ReadUInt32(); // Skip magic bytes - return parser.ParseFrom(codedInput); - } - - /// - /// Deserializes Confluent Schema Registry format by removing message indexes. - /// Based on Java reference implementation. - /// - private IMessage DeserializeConfluentFormat(byte[] data, MessageParser parser) - { - using var inputStream = new MemoryStream(data); - using var codedInput = new CodedInputStream(inputStream); - - /* - ReadSInt32() behavior: - ReadSInt32() properly handles signed varint encoding using ZigZag encoding - ZigZag encoding maps signed integers to unsigned integers: (n << 1) ^ (n >> 31) - This allows both positive and negative numbers to be efficiently encoded - The key insight is that Confluent Schema Registry uses signed varint encoding for the message index count, not unsigned length encoding. - The ByteUtils.readVarint() in Java typically reads signed varints, which corresponds to ReadSInt32() in C# Google.Protobuf. - */ - - // Read number of message indexes - var indexCount = codedInput.ReadSInt32(); - - // Skip message indexes if any exist - if (indexCount > 0) - { - for (int i = 0; i < indexCount; i++) - { - codedInput.ReadSInt32(); // Read and discard each index - } - } - - return parser.ParseFrom(codedInput); - } - - /// - /// Gets the Protobuf parser for the specified type. - /// - private MessageParser? GetProtobufParser(Type messageType) - { - var parserProperty = messageType.GetProperty("Parser", BindingFlags.Public | BindingFlags.Static); - return parserProperty?.GetValue(null) as MessageParser; - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Kafka/AWS.Lambda.Powertools.Kafka.csproj b/libraries/src/AWS.Lambda.Powertools.Kafka/AWS.Lambda.Powertools.Kafka.csproj deleted file mode 100644 index d947528d6..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Kafka/AWS.Lambda.Powertools.Kafka.csproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - - AWS.Lambda.Powertools.Kafka - Powertools for AWS Lambda (.NET) - Kafka consumer package. - AWS.Lambda.Powertools.Kafka - AWS.Lambda.Powertools.Kafka - net8.0 - false - enable - enable - true - - - - - - - - diff --git a/libraries/src/AWS.Lambda.Powertools.Kafka/ConsumerRecord.cs b/libraries/src/AWS.Lambda.Powertools.Kafka/ConsumerRecord.cs deleted file mode 100644 index 8e90ec225..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Kafka/ConsumerRecord.cs +++ /dev/null @@ -1,78 +0,0 @@ -#if KAFKA_JSON -namespace AWS.Lambda.Powertools.Kafka.Json; -#elif KAFKA_AVRO -namespace AWS.Lambda.Powertools.Kafka.Avro; -#elif KAFKA_PROTOBUF -namespace AWS.Lambda.Powertools.Kafka.Protobuf; -#else -namespace AWS.Lambda.Powertools.Kafka; -#endif - -/// -/// Represents a single record consumed from a Kafka topic. -/// -/// The type of the record's value. -/// The type of the key value -/// -/// -/// var record = new ConsumerRecord<Customer> -/// { -/// Topic = "customers", -/// Partition = 0, -/// Offset = 42, -/// Value = new Customer { Id = 123, Name = "John Doe" } -/// }; -/// -/// -public class ConsumerRecord -{ - /// - /// Gets or sets the Kafka topic name from which the record was consumed. - /// - public string Topic { get; internal set; } = null!; - - /// - /// Gets the Kafka partition from which the record was consumed. - /// - public int Partition { get; internal set; } - - /// - /// Gets the offset of the record within its Kafka partition. - /// - public long Offset { get; internal set; } - - /// - /// Gets the timestamp of the record (typically in Unix time). - /// - public long Timestamp { get; internal set; } - - /// - /// Gets the type of timestamp (e.g., "CREATE_TIME" or "LOG_APPEND_TIME"). - /// - public string TimestampType { get; internal set; } = null!; - - /// - /// Gets the key of the record (often used for partitioning). - /// - public TK Key { get; internal set; } = default!; - - /// - /// Gets the deserialized value of the record. - /// - public T Value { get; internal set; } = default!; - - /// - /// Gets the headers associated with the record. - /// - public Dictionary Headers { get; internal set; } = null!; - - /// - /// Gets the schema metadata for the record's value. - /// - public SchemaMetadata ValueSchemaMetadata { get; internal set; } = null!; - - /// - /// Gets the schema metadata for the record's key. - /// - public SchemaMetadata KeySchemaMetadata { get; internal set; } = null!; -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Kafka/ConsumerRecords.cs b/libraries/src/AWS.Lambda.Powertools.Kafka/ConsumerRecords.cs deleted file mode 100644 index bb105c447..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Kafka/ConsumerRecords.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.Collections; - -#if KAFKA_JSON -namespace AWS.Lambda.Powertools.Kafka.Json; -#elif KAFKA_AVRO -namespace AWS.Lambda.Powertools.Kafka.Avro; -#elif KAFKA_PROTOBUF -namespace AWS.Lambda.Powertools.Kafka.Protobuf; -#else -namespace AWS.Lambda.Powertools.Kafka; -#endif - -/// -/// Represents a collection of Kafka consumer records that can be enumerated. -/// Contains event metadata and records organized by topics. -/// -/// The type of the record values from the event. -/// The type of Key values from the event. -public class ConsumerRecords : IEnumerable> -{ - /// - /// Gets the event source (typically "aws:kafka"). - /// - public string EventSource { get; internal set; } = null!; - - /// - /// Gets the ARN of the event source (MSK cluster or Self-managed Kafka). - /// - public string EventSourceArn { get; internal set; } = null!; - - /// - /// Gets the Kafka bootstrap servers connection string. - /// - public string BootstrapServers { get; internal set; } = null!; - - internal Dictionary>> Records { get; set; } = new(); - - /// - /// Returns an enumerator that iterates through all consumer records across all topics. - /// - /// An enumerator of ConsumerRecord<T> objects. - public IEnumerator> GetEnumerator() - { - foreach (var topicRecords in Records) - { - foreach (var record in topicRecords.Value) - { - yield return record; - } - } - } - - // Implement non-generic IEnumerable (required) - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Kafka/HeaderExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Kafka/HeaderExtensions.cs deleted file mode 100644 index ea1323db0..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Kafka/HeaderExtensions.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System.Text; - -#if KAFKA_JSON -namespace AWS.Lambda.Powertools.Kafka.Json; -#elif KAFKA_AVRO -namespace AWS.Lambda.Powertools.Kafka.Avro; -#elif KAFKA_PROTOBUF -namespace AWS.Lambda.Powertools.Kafka.Protobuf; -#else -namespace AWS.Lambda.Powertools.Kafka; -#endif - -/// -/// Extension methods for Kafka headers in ConsumerRecord. -/// -public static class HeaderExtensions -{ - /// - /// Gets the decoded value of a Kafka header from the ConsumerRecord's Headers dictionary. - /// - /// The header key-value pair from ConsumerRecord.Headers - /// The decoded string value. - public static Dictionary DecodedValues(this Dictionary headers) - { - if (headers == null) - { - return new Dictionary(); - } - - return headers.ToDictionary( - pair => pair.Key, - pair => pair.Value.DecodedValue() - ); - } - - /// - /// Decodes a byte array from a Kafka header into a UTF-8 string. - /// Returns an empty string if the byte array is null or empty. - /// - public static string DecodedValue(this byte[]? headerBytes) - { - if (headerBytes == null || headerBytes.Length == 0) - { - return string.Empty; - } - - return Encoding.UTF8.GetString(headerBytes); - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Kafka/InternalsVisibleTo.cs b/libraries/src/AWS.Lambda.Powertools.Kafka/InternalsVisibleTo.cs deleted file mode 100644 index fbcd85e53..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Kafka/InternalsVisibleTo.cs +++ /dev/null @@ -1,3 +0,0 @@ -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("AWS.Lambda.Powertools.Kafka.Tests")] \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Kafka/PowertoolsKafkaSerializerBase.cs b/libraries/src/AWS.Lambda.Powertools.Kafka/PowertoolsKafkaSerializerBase.cs deleted file mode 100644 index 72b0fef34..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Kafka/PowertoolsKafkaSerializerBase.cs +++ /dev/null @@ -1,661 +0,0 @@ -using Amazon.Lambda.Core; -using System.Diagnostics.CodeAnalysis; -using System.Reflection; -using System.Runtime.Serialization; -using System.Text; -using System.Text.Json; -using System.Text.Json.Serialization; -using System.Text.Json.Serialization.Metadata; -using AWS.Lambda.Powertools.Common; - -#if KAFKA_JSON -namespace AWS.Lambda.Powertools.Kafka.Json; -#elif KAFKA_AVRO -namespace AWS.Lambda.Powertools.Kafka.Avro; -#elif KAFKA_PROTOBUF -namespace AWS.Lambda.Powertools.Kafka.Protobuf; -#else -namespace AWS.Lambda.Powertools.Kafka; -#endif - -/// -/// Base class for Kafka event serializers that provides common functionality -/// for deserializing Kafka event structures in Lambda functions. -/// -/// -/// Inherit from this class to implement specific formats like Avro, Protobuf or JSON. -/// -public abstract class PowertoolsKafkaSerializerBase : ILambdaSerializer -{ - /// - /// JSON serializer options used for deserialization. - /// - protected readonly JsonSerializerOptions JsonOptions; - - /// - /// JSON serializer context used for AOT-compatible serialization/deserialization. - /// - protected readonly JsonSerializerContext? SerializerContext; - - /// - /// Initializes a new instance of the class - /// with default JSON serialization options. - /// - protected PowertoolsKafkaSerializerBase() : this(new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true - }, null) - { - } - - /// - /// Initializes a new instance of the class - /// with custom JSON serialization options. - /// - /// Custom JSON serializer options to use during deserialization. - protected PowertoolsKafkaSerializerBase(JsonSerializerOptions jsonOptions) : this(jsonOptions, null) - { - } - - /// - /// Initializes a new instance of the class - /// with a JSON serializer context for AOT-compatible serialization/deserialization. - /// - /// The JSON serializer context for AOT compatibility. - protected PowertoolsKafkaSerializerBase(JsonSerializerContext serializerContext) : this(serializerContext.Options, - serializerContext) - { - } - - /// - /// Initializes a new instance of the class - /// with custom JSON serialization options and an optional serializer context. - /// - /// Custom JSON serializer options to use during deserialization. - /// Optional JSON serializer context for AOT compatibility. - protected PowertoolsKafkaSerializerBase(JsonSerializerOptions jsonOptions, JsonSerializerContext? serializerContext) - { - JsonOptions = jsonOptions ?? new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; - SerializerContext = serializerContext; - - PowertoolsEnvironment.Instance.SetExecutionEnvironment(this); - } - - /// - /// Deserializes the Lambda input stream into the specified type. - /// Handles Kafka events with various serialization formats. - /// - public T Deserialize(Stream requestStream) - { - if (SerializerContext != null && typeof(T) != typeof(ConsumerRecords<,>)) - { - // Fast path for regular JSON types when serializer context is provided - var typeInfo = GetJsonTypeInfo(); - if (typeInfo != null) - { - return JsonSerializer.Deserialize(requestStream, typeInfo) ?? throw new InvalidOperationException(); - } - } - - using var reader = new StreamReader(requestStream); - var json = reader.ReadToEnd(); - - var targetType = typeof(T); - - if (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(ConsumerRecords<,>)) - { - return DeserializeConsumerRecords(json); - } - - if (SerializerContext != null) - { - var typeInfo = SerializerContext.GetTypeInfo(targetType); - if (typeInfo != null) - { - return (T)JsonSerializer.Deserialize(json, typeInfo)!; - } - } - -#pragma warning disable IL2026, IL3050 - var result = JsonSerializer.Deserialize(json, JsonOptions); -#pragma warning restore IL2026, IL3050 - - return result ?? throw new InvalidOperationException($"Failed to deserialize to type {typeof(T).Name}"); - } - - /// - /// Deserializes a Kafka ConsumerRecords event from JSON string. - /// - /// The ConsumerRecords type with key and value generics. - /// The JSON string to deserialize. - /// The deserialized ConsumerRecords object. - [RequiresUnreferencedCode("ConsumerRecords deserialization uses reflection and may be incompatible with trimming.")] - [RequiresDynamicCode( - "ConsumerRecords deserialization dynamically creates generic types and may be incompatible with NativeAOT.")] - private T DeserializeConsumerRecords(string json) - { - var targetType = typeof(T); - var typeArgs = targetType.GetGenericArguments(); - var keyType = typeArgs[0]; - var valueType = typeArgs[1]; - - using var document = JsonDocument.Parse(json); - var root = document.RootElement; - - // Create the typed instance and set basic properties - var typedEvent = CreateConsumerRecordsInstance(targetType); - SetBasicProperties(root, typedEvent, targetType); - - // Create and populate records dictionary - if (root.TryGetProperty("records", out var recordsElement)) - { - var records = CreateRecordsDictionary(recordsElement, keyType, valueType); - targetType.GetProperty("Records", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) - ?.SetValue(typedEvent, records); - } - - return (T)typedEvent; - } - - private object CreateConsumerRecordsInstance(Type targetType) - { - return Activator.CreateInstance(targetType) ?? - throw new InvalidOperationException($"Failed to create instance of {targetType.Name}"); - } - - private void SetBasicProperties(JsonElement root, object instance, Type targetType) - { - if (root.TryGetProperty("eventSource", out var eventSource)) - targetType.GetProperty("EventSource", BindingFlags.Public | BindingFlags.Instance) - ?.SetValue(instance, eventSource.GetString()); - - if (root.TryGetProperty("eventSourceArn", out var eventSourceArn)) - targetType.GetProperty("EventSourceArn")?.SetValue(instance, eventSourceArn.GetString()); - - if (root.TryGetProperty("bootstrapServers", out var bootstrapServers)) - targetType.GetProperty("BootstrapServers")?.SetValue(instance, bootstrapServers.GetString()); - } - - private object CreateRecordsDictionary(JsonElement recordsElement, Type keyType, Type valueType) - { - // Create dictionary with correct generic types - var dictType = typeof(Dictionary<,>).MakeGenericType( - typeof(string), - typeof(List<>).MakeGenericType(typeof(ConsumerRecord<,>).MakeGenericType(keyType, valueType)) - ); - var records = Activator.CreateInstance(dictType) ?? - throw new InvalidOperationException($"Failed to create dictionary of type {dictType.Name}"); - var dictAddMethod = dictType.GetMethod("Add") ?? - throw new InvalidOperationException("Add method not found on dictionary type"); - - // Process each topic partition - foreach (var topicPartition in recordsElement.EnumerateObject()) - { - var topicName = topicPartition.Name; - var recordsList = ProcessTopicPartition(topicPartition.Value, keyType, valueType); - dictAddMethod.Invoke(records, new[] { topicName, recordsList }); - } - - return records; - } - - private object ProcessTopicPartition(JsonElement partitionData, Type keyType, Type valueType) - { - // Create list type with correct generics - var listType = typeof(List<>).MakeGenericType( - typeof(ConsumerRecord<,>).MakeGenericType(keyType, valueType)); - var recordsList = Activator.CreateInstance(listType) ?? - throw new InvalidOperationException($"Failed to create list of type {listType.Name}"); - var listAddMethod = listType.GetMethod("Add") ?? - throw new InvalidOperationException("Add method not found on list type"); - - // Process each record - foreach (var recordElement in partitionData.EnumerateArray()) - { - var record = CreateAndPopulateRecord(recordElement, keyType, valueType); - if (record != null) - { - listAddMethod.Invoke(recordsList, new[] { record }); - } - } - - return recordsList; - } - - private object? CreateAndPopulateRecord(JsonElement recordElement, Type keyType, Type valueType) - { - // Create record instance - var recordType = typeof(ConsumerRecord<,>).MakeGenericType(keyType, valueType); - var record = Activator.CreateInstance(recordType); - if (record == null) - return null; - - // Set basic properties - SetProperty(recordType, record, "Topic", recordElement, "topic"); - SetProperty(recordType, record, "Partition", recordElement, "partition"); - SetProperty(recordType, record, "Offset", recordElement, "offset"); - SetProperty(recordType, record, "Timestamp", recordElement, "timestamp"); - SetProperty(recordType, record, "TimestampType", recordElement, "timestampType"); - - // Process schema metadata for both key and value FIRST - SchemaMetadata? keySchemaMetadata = null; - SchemaMetadata? valueSchemaMetadata = null; - - ProcessSchemaMetadata(recordElement, record, recordType, "keySchemaMetadata", "KeySchemaMetadata"); - ProcessSchemaMetadata(recordElement, record, recordType, "valueSchemaMetadata", "ValueSchemaMetadata"); - - // Get the schema metadata for use in deserialization - if (recordElement.TryGetProperty("keySchemaMetadata", out var keyMetadataElement)) - { - keySchemaMetadata = ExtractSchemaMetadata(keyMetadataElement); - } - - if (recordElement.TryGetProperty("valueSchemaMetadata", out var valueMetadataElement)) - { - valueSchemaMetadata = ExtractSchemaMetadata(valueMetadataElement); - } - - // Process key with schema metadata context - ProcessKey(recordElement, record, recordType, keyType, keySchemaMetadata); - - // Process value with schema metadata context - ProcessValue(recordElement, record, recordType, valueType, valueSchemaMetadata); - - // Process headers - ProcessHeaders(recordElement, record, recordType); - - return record; - } - - private SchemaMetadata? ExtractSchemaMetadata(JsonElement metadataElement) - { - var schemaMetadata = new SchemaMetadata(); - var hasData = false; - - if (metadataElement.TryGetProperty("dataFormat", out var dataFormatElement)) - { - schemaMetadata.DataFormat = dataFormatElement.GetString() ?? string.Empty; - hasData = true; - } - - if (metadataElement.TryGetProperty("schemaId", out var schemaIdElement)) - { - schemaMetadata.SchemaId = schemaIdElement.GetString() ?? string.Empty; - hasData = true; - } - - return hasData ? schemaMetadata : null; - } - - private void ProcessKey(JsonElement recordElement, object record, Type recordType, Type keyType, SchemaMetadata? keySchemaMetadata) - { - if (recordElement.TryGetProperty("key", out var keyElement) && keyElement.ValueKind == JsonValueKind.String) - { - var base64Key = keyElement.GetString(); - if (!string.IsNullOrEmpty(base64Key)) - { - try - { - var keyBytes = Convert.FromBase64String(base64Key); - var decodedKey = DeserializeKey(keyBytes, keyType, keySchemaMetadata); - recordType.GetProperty("Key")?.SetValue(record, decodedKey); - } - catch (Exception ex) - { - throw new SerializationException($"Failed to deserialize key data: {ex.Message}", ex); - } - } - } - } - - private void ProcessValue(JsonElement recordElement, object record, Type recordType, Type valueType, SchemaMetadata? valueSchemaMetadata) - { - if (recordElement.TryGetProperty("value", out var valueElement) && valueElement.ValueKind == JsonValueKind.String) - { - var base64Value = valueElement.GetString(); - var valueProperty = recordType.GetProperty("Value"); - - if (base64Value != null && valueProperty != null) - { - try - { - var deserializedValue = DeserializeValue(base64Value, valueType, valueSchemaMetadata); - valueProperty.SetValue(record, deserializedValue); - } - catch (Exception ex) - { - throw new SerializationException($"Failed to deserialize value data: {ex.Message}", ex); - } - } - } - } - - /// - /// Deserializes a key from bytes based on the specified key type. - /// - /// The key bytes to deserialize. - /// The target type for the key. - /// Optional schema metadata for the key. - /// The deserialized key object. - private object? DeserializeKey(byte[] keyBytes, Type keyType, SchemaMetadata? keySchemaMetadata) - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract - if (keyBytes == null || keyBytes.Length == 0) - return null; - - if (IsPrimitiveOrSimpleType(keyType)) - { - return DeserializePrimitiveValue(keyBytes, keyType); - } - - // For complex types, use format-specific deserialization - return DeserializeFormatSpecific(keyBytes, keyType, isKey: true, keySchemaMetadata); - } - - /// - /// Sets a property value on an object instance from a JsonElement. - /// - /// The type of the object. - /// The object instance. - /// The name of the property to set. - /// The JsonElement containing the source data. - /// The property name within the JsonElement. - [RequiresDynamicCode("Dynamically accesses properties which might be trimmed.")] - [RequiresUnreferencedCode("Dynamically accesses properties which might be trimmed.")] - private void SetProperty( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] - Type type, object instance, string propertyName, - JsonElement element, string jsonPropertyName) - { - if (!element.TryGetProperty(jsonPropertyName, out var jsonValue) || - jsonValue.ValueKind == JsonValueKind.Null) - return; - - // Add BindingFlags to find internal properties too - var property = type.GetProperty(propertyName, - BindingFlags.Public | BindingFlags.Instance); - if (property == null) return; - var propertyType = property.PropertyType; - - object value; - if (propertyType == typeof(int)) value = jsonValue.GetInt32(); - else if (propertyType == typeof(long)) value = jsonValue.GetInt64(); - else if (propertyType == typeof(double)) value = jsonValue.GetDouble(); - else if (propertyType == typeof(string)) value = jsonValue.GetString()!; - else return; - - property.SetValue(instance, value); - } - - /// - /// Serializes an object to JSON and writes it to the provided stream. - /// - public void Serialize(T response, Stream responseStream) - { - if (EqualityComparer.Default.Equals(response, default(T))) - { - if (responseStream.CanWrite) - { - var nullBytes = Encoding.UTF8.GetBytes("null"); - responseStream.Write(nullBytes, 0, nullBytes.Length); - } - return; - } - - if (SerializerContext != null) - { - var typeInfo = SerializerContext.GetTypeInfo(response.GetType()) ?? - SerializerContext.GetTypeInfo(typeof(T)); - if (typeInfo != null) - { - JsonSerializer.Serialize(responseStream, response, typeInfo); - return; - } - } - - using var writer = new StreamWriter(responseStream, encoding: Encoding.UTF8, bufferSize: 1024, leaveOpen: true); -#pragma warning disable IL2026, IL3050 - var jsonResponse = JsonSerializer.Serialize(response, JsonOptions); -#pragma warning restore IL2026, IL3050 - writer.Write(jsonResponse); - writer.Flush(); - } - - // Helper to get non-generic JsonTypeInfo from context based on a Type argument - private JsonTypeInfo? GetJsonTypeInfoFromContext(Type type) - { - if (SerializerContext == null) - return null; - - return SerializerContext.GetTypeInfo(type); - } - - private JsonTypeInfo? GetJsonTypeInfo() - { - if (SerializerContext == null) return null; - - foreach (var prop in SerializerContext.GetType().GetProperties()) - { - if (prop.PropertyType == typeof(JsonTypeInfo)) - { - return prop.GetValue(SerializerContext) as JsonTypeInfo; - } - } - return null; - } - - /// - /// Deserializes a base64-encoded value into an object using the appropriate format. - /// - [RequiresDynamicCode("Deserializing values might require runtime code generation.")] - [RequiresUnreferencedCode("Deserializing values might require types that cannot be statically analyzed.")] - protected virtual object DeserializeValue(string base64Value, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | - DynamicallyAccessedMemberTypes.PublicFields)] - Type valueType, SchemaMetadata? valueSchemaMetadata = null) - { - if (IsPrimitiveOrSimpleType(valueType)) - { - var bytes = Convert.FromBase64String(base64Value); - return DeserializePrimitiveValue(bytes, valueType); - } - - var data = Convert.FromBase64String(base64Value); - return DeserializeFormatSpecific(data, valueType, isKey: false, valueSchemaMetadata); - } - - /// - /// Deserializes binary data using format-specific implementation. - /// - [RequiresDynamicCode("Format-specific deserialization might require runtime code generation.")] - [RequiresUnreferencedCode("Format-specific deserialization might require types that cannot be statically analyzed.")] - protected virtual object? DeserializeFormatSpecific(byte[] data, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | - DynamicallyAccessedMemberTypes.PublicFields)] - Type targetType, bool isKey, SchemaMetadata? schemaMetadata = null) - { - if (IsPrimitiveOrSimpleType(targetType)) - { - return DeserializePrimitiveValue(data, targetType); - } - - return DeserializeComplexTypeFormat(data, targetType, isKey, schemaMetadata); - } - - /// - /// Deserializes complex (non-primitive) types using format-specific implementation. - /// Each derived class must implement this method to handle its specific format. - /// - [RequiresDynamicCode("Format-specific deserialization might require runtime code generation.")] - [RequiresUnreferencedCode("Format-specific deserialization might require types that cannot be statically analyzed.")] - protected abstract object? DeserializeComplexTypeFormat(byte[] data, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | - DynamicallyAccessedMemberTypes.PublicFields)] - Type targetType, bool isKey, SchemaMetadata? schemaMetadata = null); - - /// - /// Checks if the specified type is a primitive or simple type. - /// - protected bool IsPrimitiveOrSimpleType(Type type) - { - return type.IsPrimitive || - type == typeof(string) || - type == typeof(decimal) || - type == typeof(DateTime) || - type == typeof(Guid); - } - - /// - /// Deserializes a primitive value from bytes based on the specified type. - /// - protected object? DeserializePrimitiveValue(byte[] bytes, Type valueType) - { - if (bytes == null! || bytes.Length == 0) - return null!; - - if (valueType == typeof(string)) - return Encoding.UTF8.GetString(bytes); - - var stringValue = Encoding.UTF8.GetString(bytes); - - return valueType.Name switch - { - nameof(Int32) => DeserializeIntValue(bytes, stringValue), - nameof(Int64) => DeserializeLongValue(bytes, stringValue), - nameof(Double) => DeserializeDoubleValue(bytes, stringValue), - nameof(Boolean) => DeserializeBoolValue(bytes, stringValue), - nameof(Guid) => DeserializeGuidValue(bytes, stringValue), - _ => DeserializeGenericValue(stringValue, valueType) - }; - } - - private object DeserializeIntValue(byte[] bytes, string stringValue) - { - // Try string parsing first - if (int.TryParse(stringValue, out var parsedValue)) - return parsedValue; - - // Fall back to binary representation - return bytes.Length switch - { - >= 4 => BitConverter.ToInt32(bytes, 0), - 1 => bytes[0], - _ => 0 - }; - } - - private object DeserializeLongValue(byte[] bytes, string stringValue) - { - if (long.TryParse(stringValue, out var parsedValue)) - return parsedValue; - - return bytes.Length switch - { - >= 8 => BitConverter.ToInt64(bytes, 0), - >= 4 => BitConverter.ToInt32(bytes, 0), - _ => 0L - }; - } - - private object DeserializeDoubleValue(byte[] bytes, string stringValue) - { - if (double.TryParse(stringValue, out var doubleValue)) - return doubleValue; - - return bytes.Length >= 8 ? BitConverter.ToDouble(bytes, 0) : 0.0; - } - - private object DeserializeBoolValue(byte[] bytes, string stringValue) - { - if (bool.TryParse(stringValue, out var boolValue)) - return boolValue; - - return bytes[0] != 0; - } - - private object? DeserializeGuidValue(byte[] bytes, string stringValue) - { - if (bytes.Length < 16) - return Guid.Empty; - - try - { - return new Guid(bytes); - } - catch - { - // If binary parsing fails, try string parsing - return Guid.TryParse(stringValue, out var guidValue) ? guidValue : Guid.Empty; - } - } - - private object? DeserializeGenericValue(string stringValue, Type valueType) - { - try - { - return Convert.ChangeType(stringValue, valueType); - } - catch - { - return valueType.IsValueType ? Activator.CreateInstance(valueType) : null; - } - } - - private void ProcessSchemaMetadata(JsonElement recordElement, object record, Type recordType, - string jsonPropertyName, string recordPropertyName) - { - if (recordElement.TryGetProperty(jsonPropertyName, out var metadataElement)) - { - var schemaMetadata = new SchemaMetadata(); - - if (metadataElement.TryGetProperty("dataFormat", out var dataFormatElement)) - { - schemaMetadata.DataFormat = dataFormatElement.GetString() ?? string.Empty; - } - - if (metadataElement.TryGetProperty("schemaId", out var schemaIdElement)) - { - schemaMetadata.SchemaId = schemaIdElement.GetString() ?? string.Empty; - } - - recordType.GetProperty(recordPropertyName)?.SetValue(record, schemaMetadata); - } - } - - private void ProcessHeaders(JsonElement recordElement, object record, Type recordType) - { - if (recordElement.TryGetProperty("headers", out var headersElement) && - headersElement.ValueKind == JsonValueKind.Array) - { - var headers = new Dictionary(); - - foreach (var headerObj in headersElement.EnumerateArray()) - { - foreach (var header in headerObj.EnumerateObject()) - { - if (header.Value.ValueKind == JsonValueKind.Array) - { - headers[header.Name] = ExtractHeaderBytes(header.Value); - } - } - } - - var headersProperty = recordType.GetProperty("Headers", - BindingFlags.Public | BindingFlags.Instance); - headersProperty?.SetValue(record, headers); - } - } - - private byte[] ExtractHeaderBytes(JsonElement headerArray) - { - var headerBytes = new byte[headerArray.GetArrayLength()]; - var i = 0; - foreach (var byteVal in headerArray.EnumerateArray()) - { - headerBytes[i++] = (byte)byteVal.GetInt32(); - } - - return headerBytes; - } -} - diff --git a/libraries/src/AWS.Lambda.Powertools.Kafka/SchemaMetadata.cs b/libraries/src/AWS.Lambda.Powertools.Kafka/SchemaMetadata.cs deleted file mode 100644 index 6947930b0..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Kafka/SchemaMetadata.cs +++ /dev/null @@ -1,25 +0,0 @@ -#if KAFKA_JSON -namespace AWS.Lambda.Powertools.Kafka.Json; -#elif KAFKA_AVRO -namespace AWS.Lambda.Powertools.Kafka.Avro; -#elif KAFKA_PROTOBUF -namespace AWS.Lambda.Powertools.Kafka.Protobuf; -#else -namespace AWS.Lambda.Powertools.Kafka; -#endif - -/// -/// Represents metadata about the schema used for serializing the record's value or key. -/// -public class SchemaMetadata -{ - /// - /// Gets or sets the format of the data (e.g., "JSON", "AVRO" "Protobuf"). - /// /// - public string DataFormat { get; internal set; } = null!; - - /// - /// Gets or sets the schema ID associated with the record's value or key. - /// - public string SchemaId { get; internal set; } = null!; -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/AWS.Lambda.Powertools.Logging.csproj b/libraries/src/AWS.Lambda.Powertools.Logging/AWS.Lambda.Powertools.Logging.csproj index ccf8c3ead..a4a1478f2 100644 --- a/libraries/src/AWS.Lambda.Powertools.Logging/AWS.Lambda.Powertools.Logging.csproj +++ b/libraries/src/AWS.Lambda.Powertools.Logging/AWS.Lambda.Powertools.Logging.csproj @@ -15,7 +15,6 @@ - diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/BufferedLogEntry.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/BufferedLogEntry.cs deleted file mode 100644 index 2b0aaadba..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/BufferedLogEntry.cs +++ /dev/null @@ -1,14 +0,0 @@ - -namespace AWS.Lambda.Powertools.Logging.Internal; - -internal class BufferedLogEntry -{ - public string Entry { get; } - public int Size { get; } - - public BufferedLogEntry(string entry, int calculatedSize) - { - Entry = entry; - Size = calculatedSize; - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/BufferingLoggerProvider.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/BufferingLoggerProvider.cs deleted file mode 100644 index eaf70cb34..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/BufferingLoggerProvider.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System.Collections.Concurrent; -using AWS.Lambda.Powertools.Common; -using Microsoft.Extensions.Logging; - -namespace AWS.Lambda.Powertools.Logging.Internal; - -/// -/// Logger provider that supports buffering logs -/// -[ProviderAlias("PowertoolsBuffering")] -internal class BufferingLoggerProvider : PowertoolsLoggerProvider -{ - private readonly IPowertoolsConfigurations _powertoolsConfigurations; - private readonly ConcurrentDictionary _loggers = new(); - - internal BufferingLoggerProvider( - PowertoolsLoggerConfiguration config, - IPowertoolsConfigurations powertoolsConfigurations) - : base(config, powertoolsConfigurations) - { - _powertoolsConfigurations = powertoolsConfigurations; - // Register with the buffer manager - LogBufferManager.RegisterProvider(this); - } - - public override ILogger CreateLogger(string categoryName) - { - return _loggers.GetOrAdd( - categoryName, - name => new PowertoolsBufferingLogger( - base.CreateLogger(name), // Use the parent's logger creation - GetCurrentConfig, - _powertoolsConfigurations)); - } - - /// - /// Flush all buffered logs - /// - internal void FlushBuffers() - { - foreach (var logger in _loggers.Values) - { - logger.FlushBuffer(); - } - } - - /// - /// Clear all buffered logs - /// - internal void ClearBuffers() - { - foreach (var logger in _loggers.Values) - { - logger.ClearBuffer(); - } - } - - /// - /// Clear buffered logs for the current invocation only - /// - internal void ClearCurrentBuffer() - { - foreach (var logger in _loggers.Values) - { - logger.ClearCurrentInvocation(); - } - } - - public override void Dispose() - { - // Flush all buffers before disposing - foreach (var logger in _loggers.Values) - { - logger.FlushBuffer(); - } - - // Unregister from buffer manager - LogBufferManager.UnregisterProvider(this); - - _loggers.Clear(); - base.Dispose(); - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/InvocationBuffer.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/InvocationBuffer.cs deleted file mode 100644 index a8bec211d..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/InvocationBuffer.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; - -namespace AWS.Lambda.Powertools.Logging.Internal; - -/// -/// Buffer for a specific invocation -/// -internal class InvocationBuffer -{ - private readonly ConcurrentQueue _buffer = new(); - private int _currentSize; - - public void Add(string logEntry, int maxBytes, int size) - { - // If entry size exceeds max buffer size, discard the entry completely - if (size > maxBytes) - { - // Entry is too large to ever fit in buffer, discard it - return; - } - - if (_currentSize + size > maxBytes) - { - // Remove oldest entries until we have enough space - while (_currentSize + size > maxBytes && _buffer.TryDequeue(out var removed)) - { - _currentSize -= removed.Size; - HasEvictions = true; - } - - if (_currentSize < 0) _currentSize = 0; - } - - _buffer.Enqueue(new BufferedLogEntry(logEntry, size)); - _currentSize += size; - } - - public IReadOnlyCollection GetAndClear() - { - var entries = new List(); - - try - { - while (_buffer.TryDequeue(out var entry)) - { - entries.Add(entry.Entry); - } - } - catch (Exception) - { - _buffer.Clear(); - } - - _currentSize = 0; - return entries; - } - - public bool HasEntries => !_buffer.IsEmpty; - - public bool HasEvictions; -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/LogBuffer.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/LogBuffer.cs deleted file mode 100644 index db19e0961..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/LogBuffer.cs +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using AWS.Lambda.Powertools.Common; - -namespace AWS.Lambda.Powertools.Logging.Internal; - -/// -/// A buffer for storing log entries, with isolation per Lambda invocation -/// -internal class LogBuffer -{ - private readonly IPowertoolsConfigurations _powertoolsConfigurations; - -// Dictionary of buffers by invocation ID - private readonly ConcurrentDictionary _buffersByInvocation = new(); - private string _lastInvocationId; - - // Get the current invocation ID or create a fallback - private string CurrentInvocationId => _powertoolsConfigurations.XRayTraceId; - - public LogBuffer(IPowertoolsConfigurations powertoolsConfigurations) - { - _powertoolsConfigurations = powertoolsConfigurations; - } - - /// - /// Add a log entry to the buffer for the current invocation - /// - public void Add(string logEntry, int maxBytes, int size) - { - var invocationId = CurrentInvocationId; - if (string.IsNullOrEmpty(invocationId)) - { - // No invocation ID set, do not buffer - return; - } - - // If this is a new invocation ID, clear previous buffers - if (_lastInvocationId != invocationId) - { - if (_lastInvocationId != null) - _buffersByInvocation.Clear(); - _lastInvocationId = invocationId; - } - - var buffer = _buffersByInvocation.GetOrAdd(invocationId, _ => new InvocationBuffer()); - buffer.Add(logEntry, maxBytes, size); - } - - /// - /// Get all entries for the current invocation and clear that buffer - /// - public IReadOnlyCollection GetAndClear() - { - var invocationId = CurrentInvocationId; - - if (string.IsNullOrEmpty(invocationId)) - { - // No invocation ID set, return empty - return Array.Empty(); - } - - // Try to get and remove the buffer for this invocation - if (_buffersByInvocation.TryRemove(invocationId, out var buffer)) - { - return buffer.GetAndClear(); - } - - return Array.Empty(); - } - - /// - /// Clear all buffers - /// - public void Clear() - { - _buffersByInvocation.Clear(); - } - - /// - /// Clear buffer for the current invocation - /// - public void ClearCurrentInvocation() - { - var invocationId = CurrentInvocationId; - _buffersByInvocation.TryRemove(invocationId, out _); - } - - /// - /// Check if the current invocation has any buffered entries - /// - public bool HasEntries - { - get - { - var invocationId = CurrentInvocationId; - return _buffersByInvocation.TryGetValue(invocationId, out var buffer) && buffer.HasEntries; - } - } - - public bool HasEvictions - { - get - { - var invocationId = CurrentInvocationId; - return _buffersByInvocation.TryGetValue(invocationId, out var buffer) && buffer.HasEvictions; - } - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/LogBufferManager.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/LogBufferManager.cs deleted file mode 100644 index 9e3a3aa8c..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/LogBufferManager.cs +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -using System; -using System.Collections.Generic; - -namespace AWS.Lambda.Powertools.Logging.Internal; - -/// -/// Singleton manager for log buffer operations with invocation context awareness -/// -internal static class LogBufferManager -{ - private static readonly List Providers = new(); - - /// - /// Register a buffering provider with the manager - /// - internal static void RegisterProvider(BufferingLoggerProvider provider) - { - if (!Providers.Contains(provider)) - Providers.Add(provider); - } - - /// - /// Flush buffered logs for the current invocation - /// - internal static void FlushCurrentBuffer() - { - try - { - foreach (var provider in Providers) - { - provider?.FlushBuffers(); - } - } - catch (Exception) - { - // Suppress errors - } - } - - /// - /// Clear buffered logs for the current invocation - /// - internal static void ClearCurrentBuffer() - { - try - { - foreach (var provider in Providers) - { - provider?.ClearCurrentBuffer(); - } - } - catch (Exception) - { - // Suppress errors - } - } - - /// - /// Unregister a buffering provider from the manager - /// - /// - internal static void UnregisterProvider(BufferingLoggerProvider provider) - { - Providers.Remove(provider); - } - - /// - /// Reset the manager state (for testing purposes) - /// - internal static void ResetForTesting() - { - Providers.Clear(); - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/Logger.Buffer.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/Logger.Buffer.cs deleted file mode 100644 index 9e715c559..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/Logger.Buffer.cs +++ /dev/null @@ -1,40 +0,0 @@ -/* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * .cs -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -using AWS.Lambda.Powertools.Logging.Internal; - -namespace AWS.Lambda.Powertools.Logging; - -public static partial class Logger -{ - /// - /// Flush any buffered logs - /// - public static void FlushBuffer() - { - // Use the buffer manager directly - LogBufferManager.FlushCurrentBuffer(); - } - - /// - /// Clear any buffered logs without writing them - /// - public static void ClearBuffer() - { - // Use the buffer manager directly - LogBufferManager.ClearCurrentBuffer(); - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/PowertoolsBufferingLogger.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/PowertoolsBufferingLogger.cs deleted file mode 100644 index 05d24e7bd..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Buffer/PowertoolsBufferingLogger.cs +++ /dev/null @@ -1,149 +0,0 @@ -using System; -using AWS.Lambda.Powertools.Common; -using Microsoft.Extensions.Logging; - -namespace AWS.Lambda.Powertools.Logging.Internal; - -/// -/// Logger implementation that supports buffering -/// -internal class PowertoolsBufferingLogger : ILogger -{ - private readonly ILogger _logger; - private readonly Func _getCurrentConfig; - private readonly LogBuffer _buffer; - - public PowertoolsBufferingLogger( - ILogger logger, - Func getCurrentConfig, - IPowertoolsConfigurations powertoolsConfigurations) - { - _logger = logger; - _getCurrentConfig = getCurrentConfig; - _buffer = new LogBuffer(powertoolsConfigurations); - } - - public IDisposable BeginScope(TState state) - { - return _logger.BeginScope(state); - } - - public bool IsEnabled(LogLevel logLevel) - { - return true; - } - - public void Log( - LogLevel logLevel, - EventId eventId, - TState state, - Exception exception, - Func formatter) - { - var options = _getCurrentConfig(); - var bufferOptions = options.LogBuffering; - - // Check if this log should be buffered - bool shouldBuffer = logLevel <= bufferOptions.BufferAtLogLevel; - - if (shouldBuffer) - { - // Add to buffer instead of logging - try - { - if (_logger is PowertoolsLogger powertoolsLogger) - { - var logEntry = powertoolsLogger.LogEntryString(logLevel, state, exception, formatter); - - // Check the size of the log entry, log it if too large - var size = 100 + (logEntry?.Length ?? 0) * 2; - if (size > bufferOptions.MaxBytes) - { - // log the entry directly if it exceeds the buffer size - powertoolsLogger.LogLine(logEntry); - ConsoleWrapper.WriteLine(LogLevel.Warning.ToLambdaLogLevel(), "Cannot add item to the buffer"); - } - else - { - _buffer.Add(logEntry, bufferOptions.MaxBytes, size); - } - } - } - catch (Exception ex) - { - // If buffering fails, try to log an error about it - try - { - _logger.LogError(ex, "Failed to buffer log entry"); - } - catch - { - // Last resort: if even that fails, just suppress the error - } - } - } - else - { - // If this is an error and we should flush on error - if (bufferOptions.FlushOnErrorLog && - logLevel >= LogLevel.Error) - { - FlushBuffer(); - } - } - } - - /// - /// Flush buffered logs to the inner logger - /// - public void FlushBuffer() - { - try - { - if (_logger is PowertoolsLogger powertoolsLogger) - { - if (_buffer.HasEvictions) - { - ConsoleWrapper.WriteLine(LogLevel.Warning.ToLambdaLogLevel(), "Some logs are not displayed because they were evicted from the buffer. Increase buffer size to store more logs in the buffer"); - } - - // Get all buffered entries - var entries = _buffer.GetAndClear(); - - // Log each entry directly - foreach (var entry in entries) - { - powertoolsLogger.LogLine(entry); - } - } - } - catch (Exception ex) - { - // If the entire flush operation fails, try to log an error - try - { - _logger.LogError(ex, "Failed to flush log buffer"); - } - catch - { - // If even that fails, just suppress the error - } - } - } - - /// - /// Clear the buffer without logging - /// - public void ClearBuffer() - { - _buffer.Clear(); - } - - /// - /// Clear buffered logs only for the current invocation - /// - public void ClearCurrentInvocation() - { - _buffer.ClearCurrentInvocation(); - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/ByteArrayConverter.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/ByteArrayConverter.cs index b868aa645..b6d7120d1 100644 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/ByteArrayConverter.cs +++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/ByteArrayConverter.cs @@ -34,30 +34,31 @@ internal class ByteArrayConverter : JsonConverter /// public override byte[] Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - if (reader.TokenType == JsonTokenType.Null) - return []; - - if (reader.TokenType == JsonTokenType.String) - return Convert.FromBase64String(reader.GetString()!); - - throw new JsonException("Expected string value for byte array"); + throw new NotSupportedException("Deserializing ByteArray is not allowed"); } /// /// Write the exception value as JSON. /// /// The unicode JsonWriter. - /// + /// The byte array. /// The JsonSerializer options. - public override void Write(Utf8JsonWriter writer, byte[] value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, byte[] values, JsonSerializerOptions options) { - if (value == null) + if (values == null) { writer.WriteNullValue(); - return; } - - string base64 = Convert.ToBase64String(value); - writer.WriteStringValue(base64); + else + { + writer.WriteStartArray(); + + foreach (var value in values) + { + writer.WriteNumberValue(value); + } + + writer.WriteEndArray(); + } } } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/ConstantClassConverter.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/ConstantClassConverter.cs index e6c3aebbe..1bc0f6e96 100644 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/ConstantClassConverter.cs +++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/ConstantClassConverter.cs @@ -23,7 +23,7 @@ namespace AWS.Lambda.Powertools.Logging.Internal.Converters; /// /// JsonConvert to handle the AWS SDK for .NET custom enum classes that derive from the class called ConstantClass. /// -internal class ConstantClassConverter : JsonConverter +public class ConstantClassConverter : JsonConverter { private static readonly HashSet ConstantClassNames = new() { diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/DateOnlyConverter.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/DateOnlyConverter.cs index efd9425b6..a6f969e59 100644 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/DateOnlyConverter.cs +++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/DateOnlyConverter.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.Globalization; using System.Text.Json; @@ -8,7 +23,7 @@ namespace AWS.Lambda.Powertools.Logging.Internal.Converters; /// /// DateOnly JSON converter /// -internal class DateOnlyConverter : JsonConverter +public class DateOnlyConverter : JsonConverter { private const string DateFormat = "yyyy-MM-dd"; diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/TimeOnlyConverter.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/TimeOnlyConverter.cs index a97db6ab8..737362ca0 100644 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/TimeOnlyConverter.cs +++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Converters/TimeOnlyConverter.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.Globalization; using System.Text.Json; diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/LoggerFactoryHelper.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/LoggerFactoryHelper.cs deleted file mode 100644 index 17fc402ac..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/LoggerFactoryHelper.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Microsoft.Extensions.Logging; - -namespace AWS.Lambda.Powertools.Logging.Internal.Helpers; - -/// -/// Helper class for creating and configuring logger factories -/// -internal static class LoggerFactoryHelper -{ - /// - /// Creates and configures a logger factory with the provided configuration - /// - /// The Powertools logger configuration to apply - /// The configured logger factory - internal static ILoggerFactory CreateAndConfigureFactory(PowertoolsLoggerConfiguration configuration) - { - var factory = LoggerFactory.Create(builder => - { - builder.AddPowertoolsLogger(config => - { - config.Service = configuration.Service; - config.TimestampFormat = configuration.TimestampFormat; - config.MinimumLogLevel = configuration.MinimumLogLevel; - config.InitialLogLevel = configuration.InitialLogLevel; - config.SamplingRate = configuration.SamplingRate; - config.LoggerOutputCase = configuration.LoggerOutputCase; - config.LogLevelKey = configuration.LogLevelKey; - config.LogFormatter = configuration.LogFormatter; - config.JsonOptions = configuration.JsonOptions; - config.LogBuffering = configuration.LogBuffering; - config.LogOutput = configuration.LogOutput; - config.XRayTraceId = configuration.XRayTraceId; - config.LogEvent = configuration.LogEvent; - }); - - // When sampling is enabled, set the factory minimum level to Debug - // so that all logs can reach our PowertoolsLogger for dynamic filtering - if (configuration.SamplingRate > 0) - { - builder.AddFilter(null, LogLevel.Debug); - builder.SetMinimumLevel(LogLevel.Debug); - } - else if (configuration.MinimumLogLevel != LogLevel.None) - { - builder.AddFilter(null, configuration.MinimumLogLevel); - builder.SetMinimumLevel(configuration.MinimumLogLevel); - } - }); - - LoggerFactoryHolder.SetFactory(factory); - - return factory; - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/PowertoolsLoggerHelpers.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/PowertoolsLoggerHelpers.cs index f682a99f7..1245f8d6c 100644 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/PowertoolsLoggerHelpers.cs +++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/Helpers/PowertoolsLoggerHelpers.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.Collections.Generic; using System.Linq; diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerFactoryHolder.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerFactoryHolder.cs deleted file mode 100644 index ce96ea735..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerFactoryHolder.cs +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -using System; -using AWS.Lambda.Powertools.Logging.Internal.Helpers; -using Microsoft.Extensions.Logging; - -namespace AWS.Lambda.Powertools.Logging.Internal; - -/// -/// Holds and manages the shared logger factory instance -/// -internal static class LoggerFactoryHolder -{ - private static ILoggerFactory _factory; - private static readonly object _lock = new object(); - - /// - /// Gets or creates the shared logger factory - /// - public static ILoggerFactory GetOrCreateFactory() - { - lock (_lock) - { - if (_factory == null) - { - var config = PowertoolsLoggingBuilderExtensions.GetCurrentConfiguration(); - - _factory = LoggerFactoryHelper.CreateAndConfigureFactory(config); - } - return _factory; - } - } - - public static void SetFactory(ILoggerFactory factory) - { - if (factory == null) throw new ArgumentNullException(nameof(factory)); - lock (_lock) - { - _factory = factory; - Logger.ClearInstance(); - } - } - - /// - /// Resets the factory holder for testing - /// - internal static void Reset() - { - lock (_lock) - { - // Dispose the old factory if it exists - if (_factory == null) return; - try - { - _factory.Dispose(); - } - catch - { - // Ignore disposal errors - } - - _factory = null; - } - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerProvider.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerProvider.cs new file mode 100644 index 000000000..94bb1c0d1 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggerProvider.cs @@ -0,0 +1,86 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System.Collections.Concurrent; +using AWS.Lambda.Powertools.Common; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace AWS.Lambda.Powertools.Logging.Internal; + +/// +/// Class LoggerProvider. This class cannot be inherited. +/// Implements the +/// +/// +public sealed class LoggerProvider : ILoggerProvider +{ + /// + /// The powertools configurations + /// + private readonly IPowertoolsConfigurations _powertoolsConfigurations; + + /// + /// The system wrapper + /// + private readonly ISystemWrapper _systemWrapper; + + /// + /// The loggers + /// + private readonly ConcurrentDictionary _loggers = new(); + + + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + /// + /// + public LoggerProvider(IOptions config, IPowertoolsConfigurations powertoolsConfigurations, ISystemWrapper systemWrapper) + { + _powertoolsConfigurations = powertoolsConfigurations; + _systemWrapper = systemWrapper; + _powertoolsConfigurations.SetCurrentConfig(config?.Value, systemWrapper); + } + + /// + /// Initializes a new instance of the class. + /// + /// The configuration. + public LoggerProvider(IOptions config) + : this(config, PowertoolsConfigurations.Instance, SystemWrapper.Instance) { } + + /// + /// Creates a new instance. + /// + /// The category name for messages produced by the logger. + /// The instance of that was created. + public ILogger CreateLogger(string categoryName) + { + return _loggers.GetOrAdd(categoryName, + name => PowertoolsLogger.CreateLogger(name, + _powertoolsConfigurations, + _systemWrapper)); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + _loggers.Clear(); + } +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs index 37cdc1c94..c92566e27 100644 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs +++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs @@ -1,10 +1,27 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.IO; using System.Linq; +using System.Reflection; using System.Runtime.ExceptionServices; using System.Text.Json; +using AspectInjector.Broker; using AWS.Lambda.Powertools.Common; -using AWS.Lambda.Powertools.Logging.Internal.Helpers; +using AWS.Lambda.Powertools.Logging.Serializers; using Microsoft.Extensions.Logging; namespace AWS.Lambda.Powertools.Logging.Internal; @@ -14,8 +31,14 @@ namespace AWS.Lambda.Powertools.Logging.Internal; /// Scope.Global is singleton /// /// -public class LoggingAspect : IMethodAspectHandler +[Aspect(Scope.Global, Factory = typeof(LoggingAspectFactory))] +public class LoggingAspect { + /// + /// The is cold start + /// + private bool _isColdStart = true; + /// /// The initialize context /// @@ -26,6 +49,21 @@ public class LoggingAspect : IMethodAspectHandler /// private bool _clearState; + /// + /// The correlation identifier path + /// + private string _correlationIdPath; + + /// + /// The Powertools for AWS Lambda (.NET) configurations + /// + private readonly IPowertoolsConfigurations _powertoolsConfigurations; + + /// + /// The system wrapper + /// + private readonly ISystemWrapper _systemWrapper; + /// /// The is context initialized /// @@ -36,48 +74,133 @@ public class LoggingAspect : IMethodAspectHandler /// private bool _clearLambdaContext; - private ILogger _logger; - private bool _isDebug; - private bool _bufferingEnabled; - private PowertoolsLoggerConfiguration _currentConfig; - private bool _flushBufferOnUncaughtError; + /// + /// The configuration + /// + private LoggerConfiguration _config; /// /// Initializes a new instance of the class. /// - public LoggingAspect(ILogger logger) + /// The Powertools configurations. + /// The system wrapper. + public LoggingAspect(IPowertoolsConfigurations powertoolsConfigurations, ISystemWrapper systemWrapper) { - _logger = logger ?? LoggerFactoryHolder.GetOrCreateFactory().CreatePowertoolsLogger(); + _powertoolsConfigurations = powertoolsConfigurations; + _systemWrapper = systemWrapper; } - private void InitializeLogger(LoggingAttribute trigger) + /// + /// Runs before the execution of the method marked with the Logging Attribute + /// + /// + /// + /// + /// + /// + /// + /// + [Advice(Kind.Before)] + public void OnEntry( + [Argument(Source.Instance)] object instance, + [Argument(Source.Name)] string name, + [Argument(Source.Arguments)] object[] args, + [Argument(Source.Type)] Type hostType, + [Argument(Source.Metadata)] MethodBase method, + [Argument(Source.ReturnType)] Type returnType, + [Argument(Source.Triggers)] Attribute[] triggers) { - // Check which settings are explicitly provided in the attribute - var hasLogLevel = trigger.LogLevel != LogLevel.None; - var hasService = !string.IsNullOrEmpty(trigger.Service); - var hasOutputCase = trigger.LoggerOutputCase != LoggerOutputCase.Default; - var hasSamplingRate = trigger.SamplingRate > 0; + // Called before the method + var trigger = triggers.OfType().First(); + + try + { + var eventArgs = new AspectEventArgs + { + Instance = instance, + Type = hostType, + Method = method, + Name = name, + Args = args, + ReturnType = returnType, + Triggers = triggers + }; + + _config = new LoggerConfiguration + { + Service = trigger.Service, + LoggerOutputCase = trigger.LoggerOutputCase, + SamplingRate = trigger.SamplingRate, + MinimumLevel = trigger.LogLevel + }; + + var logEvent = trigger.LogEvent; + _correlationIdPath = trigger.CorrelationIdPath; + _clearState = trigger.ClearState; - // Only update configuration if any settings were provided - var needsReconfiguration = hasLogLevel || hasService || hasOutputCase || hasSamplingRate; - _currentConfig = PowertoolsLoggingBuilderExtensions.GetCurrentConfiguration(); + Logger.LoggerProvider = new LoggerProvider(_config, _powertoolsConfigurations, _systemWrapper); + + if (!_initializeContext) + return; + + Logger.AppendKey(LoggingConstants.KeyColdStart, _isColdStart); + + _isColdStart = false; + _initializeContext = false; + _isContextInitialized = true; - if (needsReconfiguration) + var eventObject = eventArgs.Args.FirstOrDefault(); + CaptureXrayTraceId(); + CaptureLambdaContext(eventArgs); + CaptureCorrelationId(eventObject); + if (logEvent || _powertoolsConfigurations.LoggerLogEvent) + LogEvent(eventObject); + } + catch (Exception exception) { - // Apply each setting directly using the existing Logger static methods - if (hasLogLevel) _currentConfig.MinimumLogLevel = trigger.LogLevel; - if (hasService) _currentConfig.Service = trigger.Service; - if (hasOutputCase) _currentConfig.LoggerOutputCase = trigger.LoggerOutputCase; - if (hasSamplingRate) _currentConfig.SamplingRate = trigger.SamplingRate; - - // Need to refresh the logger after configuration changes - _logger = LoggerFactoryHelper.CreateAndConfigureFactory(_currentConfig).CreatePowertoolsLogger(); - Logger.ClearInstance(); + // The purpose of ExceptionDispatchInfo.Capture is to capture a potentially mutating exception's StackTrace at a point in time: + // https://learn.microsoft.com/en-us/dotnet/standard/exceptions/best-practices-for-exceptions#capture-exceptions-to-rethrow-later + ExceptionDispatchInfo.Capture(exception).Throw(); } + } + + /// + /// Handles the Kind.After event. + /// + [Advice(Kind.After)] + public void OnExit() + { + if (!_isContextInitialized) + return; + if (_clearLambdaContext) + LoggingLambdaContext.Clear(); + if (_clearState) + Logger.RemoveAllKeys(); + _initializeContext = true; + } + + /// + /// Determines whether this instance is debug. + /// + /// true if this instance is debug; otherwise, false. + private bool IsDebug() + { + return LogLevel.Debug >= _powertoolsConfigurations.GetLogLevel(_config.MinimumLevel); + } + + /// + /// Captures the xray trace identifier. + /// + private void CaptureXrayTraceId() + { + var xRayTraceId = _powertoolsConfigurations.XRayTraceId; + if (string.IsNullOrWhiteSpace(xRayTraceId)) + return; - // Set operational flags based on current configuration - _isDebug = _currentConfig.MinimumLogLevel <= LogLevel.Debug; - _bufferingEnabled = _currentConfig.LogBuffering != null; + xRayTraceId = xRayTraceId + .Split(';', StringSplitOptions.RemoveEmptyEntries)[0].Replace("Root=", ""); + + Logger.AppendKey(LoggingConstants.KeyXRayTraceId, xRayTraceId); } /// @@ -90,8 +213,8 @@ private void InitializeLogger(LoggingAttribute trigger) private void CaptureLambdaContext(AspectEventArgs eventArgs) { _clearLambdaContext = LoggingLambdaContext.Extract(eventArgs); - if (LoggingLambdaContext.Instance is null && _isDebug) - ConsoleWrapper.WriteLine(LogLevel.Warning.ToLambdaLogLevel(), + if (LoggingLambdaContext.Instance is null && IsDebug()) + _systemWrapper.LogLine( "Skipping Lambda Context injection because ILambdaContext context parameter not found."); } @@ -99,13 +222,12 @@ private void CaptureLambdaContext(AspectEventArgs eventArgs) /// Captures the correlation identifier. /// /// The event argument. - /// - private void CaptureCorrelationId(object eventArg, string correlationIdPath) + private void CaptureCorrelationId(object eventArg) { - if (string.IsNullOrWhiteSpace(correlationIdPath)) + if (string.IsNullOrWhiteSpace(_correlationIdPath)) return; - var correlationIdPaths = correlationIdPath + var correlationIdPaths = _correlationIdPath .Split(CorrelationIdPaths.Separator, StringSplitOptions.RemoveEmptyEntries); if (!correlationIdPaths.Any()) @@ -113,8 +235,8 @@ private void CaptureCorrelationId(object eventArg, string correlationIdPath) if (eventArg is null) { - if (_isDebug) - ConsoleWrapper.WriteLine(LogLevel.Warning.ToLambdaLogLevel(), + if (IsDebug()) + _systemWrapper.LogLine( "Skipping CorrelationId capture because event parameter not found."); return; } @@ -124,16 +246,16 @@ private void CaptureCorrelationId(object eventArg, string correlationIdPath) var correlationId = string.Empty; var jsonDoc = - JsonDocument.Parse(_currentConfig.Serializer.Serialize(eventArg, eventArg.GetType())); + JsonDocument.Parse(PowertoolsLoggingSerializer.Serialize(eventArg, eventArg.GetType())); var element = jsonDoc.RootElement; for (var i = 0; i < correlationIdPaths.Length; i++) { - // TODO: For casing parsing to be removed from Logging v2 when we get rid of outputcase without this CorrelationIdPaths.ApiGatewayRest would not work - // TODO: This will be removed and replaced by JMesPath - - var pathWithOutputCase = correlationIdPaths[i].ToCase(_currentConfig.LoggerOutputCase); + // For casing parsing to be removed from Logging v2 when we get rid of outputcase + // without this CorrelationIdPaths.ApiGatewayRest would not work + var pathWithOutputCase = + _powertoolsConfigurations.ConvertToOutputCase(correlationIdPaths[i], _config.LoggerOutputCase); if (!element.TryGetProperty(pathWithOutputCase, out var childElement)) break; @@ -143,12 +265,12 @@ private void CaptureCorrelationId(object eventArg, string correlationIdPath) } if (!string.IsNullOrWhiteSpace(correlationId)) - _logger.AppendKey(LoggingConstants.KeyCorrelationId, correlationId); + Logger.AppendKey(LoggingConstants.KeyCorrelationId, correlationId); } catch (Exception e) { - if (_isDebug) - ConsoleWrapper.WriteLine(LogLevel.Warning.ToLambdaLogLevel(), + if (IsDebug()) + _systemWrapper.LogLine( $"Skipping CorrelationId capture because of error caused while parsing the event object {e.Message}."); } } @@ -163,30 +285,30 @@ private void LogEvent(object eventArg) { case null: { - if (_isDebug) - ConsoleWrapper.WriteLine(LogLevel.Warning.ToLambdaLogLevel(), + if (IsDebug()) + _systemWrapper.LogLine( "Skipping Event Log because event parameter not found."); break; } case Stream: try { - _logger.LogInformation(eventArg); + Logger.LogInformation(eventArg); } catch (Exception e) { - _logger.LogError(e, "Failed to log event from supplied input stream."); + Logger.LogError(e, "Failed to log event from supplied input stream."); } break; default: try { - _logger.LogInformation(eventArg); + Logger.LogInformation(eventArg); } catch (Exception e) { - _logger.LogError(e, "Failed to log event from supplied input object."); + Logger.LogError(e, "Failed to log event from supplied input object."); } break; @@ -199,95 +321,8 @@ private void LogEvent(object eventArg) internal static void ResetForTest() { LoggingLambdaContext.Clear(); - } - - /// - /// Entry point for the aspect. - /// - /// - public void OnEntry(AspectEventArgs eventArgs) - { - var trigger = eventArgs.Triggers.OfType().First(); - try - { - _clearState = trigger.ClearState; - - InitializeLogger(trigger); - - if (!_initializeContext) - return; - - _initializeContext = false; - _isContextInitialized = true; - _flushBufferOnUncaughtError = trigger.FlushBufferOnUncaughtError; - - var eventObject = eventArgs.Args.FirstOrDefault(); - CaptureLambdaContext(eventArgs); - CaptureCorrelationId(eventObject, trigger.CorrelationIdPath); - - switch (trigger.IsLogEventSet) - { - case true when trigger.LogEvent: - case false when _currentConfig.LogEvent: - LogEvent(eventObject); - break; - } - } - catch (Exception exception) - { - if (_bufferingEnabled && _flushBufferOnUncaughtError) - { - _logger.FlushBuffer(); - } - - // The purpose of ExceptionDispatchInfo.Capture is to capture a potentially mutating exception's StackTrace at a point in time: - // https://learn.microsoft.com/en-us/dotnet/standard/exceptions/best-practices-for-exceptions#capture-exceptions-to-rethrow-later - ExceptionDispatchInfo.Capture(exception).Throw(); - } - } - - /// - /// When the method returns successfully, this method is called. - /// - /// - /// - public void OnSuccess(AspectEventArgs eventArgs, object result) - { - - } - - /// - /// When the method throws an exception, this method is called. - /// - /// - /// - public void OnException(AspectEventArgs eventArgs, Exception exception) - { - if (_bufferingEnabled && _flushBufferOnUncaughtError) - { - _logger.FlushBuffer(); - } - ExceptionDispatchInfo.Capture(exception).Throw(); - } - - /// - /// WHen the method exits, this method is called even if it throws an exception. - /// - /// - public void OnExit(AspectEventArgs eventArgs) - { - if (!_isContextInitialized) - return; - if (_clearLambdaContext) - LoggingLambdaContext.Clear(); - if (_clearState) - _logger.RemoveAllKeys(); - _initializeContext = true; - - if (_bufferingEnabled) - { - // clear the buffer after the handler has finished - _logger.ClearBuffer(); - } + Logger.LoggerProvider = null; + Logger.RemoveAllKeys(); + Logger.ClearLoggerInstance(); } } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspectFactory.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspectFactory.cs index 295d8e781..5feae3cf1 100644 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspectFactory.cs +++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspectFactory.cs @@ -1,10 +1,25 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using AWS.Lambda.Powertools.Common; namespace AWS.Lambda.Powertools.Logging.Internal; /// -/// Class LoggingAspectFactory. For "dependency inject" Aspect +/// Class LoggingAspectFactory. For "dependency inject" Configuration and SystemWrapper to Aspect /// internal static class LoggingAspectFactory { @@ -15,6 +30,6 @@ internal static class LoggingAspectFactory /// An instance of the LoggingAspect class. public static object GetInstance(Type type) { - return new LoggingAspect(LoggerFactoryHolder.GetOrCreateFactory().CreatePowertoolsLogger()); + return new LoggingAspect(PowertoolsConfigurations.Instance, SystemWrapper.Instance); } } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingLambdaContext.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingLambdaContext.cs index 9732bad04..a8846b155 100644 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingLambdaContext.cs +++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingLambdaContext.cs @@ -7,7 +7,7 @@ namespace AWS.Lambda.Powertools.Logging.Internal; /// /// Lambda Context /// -internal class LoggingLambdaContext +public class LoggingLambdaContext { /// /// The AWS request ID associated with the request. @@ -74,21 +74,24 @@ public static bool Extract(AspectEventArgs args) return false; var index = Array.FindIndex(args.Method.GetParameters(), p => p.ParameterType == typeof(ILambdaContext)); - if (index < 0 || args.Args[index] == null || args.Args[index] is not ILambdaContext) return false; - - var x = (ILambdaContext)args.Args[index]; - - Instance = new LoggingLambdaContext + if (index >= 0) { - AwsRequestId = x.AwsRequestId, - FunctionName = x.FunctionName, - FunctionVersion = x.FunctionVersion, - InvokedFunctionArn = x.InvokedFunctionArn, - LogGroupName = x.LogGroupName, - LogStreamName = x.LogStreamName, - MemoryLimitInMB = x.MemoryLimitInMB - }; - return true; + var x = (ILambdaContext)args.Args[index]; + + Instance = new LoggingLambdaContext + { + AwsRequestId = x.AwsRequestId, + FunctionName = x.FunctionName, + FunctionVersion = x.FunctionVersion, + InvokedFunctionArn = x.InvokedFunctionArn, + LogGroupName = x.LogGroupName, + LogStreamName = x.LogStreamName, + MemoryLimitInMB = x.MemoryLimitInMB + }; + return true; + } + + return false; } /// diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsConfigurationsExtension.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsConfigurationsExtension.cs index ee5094d4c..148bb540a 100644 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsConfigurationsExtension.cs +++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsConfigurationsExtension.cs @@ -1,41 +1,36 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.Collections.Generic; +using System.Linq; +using System.Text; using AWS.Lambda.Powertools.Common; +using AWS.Lambda.Powertools.Logging.Serializers; using Microsoft.Extensions.Logging; namespace AWS.Lambda.Powertools.Logging.Internal; -internal static class LambdaLogLevelMapper -{ - public static string ToLambdaLogLevel(this LogLevel logLevel) - { - switch (logLevel) - { - case LogLevel.Trace: - return "trace"; - case LogLevel.Debug: - return "debug"; - case LogLevel.Information: - return "info"; - case LogLevel.Warning: - return "warn"; - case LogLevel.Error: - return "error"; - case LogLevel.Critical: - return "fatal"; - default: - return "info"; - } - } -} - - - /// /// Class PowertoolsConfigurationsExtension. /// internal static class PowertoolsConfigurationsExtension { + private static readonly object _lock = new object(); + private static LoggerConfiguration _config; + /// /// Maps AWS log level to .NET log level /// @@ -96,6 +91,88 @@ internal static LoggerOutputCase GetLoggerOutputCase(this IPowertoolsConfigurati return LoggingConstants.DefaultLoggerOutputCase; } + /// + /// Gets the current configuration. + /// + /// AWS.Lambda.Powertools.Logging.LoggerConfiguration. + internal static void SetCurrentConfig(this IPowertoolsConfigurations powertoolsConfigurations, LoggerConfiguration config, ISystemWrapper systemWrapper) + { + lock (_lock) + { + _config = config ?? new LoggerConfiguration(); + + var logLevel = powertoolsConfigurations.GetLogLevel(_config.MinimumLevel); + var lambdaLogLevel = powertoolsConfigurations.GetLambdaLogLevel(); + var lambdaLogLevelEnabled = powertoolsConfigurations.LambdaLogLevelEnabled(); + + if (lambdaLogLevelEnabled && logLevel < lambdaLogLevel) + { + systemWrapper.LogLine($"Current log level ({logLevel}) does not match AWS Lambda Advanced Logging Controls minimum log level ({lambdaLogLevel}). This can lead to data loss, consider adjusting them."); + } + + // Set service + _config.Service = _config.Service ?? powertoolsConfigurations.Service; + + // Set output case + var loggerOutputCase = powertoolsConfigurations.GetLoggerOutputCase(_config.LoggerOutputCase); + _config.LoggerOutputCase = loggerOutputCase; + PowertoolsLoggingSerializer.ConfigureNamingPolicy(loggerOutputCase); + + // Set log level + var minLogLevel = lambdaLogLevelEnabled ? lambdaLogLevel : logLevel; + _config.MinimumLevel = minLogLevel; + + // Set sampling rate + SetSamplingRate(powertoolsConfigurations, systemWrapper, minLogLevel); + } + } + + /// + /// Set sampling rate + /// + /// + /// + /// + /// + private static void SetSamplingRate(IPowertoolsConfigurations powertoolsConfigurations, ISystemWrapper systemWrapper, LogLevel minLogLevel) + { + var samplingRate = _config.SamplingRate > 0 ? _config.SamplingRate : powertoolsConfigurations.LoggerSampleRate; + samplingRate = ValidateSamplingRate(samplingRate, minLogLevel, systemWrapper); + + _config.SamplingRate = samplingRate; + + if (samplingRate > 0) + { + double sample = systemWrapper.GetRandom(); + + if (sample <= samplingRate) + { + systemWrapper.LogLine($"Changed log level to DEBUG based on Sampling configuration. Sampling Rate: {samplingRate}, Sampler Value: {sample}."); + _config.MinimumLevel = LogLevel.Debug; + } + } + } + + /// + /// Validate Sampling rate + /// + /// + /// + /// + /// + private static double ValidateSamplingRate(double samplingRate, LogLevel minLogLevel, ISystemWrapper systemWrapper) + { + if (samplingRate < 0 || samplingRate > 1) + { + if (minLogLevel is LogLevel.Debug or LogLevel.Trace) + { + systemWrapper.LogLine($"Skipping sampling rate configuration because of invalid value. Sampling rate: {samplingRate}"); + } + return 0; + } + + return samplingRate; + } /// /// Determines whether [is lambda log level enabled]. @@ -106,4 +183,149 @@ internal static bool LambdaLogLevelEnabled(this IPowertoolsConfigurations powert { return powertoolsConfigurations.GetLambdaLogLevel() != LogLevel.None; } + + /// + /// Converts the input string to the configured output case. + /// + /// + /// The string to convert. + /// + /// + /// The input string converted to the configured case (camel, pascal, or snake case). + /// + internal static string ConvertToOutputCase(this IPowertoolsConfigurations powertoolsConfigurations, + string correlationIdPath, LoggerOutputCase loggerOutputCase) + { + return powertoolsConfigurations.GetLoggerOutputCase(loggerOutputCase) switch + { + LoggerOutputCase.CamelCase => ToCamelCase(correlationIdPath), + LoggerOutputCase.PascalCase => ToPascalCase(correlationIdPath), + _ => ToSnakeCase(correlationIdPath), // default snake_case + }; + } + + /// + /// Converts a string to snake_case. + /// + /// + /// The input string converted to snake_case. + private static string ToSnakeCase(string input) + { + if (string.IsNullOrEmpty(input)) + return input; + + var result = new StringBuilder(input.Length + 10); + bool lastCharWasUnderscore = false; + bool lastCharWasUpper = false; + + for (int i = 0; i < input.Length; i++) + { + char currentChar = input[i]; + + if (currentChar == '_') + { + result.Append('_'); + lastCharWasUnderscore = true; + lastCharWasUpper = false; + } + else if (char.IsUpper(currentChar)) + { + if (i > 0 && !lastCharWasUnderscore && + (!lastCharWasUpper || (i + 1 < input.Length && char.IsLower(input[i + 1])))) + { + result.Append('_'); + } + + result.Append(char.ToLowerInvariant(currentChar)); + lastCharWasUnderscore = false; + lastCharWasUpper = true; + } + else + { + result.Append(char.ToLowerInvariant(currentChar)); + lastCharWasUnderscore = false; + lastCharWasUpper = false; + } + } + + return result.ToString(); + } + + + /// + /// Converts a string to PascalCase. + /// + /// + /// The input string converted to PascalCase. + private static string ToPascalCase(string input) + { + if (string.IsNullOrEmpty(input)) + return input; + + var words = input.Split(new[] { '_' }, StringSplitOptions.RemoveEmptyEntries); + var result = new StringBuilder(); + + foreach (var word in words) + { + if (word.Length > 0) + { + // Capitalize the first character of each word + result.Append(char.ToUpperInvariant(word[0])); + + // Handle the rest of the characters + if (word.Length > 1) + { + // If the word is all uppercase, convert the rest to lowercase + if (word.All(char.IsUpper)) + { + result.Append(word.Substring(1).ToLowerInvariant()); + } + else + { + // Otherwise, keep the original casing + result.Append(word.Substring(1)); + } + } + } + } + + return result.ToString(); + } + + /// + /// Converts a string to camelCase. + /// + /// The string to convert. + /// The input string converted to camelCase. + private static string ToCamelCase(string input) + { + if (string.IsNullOrEmpty(input)) + return input; + + // First, convert to PascalCase + string pascalCase = ToPascalCase(input); + + // Then convert the first character to lowercase + return char.ToLowerInvariant(pascalCase[0]) + pascalCase.Substring(1); + } + + /// + /// Determines whether [is log level enabled]. + /// + /// The Powertools for AWS Lambda (.NET) configurations. + /// The log level. + /// true if [is log level enabled]; otherwise, false. + internal static bool IsLogLevelEnabled(this IPowertoolsConfigurations powertoolsConfigurations, LogLevel logLevel) + { + return logLevel != LogLevel.None && logLevel >= _config.MinimumLevel; + } + + /// + /// Gets the current configuration. + /// + /// AWS.Lambda.Powertools.Logging.LoggerConfiguration. + internal static LoggerConfiguration CurrentConfig(this IPowertoolsConfigurations powertoolsConfigurations) + { + return _config; + } } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs index 6ad3d34c2..5e603c02d 100644 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs +++ b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLogger.cs @@ -1,10 +1,25 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; -using System.Text.RegularExpressions; using AWS.Lambda.Powertools.Common; using AWS.Lambda.Powertools.Logging.Internal.Helpers; +using AWS.Lambda.Powertools.Logging.Serializers; using Microsoft.Extensions.Logging; namespace AWS.Lambda.Powertools.Logging.Internal; @@ -16,20 +31,21 @@ namespace AWS.Lambda.Powertools.Logging.Internal; /// internal sealed class PowertoolsLogger : ILogger { - private static string _originalformat = "{OriginalFormat}"; - /// /// The name /// - private readonly string _categoryName; + private readonly string _name; /// /// The current configuration /// - private readonly Func _currentConfig; - private readonly IPowertoolsConfigurations _powertoolsConfigurations; + /// + /// The system wrapper + /// + private readonly ISystemWrapper _systemWrapper; + /// /// The current scope /// @@ -38,17 +54,32 @@ internal sealed class PowertoolsLogger : ILogger /// /// Private constructor - Is initialized on CreateLogger /// - /// The name. - /// - /// - public PowertoolsLogger( - string categoryName, - Func getCurrentConfig, - IPowertoolsConfigurations powertoolsConfigurations) + /// The name. + /// The Powertools for AWS Lambda (.NET) configurations. + /// The system wrapper. + private PowertoolsLogger( + string name, + IPowertoolsConfigurations powertoolsConfigurations, + ISystemWrapper systemWrapper) { - _categoryName = categoryName; - _currentConfig = getCurrentConfig; + _name = name; _powertoolsConfigurations = powertoolsConfigurations; + _systemWrapper = systemWrapper; + + _powertoolsConfigurations.SetExecutionEnvironment(this); + } + + /// + /// Initializes a new instance of the class. + /// + /// The name. + /// The Powertools for AWS Lambda (.NET) configurations. + /// The system wrapper. + internal static PowertoolsLogger CreateLogger(string name, + IPowertoolsConfigurations powertoolsConfigurations, + ISystemWrapper systemWrapper) + { + return new PowertoolsLogger(name, powertoolsConfigurations, systemWrapper); } /// @@ -77,43 +108,7 @@ internal void EndScope() /// The log level. /// bool. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool IsEnabled(LogLevel logLevel) - { - var config = _currentConfig(); - return IsEnabledForConfig(logLevel, config); - } - - /// - /// Determines whether the specified log level is enabled for a specific configuration. - /// - /// The log level. - /// The configuration to check against. - /// bool. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool IsEnabledForConfig(LogLevel logLevel, PowertoolsLoggerConfiguration config) - { - //if Buffering is enabled and the log level is below the buffer threshold, skip logging only if bellow error - if (logLevel <= config.LogBuffering?.BufferAtLogLevel - && config.LogBuffering?.BufferAtLogLevel != LogLevel.Error - && config.LogBuffering?.BufferAtLogLevel != LogLevel.Critical) - { - return false; - } - - // If we have no explicit minimum level, use the default - var effectiveMinLevel = config.MinimumLogLevel != LogLevel.None - ? config.MinimumLogLevel - : LoggingConstants.DefaultLogLevel; - - // Log diagnostic info for Debug/Trace levels - if (logLevel <= LogLevel.Debug) - { - return logLevel >= effectiveMinLevel; - } - - // Standard check - return logLevel >= effectiveMinLevel; - } + public bool IsEnabled(LogLevel logLevel) => _powertoolsConfigurations.IsLogLevelEnabled(logLevel); /// /// Writes a log entry. @@ -127,67 +122,23 @@ private bool IsEnabledForConfig(LogLevel logLevel, PowertoolsLoggerConfiguration public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { - var config = _currentConfig(); - if (config.SamplingRate > 0) - { - var samplingActivated = config.RefreshSampleRateCalculation(out double samplerValue); - if (samplingActivated) - { - config.LogOutput.WriteLine($"Changed log level to DEBUG based on Sampling configuration. Sampling Rate: {config.SamplingRate}, Sampler Value: {samplerValue}."); - } - } - - // Use the same config reference for IsEnabled check to ensure we see the updated MinimumLogLevel - if (!IsEnabledForConfig(logLevel, config)) - { - return; - } - - config.LogOutput.WriteLine(LogEntryString(logLevel, state, exception, formatter)); - } - - internal void LogLine(string message) - { - _currentConfig().LogOutput.WriteLine(message); - } - - private void LogDebug(string message) - { - if (IsEnabled(LogLevel.Debug)) - { - Log(LogLevel.Debug, new EventId(), message, null, (msg, ex) => msg); - } - } - - internal string LogEntryString(LogLevel logLevel, TState state, Exception exception, - Func formatter) - { - var logEntry = LogEntry(logLevel, state, exception, formatter); - return _currentConfig().Serializer.Serialize(logEntry, typeof(object)); - } - - internal object LogEntry(LogLevel logLevel, TState state, Exception exception, - Func formatter) - { - var timestamp = DateTime.UtcNow; - if (formatter is null) throw new ArgumentNullException(nameof(formatter)); - // Extract structured parameters for template-style logging - var structuredParameters = ExtractStructuredParameters(state, out _); + if (!IsEnabled(logLevel)) + return; - // Format the message + var timestamp = DateTime.UtcNow; var message = CustomFormatter(state, exception, out var customMessage) && customMessage is not null ? customMessage : formatter(state, exception); - // Get log entry - var logFormatter = _currentConfig().LogFormatter; + var logFormatter = Logger.GetFormatter(); var logEntry = logFormatter is null - ? GetLogEntry(logLevel, timestamp, message, exception, structuredParameters) - : GetFormattedLogEntry(logLevel, timestamp, message, exception, logFormatter, structuredParameters); - return logEntry; + ? GetLogEntry(logLevel, timestamp, message, exception) + : GetFormattedLogEntry(logLevel, timestamp, message, exception, logFormatter); + + _systemWrapper.LogLine(PowertoolsLoggingSerializer.Serialize(logEntry, typeof(object))); } /// @@ -197,18 +148,16 @@ internal object LogEntry(LogLevel logLevel, TState state, Exception exce /// Entry timestamp. /// The message to be written. Can be also an object. /// The exception related to this entry. - /// The parameters for structured formatting private Dictionary GetLogEntry(LogLevel logLevel, DateTime timestamp, object message, - Exception exception, Dictionary structuredParameters = null) + Exception exception) { var logEntry = new Dictionary(); - var config = _currentConfig(); - logEntry.TryAdd(config.LogLevelKey, logLevel.ToString()); - logEntry.TryAdd(LoggingConstants.KeyMessage, message); - logEntry.TryAdd(LoggingConstants.KeyTimestamp, timestamp.ToString(config.TimestampFormat ?? "o")); - logEntry.TryAdd(LoggingConstants.KeyService, config.Service); - logEntry.TryAdd(LoggingConstants.KeyColdStart, _powertoolsConfigurations.IsColdStart); + // Add Custom Keys + foreach (var (key, value) in Logger.GetAllKeys()) + { + logEntry.TryAdd(key, value); + } // Add Lambda Context Keys if (LoggingLambdaContext.Instance is not null) @@ -216,86 +165,31 @@ private Dictionary GetLogEntry(LogLevel logLevel, DateTime times AddLambdaContextKeys(logEntry); } - if (!string.IsNullOrWhiteSpace(_powertoolsConfigurations.XRayTraceId)) - logEntry.TryAdd(LoggingConstants.KeyXRayTraceId, - _powertoolsConfigurations.XRayTraceId.Split(';', StringSplitOptions.RemoveEmptyEntries)[0] - .Replace("Root=", "")); - logEntry.TryAdd(LoggingConstants.KeyLoggerName, _categoryName); - - if (config.SamplingRate > 0) - logEntry.TryAdd(LoggingConstants.KeySamplingRate, config.SamplingRate); - - // Add Custom Keys - foreach (var (key, value) in this.GetAllKeys()) - { - // Skip keys that are already defined in LoggingConstants - if (!IsLogConstantKey(key)) - { - logEntry.TryAdd(key, value); - } - } - // Add Extra Fields if (CurrentScope?.ExtraKeys is not null) { foreach (var (key, value) in CurrentScope.ExtraKeys) { - if (string.IsNullOrWhiteSpace(key)) continue; - if (!IsLogConstantKey(key)) - { + if (!string.IsNullOrWhiteSpace(key)) logEntry.TryAdd(key, value); - } } } - // Add structured parameters - if (structuredParameters != null && structuredParameters.Count > 0) - { - foreach (var (key, value) in structuredParameters) - { - if (string.IsNullOrWhiteSpace(key) || key == "json") continue; - if (!IsLogConstantKey(key)) - { - logEntry.TryAdd(key, value); - } - } - } + var keyLogLevel = GetLogLevelKey(); - // Use the AddExceptionDetails method instead of adding exception directly + logEntry.TryAdd(LoggingConstants.KeyTimestamp, timestamp.ToString("o")); + logEntry.TryAdd(keyLogLevel, logLevel.ToString()); + logEntry.TryAdd(LoggingConstants.KeyService, _powertoolsConfigurations.CurrentConfig().Service); + logEntry.TryAdd(LoggingConstants.KeyLoggerName, _name); + logEntry.TryAdd(LoggingConstants.KeyMessage, message); + if (_powertoolsConfigurations.CurrentConfig().SamplingRate > 0) + logEntry.TryAdd(LoggingConstants.KeySamplingRate, _powertoolsConfigurations.CurrentConfig().SamplingRate); if (exception != null) - { logEntry.TryAdd(LoggingConstants.KeyException, exception); - } return logEntry; } - /// - /// Checks if a key is defined in LoggingConstants - /// - /// The key to check - /// true if the key is a LoggingConstants key - private bool IsLogConstantKey(string key) - { - return string.Equals(key.ToPascal(), LoggingConstants.KeyColdStart, StringComparison.OrdinalIgnoreCase) - // || string.Equals(key.ToPascal(), LoggingConstants.KeyCorrelationId, StringComparison.OrdinalIgnoreCase) - || string.Equals(key.ToPascal(), LoggingConstants.KeyException, StringComparison.OrdinalIgnoreCase) - || string.Equals(key.ToPascal(), LoggingConstants.KeyFunctionArn, StringComparison.OrdinalIgnoreCase) - || string.Equals(key.ToPascal(), LoggingConstants.KeyFunctionMemorySize, - StringComparison.OrdinalIgnoreCase) - || string.Equals(key.ToPascal(), LoggingConstants.KeyFunctionName, StringComparison.OrdinalIgnoreCase) - || string.Equals(key.ToPascal(), LoggingConstants.KeyFunctionRequestId, - StringComparison.OrdinalIgnoreCase) - || string.Equals(key.ToPascal(), LoggingConstants.KeyFunctionVersion, StringComparison.OrdinalIgnoreCase) - || string.Equals(key.ToPascal(), LoggingConstants.KeyLoggerName, StringComparison.OrdinalIgnoreCase) - || string.Equals(key.ToPascal(), LoggingConstants.KeyLogLevel, StringComparison.OrdinalIgnoreCase) - || string.Equals(key.ToPascal(), LoggingConstants.KeyMessage, StringComparison.OrdinalIgnoreCase) - || string.Equals(key.ToPascal(), LoggingConstants.KeySamplingRate, StringComparison.OrdinalIgnoreCase) - || string.Equals(key.ToPascal(), LoggingConstants.KeyService, StringComparison.OrdinalIgnoreCase) - || string.Equals(key.ToPascal(), LoggingConstants.KeyTimestamp, StringComparison.OrdinalIgnoreCase) - || string.Equals(key.ToPascal(), LoggingConstants.KeyXRayTraceId, StringComparison.OrdinalIgnoreCase); - } - /// /// Gets a formatted log entry. For custom log formatter /// @@ -304,29 +198,27 @@ private bool IsLogConstantKey(string key) /// The message to be written. Can be also an object. /// The exception related to this entry. /// The custom log entry formatter. - /// The structured parameters. private object GetFormattedLogEntry(LogLevel logLevel, DateTime timestamp, object message, - Exception exception, ILogFormatter logFormatter, Dictionary structuredParameters) + Exception exception, ILogFormatter logFormatter) { if (logFormatter is null) return null; - var config = _currentConfig(); var logEntry = new LogEntry { Timestamp = timestamp, Level = logLevel, - Service = config.Service, - Name = _categoryName, + Service = _powertoolsConfigurations.CurrentConfig().Service, + Name = _name, Message = message, - Exception = exception, // Keep this to maintain compatibility - SamplingRate = config.SamplingRate, + Exception = exception, + SamplingRate = _powertoolsConfigurations.CurrentConfig().SamplingRate, }; var extraKeys = new Dictionary(); // Add Custom Keys - foreach (var (key, value) in this.GetAllKeys()) + foreach (var (key, value) in Logger.GetAllKeys()) { switch (key) { @@ -351,34 +243,7 @@ private object GetFormattedLogEntry(LogLevel logLevel, DateTime timestamp, objec foreach (var (key, value) in CurrentScope.ExtraKeys) { if (!string.IsNullOrWhiteSpace(key)) - { extraKeys.TryAdd(key, value); - } - } - } - - // Add structured parameters - if (structuredParameters != null && structuredParameters.Count > 0) - { - foreach (var (key, value) in structuredParameters) - { - if (!string.IsNullOrWhiteSpace(key) && key != "json") - { - extraKeys.TryAdd(key, value); - } - } - } - - // Add detailed exception information - if (exception != null) - { - var exceptionDetails = new Dictionary(); - exceptionDetails.TryAdd(LoggingConstants.KeyException, exception); - - // Add exception details to extra keys - foreach (var (key, value) in exceptionDetails) - { - extraKeys.TryAdd(key, value); } } @@ -396,12 +261,7 @@ private object GetFormattedLogEntry(LogLevel logLevel, DateTime timestamp, objec var logObject = logFormatter.FormatLogEntry(logEntry); if (logObject is null) throw new LogFormatException($"{logFormatter.GetType().FullName} returned Null value."); - -#if NET8_0_OR_GREATER return PowertoolsLoggerHelpers.ObjectToDictionary(logObject); -#else - return logObject; -#endif } catch (Exception e) { @@ -425,40 +285,36 @@ private static bool CustomFormatter(TState state, Exception exception, o if (exception is not null) return false; -#if NET8_0_OR_GREATER - var stateKeys = new Dictionary(); - if (state is IEnumerable> keyValuePairs) - { - foreach (var kvp in keyValuePairs) - { - stateKeys[kvp.Key] = PowertoolsLoggerHelpers.ObjectToDictionary(kvp.Value); - } - } -#else -var stateKeys = new Dictionary(); -if (state is IEnumerable> keyValuePairs) -{ - foreach (var kvp in keyValuePairs) - { - stateKeys[kvp.Key] = kvp.Value; - } -} -#endif + var stateKeys = (state as IEnumerable>)? + .ToDictionary(i => i.Key, i => PowertoolsLoggerHelpers.ObjectToDictionary(i.Value)); - if (stateKeys.Count != 2) + if (stateKeys is null || stateKeys.Count != 2) return false; - if (!stateKeys.TryGetValue(_originalformat, out var originalFormat)) + if (!stateKeys.TryGetValue("{OriginalFormat}", out var originalFormat)) return false; if (originalFormat?.ToString() != LoggingConstants.KeyJsonFormatter) return false; - message = stateKeys.First(k => k.Key != _originalformat).Value; + message = stateKeys.First(k => k.Key != "{OriginalFormat}").Value; return true; } + /// + /// Gets the log level key. + /// + /// System.String. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private string GetLogLevelKey() + { + return _powertoolsConfigurations.LambdaLogLevelEnabled() && + _powertoolsConfigurations.CurrentConfig().LoggerOutputCase == LoggerOutputCase.PascalCase + ? "LogLevel" + : LoggingConstants.KeyLogLevel; + } + /// /// Adds the lambda context keys. /// @@ -468,10 +324,10 @@ private void AddLambdaContextKeys(Dictionary logEntry) { var context = LoggingLambdaContext.Instance; logEntry.TryAdd(LoggingConstants.KeyFunctionName, context.FunctionName); + logEntry.TryAdd(LoggingConstants.KeyFunctionVersion, context.FunctionVersion); logEntry.TryAdd(LoggingConstants.KeyFunctionMemorySize, context.MemoryLimitInMB); logEntry.TryAdd(LoggingConstants.KeyFunctionArn, context.InvokedFunctionArn); logEntry.TryAdd(LoggingConstants.KeyFunctionRequestId, context.AwsRequestId); - logEntry.TryAdd(LoggingConstants.KeyFunctionVersion, context.FunctionVersion); } /// @@ -515,7 +371,6 @@ private static Dictionary GetScopeKeys(TState state) } break; - case IEnumerable> objectPairs: foreach (var (key, value) in objectPairs) { @@ -524,28 +379,10 @@ private static Dictionary GetScopeKeys(TState state) } break; - default: - // Skip property reflection for primitive types, strings and value types - if (state is string || - (state.GetType().IsPrimitive) || - state is ValueType) - { - // Don't extract properties from primitives or strings - break; - } - - // For complex objects, use reflection to get properties foreach (var property in state.GetType().GetProperties()) { - try - { - keys.TryAdd(property.Name, property.GetValue(state)); - } - catch - { - // Safely ignore reflection exceptions - } + keys.TryAdd(property.Name, property.GetValue(state)); } break; @@ -553,143 +390,4 @@ private static Dictionary GetScopeKeys(TState state) return keys; } - - /// - /// Extracts structured parameter key-value pairs from the log state - /// - /// Type of the state being logged - /// The log state containing parameters - /// Output parameter for the message template - /// Dictionary of extracted parameter names and values - private Dictionary ExtractStructuredParameters(TState state, out string messageTemplate) - { - messageTemplate = string.Empty; - var parameters = new Dictionary(); - - if (!(state is IEnumerable> stateProps)) - { - return parameters; - } - - // Dictionary to store format specifiers for each parameter - var formatSpecifiers = new Dictionary(); - var statePropsArray = stateProps.ToArray(); - - // First pass - extract message template and identify format specifiers - ExtractFormatSpecifiers(ref messageTemplate, statePropsArray, formatSpecifiers); - - // Second pass - process values with extracted format specifiers - ProcessValuesWithSpecifiers(statePropsArray, formatSpecifiers, parameters); - - return parameters; - } - - private void ProcessValuesWithSpecifiers(KeyValuePair[] statePropsArray, Dictionary formatSpecifiers, - Dictionary parameters) - { - foreach (var prop in statePropsArray) - { - if (prop.Key == _originalformat) - continue; - - // Extract parameter name without braces - var paramName = ExtractParameterName(prop.Key); - if (string.IsNullOrEmpty(paramName)) - continue; - - // Handle special serialization designators (like @) - var useStructuredSerialization = paramName.StartsWith('@'); - var actualParamName = useStructuredSerialization ? paramName.Substring(1) : paramName; - - if (!useStructuredSerialization && - formatSpecifiers.TryGetValue(paramName, out var format) && - prop.Value is IFormattable formattable) - { - // Format the value using the specified format - var formattedValue = formattable.ToString(format, System.Globalization.CultureInfo.InvariantCulture); - - // Try to preserve the numeric type if possible - if (double.TryParse(formattedValue, out var numericValue)) - { - parameters[actualParamName] = numericValue; - } - else - { - parameters[actualParamName] = formattedValue; - } - } - else if (useStructuredSerialization) - { - // Serialize the entire object - parameters[actualParamName] = prop.Value; - } - else - { - // Handle regular values appropriately - if (prop.Value != null && - !(prop.Value is string) && - !(prop.Value is ValueType) && - !(prop.Value.GetType().IsPrimitive)) - { - // For complex objects, use ToString() representation - parameters[actualParamName] = prop.Value.ToString(); - } - else - { - // For primitives and other simple types, use the value directly - parameters[actualParamName] = prop.Value; - } - } - } - } - - private static void ExtractFormatSpecifiers(ref string messageTemplate, KeyValuePair[] statePropsArray, - Dictionary formatSpecifiers) - { - foreach (var prop in statePropsArray) - { - // The original message template is stored with key "{OriginalFormat}" - if (prop.Key == _originalformat && prop.Value is string template) - { - messageTemplate = template; - - // Extract format specifiers using regex pattern for parameters - var matches = Regex.Matches( - template, - @"{([@\w]+)(?::([^{}]+))?}", - RegexOptions.None, - TimeSpan.FromSeconds(1)); - - foreach (Match match in matches) - { - var paramName = match.Groups[1].Value; - if (match.Groups.Count > 2 && match.Groups[2].Success) - { - formatSpecifiers[paramName] = match.Groups[2].Value; - } - } - - break; - } - } - } - - /// - /// Extracts the parameter name from a template placeholder (e.g. "{paramName}" or "{paramName:format}") - /// - private string ExtractParameterName(string key) - { - // If it's already a proper parameter name without braces, return it - if (!key.StartsWith('{') || !key.EndsWith('}')) - return key; - - // Remove the braces - var nameWithPossibleFormat = key.Substring(1, key.Length - 2); - - // If there's a format specifier, remove it - var colonIndex = nameWithPossibleFormat.IndexOf(':'); - return colonIndex > 0 - ? nameWithPossibleFormat.Substring(0, colonIndex) - : nameWithPossibleFormat; - } -} +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLoggerProvider.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLoggerProvider.cs deleted file mode 100644 index 66536527a..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/PowertoolsLoggerProvider.cs +++ /dev/null @@ -1,151 +0,0 @@ -using System; -using System.Collections.Concurrent; -using AWS.Lambda.Powertools.Common; -using Microsoft.Extensions.Logging; - -namespace AWS.Lambda.Powertools.Logging.Internal; - -/// -/// Class LoggerProvider. This class cannot be inherited. -/// Implements the -/// -/// -[ProviderAlias("PowertoolsLogger")] -internal class PowertoolsLoggerProvider : ILoggerProvider -{ - private readonly ConcurrentDictionary _loggers = new(StringComparer.OrdinalIgnoreCase); - private PowertoolsLoggerConfiguration _currentConfig; - private readonly IPowertoolsConfigurations _powertoolsConfigurations; - private bool _environmentConfigured; - - public PowertoolsLoggerProvider( - PowertoolsLoggerConfiguration config, - IPowertoolsConfigurations powertoolsConfigurations) - { - _powertoolsConfigurations = powertoolsConfigurations; - _currentConfig = config; - - // Set execution environment - _powertoolsConfigurations.SetExecutionEnvironment(this); - - // Apply environment configurations if available - ConfigureFromEnvironment(); - } - - public void ConfigureFromEnvironment() - { - var logLevel = _powertoolsConfigurations.GetLogLevel(_currentConfig.MinimumLogLevel); - var lambdaLogLevel = _powertoolsConfigurations.GetLambdaLogLevel(); - var lambdaLogLevelEnabled = _powertoolsConfigurations.LambdaLogLevelEnabled(); - - // Warn if Lambda log level doesn't match - if (lambdaLogLevelEnabled && logLevel < lambdaLogLevel) - { - _currentConfig.LogOutput.WriteLine( - $"Current log level ({logLevel}) does not match AWS Lambda Advanced Logging Controls minimum log level ({lambdaLogLevel}). This can lead to data loss, consider adjusting them."); - } - - // Set service from environment if not explicitly set - if (string.IsNullOrEmpty(_currentConfig.Service)) - { - _currentConfig.Service = _powertoolsConfigurations.Service; - } - - // Set output case from environment if not explicitly set - if (_currentConfig.LoggerOutputCase == LoggerOutputCase.Default) - { - var loggerOutputCase = _powertoolsConfigurations.GetLoggerOutputCase(_currentConfig.LoggerOutputCase); - _currentConfig.LoggerOutputCase = loggerOutputCase; - } - - var minLogLevel = lambdaLogLevelEnabled ? lambdaLogLevel : logLevel; - var effectiveLogLevel = minLogLevel != LogLevel.None ? minLogLevel : LoggingConstants.DefaultLogLevel; - - // Only set InitialLogLevel if it hasn't been explicitly configured - if (_currentConfig.InitialLogLevel == LogLevel.Information) - { - _currentConfig.InitialLogLevel = effectiveLogLevel; - } - - _currentConfig.MinimumLogLevel = effectiveLogLevel; - - _currentConfig.XRayTraceId = _powertoolsConfigurations.XRayTraceId; - _currentConfig.LogEvent = _powertoolsConfigurations.LoggerLogEvent; - - // Configure the log level key based on output case - _currentConfig.LogLevelKey = _powertoolsConfigurations.LambdaLogLevelEnabled() && - _currentConfig.LoggerOutputCase == LoggerOutputCase.PascalCase - ? "LogLevel" - : LoggingConstants.KeyLogLevel; - - ProcessSamplingRate(_currentConfig, _powertoolsConfigurations); - _environmentConfigured = true; - } - - /// - /// Process sampling rate configuration - /// - private void ProcessSamplingRate(PowertoolsLoggerConfiguration config, IPowertoolsConfigurations configurations) - { - var samplingRate = config.SamplingRate > 0 - ? config.SamplingRate - : configurations.LoggerSampleRate; - - samplingRate = ValidateSamplingRate(samplingRate, config); - config.SamplingRate = samplingRate; - } - - /// - /// Validate sampling rate - /// - private double ValidateSamplingRate(double samplingRate, PowertoolsLoggerConfiguration config) - { - if (samplingRate < 0 || samplingRate > 1) - { - if (config.MinimumLogLevel is LogLevel.Debug or LogLevel.Trace) - { - config.LogOutput.WriteLine( - $"Skipping sampling rate configuration because of invalid value. Sampling rate: {samplingRate}"); - } - - return 0; - } - - return samplingRate; - } - - public virtual ILogger CreateLogger(string categoryName) - { - return _loggers.GetOrAdd(categoryName, name => new PowertoolsLogger( - name, - GetCurrentConfig, - _powertoolsConfigurations)); - } - - internal PowertoolsLoggerConfiguration GetCurrentConfig() => _currentConfig; - - public void UpdateConfiguration(PowertoolsLoggerConfiguration config) - { - _currentConfig = config; - - // Apply environment configurations if available - if (_powertoolsConfigurations != null && !_environmentConfigured) - { - ConfigureFromEnvironment(); - } - } - - /// - /// Refresh the sampling calculation and update the minimum log level if needed - /// - /// True if debug sampling was enabled, false otherwise - internal bool RefreshSampleRateCalculation() - { - return _currentConfig.RefreshSampleRateCalculation(); - } - - public virtual void Dispose() - { - _loggers.Clear(); - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/StringCaseExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Internal/StringCaseExtensions.cs deleted file mode 100644 index 7e7b390a4..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Internal/StringCaseExtensions.cs +++ /dev/null @@ -1,132 +0,0 @@ -using System; -using System.Linq; -using System.Text; - -namespace AWS.Lambda.Powertools.Logging.Internal; - -/// -/// Extension methods for string case conversion. -/// -internal static class StringCaseExtensions -{ - /// - /// Converts a string to camelCase. - /// - /// The string to convert. - /// A camelCase formatted string. - public static string ToCamel(this string value) - { - if (string.IsNullOrEmpty(value)) - return value; - - // Convert to PascalCase first to handle potential snake_case or kebab-case - string pascalCase = ToPascal(value); - - // Convert first char to lowercase - return char.ToLowerInvariant(pascalCase[0]) + pascalCase.Substring(1); - } - - /// - /// Converts a string to PascalCase. - /// - /// The string to convert. - /// A PascalCase formatted string. - public static string ToPascal(this string input) - { - if (string.IsNullOrEmpty(input)) - return input; - - var words = input.Split(new[] { '_' }, StringSplitOptions.RemoveEmptyEntries); - var result = new StringBuilder(); - - foreach (var word in words) - { - if (word.Length > 0) - { - // Capitalize the first character of each word - result.Append(char.ToUpperInvariant(word[0])); - - // Handle the rest of the characters - if (word.Length > 1) - { - // If the word is all uppercase, convert the rest to lowercase - if (word.All(char.IsUpper)) - { - result.Append(word.Substring(1).ToLowerInvariant()); - } - else - { - // Otherwise, keep the original casing - result.Append(word.Substring(1)); - } - } - } - } - - return result.ToString(); - } - - /// - /// Converts a string to snake_case. - /// - /// The string to convert. - /// A snake_case formatted string. - public static string ToSnake(this string input) - { - if (string.IsNullOrEmpty(input)) - return input; - - var result = new StringBuilder(input.Length + 10); - bool lastCharWasUnderscore = false; - bool lastCharWasUpper = false; - - for (int i = 0; i < input.Length; i++) - { - char currentChar = input[i]; - - if (currentChar == '_') - { - result.Append('_'); - lastCharWasUnderscore = true; - lastCharWasUpper = false; - } - else if (char.IsUpper(currentChar)) - { - if (i > 0 && !lastCharWasUnderscore && - (!lastCharWasUpper || (i + 1 < input.Length && char.IsLower(input[i + 1])))) - { - result.Append('_'); - } - - result.Append(char.ToLowerInvariant(currentChar)); - lastCharWasUnderscore = false; - lastCharWasUpper = true; - } - else - { - result.Append(char.ToLowerInvariant(currentChar)); - lastCharWasUnderscore = false; - lastCharWasUpper = false; - } - } - - return result.ToString(); - } - - /// - /// Converts a string to the specified case format. - /// - /// The string to convert. - /// The target case format. - /// A formatted string in the specified case. - public static string ToCase(this string value, LoggerOutputCase outputCase) - { - return outputCase switch - { - LoggerOutputCase.CamelCase => value.ToCamel(), - LoggerOutputCase.PascalCase => value.ToPascal(), - LoggerOutputCase.SnakeCase => value.ToSnake(), - _ => value.ToSnake() // Default/unchanged - }; - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/LogBufferingOptions.cs b/libraries/src/AWS.Lambda.Powertools.Logging/LogBufferingOptions.cs deleted file mode 100644 index 9d31471d2..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Logging/LogBufferingOptions.cs +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -using Microsoft.Extensions.Logging; - -namespace AWS.Lambda.Powertools.Logging; - -/// -/// Configuration options for log buffering -/// -public class LogBufferingOptions -{ - /// - /// Gets or sets the maximum size of the buffer in bytes - /// - /// Default is 20KB (20480 bytes) - /// - public int MaxBytes { get; set; } = 20480; - - /// - /// Gets or sets the minimum log level to buffer - /// Defaults to Debug - /// - /// Valid values are: Trace, Debug, Information, Warning - /// - public LogLevel BufferAtLogLevel { get; set; } = LogLevel.Debug; - - /// - /// Gets or sets whether to flush the buffer when logging an error - /// - public bool FlushOnErrorLog { get; set; } = true; -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Formatter.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Formatter.cs deleted file mode 100644 index edefc1aca..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Formatter.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace AWS.Lambda.Powertools.Logging; - -public static partial class Logger -{ - /// - /// Set the log formatter. - /// - /// The log formatter. - /// WARNING: This method should not be called when using AOT. ILogFormatter should be passed to PowertoolsSourceGeneratorSerializer constructor - public static void UseFormatter(ILogFormatter logFormatter) - { - Configure(config => { - config.LogFormatter = logFormatter; - }); - } - - /// - /// Set the log formatter to default. - /// - public static void UseDefaultFormatter() - { - Configure(config => { - config.LogFormatter = null; - }); - } -} diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.JsonLogs.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.JsonLogs.cs deleted file mode 100644 index a593aed8a..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.JsonLogs.cs +++ /dev/null @@ -1,153 +0,0 @@ -using System; -using Microsoft.Extensions.Logging; - -namespace AWS.Lambda.Powertools.Logging; - -public static partial class Logger -{ - #region JSON Logger Methods - - /// - /// Formats and writes a trace log message as JSON. - /// - /// The object to be serialized as JSON. - /// logger.LogTrace(new {User = user, Address = address}) - public static void LogTrace(object message) - { - LoggerInstance.LogTrace(message); - } - - /// - /// Formats and writes an trace log message. - /// - /// The exception to log. - /// logger.LogTrace(exception) - public static void LogTrace(Exception exception) - { - LoggerInstance.LogTrace(exception); - } - - /// - /// Formats and writes a debug log message as JSON. - /// - /// The object to be serialized as JSON. - /// logger.LogDebug(new {User = user, Address = address}) - public static void LogDebug(object message) - { - LoggerInstance.LogDebug(message); - } - - /// - /// Formats and writes an debug log message. - /// - /// The exception to log. - /// logger.LogDebug(exception) - public static void LogDebug(Exception exception) - { - LoggerInstance.LogDebug(exception); - } - - /// - /// Formats and writes an information log message as JSON. - /// - /// The object to be serialized as JSON. - /// logger.LogInformation(new {User = user, Address = address}) - public static void LogInformation(object message) - { - LoggerInstance.LogInformation(message); - } - - /// - /// Formats and writes an information log message. - /// - /// The exception to log. - /// logger.LogInformation(exception) - public static void LogInformation(Exception exception) - { - LoggerInstance.LogInformation(exception); - } - - /// - /// Formats and writes a warning log message as JSON. - /// - /// The object to be serialized as JSON. - /// logger.LogWarning(new {User = user, Address = address}) - public static void LogWarning(object message) - { - LoggerInstance.LogWarning(message); - } - - /// - /// Formats and writes an warning log message. - /// - /// The exception to log. - /// logger.LogWarning(exception) - public static void LogWarning(Exception exception) - { - LoggerInstance.LogWarning(exception); - } - - /// - /// Formats and writes a error log message as JSON. - /// - /// The object to be serialized as JSON. - /// logger.LogCritical(new {User = user, Address = address}) - public static void LogError(object message) - { - LoggerInstance.LogError(message); - } - - /// - /// Formats and writes an error log message. - /// - /// The exception to log. - /// logger.LogError(exception) - public static void LogError(Exception exception) - { - LoggerInstance.LogError(exception); - } - - /// - /// Formats and writes a critical log message as JSON. - /// - /// The object to be serialized as JSON. - /// logger.LogCritical(new {User = user, Address = address}) - public static void LogCritical(object message) - { - LoggerInstance.LogCritical(message); - } - - /// - /// Formats and writes an critical log message. - /// - /// The exception to log. - /// logger.LogCritical(exception) - public static void LogCritical(Exception exception) - { - LoggerInstance.LogCritical(exception); - } - - /// - /// Formats and writes a log message as JSON at the specified log level. - /// - /// Entry will be written on this level. - /// The object to be serialized as JSON. - /// logger.Log(LogLevel.Information, new {User = user, Address = address}) - public static void Log(LogLevel logLevel, object message) - { - LoggerInstance.Log(logLevel, message); - } - - /// - /// Formats and writes a log message at the specified log level. - /// - /// Entry will be written on this level. - /// The exception to log. - /// logger.Log(LogLevel.Information, exception) - public static void Log(LogLevel logLevel, Exception exception) - { - LoggerInstance.Log(logLevel, exception); - } - - #endregion -} diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Sampling.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Sampling.cs deleted file mode 100644 index 0353df181..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Sampling.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace AWS.Lambda.Powertools.Logging; - -public static partial class Logger -{ - /// - /// Refresh the sampling calculation and update the minimum log level if needed - /// - /// True if debug sampling was enabled, false otherwise - public static bool RefreshSampleRateCalculation() - { - return _config.RefreshSampleRateCalculation(); - } -} diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Scope.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Scope.cs deleted file mode 100644 index 4a661da4e..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.Scope.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using AWS.Lambda.Powertools.Logging.Internal.Helpers; - -namespace AWS.Lambda.Powertools.Logging; - -public static partial class Logger -{ - /// - /// Gets the scope. - /// - /// The scope. - private static IDictionary Scope { get; } = new Dictionary(StringComparer.Ordinal); - - /// - /// Appending additional key to the log context. - /// - /// The key. - /// The value. - /// key - /// value - public static void AppendKey(string key, object value) - { - if (string.IsNullOrWhiteSpace(key)) - throw new ArgumentNullException(nameof(key)); - -#if NET8_0_OR_GREATER - Scope[key] = PowertoolsLoggerHelpers.ObjectToDictionary(value) ?? - throw new ArgumentNullException(nameof(value)); -#else - Scope[key] = value ?? throw new ArgumentNullException(nameof(value)); -#endif - } - - /// - /// Appending additional key to the log context. - /// - /// The list of keys. - public static void AppendKeys(IEnumerable> keys) - { - foreach (var (key, value) in keys) - AppendKey(key, value); - } - - /// - /// Appending additional key to the log context. - /// - /// The list of keys. - public static void AppendKeys(IEnumerable> keys) - { - foreach (var (key, value) in keys) - AppendKey(key, value); - } - - /// - /// Remove additional keys from the log context. - /// - /// The list of keys. - public static void RemoveKeys(params string[] keys) - { - if (keys == null) return; - foreach (var key in keys) - if (Scope.ContainsKey(key)) - Scope.Remove(key); - } - - /// - /// Returns all additional keys added to the log context. - /// - /// IEnumerable<KeyValuePair<System.String, System.Object>>. - public static IEnumerable> GetAllKeys() - { - return Scope.AsEnumerable(); - } - - /// - /// Removes all additional keys from the log context. - /// - internal static void RemoveAllKeys() - { - Scope.Clear(); - } - - /// - /// Removes a key from the log context. - /// - public static void RemoveKey(string key) - { - if (Scope.ContainsKey(key)) - Scope.Remove(key); - } -} diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.StandardLogs.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.StandardLogs.cs deleted file mode 100644 index 0162e0e90..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.StandardLogs.cs +++ /dev/null @@ -1,417 +0,0 @@ -using System; -using Microsoft.Extensions.Logging; - -namespace AWS.Lambda.Powertools.Logging; - -public static partial class Logger -{ - /// - /// Formats and writes a debug log message. - /// - /// The event id associated with the log. - /// The exception to log. - /// - /// Format string of the log message in message template format. Example: - /// "User {User} logged in from {Address}" - /// - /// An object array that contains zero or more objects to format. - /// Logger.LogDebug(0, exception, "Error while processing request from {Address}", address) - public static void LogDebug(EventId eventId, Exception exception, string message, params object[] args) - { - LoggerInstance.LogDebug(eventId, exception, message, args); - } - - /// - /// Formats and writes a debug log message. - /// - /// The event id associated with the log. - /// - /// Format string of the log message in message template format. Example: - /// "User {User} logged in from {Address}" - /// - /// An object array that contains zero or more objects to format. - /// Logger.LogDebug(0, "Processing request from {Address}", address) - public static void LogDebug(EventId eventId, string message, params object[] args) - { - LoggerInstance.LogDebug(eventId, message, args); - } - - /// - /// Formats and writes a debug log message. - /// - /// The exception to log. - /// - /// Format string of the log message in message template format. Example: - /// "User {User} logged in from {Address}" - /// - /// An object array that contains zero or more objects to format. - /// Logger.LogDebug(exception, "Error while processing request from {Address}", address) - public static void LogDebug(Exception exception, string message, params object[] args) - { - LoggerInstance.LogDebug(exception, message, args); - } - - /// - /// Formats and writes a debug log message. - /// - /// - /// Format string of the log message in message template format. Example: - /// "User {User} logged in from {Address}" - /// - /// An object array that contains zero or more objects to format. - /// Logger.LogDebug("Processing request from {Address}", address) - public static void LogDebug(string message, params object[] args) - { - LoggerInstance.LogDebug(message, args); - } - - /// - /// Formats and writes a trace log message. - /// - /// The event id associated with the log. - /// The exception to log. - /// - /// Format string of the log message in message template format. Example: - /// "User {User} logged in from {Address}" - /// - /// An object array that contains zero or more objects to format. - /// Logger.LogTrace(0, exception, "Error while processing request from {Address}", address) - public static void LogTrace(EventId eventId, Exception exception, string message, params object[] args) - { - LoggerInstance.LogTrace(eventId, exception, message, args); - } - - /// - /// Formats and writes a trace log message. - /// - /// The event id associated with the log. - /// - /// Format string of the log message in message template format. Example: - /// "User {User} logged in from {Address}" - /// - /// An object array that contains zero or more objects to format. - /// Logger.LogTrace(0, "Processing request from {Address}", address) - public static void LogTrace(EventId eventId, string message, params object[] args) - { - LoggerInstance.LogTrace(eventId, message, args); - } - - /// - /// Formats and writes a trace log message. - /// - /// The exception to log. - /// - /// Format string of the log message in message template format. Example: - /// "User {User} logged in from {Address}" - /// - /// An object array that contains zero or more objects to format. - /// Logger.LogTrace(exception, "Error while processing request from {Address}", address) - public static void LogTrace(Exception exception, string message, params object[] args) - { - LoggerInstance.LogTrace(exception, message, args); - } - - /// - /// Formats and writes a trace log message. - /// - /// - /// Format string of the log message in message template format. Example: - /// "User {User} logged in from {Address}" - /// - /// An object array that contains zero or more objects to format. - /// Logger.LogTrace("Processing request from {Address}", address) - public static void LogTrace(string message, params object[] args) - { - LoggerInstance.LogTrace(message, args); - } - - /// - /// Formats and writes an informational log message. - /// - /// The event id associated with the log. - /// The exception to log. - /// - /// Format string of the log message in message template format. Example: - /// "User {User} logged in from {Address}" - /// - /// An object array that contains zero or more objects to format. - /// Logger.LogInformation(0, exception, "Error while processing request from {Address}", address) - public static void LogInformation(EventId eventId, Exception exception, string message, params object[] args) - { - LoggerInstance.LogInformation(eventId, exception, message, args); - } - - /// - /// Formats and writes an informational log message. - /// - /// The event id associated with the log. - /// - /// Format string of the log message in message template format. Example: - /// "User {User} logged in from {Address}" - /// - /// An object array that contains zero or more objects to format. - /// Logger.LogInformation(0, "Processing request from {Address}", address) - public static void LogInformation(EventId eventId, string message, params object[] args) - { - LoggerInstance.LogInformation(eventId, message, args); - } - - /// - /// Formats and writes an informational log message. - /// - /// The exception to log. - /// - /// Format string of the log message in message template format. Example: - /// "User {User} logged in from {Address}" - /// - /// An object array that contains zero or more objects to format. - /// Logger.LogInformation(exception, "Error while processing request from {Address}", address) - public static void LogInformation(Exception exception, string message, params object[] args) - { - LoggerInstance.LogInformation(exception, message, args); - } - - /// - /// Formats and writes an informational log message. - /// - /// - /// Format string of the log message in message template format. Example: - /// "User {User} logged in from {Address}" - /// - /// An object array that contains zero or more objects to format. - /// Logger.LogInformation("Processing request from {Address}", address) - public static void LogInformation(string message, params object[] args) - { - LoggerInstance.LogInformation(message, args); - } - - /// - /// Formats and writes a warning log message. - /// - /// The event id associated with the log. - /// The exception to log. - /// - /// Format string of the log message in message template format. Example: - /// "User {User} logged in from {Address}" - /// - /// An object array that contains zero or more objects to format. - /// Logger.LogWarning(0, exception, "Error while processing request from {Address}", address) - public static void LogWarning(EventId eventId, Exception exception, string message, params object[] args) - { - LoggerInstance.LogWarning(eventId, exception, message, args); - } - - /// - /// Formats and writes a warning log message. - /// - /// The event id associated with the log. - /// - /// Format string of the log message in message template format. Example: - /// "User {User} logged in from {Address}" - /// - /// An object array that contains zero or more objects to format. - /// Logger.LogWarning(0, "Processing request from {Address}", address) - public static void LogWarning(EventId eventId, string message, params object[] args) - { - LoggerInstance.LogWarning(eventId, message, args); - } - - /// - /// Formats and writes a warning log message. - /// - /// The exception to log. - /// - /// Format string of the log message in message template format. Example: - /// "User {User} logged in from {Address}" - /// - /// An object array that contains zero or more objects to format. - /// Logger.LogWarning(exception, "Error while processing request from {Address}", address) - public static void LogWarning(Exception exception, string message, params object[] args) - { - LoggerInstance.LogWarning(exception, message, args); - } - - /// - /// Formats and writes a warning log message. - /// - /// - /// Format string of the log message in message template format. Example: - /// "User {User} logged in from {Address}" - /// - /// An object array that contains zero or more objects to format. - /// Logger.LogWarning("Processing request from {Address}", address) - public static void LogWarning(string message, params object[] args) - { - LoggerInstance.LogWarning(message, args); - } - - /// - /// Formats and writes an error log message. - /// - /// The event id associated with the log. - /// The exception to log. - /// - /// Format string of the log message in message template format. Example: - /// "User {User} logged in from {Address}" - /// - /// An object array that contains zero or more objects to format. - /// Logger.LogError(0, exception, "Error while processing request from {Address}", address) - public static void LogError(EventId eventId, Exception exception, string message, params object[] args) - { - LoggerInstance.LogError(eventId, exception, message, args); - } - - /// - /// Formats and writes an error log message. - /// - /// The event id associated with the log. - /// - /// Format string of the log message in message template format. Example: - /// "User {User} logged in from {Address}" - /// - /// An object array that contains zero or more objects to format. - /// Logger.LogError(0, "Processing request from {Address}", address) - public static void LogError(EventId eventId, string message, params object[] args) - { - LoggerInstance.LogError(eventId, message, args); - } - - /// - /// Formats and writes an error log message. - /// - /// The exception to log. - /// - /// Format string of the log message in message template format. Example: - /// "User {User} logged in from {Address}" - /// - /// An object array that contains zero or more objects to format. - /// Logger.LogError(exception, "Error while processing request from {Address}", address) - public static void LogError(Exception exception, string message, params object[] args) - { - LoggerInstance.LogError(exception, message, args); - } - - /// - /// Formats and writes an error log message. - /// - /// - /// Format string of the log message in message template format. Example: - /// "User {User} logged in from {Address}" - /// - /// An object array that contains zero or more objects to format. - /// Logger.LogError("Processing request from {Address}", address) - public static void LogError(string message, params object[] args) - { - LoggerInstance.LogError(message, args); - } - - /// - /// Formats and writes a critical log message. - /// - /// The event id associated with the log. - /// The exception to log. - /// - /// Format string of the log message in message template format. Example: - /// "User {User} logged in from {Address}" - /// - /// An object array that contains zero or more objects to format. - /// Logger.LogCritical(0, exception, "Error while processing request from {Address}", address) - public static void LogCritical(EventId eventId, Exception exception, string message, params object[] args) - { - LoggerInstance.LogCritical(eventId, exception, message, args); - } - - /// - /// Formats and writes a critical log message. - /// - /// The event id associated with the log. - /// - /// Format string of the log message in message template format. Example: - /// "User {User} logged in from {Address}" - /// - /// An object array that contains zero or more objects to format. - /// Logger.LogCritical(0, "Processing request from {Address}", address) - public static void LogCritical(EventId eventId, string message, params object[] args) - { - LoggerInstance.LogCritical(eventId, message, args); - } - - /// - /// Formats and writes a critical log message. - /// - /// The exception to log. - /// - /// Format string of the log message in message template format. Example: - /// "User {User} logged in from {Address}" - /// - /// An object array that contains zero or more objects to format. - /// Logger.LogCritical(exception, "Error while processing request from {Address}", address) - public static void LogCritical(Exception exception, string message, params object[] args) - { - LoggerInstance.LogCritical(exception, message, args); - } - - /// - /// Formats and writes a critical log message. - /// - /// - /// Format string of the log message in message template format. Example: - /// "User {User} logged in from {Address}" - /// - /// An object array that contains zero or more objects to format. - /// Logger.LogCritical("Processing request from {Address}", address) - public static void LogCritical(string message, params object[] args) - { - LoggerInstance.LogCritical(message, args); - } - - /// - /// Formats and writes a log message at the specified log level. - /// - /// Entry will be written on this level. - /// Format string of the log message. - /// An object array that contains zero or more objects to format. - public static void Log(LogLevel logLevel, string message, params object[] args) - { - LoggerInstance.Log(logLevel, message, args); - } - - /// - /// Formats and writes a log message at the specified log level. - /// - /// Entry will be written on this level. - /// The event id associated with the log. - /// Format string of the log message. - /// An object array that contains zero or more objects to format. - public static void Log(LogLevel logLevel, EventId eventId, string message, params object[] args) - { - LoggerInstance.Log(logLevel, eventId, message, args); - } - - /// - /// Formats and writes a log message at the specified log level. - /// - /// Entry will be written on this level. - /// The exception to log. - /// Format string of the log message. - /// An object array that contains zero or more objects to format. - public static void Log(LogLevel logLevel, Exception exception, string message, params object[] args) - { - LoggerInstance.Log(logLevel, exception, message, args); - } - - /// - /// Formats and writes a log message at the specified log level. - /// - /// Entry will be written on this level. - /// The event id associated with the log. - /// The exception to log. - /// Format string of the log message. - /// An object array that contains zero or more objects to format. - public static void Log(LogLevel logLevel, EventId eventId, Exception exception, string message, - params object[] args) - { - LoggerInstance.Log(logLevel, eventId, exception, message, args); - } - -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs index 91d786d4f..f07464ed6 100644 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs +++ b/libraries/src/AWS.Lambda.Powertools.Logging/Logger.cs @@ -1,6 +1,21 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; -using System.Text.Json; -using AWS.Lambda.Powertools.Common; +using System.Collections.Generic; +using System.Linq; using AWS.Lambda.Powertools.Logging.Internal; using AWS.Lambda.Powertools.Logging.Internal.Helpers; using Microsoft.Extensions.Logging; @@ -10,74 +25,1184 @@ namespace AWS.Lambda.Powertools.Logging; /// /// Class Logger. /// -public static partial class Logger +public class Logger { - private static PowertoolsLoggerConfiguration _config; + /// + /// The logger instance + /// private static ILogger _loggerInstance; - private static readonly object Lock = new object(); - // Change this to a property with getter that recreates if needed - private static ILogger LoggerInstance + /// + /// Gets the logger instance. + /// + /// The logger instance. + private static ILogger LoggerInstance => _loggerInstance ??= Create(); + + /// + /// Gets or sets the logger provider. + /// + /// The logger provider. + internal static ILoggerProvider LoggerProvider { get; set; } + + /// + /// The logger formatter instance + /// + private static ILogFormatter _logFormatter; + + /// + /// Gets the scope. + /// + /// The scope. + private static IDictionary Scope { get; } = new Dictionary(StringComparer.Ordinal); + + /// + /// Creates a new instance. + /// + /// The category name for messages produced by the logger. + /// The instance of that was created. + /// categoryName + public static ILogger Create(string categoryName) { - get - { - // If we have no instance or configuration has changed, get a new logger - if (_loggerInstance == null) - { - lock (Lock) - { - if (_loggerInstance == null) - { - _loggerInstance = Initialize(); - } - } - } - return _loggerInstance; - } + if (string.IsNullOrWhiteSpace(categoryName)) + throw new ArgumentNullException(nameof(categoryName)); + + // Needed for when using Logger directly with decorator + LoggerProvider ??= new LoggerProvider(null); + + return LoggerProvider.CreateLogger(categoryName); } - private static ILogger Initialize() + /// + /// Creates a new instance. + /// + /// + /// The instance of that was created. + public static ILogger Create() { - return LoggerFactoryHolder.GetOrCreateFactory().CreatePowertoolsLogger(); + return Create(typeof(T).FullName); } + #region Scope Variables + /// - /// Configure with an existing logger factory + /// Appending additional key to the log context. /// - /// The factory to use - internal static void Configure(ILoggerFactory loggerFactory) + /// The key. + /// The value. + /// key + /// value + public static void AppendKey(string key, object value) { - if (loggerFactory == null) throw new ArgumentNullException(nameof(loggerFactory)); - LoggerFactoryHolder.SetFactory(loggerFactory); + if (string.IsNullOrWhiteSpace(key)) + throw new ArgumentNullException(nameof(key)); + + Scope[key] = PowertoolsLoggerHelpers.ObjectToDictionary(value) ?? + throw new ArgumentNullException(nameof(value)); } /// - /// Configure using a configuration action + /// Appending additional key to the log context. /// - /// - public static void Configure(Action configure) + /// The list of keys. + public static void AppendKeys(IEnumerable> keys) { - lock (Lock) - { - _config = new PowertoolsLoggerConfiguration(); - configure(_config); - _loggerInstance = LoggerFactoryHelper.CreateAndConfigureFactory(_config).CreatePowertoolsLogger(); - } + foreach (var (key, value) in keys) + AppendKey(key, value); } - - + /// - /// Reset the logger for testing + /// Appending additional key to the log context. /// - internal static void Reset() + /// The list of keys. + public static void AppendKeys(IEnumerable> keys) { - LoggerFactoryHolder.Reset(); - _loggerInstance = null; - RemoveAllKeys(); + foreach (var (key, value) in keys) + AppendKey(key, value); + } + + /// + /// Remove additional keys from the log context. + /// + /// The list of keys. + public static void RemoveKeys(params string[] keys) + { + if (keys == null) return; + foreach (var key in keys) + if (Scope.ContainsKey(key)) + Scope.Remove(key); + } + + /// + /// Returns all additional keys added to the log context. + /// + /// IEnumerable<KeyValuePair<System.String, System.Object>>. + public static IEnumerable> GetAllKeys() + { + return Scope.AsEnumerable(); + } + + /// + /// Removes all additional keys from the log context. + /// + internal static void RemoveAllKeys() + { + Scope.Clear(); } - - internal static void ClearInstance() + + internal static void ClearLoggerInstance() { _loggerInstance = null; } + + #endregion + + #region Core Logger Methods + + #region Debug + + /// + /// Formats and writes a debug log message. + /// + /// The event id associated with the log. + /// The exception to log. + /// + /// Format string of the log message in message template format. Example: + /// "User {User} logged in from {Address}" + /// + /// An object array that contains zero or more objects to format. + /// Logger.LogDebug(0, exception, "Error while processing request from {Address}", address) + public static void LogDebug(EventId eventId, Exception exception, string message, params object[] args) + { + LoggerInstance.LogDebug(eventId, exception, message, args); + } + + /// + /// Formats and writes a debug log message. + /// + /// The event id associated with the log. + /// + /// Format string of the log message in message template format. Example: + /// "User {User} logged in from {Address}" + /// + /// An object array that contains zero or more objects to format. + /// Logger.LogDebug(0, "Processing request from {Address}", address) + public static void LogDebug(EventId eventId, string message, params object[] args) + { + LoggerInstance.LogDebug(eventId, message, args); + } + + /// + /// Formats and writes a debug log message. + /// + /// The exception to log. + /// + /// Format string of the log message in message template format. Example: + /// "User {User} logged in from {Address}" + /// + /// An object array that contains zero or more objects to format. + /// Logger.LogDebug(exception, "Error while processing request from {Address}", address) + public static void LogDebug(Exception exception, string message, params object[] args) + { + LoggerInstance.LogDebug(exception, message, args); + } + + /// + /// Formats and writes a debug log message. + /// + /// + /// Format string of the log message in message template format. Example: + /// "User {User} logged in from {Address}" + /// + /// An object array that contains zero or more objects to format. + /// Logger.LogDebug("Processing request from {Address}", address) + public static void LogDebug(string message, params object[] args) + { + LoggerInstance.LogDebug(message, args); + } + + #endregion + + #region Trace + + /// + /// Formats and writes a trace log message. + /// + /// The event id associated with the log. + /// The exception to log. + /// + /// Format string of the log message in message template format. Example: + /// "User {User} logged in from {Address}" + /// + /// An object array that contains zero or more objects to format. + /// Logger.LogTrace(0, exception, "Error while processing request from {Address}", address) + public static void LogTrace(EventId eventId, Exception exception, string message, params object[] args) + { + LoggerInstance.LogTrace(eventId, exception, message, args); + } + + /// + /// Formats and writes a trace log message. + /// + /// The event id associated with the log. + /// + /// Format string of the log message in message template format. Example: + /// "User {User} logged in from {Address}" + /// + /// An object array that contains zero or more objects to format. + /// Logger.LogTrace(0, "Processing request from {Address}", address) + public static void LogTrace(EventId eventId, string message, params object[] args) + { + LoggerInstance.LogTrace(eventId, message, args); + } + + /// + /// Formats and writes a trace log message. + /// + /// The exception to log. + /// + /// Format string of the log message in message template format. Example: + /// "User {User} logged in from {Address}" + /// + /// An object array that contains zero or more objects to format. + /// Logger.LogTrace(exception, "Error while processing request from {Address}", address) + public static void LogTrace(Exception exception, string message, params object[] args) + { + LoggerInstance.LogTrace(exception, message, args); + } + + /// + /// Formats and writes a trace log message. + /// + /// + /// Format string of the log message in message template format. Example: + /// "User {User} logged in from {Address}" + /// + /// An object array that contains zero or more objects to format. + /// Logger.LogTrace("Processing request from {Address}", address) + public static void LogTrace(string message, params object[] args) + { + LoggerInstance.LogTrace(message, args); + } + + #endregion + + #region Information + + /// + /// Formats and writes an informational log message. + /// + /// The event id associated with the log. + /// The exception to log. + /// + /// Format string of the log message in message template format. Example: + /// "User {User} logged in from {Address}" + /// + /// An object array that contains zero or more objects to format. + /// Logger.LogInformation(0, exception, "Error while processing request from {Address}", address) + public static void LogInformation(EventId eventId, Exception exception, string message, params object[] args) + { + LoggerInstance.LogInformation(eventId, exception, message, args); + } + + /// + /// Formats and writes an informational log message. + /// + /// The event id associated with the log. + /// + /// Format string of the log message in message template format. Example: + /// "User {User} logged in from {Address}" + /// + /// An object array that contains zero or more objects to format. + /// Logger.LogInformation(0, "Processing request from {Address}", address) + public static void LogInformation(EventId eventId, string message, params object[] args) + { + LoggerInstance.LogInformation(eventId, message, args); + } + + /// + /// Formats and writes an informational log message. + /// + /// The exception to log. + /// + /// Format string of the log message in message template format. Example: + /// "User {User} logged in from {Address}" + /// + /// An object array that contains zero or more objects to format. + /// Logger.LogInformation(exception, "Error while processing request from {Address}", address) + public static void LogInformation(Exception exception, string message, params object[] args) + { + LoggerInstance.LogInformation(exception, message, args); + } + + /// + /// Formats and writes an informational log message. + /// + /// + /// Format string of the log message in message template format. Example: + /// "User {User} logged in from {Address}" + /// + /// An object array that contains zero or more objects to format. + /// Logger.LogInformation("Processing request from {Address}", address) + public static void LogInformation(string message, params object[] args) + { + LoggerInstance.LogInformation(message, args); + } + + #endregion + + #region Warning + + /// + /// Formats and writes a warning log message. + /// + /// The event id associated with the log. + /// The exception to log. + /// + /// Format string of the log message in message template format. Example: + /// "User {User} logged in from {Address}" + /// + /// An object array that contains zero or more objects to format. + /// Logger.LogWarning(0, exception, "Error while processing request from {Address}", address) + public static void LogWarning(EventId eventId, Exception exception, string message, params object[] args) + { + LoggerInstance.LogWarning(eventId, exception, message, args); + } + + /// + /// Formats and writes a warning log message. + /// + /// The event id associated with the log. + /// + /// Format string of the log message in message template format. Example: + /// "User {User} logged in from {Address}" + /// + /// An object array that contains zero or more objects to format. + /// Logger.LogWarning(0, "Processing request from {Address}", address) + public static void LogWarning(EventId eventId, string message, params object[] args) + { + LoggerInstance.LogWarning(eventId, message, args); + } + + /// + /// Formats and writes a warning log message. + /// + /// The exception to log. + /// + /// Format string of the log message in message template format. Example: + /// "User {User} logged in from {Address}" + /// + /// An object array that contains zero or more objects to format. + /// Logger.LogWarning(exception, "Error while processing request from {Address}", address) + public static void LogWarning(Exception exception, string message, params object[] args) + { + LoggerInstance.LogWarning(exception, message, args); + } + + /// + /// Formats and writes a warning log message. + /// + /// + /// Format string of the log message in message template format. Example: + /// "User {User} logged in from {Address}" + /// + /// An object array that contains zero or more objects to format. + /// Logger.LogWarning("Processing request from {Address}", address) + public static void LogWarning(string message, params object[] args) + { + LoggerInstance.LogWarning(message, args); + } + + #endregion + + #region Error + + /// + /// Formats and writes an error log message. + /// + /// The event id associated with the log. + /// The exception to log. + /// + /// Format string of the log message in message template format. Example: + /// "User {User} logged in from {Address}" + /// + /// An object array that contains zero or more objects to format. + /// Logger.LogError(0, exception, "Error while processing request from {Address}", address) + public static void LogError(EventId eventId, Exception exception, string message, params object[] args) + { + LoggerInstance.LogError(eventId, exception, message, args); + } + + /// + /// Formats and writes an error log message. + /// + /// The event id associated with the log. + /// + /// Format string of the log message in message template format. Example: + /// "User {User} logged in from {Address}" + /// + /// An object array that contains zero or more objects to format. + /// Logger.LogError(0, "Processing request from {Address}", address) + public static void LogError(EventId eventId, string message, params object[] args) + { + LoggerInstance.LogError(eventId, message, args); + } + + /// + /// Formats and writes an error log message. + /// + /// The exception to log. + /// + /// Format string of the log message in message template format. Example: + /// "User {User} logged in from {Address}" + /// + /// An object array that contains zero or more objects to format. + /// > + /// Logger.LogError(exception, "Error while processing request from {Address}", address) + public static void LogError(Exception exception, string message, params object[] args) + { + LoggerInstance.LogError(exception, message, args); + } + + /// + /// Formats and writes an error log message. + /// + /// + /// Format string of the log message in message template format. Example: + /// "User {User} logged in from {Address}" + /// + /// An object array that contains zero or more objects to format. + /// Logger.LogError("Processing request from {Address}", address) + public static void LogError(string message, params object[] args) + { + LoggerInstance.LogError(message, args); + } + + #endregion + + #region Critical + + /// + /// Formats and writes a critical log message. + /// + /// The event id associated with the log. + /// The exception to log. + /// + /// Format string of the log message in message template format. Example: + /// "User {User} logged in from {Address}" + /// + /// An object array that contains zero or more objects to format. + /// Logger.LogCritical(0, exception, "Error while processing request from {Address}", address) + public static void LogCritical(EventId eventId, Exception exception, string message, params object[] args) + { + LoggerInstance.LogCritical(eventId, exception, message, args); + } + + /// + /// Formats and writes a critical log message. + /// + /// The event id associated with the log. + /// + /// Format string of the log message in message template format. Example: + /// "User {User} logged in from {Address}" + /// + /// An object array that contains zero or more objects to format. + /// Logger.LogCritical(0, "Processing request from {Address}", address) + public static void LogCritical(EventId eventId, string message, params object[] args) + { + LoggerInstance.LogCritical(eventId, message, args); + } + + /// + /// Formats and writes a critical log message. + /// + /// The exception to log. + /// + /// Format string of the log message in message template format. Example: + /// "User {User} logged in from {Address}" + /// + /// An object array that contains zero or more objects to format. + /// Logger.LogCritical(exception, "Error while processing request from {Address}", address) + public static void LogCritical(Exception exception, string message, params object[] args) + { + LoggerInstance.LogCritical(exception, message, args); + } + + /// + /// Formats and writes a critical log message. + /// + /// + /// Format string of the log message in message template format. Example: + /// "User {User} logged in from {Address}" + /// + /// An object array that contains zero or more objects to format. + /// Logger.LogCritical("Processing request from {Address}", address) + public static void LogCritical(string message, params object[] args) + { + LoggerInstance.LogCritical(message, args); + } + + #endregion + + #region Log + + /// + /// Formats and writes a log message at the specified log level. + /// + /// Entry will be written on this level. + /// Format string of the log message. + /// An object array that contains zero or more objects to format. + public static void Log(LogLevel logLevel, string message, params object[] args) + { + LoggerInstance.Log(logLevel, message, args); + } + + /// + /// Formats and writes a log message at the specified log level. + /// + /// Entry will be written on this level. + /// The event id associated with the log. + /// Format string of the log message. + /// An object array that contains zero or more objects to format. + public static void Log(LogLevel logLevel, EventId eventId, string message, params object[] args) + { + LoggerInstance.Log(logLevel, eventId, message, args); + } + + /// + /// Formats and writes a log message at the specified log level. + /// + /// Entry will be written on this level. + /// The exception to log. + /// Format string of the log message. + /// An object array that contains zero or more objects to format. + public static void Log(LogLevel logLevel, Exception exception, string message, params object[] args) + { + LoggerInstance.Log(logLevel, exception, message, args); + } + + /// + /// Formats and writes a log message at the specified log level. + /// + /// Entry will be written on this level. + /// The event id associated with the log. + /// The exception to log. + /// Format string of the log message. + /// An object array that contains zero or more objects to format. + public static void Log(LogLevel logLevel, EventId eventId, Exception exception, string message, + params object[] args) + { + LoggerInstance.Log(logLevel, eventId, exception, message, args); + } + + /// + /// Writes a log entry. + /// + /// The type of the object to be written. + /// Entry will be written on this level. + /// Id of the event. + /// The entry to be written. Can be also an object. + /// The exception related to this entry. + /// + /// Function to create a message of the + /// and . + /// + public static void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, + Func formatter) + { + LoggerInstance.Log(logLevel, eventId, state, exception, formatter); + } + + #endregion + + #endregion + + #region JSON Logger Methods + + /// + /// Formats and writes a trace log message as JSON. + /// + /// The object to be serialized as JSON. + /// logger.LogTrace(new {User = user, Address = address}) + public static void LogTrace(object message) + { + LoggerInstance.LogTrace(message); + } + + /// + /// Formats and writes an trace log message. + /// + /// The exception to log. + /// logger.LogTrace(exception) + public static void LogTrace(Exception exception) + { + LoggerInstance.LogTrace(exception); + } + + /// + /// Formats and writes a debug log message as JSON. + /// + /// The object to be serialized as JSON. + /// logger.LogDebug(new {User = user, Address = address}) + public static void LogDebug(object message) + { + LoggerInstance.LogDebug(message); + } + + /// + /// Formats and writes an debug log message. + /// + /// The exception to log. + /// logger.LogDebug(exception) + public static void LogDebug(Exception exception) + { + LoggerInstance.LogDebug(exception); + } + + /// + /// Formats and writes an information log message as JSON. + /// + /// The object to be serialized as JSON. + /// logger.LogInformation(new {User = user, Address = address}) + public static void LogInformation(object message) + { + LoggerInstance.LogInformation(message); + } + + /// + /// Formats and writes an information log message. + /// + /// The exception to log. + /// logger.LogInformation(exception) + public static void LogInformation(Exception exception) + { + LoggerInstance.LogInformation(exception); + } + + /// + /// Formats and writes a warning log message as JSON. + /// + /// The object to be serialized as JSON. + /// logger.LogWarning(new {User = user, Address = address}) + public static void LogWarning(object message) + { + LoggerInstance.LogWarning(message); + } + + /// + /// Formats and writes an warning log message. + /// + /// The exception to log. + /// logger.LogWarning(exception) + public static void LogWarning(Exception exception) + { + LoggerInstance.LogWarning(exception); + } + + /// + /// Formats and writes a error log message as JSON. + /// + /// The object to be serialized as JSON. + /// logger.LogCritical(new {User = user, Address = address}) + public static void LogError(object message) + { + LoggerInstance.LogError(message); + } + + /// + /// Formats and writes an error log message. + /// + /// The exception to log. + /// logger.LogError(exception) + public static void LogError(Exception exception) + { + LoggerInstance.LogError(exception); + } + + /// + /// Formats and writes a critical log message as JSON. + /// + /// The object to be serialized as JSON. + /// logger.LogCritical(new {User = user, Address = address}) + public static void LogCritical(object message) + { + LoggerInstance.LogCritical(message); + } + + /// + /// Formats and writes an critical log message. + /// + /// The exception to log. + /// logger.LogCritical(exception) + public static void LogCritical(Exception exception) + { + LoggerInstance.LogCritical(exception); + } + + /// + /// Formats and writes a log message as JSON at the specified log level. + /// + /// Entry will be written on this level. + /// The object to be serialized as JSON. + /// logger.Log(LogLevel.Information, new {User = user, Address = address}) + public static void Log(LogLevel logLevel, object message) + { + LoggerInstance.Log(logLevel, message); + } + + /// + /// Formats and writes a log message at the specified log level. + /// + /// Entry will be written on this level. + /// The exception to log. + /// logger.Log(LogLevel.Information, exception) + public static void Log(LogLevel logLevel, Exception exception) + { + LoggerInstance.Log(logLevel, exception); + } + + #endregion + + #region ExtraKeys Logger Methods + + #region Debug + + /// + /// Formats and writes a debug log message. + /// + /// Additional keys will be appended to the log entry. + /// The event id associated with the log. + /// The exception to log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogDebug(extraKeys, 0, exception, "Error while processing request from {Address}", address) + public static void LogDebug(T extraKeys, EventId eventId, Exception exception, string message, + params object[] args) where T : class + { + LoggerInstance.LogDebug(extraKeys, eventId, exception, message, args); + } + + /// + /// Formats and writes a debug log message. + /// + /// Additional keys will be appended to the log entry. + /// The event id associated with the log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogDebug(extraKeys, 0, "Processing request from {Address}", address) + public static void LogDebug(T extraKeys, EventId eventId, string message, params object[] args) where T : class + { + LoggerInstance.LogDebug(extraKeys, eventId, message, args); + } + + /// + /// Formats and writes a debug log message. + /// + /// Additional keys will be appended to the log entry. + /// The exception to log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogDebug(extraKeys, exception, "Error while processing request from {Address}", address) + public static void LogDebug(T extraKeys, Exception exception, string message, params object[] args) + where T : class + { + LoggerInstance.LogDebug(extraKeys, exception, message, args); + } + + /// + /// Formats and writes a debug log message. + /// + /// Additional keys will be appended to the log entry. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogDebug(extraKeys, "Processing request from {Address}", address) + public static void LogDebug(T extraKeys, string message, params object[] args) where T : class + { + LoggerInstance.LogDebug(extraKeys, message, args); + } + + #endregion + + #region Trace + + /// + /// Formats and writes a trace log message. + /// + /// Additional keys will be appended to the log entry. + /// The event id associated with the log. + /// The exception to log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogTrace(extraKeys, 0, exception, "Error while processing request from {Address}", address) + public static void LogTrace(T extraKeys, EventId eventId, Exception exception, string message, + params object[] args) where T : class + { + LoggerInstance.LogTrace(extraKeys, eventId, exception, message, args); + } + + /// + /// Formats and writes a trace log message. + /// + /// Additional keys will be appended to the log entry. + /// The event id associated with the log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogTrace(extraKeys, 0, "Processing request from {Address}", address) + public static void LogTrace(T extraKeys, EventId eventId, string message, params object[] args) where T : class + { + LoggerInstance.LogTrace(extraKeys, eventId, message, args); + } + + /// + /// Formats and writes a trace log message. + /// + /// Additional keys will be appended to the log entry. + /// The exception to log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogTrace(extraKeys, exception, "Error while processing request from {Address}", address) + public static void LogTrace(T extraKeys, Exception exception, string message, params object[] args) + where T : class + { + LoggerInstance.LogTrace(extraKeys, exception, message, args); + } + + /// + /// Formats and writes a trace log message. + /// + /// Additional keys will be appended to the log entry. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogTrace(extraKeys, "Processing request from {Address}", address) + public static void LogTrace(T extraKeys, string message, params object[] args) where T : class + { + LoggerInstance.LogTrace(extraKeys, message, args); + } + + #endregion + + #region Information + + /// + /// Formats and writes an informational log message. + /// + /// Additional keys will be appended to the log entry. + /// The event id associated with the log. + /// The exception to log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogInformation(extraKeys, 0, exception, "Error while processing request from {Address}", address) + public static void LogInformation(T extraKeys, EventId eventId, Exception exception, string message, + params object[] args) where T : class + { + LoggerInstance.LogInformation(extraKeys, eventId, exception, message, args); + } + + /// + /// Formats and writes an informational log message. + /// + /// Additional keys will be appended to the log entry. + /// The event id associated with the log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogInformation(extraKeys, 0, "Processing request from {Address}", address) + public static void LogInformation(T extraKeys, EventId eventId, string message, params object[] args) + where T : class + { + LoggerInstance.LogInformation(extraKeys, eventId, message, args); + } + + /// + /// Formats and writes an informational log message. + /// + /// Additional keys will be appended to the log entry. + /// The exception to log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogInformation(extraKeys, exception, "Error while processing request from {Address}", address) + public static void LogInformation(T extraKeys, Exception exception, string message, params object[] args) + where T : class + { + LoggerInstance.LogInformation(extraKeys, exception, message, args); + } + + /// + /// Formats and writes an informational log message. + /// + /// Additional keys will be appended to the log entry. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogInformation(extraKeys, "Processing request from {Address}", address) + public static void LogInformation(T extraKeys, string message, params object[] args) where T : class + { + LoggerInstance.LogInformation(extraKeys, message, args); + } + + #endregion + + #region Warning + + /// + /// Formats and writes a warning log message. + /// + /// Additional keys will be appended to the log entry. + /// The event id associated with the log. + /// The exception to log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogWarning(extraKeys, 0, exception, "Error while processing request from {Address}", address) + public static void LogWarning(T extraKeys, EventId eventId, Exception exception, string message, + params object[] args) where T : class + { + LoggerInstance.LogWarning(extraKeys, eventId, exception, message, args); + } + + /// + /// Formats and writes a warning log message. + /// + /// Additional keys will be appended to the log entry. + /// The event id associated with the log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogWarning(extraKeys, 0, "Processing request from {Address}", address) + public static void LogWarning(T extraKeys, EventId eventId, string message, params object[] args) where T : class + { + LoggerInstance.LogWarning(extraKeys, eventId, message, args); + } + + /// + /// Formats and writes a warning log message. + /// + /// Additional keys will be appended to the log entry. + /// The exception to log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogWarning(extraKeys, exception, "Error while processing request from {Address}", address) + public static void LogWarning(T extraKeys, Exception exception, string message, params object[] args) + where T : class + { + LoggerInstance.LogWarning(extraKeys, exception, message, args); + } + + /// + /// Formats and writes a warning log message. + /// + /// Additional keys will be appended to the log entry. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogWarning(extraKeys, "Processing request from {Address}", address) + public static void LogWarning(T extraKeys, string message, params object[] args) where T : class + { + LoggerInstance.LogWarning(extraKeys, message, args); + } + + #endregion + + #region Error + + /// + /// Formats and writes an error log message. + /// + /// Additional keys will be appended to the log entry. + /// The event id associated with the log. + /// The exception to log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogError(extraKeys, 0, exception, "Error while processing request from {Address}", address) + public static void LogError(T extraKeys, EventId eventId, Exception exception, string message, + params object[] args) where T : class + { + LoggerInstance.LogError(extraKeys, eventId, exception, message, args); + } + + /// + /// Formats and writes an error log message. + /// + /// Additional keys will be appended to the log entry. + /// The event id associated with the log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogError(extraKeys, 0, "Processing request from {Address}", address) + public static void LogError(T extraKeys, EventId eventId, string message, params object[] args) where T : class + { + LoggerInstance.LogError(extraKeys, eventId, message, args); + } + + /// + /// Formats and writes an error log message. + /// + /// Additional keys will be appended to the log entry. + /// The exception to log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogError(extraKeys, exception, "Error while processing request from {Address}", address) + public static void LogError(T extraKeys, Exception exception, string message, params object[] args) + where T : class + { + LoggerInstance.LogError(extraKeys, exception, message, args); + } + + /// + /// Formats and writes an error log message. + /// + /// Additional keys will be appended to the log entry. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogError(extraKeys, "Processing request from {Address}", address) + public static void LogError(T extraKeys, string message, params object[] args) where T : class + { + LoggerInstance.LogError(extraKeys, message, args); + } + + #endregion + + #region Critical + + /// + /// Formats and writes a critical log message. + /// + /// Additional keys will be appended to the log entry. + /// The event id associated with the log. + /// The exception to log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogCritical(extraKeys, 0, exception, "Error while processing request from {Address}", address) + public static void LogCritical(T extraKeys, EventId eventId, Exception exception, string message, + params object[] args) where T : class + { + LoggerInstance.LogCritical(extraKeys, eventId, exception, message, args); + } + + /// + /// Formats and writes a critical log message. + /// + /// Additional keys will be appended to the log entry. + /// The event id associated with the log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogCritical(extraKeys, 0, "Processing request from {Address}", address) + public static void LogCritical(T extraKeys, EventId eventId, string message, params object[] args) + where T : class + { + LoggerInstance.LogCritical(extraKeys, eventId, message, args); + } + + /// + /// Formats and writes a critical log message. + /// + /// Additional keys will be appended to the log entry. + /// The exception to log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogCritical(extraKeys, exception, "Error while processing request from {Address}", address) + public static void LogCritical(T extraKeys, Exception exception, string message, params object[] args) + where T : class + { + LoggerInstance.LogCritical(extraKeys, exception, message, args); + } + + /// + /// Formats and writes a critical log message. + /// + /// Additional keys will be appended to the log entry. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogCritical(extraKeys, "Processing request from {Address}", address) + public static void LogCritical(T extraKeys, string message, params object[] args) where T : class + { + LoggerInstance.LogCritical(extraKeys, message, args); + } + + #endregion + + #region Log + + /// + /// Formats and writes a log message at the specified log level. + /// + /// Entry will be written on this level. + /// Additional keys will be appended to the log entry. + /// The event id associated with the log. + /// The exception to log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.Log(LogLevel.Information, extraKeys, 0, exception, "Error while processing request from {Address}", address) + public static void Log(LogLevel logLevel, T extraKeys, EventId eventId, Exception exception, string message, + params object[] args) where T : class + { + LoggerInstance.Log(logLevel, extraKeys, eventId, exception, message, args); + } + + /// + /// Formats and writes a log message at the specified log level. + /// + /// Entry will be written on this level. + /// Additional keys will be appended to the log entry. + /// The event id associated with the log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.Log(LogLevel.Information, extraKeys, 0, "Processing request from {Address}", address) + public static void Log(LogLevel logLevel, T extraKeys, EventId eventId, string message, params object[] args) + where T : class + { + LoggerInstance.Log(logLevel, extraKeys, eventId, message, args); + } + + /// + /// Formats and writes a log message at the specified log level. + /// + /// Entry will be written on this level. + /// Additional keys will be appended to the log entry. + /// The exception to log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.Log(LogLevel.Information, extraKeys, exception, "Error while processing request from {Address}", address) + public static void Log(LogLevel logLevel, T extraKeys, Exception exception, string message, params object[] args) + where T : class + { + LoggerInstance.Log(logLevel, extraKeys, exception, message, args); + } + + /// + /// Formats and writes a log message at the specified log level. + /// + /// Entry will be written on this level. + /// Additional keys will be appended to the log entry. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.Log(LogLevel.Information, extraKeys, "Processing request from {Address}", address) + public static void Log(LogLevel logLevel, T extraKeys, string message, params object[] args) where T : class + { + LoggerInstance.Log(logLevel, extraKeys, message, args); + } + + #endregion + + #endregion + + #region Custom Log Formatter + + /// + /// Set the log formatter. + /// + /// The log formatter. + /// WARNING: This method should not be called when using AOT. ILogFormatter should be passed to PowertoolsSourceGeneratorSerializer constructor + public static void UseFormatter(ILogFormatter logFormatter) + { + _logFormatter = logFormatter ?? throw new ArgumentNullException(nameof(logFormatter)); + } + + /// + /// Set the log formatter to default. + /// + public static void UseDefaultFormatter() + { + _logFormatter = null; + } + + /// + /// Returns the log formatter. + /// + internal static ILogFormatter GetFormatter() => _logFormatter; + + #endregion } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/LoggerConfiguration.cs b/libraries/src/AWS.Lambda.Powertools.Logging/LoggerConfiguration.cs new file mode 100644 index 000000000..aab959af2 --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.Logging/LoggerConfiguration.cs @@ -0,0 +1,62 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace AWS.Lambda.Powertools.Logging; + +/// +/// Class LoggerConfiguration. +/// Implements the +/// +/// +/// +public class LoggerConfiguration : IOptions +{ + /// + /// Service name is used for logging. + /// This can be also set using the environment variable POWERTOOLS_SERVICE_NAME. + /// + /// The service. + public string Service { get; set; } + + /// + /// Specify the minimum log level for logging (Information, by default). + /// This can be also set using the environment variable POWERTOOLS_LOG_LEVEL. + /// + /// The minimum level. + public LogLevel MinimumLevel { get; set; } = LogLevel.None; + + /// + /// Dynamically set a percentage of logs to DEBUG level. + /// This can be also set using the environment variable POWERTOOLS_LOGGER_SAMPLE_RATE. + /// + /// The sampling rate. + public double SamplingRate { get; set; } + + /// + /// The default configured options instance + /// + /// The value. + LoggerConfiguration IOptions.Value => this; + + /// + /// The logger output case. + /// This can be also set using the environment variable POWERTOOLS_LOGGER_CASE. + /// + /// The logger output case. + public LoggerOutputCase LoggerOutputCase { get; set; } = LoggerOutputCase.Default; +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/LoggerExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Logging/LoggerExtensions.cs new file mode 100644 index 000000000..200cf46ed --- /dev/null +++ b/libraries/src/AWS.Lambda.Powertools.Logging/LoggerExtensions.cs @@ -0,0 +1,655 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System; +using AWS.Lambda.Powertools.Logging.Internal; +using Microsoft.Extensions.Logging; + +namespace AWS.Lambda.Powertools.Logging; + +/// +/// Class LoggerExtensions. +/// +public static class LoggerExtensions +{ + #region JSON Logger Extentions + + /// + /// Formats and writes a trace log message as JSON. + /// + /// The to write to. + /// The object to be serialized as JSON. + /// logger.LogTrace(new {User = user, Address = address}) + public static void LogTrace(this ILogger logger, object message) + { + logger.LogTrace(LoggingConstants.KeyJsonFormatter, message); + } + + /// + /// Formats and writes an trace log message. + /// + /// The to write to. + /// The exception to log. + /// logger.LogTrace(exception) + public static void LogTrace(this ILogger logger, Exception exception) + { + logger.LogTrace(exception: exception, message: exception.Message); + } + + /// + /// Formats and writes a debug log message as JSON. + /// + /// The to write to. + /// The object to be serialized as JSON. + /// logger.LogDebug(new {User = user, Address = address}) + public static void LogDebug(this ILogger logger, object message) + { + logger.LogDebug(LoggingConstants.KeyJsonFormatter, message); + } + + /// + /// Formats and writes an debug log message. + /// + /// The to write to. + /// The exception to log. + /// logger.LogDebug(exception) + public static void LogDebug(this ILogger logger, Exception exception) + { + logger.LogDebug(exception: exception, message: exception.Message); + } + + /// + /// Formats and writes an information log message as JSON. + /// + /// The to write to. + /// The object to be serialized as JSON. + /// logger.LogInformation(new {User = user, Address = address}) + public static void LogInformation(this ILogger logger, object message) + { + logger.LogInformation(LoggingConstants.KeyJsonFormatter, message); + } + + /// + /// Formats and writes an information log message. + /// + /// The to write to. + /// The exception to log. + /// logger.LogInformation(exception) + public static void LogInformation(this ILogger logger, Exception exception) + { + logger.LogInformation(exception: exception, message: exception.Message); + } + + /// + /// Formats and writes a warning log message as JSON. + /// + /// The to write to. + /// The object to be serialized as JSON. + /// logger.LogWarning(new {User = user, Address = address}) + public static void LogWarning(this ILogger logger, object message) + { + logger.LogWarning(LoggingConstants.KeyJsonFormatter, message); + } + + /// + /// Formats and writes an warning log message. + /// + /// The to write to. + /// The exception to log. + /// logger.LogWarning(exception) + public static void LogWarning(this ILogger logger, Exception exception) + { + logger.LogWarning(exception: exception, message: exception.Message); + } + + /// + /// Formats and writes a error log message as JSON. + /// + /// The to write to. + /// The object to be serialized as JSON. + /// logger.LogCritical(new {User = user, Address = address}) + public static void LogError(this ILogger logger, object message) + { + logger.LogError(LoggingConstants.KeyJsonFormatter, message); + } + + /// + /// Formats and writes an error log message. + /// + /// The to write to. + /// The exception to log. + /// logger.LogError(exception) + public static void LogError(this ILogger logger, Exception exception) + { + logger.LogError(exception: exception, message: exception.Message); + } + + /// + /// Formats and writes a critical log message as JSON. + /// + /// The to write to. + /// The object to be serialized as JSON. + /// logger.LogCritical(new {User = user, Address = address}) + public static void LogCritical(this ILogger logger, object message) + { + logger.LogCritical(LoggingConstants.KeyJsonFormatter, message); + } + + /// + /// Formats and writes an critical log message. + /// + /// The to write to. + /// The exception to log. + /// logger.LogCritical(exception) + public static void LogCritical(this ILogger logger, Exception exception) + { + logger.LogCritical(exception: exception, message: exception.Message); + } + + /// + /// Formats and writes a log message as JSON at the specified log level. + /// + /// The to write to. + /// Entry will be written on this level. + /// The object to be serialized as JSON. + /// logger.Log(LogLevel.Information, new {User = user, Address = address}) + public static void Log(this ILogger logger, LogLevel logLevel, object message) + { + logger.Log(logLevel, LoggingConstants.KeyJsonFormatter, message); + } + + /// + /// Formats and writes a log message at the specified log level. + /// + /// The to write to. + /// Entry will be written on this level. + /// The exception to log. + /// logger.Log(LogLevel.Information, exception) + public static void Log(this ILogger logger, LogLevel logLevel, Exception exception) + { + logger.Log(logLevel, exception: exception, message: exception.Message); + } + + #endregion + + #region ExtraKeys Logger Extentions + + #region Debug + + /// + /// Formats and writes a debug log message. + /// + /// The to write to. + /// Additional keys will be appended to the log entry. + /// The event id associated with the log. + /// The exception to log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogDebug(extraKeys, 0, exception, "Error while processing request from {Address}", address) + public static void LogDebug(this ILogger logger, T extraKeys, EventId eventId, Exception exception, + string message, params object[] args) where T : class + { + Log(logger, LogLevel.Debug, extraKeys, eventId, exception, message, args); + } + + /// + /// Formats and writes a debug log message. + /// + /// The to write to. + /// Additional keys will be appended to the log entry. + /// The event id associated with the log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogDebug(extraKeys, 0, "Processing request from {Address}", address) + public static void LogDebug(this ILogger logger, T extraKeys, EventId eventId, string message, + params object[] args) where T : class + { + Log(logger, LogLevel.Debug, extraKeys, eventId, message, args); + } + + /// + /// Formats and writes a debug log message. + /// + /// The to write to. + /// Additional keys will be appended to the log entry. + /// The exception to log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogDebug(extraKeys, exception, "Error while processing request from {Address}", address) + public static void LogDebug(this ILogger logger, T extraKeys, Exception exception, string message, + params object[] args) where T : class + { + Log(logger, LogLevel.Debug, extraKeys, exception, message, args); + } + + /// + /// Formats and writes a debug log message. + /// + /// The to write to. + /// Additional keys will be appended to the log entry. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogDebug(extraKeys, "Processing request from {Address}", address) + public static void LogDebug(this ILogger logger, T extraKeys, string message, params object[] args) + where T : class + { + Log(logger, LogLevel.Debug, extraKeys, message, args); + } + + #endregion + + #region Trace + + /// + /// Formats and writes a trace log message. + /// + /// The to write to. + /// Additional keys will be appended to the log entry. + /// The event id associated with the log. + /// The exception to log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogTrace(extraKeys, 0, exception, "Error while processing request from {Address}", address) + public static void LogTrace(this ILogger logger, T extraKeys, EventId eventId, Exception exception, + string message, params object[] args) where T : class + { + Log(logger, LogLevel.Trace, extraKeys, eventId, exception, message, args); + } + + /// + /// Formats and writes a trace log message. + /// + /// The to write to. + /// Additional keys will be appended to the log entry. + /// The event id associated with the log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogTrace(extraKeys, 0, "Processing request from {Address}", address) + public static void LogTrace(this ILogger logger, T extraKeys, EventId eventId, string message, + params object[] args) where T : class + { + Log(logger, LogLevel.Trace, extraKeys, eventId, message, args); + } + + /// + /// Formats and writes a trace log message. + /// + /// The to write to. + /// Additional keys will be appended to the log entry. + /// The exception to log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogTrace(extraKeys, exception, "Error while processing request from {Address}", address) + public static void LogTrace(this ILogger logger, T extraKeys, Exception exception, string message, + params object[] args) where T : class + { + Log(logger, LogLevel.Trace, extraKeys, exception, message, args); + } + + /// + /// Formats and writes a trace log message. + /// + /// The to write to. + /// Additional keys will be appended to the log entry. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogTrace(extraKeys, "Processing request from {Address}", address) + public static void LogTrace(this ILogger logger, T extraKeys, string message, params object[] args) + where T : class + { + Log(logger, LogLevel.Trace, extraKeys, message, args); + } + + #endregion + + #region Information + + /// + /// Formats and writes an informational log message. + /// + /// The to write to. + /// Additional keys will be appended to the log entry. + /// The event id associated with the log. + /// The exception to log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogInformation(extraKeys, 0, exception, "Error while processing request from {Address}", address) + public static void LogInformation(this ILogger logger, T extraKeys, EventId eventId, Exception exception, + string message, params object[] args) where T : class + { + Log(logger, LogLevel.Information, extraKeys, eventId, exception, message, args); + } + + /// + /// Formats and writes an informational log message. + /// + /// The to write to. + /// Additional keys will be appended to the log entry. + /// The event id associated with the log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogInformation(extraKeys, 0, "Processing request from {Address}", address) + public static void LogInformation(this ILogger logger, T extraKeys, EventId eventId, string message, + params object[] args) where T : class + { + Log(logger, LogLevel.Information, extraKeys, eventId, message, args); + } + + /// + /// Formats and writes an informational log message. + /// + /// The to write to. + /// Additional keys will be appended to the log entry. + /// The exception to log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogInformation(extraKeys, exception, "Error while processing request from {Address}", address) + public static void LogInformation(this ILogger logger, T extraKeys, Exception exception, string message, + params object[] args) where T : class + { + Log(logger, LogLevel.Information, extraKeys, exception, message, args); + } + + /// + /// Formats and writes an informational log message. + /// + /// The to write to. + /// Additional keys will be appended to the log entry. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogInformation(extraKeys, "Processing request from {Address}", address) + public static void LogInformation(this ILogger logger, T extraKeys, string message, params object[] args) + where T : class + { + Log(logger, LogLevel.Information, extraKeys, message, args); + } + + #endregion + + #region Warning + + /// + /// Formats and writes a warning log message. + /// + /// The to write to. + /// Additional keys will be appended to the log entry. + /// The event id associated with the log. + /// The exception to log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogWarning(extraKeys, 0, exception, "Error while processing request from {Address}", address) + public static void LogWarning(this ILogger logger, T extraKeys, EventId eventId, Exception exception, + string message, params object[] args) where T : class + { + Log(logger, LogLevel.Warning, extraKeys, eventId, exception, message, args); + } + + /// + /// Formats and writes a warning log message. + /// + /// The to write to. + /// Additional keys will be appended to the log entry. + /// The event id associated with the log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogWarning(extraKeys, 0, "Processing request from {Address}", address) + public static void LogWarning(this ILogger logger, T extraKeys, EventId eventId, string message, + params object[] args) where T : class + { + Log(logger, LogLevel.Warning, extraKeys, eventId, message, args); + } + + /// + /// Formats and writes a warning log message. + /// + /// The to write to. + /// Additional keys will be appended to the log entry. + /// The exception to log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogWarning(extraKeys, exception, "Error while processing request from {Address}", address) + public static void LogWarning(this ILogger logger, T extraKeys, Exception exception, string message, + params object[] args) where T : class + { + Log(logger, LogLevel.Warning, extraKeys, exception, message, args); + } + + /// + /// Formats and writes a warning log message. + /// + /// The to write to. + /// Additional keys will be appended to the log entry. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogWarning(extraKeys, "Processing request from {Address}", address) + public static void LogWarning(this ILogger logger, T extraKeys, string message, params object[] args) + where T : class + { + Log(logger, LogLevel.Warning, extraKeys, message, args); + } + + #endregion + + #region Error + + /// + /// Formats and writes an error log message. + /// + /// The to write to. + /// Additional keys will be appended to the log entry. + /// The event id associated with the log. + /// The exception to log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogError(extraKeys, 0, exception, "Error while processing request from {Address}", address) + public static void LogError(this ILogger logger, T extraKeys, EventId eventId, Exception exception, + string message, params object[] args) where T : class + { + Log(logger, LogLevel.Error, extraKeys, eventId, exception, message, args); + } + + /// + /// Formats and writes an error log message. + /// + /// The to write to. + /// Additional keys will be appended to the log entry. + /// The event id associated with the log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogError(extraKeys, 0, "Processing request from {Address}", address) + public static void LogError(this ILogger logger, T extraKeys, EventId eventId, string message, + params object[] args) where T : class + { + Log(logger, LogLevel.Error, extraKeys, eventId, message, args); + } + + /// + /// Formats and writes an error log message. + /// + /// The to write to. + /// Additional keys will be appended to the log entry. + /// The exception to log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogError(extraKeys, exception, "Error while processing request from {Address}", address) + public static void LogError(this ILogger logger, T extraKeys, Exception exception, string message, + params object[] args) where T : class + { + Log(logger, LogLevel.Error, extraKeys, exception, message, args); + } + + /// + /// Formats and writes an error log message. + /// + /// The to write to. + /// Additional keys will be appended to the log entry. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogError(extraKeys, "Processing request from {Address}", address) + public static void LogError(this ILogger logger, T extraKeys, string message, params object[] args) + where T : class + { + Log(logger, LogLevel.Error, extraKeys, message, args); + } + + #endregion + + #region Critical + + /// + /// Formats and writes a critical log message. + /// + /// The to write to. + /// Additional keys will be appended to the log entry. + /// The event id associated with the log. + /// The exception to log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogCritical(extraKeys, 0, exception, "Error while processing request from {Address}", address) + public static void LogCritical(this ILogger logger, T extraKeys, EventId eventId, Exception exception, + string message, params object[] args) where T : class + { + Log(logger, LogLevel.Critical, extraKeys, eventId, exception, message, args); + } + + /// + /// Formats and writes a critical log message. + /// + /// The to write to. + /// Additional keys will be appended to the log entry. + /// The event id associated with the log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogCritical(extraKeys, 0, "Processing request from {Address}", address) + public static void LogCritical(this ILogger logger, T extraKeys, EventId eventId, string message, + params object[] args) where T : class + { + Log(logger, LogLevel.Critical, extraKeys, eventId, message, args); + } + + /// + /// Formats and writes a critical log message. + /// + /// The to write to. + /// Additional keys will be appended to the log entry. + /// The exception to log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogCritical(extraKeys, exception, "Error while processing request from {Address}", address) + public static void LogCritical(this ILogger logger, T extraKeys, Exception exception, string message, + params object[] args) where T : class + { + Log(logger, LogLevel.Critical, extraKeys, exception, message, args); + } + + /// + /// Formats and writes a critical log message. + /// + /// The to write to. + /// Additional keys will be appended to the log entry. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.LogCritical(extraKeys, "Processing request from {Address}", address) + public static void LogCritical(this ILogger logger, T extraKeys, string message, params object[] args) + where T : class + { + Log(logger, LogLevel.Critical, extraKeys, message, args); + } + + #endregion + + #region Log + + /// + /// Formats and writes a log message at the specified log level. + /// + /// The to write to. + /// Entry will be written on this level. + /// Additional keys will be appended to the log entry. + /// The event id associated with the log. + /// The exception to log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.Log(LogLevel.Information, extraKeys, 0, exception, "Error while processing request from {Address}", address) + public static void Log(this ILogger logger, LogLevel logLevel, T extraKeys, EventId eventId, Exception exception, + string message, params object[] args) where T : class + { + if (extraKeys is Exception ex && exception is null) + logger.Log(logLevel, eventId, ex, message, args); + else if (extraKeys is not null) + using (logger.BeginScope(extraKeys)) + logger.Log(logLevel, eventId, exception, message, args); + else + logger.Log(logLevel, eventId, exception, message, args); + } + + /// + /// Formats and writes a log message at the specified log level. + /// + /// The to write to. + /// Entry will be written on this level. + /// Additional keys will be appended to the log entry. + /// The event id associated with the log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.Log(LogLevel.Information, extraKeys, 0, "Processing request from {Address}", address) + public static void Log(this ILogger logger, LogLevel logLevel, T extraKeys, EventId eventId, string message, + params object[] args) where T : class + { + Log(logger, logLevel, extraKeys, eventId, null, message, args); + } + + /// + /// Formats and writes a log message at the specified log level. + /// + /// The to write to. + /// Entry will be written on this level. + /// Additional keys will be appended to the log entry. + /// The exception to log. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.Log(LogLevel.Information, extraKeys, exception, "Error while processing request from {Address}", address) + public static void Log(this ILogger logger, LogLevel logLevel, T extraKeys, Exception exception, string message, + params object[] args) where T : class + { + Log(logger, logLevel, extraKeys, 0, exception, message, args); + } + + /// + /// Formats and writes a log message at the specified log level. + /// + /// The to write to. + /// Entry will be written on this level. + /// Additional keys will be appended to the log entry. + /// Format string of the log message in message template format. Example: "User {User} logged in from {Address}" + /// An object array that contains zero or more objects to format. + /// logger.Log(LogLevel.Information, extraKeys, "Processing request from {Address}", address) + public static void Log(this ILogger logger, LogLevel logLevel, T extraKeys, string message, params object[] args) + where T : class + { + try + { + Log(logger, logLevel, extraKeys, 0, null, message, args); + } + catch (Exception e) + { + logger.Log(LogLevel.Error, 0, e, "Powertools internal error"); + } + } + + #endregion + + #endregion +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/LoggingAttribute.cs b/libraries/src/AWS.Lambda.Powertools.Logging/LoggingAttribute.cs index 747cf7dbc..4a5da9309 100644 --- a/libraries/src/AWS.Lambda.Powertools.Logging/LoggingAttribute.cs +++ b/libraries/src/AWS.Lambda.Powertools.Logging/LoggingAttribute.cs @@ -15,7 +15,6 @@ using System; using AspectInjector.Broker; -using AWS.Lambda.Powertools.Common; using AWS.Lambda.Powertools.Logging.Internal; using Microsoft.Extensions.Logging; @@ -117,8 +116,8 @@ namespace AWS.Lambda.Powertools.Logging; /// /// [AttributeUsage(AttributeTargets.Method)] -// [Injection(typeof(LoggingAspect))] -public class LoggingAttribute : MethodAspectAttribute +[Injection(typeof(LoggingAspect))] +public class LoggingAttribute : Attribute { /// /// Service name is used for logging. @@ -147,19 +146,7 @@ public class LoggingAttribute : MethodAspectAttribute /// such as a string or any custom data object. /// /// true if [log event]; otherwise, false. - public bool LogEvent - { - get => _logEvent; - set - { - _logEvent = value; - _logEventSet = true; - } - } - - private bool _logEventSet; - private bool _logEvent; - internal bool IsLogEventSet => _logEventSet; + public bool LogEvent { get; set; } /// /// Pointer path to extract correlation id from input parameter. @@ -184,19 +171,4 @@ public bool LogEvent /// /// The log level. public LoggerOutputCase LoggerOutputCase { get; set; } = LoggerOutputCase.Default; - - /// - /// Flush buffer on uncaught error - /// When buffering is enabled, this property will flush the buffer on uncaught exceptions - /// - public bool FlushBufferOnUncaughtError { get; set; } - - /// - /// Creates the aspect with the Logger - /// - /// - protected override IMethodAspectHandler CreateHandler() - { - return new LoggingAspect(LoggerFactoryHolder.GetOrCreateFactory().CreatePowertoolsLogger()); - } } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerBuilder.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerBuilder.cs deleted file mode 100644 index e822b3c58..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerBuilder.cs +++ /dev/null @@ -1,148 +0,0 @@ -using System; -using System.Text.Json; -using AWS.Lambda.Powertools.Common; -using AWS.Lambda.Powertools.Logging.Internal.Helpers; -using Microsoft.Extensions.Logging; - -namespace AWS.Lambda.Powertools.Logging; - -/// -/// Builder class for creating configured PowertoolsLogger instances. -/// Provides a fluent interface for configuring logging options. -/// -public class PowertoolsLoggerBuilder -{ - private readonly PowertoolsLoggerConfiguration _configuration = new(); - - /// - /// Sets the service name for the logger. - /// - /// The service name to be included in logs. - /// The builder instance for method chaining. - public PowertoolsLoggerBuilder WithService(string service) - { - _configuration.Service = service; - return this; - } - - /// - /// Sets the sampling rate for logs. - /// - /// The sampling rate between 0 and 1. - /// The builder instance for method chaining. - public PowertoolsLoggerBuilder WithSamplingRate(double rate) - { - _configuration.SamplingRate = rate; - return this; - } - - /// - /// Sets the minimum log level for the logger. - /// - /// The minimum LogLevel to capture. - /// The builder instance for method chaining. - public PowertoolsLoggerBuilder WithMinimumLogLevel(LogLevel level) - { - _configuration.MinimumLogLevel = level; - return this; - } - - /// - /// Sets custom JSON serialization options. - /// - /// JSON serializer options to use for log formatting. - /// The builder instance for method chaining. - public PowertoolsLoggerBuilder WithJsonOptions(JsonSerializerOptions options) - { - _configuration.JsonOptions = options; - return this; - } - - /// - /// Sets the timestamp format for log entries. - /// - /// The timestamp format string. - /// The builder instance for method chaining. - public PowertoolsLoggerBuilder WithTimestampFormat(string format) - { - _configuration.TimestampFormat = format; - return this; - } - - /// - /// Sets the output casing style for log properties. - /// - /// The casing style to use for log output. - /// The builder instance for method chaining. - public PowertoolsLoggerBuilder WithOutputCase(LoggerOutputCase outputCase) - { - _configuration.LoggerOutputCase = outputCase; - return this; - } - - /// - /// Sets a custom log formatter. - /// - /// The formatter to use for log formatting. - /// The builder instance for method chaining. - /// Thrown when formatter is null. - public PowertoolsLoggerBuilder WithFormatter(ILogFormatter formatter) - { - _configuration.LogFormatter = formatter ?? throw new ArgumentNullException(nameof(formatter)); - return this; - } - - /// - /// Configures log buffering with custom options. - /// - /// Action to configure the log buffering options. - /// The builder instance for method chaining. - public PowertoolsLoggerBuilder WithLogBuffering(Action configure) - { - _configuration.LogBuffering = new LogBufferingOptions(); - configure?.Invoke(_configuration.LogBuffering); - return this; - } - - /// - /// Specifies the console output wrapper used for writing logs. This property allows - /// redirecting log output for testing or specialized handling scenarios. - /// Defaults to standard console output via ConsoleWrapper. - /// - /// - /// - /// // Using TestLoggerOutput - /// .WithLogOutput(new TestLoggerOutput()); - /// - /// // Custom console output for testing - /// .WithLogOutput(new TestConsoleWrapper()); - /// - /// // Example implementation for testing: - /// public class TestConsoleWrapper : IConsoleWrapper - /// { - /// public List<string> CapturedOutput { get; } = new(); - /// - /// public void WriteLine(string message) - /// { - /// CapturedOutput.Add(message); - /// } - /// } - /// - /// - public PowertoolsLoggerBuilder WithLogOutput(IConsoleWrapper console) - { - _configuration.LogOutput = console ?? throw new ArgumentNullException(nameof(console)); - return this; - } - - - /// - /// Builds and returns a configured logger instance. - /// - /// An ILogger configured with the specified options. - public ILogger Build() - { - var factory = LoggerFactoryHelper.CreateAndConfigureFactory(_configuration); - return factory.CreatePowertoolsLogger(); - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs deleted file mode 100644 index 9b25d6539..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerConfiguration.cs +++ /dev/null @@ -1,396 +0,0 @@ -using System; -using System.Security.Cryptography; -using System.Text.Json; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using AWS.Lambda.Powertools.Common; -using AWS.Lambda.Powertools.Logging.Serializers; - -namespace AWS.Lambda.Powertools.Logging; - -/// -/// Configuration for the Powertools Logger. -/// -/// -/// -/// Basic logging configuration: -/// -/// builder.Logging.AddPowertoolsLogger(options => -/// { -/// options.Service = "OrderService"; -/// options.MinimumLogLevel = LogLevel.Information; -/// options.LoggerOutputCase = LoggerOutputCase.CamelCase; -/// }); -/// -/// -/// Using with log buffering: -/// -/// builder.Logging.AddPowertoolsLogger(options => -/// { -/// options.LogBuffering = new LogBufferingOptions -/// { -/// Enabled = true, -/// BufferAtLogLevel = LogLevel.Debug, -/// FlushOnErrorLog = true -/// }; -/// }); -/// -/// -/// Custom JSON formatting: -/// -/// builder.Logging.AddPowertoolsLogger(options => -/// { -/// options.JsonOptions = new JsonSerializerOptions -/// { -/// PropertyNamingPolicy = JsonNamingPolicy.CamelCase, -/// WriteIndented = true -/// }; -/// }); -/// -/// -public class PowertoolsLoggerConfiguration : IOptions -{ - /// - /// The configuration section name used when retrieving configuration from appsettings.json - /// or other configuration providers. - /// - public const string ConfigurationSectionName = "AWS.Lambda.Powertools.Logging.Logger"; - - /// - /// Specifies the service name that will be added to all logs to improve discoverability. - /// This value can also be set using the environment variable POWERTOOLS_SERVICE_NAME. - /// - /// - /// - /// options.Service = "OrderProcessingService"; - /// - /// - public string Service { get; set; } = null; - - /// - /// Defines the format for timestamps in log entries. Supports standard .NET date format strings. - /// When not specified, the default ISO 8601 format is used. - /// - /// - /// - /// // Use specific format - /// options.TimestampFormat = "yyyy-MM-dd HH:mm:ss"; - /// - /// // Use ISO 8601 with milliseconds - /// options.TimestampFormat = "o"; - /// - /// - public string TimestampFormat { get; set; } - - /// - /// Defines the minimum log level that will be processed by the logger. - /// Messages below this level will be ignored. Defaults to LogLevel.None, which means - /// the minimum level is determined by other configuration mechanisms. - /// This can also be set using the environment variable POWERTOOLS_LOG_LEVEL. - /// - /// - /// - /// // Only log warnings and above - /// options.MinimumLogLevel = LogLevel.Warning; - /// - /// // Log everything including trace messages - /// options.MinimumLogLevel = LogLevel.Trace; - /// - /// - public LogLevel MinimumLogLevel { get; set; } = LogLevel.None; - - /// - /// Sets a percentage (0.0 to 1.0) of logs that will be dynamically elevated to DEBUG level, - /// allowing for production debugging without increasing log verbosity for all requests. - /// This can also be set using the environment variable POWERTOOLS_LOGGER_SAMPLE_RATE. - /// - /// - /// - /// // Sample 10% of logs to DEBUG level - /// options.SamplingRate = 0.1; - /// - /// // Sample 100% (all logs) to DEBUG level - /// options.SamplingRate = 1.0; - /// - /// - public double SamplingRate { get; set; } - - /// - /// Controls the case format used for log field names in the JSON output. - /// Available options are Default, CamelCase, PascalCase, or SnakeCase. - /// This can also be set using the environment variable POWERTOOLS_LOGGER_CASE. - /// - /// - /// - /// // Use camelCase for JSON field names - /// options.LoggerOutputCase = LoggerOutputCase.CamelCase; - /// - /// // Use snake_case for JSON field names - /// options.LoggerOutputCase = LoggerOutputCase.SnakeCase; - /// - /// - public LoggerOutputCase LoggerOutputCase { get; set; } = LoggerOutputCase.Default; - - /// - /// Internal key used for log level in output - /// - internal string LogLevelKey { get; set; } = "level"; - - /// - /// Provides a custom log formatter implementation to control how log entries are formatted. - /// Set this to override the default JSON formatting with your own custom format. - /// - /// - /// - /// // Use a custom formatter implementation - /// options.LogFormatter = new MyCustomLogFormatter(); - /// - /// // Example with a simple custom formatter class this will just return a string: - /// public class MyCustomLogFormatter : ILogFormatter - /// { - /// public object FormatLog(LogEntry entry) - /// { - /// // Custom formatting logic here - /// return $"{logEntry.Timestamp}: [{logEntry.Level}] {logEntry.Message}"; - /// } - /// } - /// // Example with a complete formatter class this will just return a json object: - /// public object FormatLogEntry(LogEntry logEntry) - /// { - /// return new - /// { - /// Message = logEntry.Message, - /// Service = logEntry.Service, - /// CorrelationIds = new - /// { - /// AwsRequestId = logEntry.LambdaContext?.AwsRequestId, - /// XRayTraceId = logEntry.XRayTraceId, - /// CorrelationId = logEntry.CorrelationId - /// }, - /// LambdaFunction = new - /// { - /// Name = logEntry.LambdaContext?.FunctionName, - /// Arn = logEntry.LambdaContext?.InvokedFunctionArn, - /// MemoryLimitInMB = logEntry.LambdaContext?.MemoryLimitInMB, - /// Version = logEntry.LambdaContext?.FunctionVersion, - /// ColdStart = true, - /// }, - /// Level = logEntry.Level.ToString(), - /// Timestamp = new DateTime(2024, 1, 1).ToString("o"), - /// Logger = new - /// { - /// Name = logEntry.Name, - /// SampleRate = logEntry.SamplingRate - /// }, - /// }; - /// } - /// - /// - public ILogFormatter LogFormatter { get; set; } - - private JsonSerializerOptions _jsonOptions; - - /// - /// Configures the JSON serialization options used when converting log entries to JSON. - /// This allows customization of property naming, indentation, and other serialization behaviors. - /// Setting this property automatically updates the internal serializer. - /// - /// - /// - /// // DictionaryNamingPolicy allows you to control the naming policy for dictionary keys - /// options.JsonOptions = new JsonSerializerOptions - /// { - /// DictionaryNamingPolicy = JsonNamingPolicy.CamelCase - /// }; - /// // Pretty-print JSON logs with indentation - /// options.JsonOptions = new JsonSerializerOptions - /// { - /// WriteIndented = true, - /// PropertyNamingPolicy = JsonNamingPolicy.CamelCase - /// }; - /// - /// // Configure to ignore null values in output - /// options.JsonOptions = new JsonSerializerOptions - /// { - /// DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull - /// }; - /// - /// - public JsonSerializerOptions JsonOptions - { - get => _jsonOptions; - set - { - _jsonOptions = value; - if (_jsonOptions != null && _serializer != null) - { - _serializer.SetOptions(_jsonOptions); - } - } - } - - /// - /// Enables or disables log buffering. Logs below the specified level will be buffered - /// until the buffer is flushed or an error occurs. - /// Buffer logs at the WARNING, INFO, and DEBUG levels and reduce CloudWatch costs by decreasing the number of emitted log messages - /// - /// - /// - /// // Enable buffering for debug logs - /// options.LogBuffering = new LogBufferingOptions - /// { - /// Enabled = true, - /// BufferAtLogLevel = LogLevel.Debug, - /// FlushOnErrorLog = true - /// }; - /// - /// // Buffer all logs below Error level - /// options.LogBuffering = new LogBufferingOptions - /// { - /// Enabled = true, - /// BufferAtLogLevel = LogLevel.Warning, - /// FlushOnErrorLog = true - /// }; - /// - /// - public LogBufferingOptions LogBuffering { get; set; } - - /// - /// Serializer instance for this configuration - /// - private PowertoolsLoggingSerializer _serializer; - - /// - /// Gets the serializer instance for this configuration - /// - internal PowertoolsLoggingSerializer Serializer => _serializer ??= InitializeSerializer(); - - /// - /// Specifies the console output wrapper used for writing logs. This property allows - /// redirecting log output for testing or specialized handling scenarios. - /// Defaults to standard console output via ConsoleWrapper. - /// - /// - /// - /// // Using TestLoggerOutput - /// options.LogOutput = new TestLoggerOutput(); - /// - /// // Custom console output for testing - /// options.LogOutput = new TestConsoleWrapper(); - /// - /// // Example implementation for testing: - /// public class TestConsoleWrapper : IConsoleWrapper - /// { - /// public List<string> CapturedOutput { get; } = new(); - /// - /// public void WriteLine(string message) - /// { - /// CapturedOutput.Add(message); - /// } - /// } - /// - /// - public IConsoleWrapper LogOutput { get; set; } = new ConsoleWrapper(); - - /// - /// Initialize serializer with the current configuration - /// - private PowertoolsLoggingSerializer InitializeSerializer() - { - var serializer = new PowertoolsLoggingSerializer(); - if (_jsonOptions != null) - { - serializer.SetOptions(_jsonOptions); - } - - serializer.ConfigureNamingPolicy(LoggerOutputCase); - return serializer; - } - - // IOptions implementation - PowertoolsLoggerConfiguration IOptions.Value => this; - - internal string XRayTraceId { get; set; } - internal bool LogEvent { get; set; } - - internal int SamplingRefreshCount { get; set; } = 0; - internal LogLevel InitialLogLevel { get; set; } = LogLevel.Information; - - /// - /// Gets random number - /// - /// System.Double. - internal virtual double GetRandom() - { - return GetSafeRandom(); - } - - /// - /// Refresh the sampling calculation and update the minimum log level if needed - /// - /// True if debug sampling was enabled, false otherwise - internal bool RefreshSampleRateCalculation() - { - return RefreshSampleRateCalculation(out _); - } - - /// - /// Refresh the sampling calculation and update the minimum log level if needed - /// - /// - /// True if debug sampling was enabled, false otherwise - internal bool RefreshSampleRateCalculation(out double samplerValue) - { - samplerValue = 0.0; - - if (SamplingRate <= 0) - return false; - - // Increment counter at the beginning for proper cold start protection - SamplingRefreshCount++; - - // Skip first call for cold start protection - if (SamplingRefreshCount == 1) - { - return false; - } - - var shouldEnableDebugSampling = ShouldEnableDebugSampling(out samplerValue); - - if (shouldEnableDebugSampling && MinimumLogLevel > LogLevel.Debug) - { - MinimumLogLevel = LogLevel.Debug; - return true; - } - else if (!shouldEnableDebugSampling) - { - MinimumLogLevel = InitialLogLevel; - } - - return shouldEnableDebugSampling; - } - - - internal bool ShouldEnableDebugSampling() - { - return ShouldEnableDebugSampling(out _); - } - - internal bool ShouldEnableDebugSampling(out double samplerValue) - { - samplerValue = 0.0; - if (SamplingRate <= 0) return false; - - samplerValue = GetRandom(); - return samplerValue <= SamplingRate; - } - - internal static double GetSafeRandom() - { - var randomGenerator = RandomNumberGenerator.Create(); - byte[] data = new byte[4]; - randomGenerator.GetBytes(data); - uint randomUInt = BitConverter.ToUInt32(data, 0); - return (double)randomUInt / uint.MaxValue; - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerExtensions.cs deleted file mode 100644 index 9a6cda819..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerExtensions.cs +++ /dev/null @@ -1,269 +0,0 @@ -using System; -using System.Collections.Generic; -using AWS.Lambda.Powertools.Logging.Internal; -using Microsoft.Extensions.Logging; - -namespace AWS.Lambda.Powertools.Logging; - -/// -/// Class LoggerExtensions. -/// -public static class PowertoolsLoggerExtensions -{ - #region JSON Logger Extentions - - /// - /// Formats and writes a trace log message as JSON. - /// - /// The to write to. - /// The object to be serialized as JSON. - /// logger.LogTrace(new {User = user, Address = address}) - public static void LogTrace(this ILogger logger, object message) - { - logger.LogTrace(LoggingConstants.KeyJsonFormatter, message); - } - - /// - /// Formats and writes an trace log message. - /// - /// The to write to. - /// The exception to log. - /// logger.LogTrace(exception) - public static void LogTrace(this ILogger logger, Exception exception) - { - logger.LogTrace(exception: exception, message: exception.Message); - } - - /// - /// Formats and writes a debug log message as JSON. - /// - /// The to write to. - /// The object to be serialized as JSON. - /// logger.LogDebug(new {User = user, Address = address}) - public static void LogDebug(this ILogger logger, object message) - { - logger.LogDebug(LoggingConstants.KeyJsonFormatter, message); - } - - /// - /// Formats and writes an debug log message. - /// - /// The to write to. - /// The exception to log. - /// logger.LogDebug(exception) - public static void LogDebug(this ILogger logger, Exception exception) - { - logger.LogDebug(exception: exception, message: exception.Message); - } - - /// - /// Formats and writes an information log message as JSON. - /// - /// The to write to. - /// The object to be serialized as JSON. - /// logger.LogInformation(new {User = user, Address = address}) - public static void LogInformation(this ILogger logger, object message) - { - logger.LogInformation(LoggingConstants.KeyJsonFormatter, message); - } - - /// - /// Formats and writes an information log message. - /// - /// The to write to. - /// The exception to log. - /// logger.LogInformation(exception) - public static void LogInformation(this ILogger logger, Exception exception) - { - logger.LogInformation(exception: exception, message: exception.Message); - } - - /// - /// Formats and writes a warning log message as JSON. - /// - /// The to write to. - /// The object to be serialized as JSON. - /// logger.LogWarning(new {User = user, Address = address}) - public static void LogWarning(this ILogger logger, object message) - { - logger.LogWarning(LoggingConstants.KeyJsonFormatter, message); - } - - /// - /// Formats and writes an warning log message. - /// - /// The to write to. - /// The exception to log. - /// logger.LogWarning(exception) - public static void LogWarning(this ILogger logger, Exception exception) - { - logger.LogWarning(exception: exception, message: exception.Message); - } - - /// - /// Formats and writes a error log message as JSON. - /// - /// The to write to. - /// The object to be serialized as JSON. - /// logger.LogCritical(new {User = user, Address = address}) - public static void LogError(this ILogger logger, object message) - { - logger.LogError(LoggingConstants.KeyJsonFormatter, message); - } - - /// - /// Formats and writes an error log message. - /// - /// The to write to. - /// The exception to log. - /// logger.LogError(exception) - public static void LogError(this ILogger logger, Exception exception) - { - logger.LogError(exception: exception, message: exception.Message); - } - - /// - /// Formats and writes a critical log message as JSON. - /// - /// The to write to. - /// The object to be serialized as JSON. - /// logger.LogCritical(new {User = user, Address = address}) - public static void LogCritical(this ILogger logger, object message) - { - logger.LogCritical(LoggingConstants.KeyJsonFormatter, message); - } - - /// - /// Formats and writes an critical log message. - /// - /// The to write to. - /// The exception to log. - /// logger.LogCritical(exception) - public static void LogCritical(this ILogger logger, Exception exception) - { - logger.LogCritical(exception: exception, message: exception.Message); - } - - /// - /// Formats and writes a log message as JSON at the specified log level. - /// - /// The to write to. - /// Entry will be written on this level. - /// The object to be serialized as JSON. - /// logger.Log(LogLevel.Information, new {User = user, Address = address}) - public static void Log(this ILogger logger, LogLevel logLevel, object message) - { - logger.Log(logLevel, LoggingConstants.KeyJsonFormatter, message); - } - - /// - /// Formats and writes a log message at the specified log level. - /// - /// The to write to. - /// Entry will be written on this level. - /// The exception to log. - /// logger.Log(LogLevel.Information, exception) - public static void Log(this ILogger logger, LogLevel logLevel, Exception exception) - { - logger.Log(logLevel, exception: exception, message: exception.Message); - } - - #endregion - - /// - /// Appending additional key to the log context. - /// - /// - /// The list of keys. - public static void AppendKeys(this ILogger logger,IEnumerable> keys) - { - Logger.AppendKeys(keys); - } - - /// - /// Appending additional key to the log context. - /// - /// - /// The list of keys. - public static void AppendKeys(this ILogger logger,IEnumerable> keys) - { - Logger.AppendKeys(keys); - } - - /// - /// Appending additional key to the log context. - /// - /// - /// The key. - /// The value. - /// key - /// value - public static void AppendKey(this ILogger logger, string key, object value) - { - Logger.AppendKey(key, value); - } - - /// - /// Returns all additional keys added to the log context. - /// - /// IEnumerable<KeyValuePair<System.String, System.Object>>. - public static IEnumerable> GetAllKeys(this ILogger logger) - { - return Logger.GetAllKeys(); - } - - /// - /// Removes all additional keys from the log context. - /// - internal static void RemoveAllKeys(this ILogger logger) - { - Logger.RemoveAllKeys(); - } - - /// - /// Remove additional keys from the log context. - /// - /// - /// The list of keys. - public static void RemoveKeys(this ILogger logger, params string[] keys) - { - Logger.RemoveKeys(keys); - } - - /// - /// Removes a key from the log context. - /// - public static void RemoveKey(this ILogger logger, string key) - { - Logger.RemoveKey(key); - } - - // Replace the buffer methods with direct calls to the manager - - /// - /// Flush any buffered logs - /// - public static void FlushBuffer(this ILogger logger) - { - // Direct call to the buffer manager to avoid any recursion - LogBufferManager.FlushCurrentBuffer(); - } - - /// - /// Clear any buffered logs without writing them - /// - public static void ClearBuffer(this ILogger logger) - { - // Direct call to the buffer manager to avoid any recursion - LogBufferManager.ClearCurrentBuffer(); - } - - /// - /// Refresh the sampling calculation and update the minimum log level if needed - /// - /// - public static bool RefreshSampleRateCalculation(this ILogger logger) - { - return Logger.RefreshSampleRateCalculation(); - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactory.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactory.cs deleted file mode 100644 index 062a7c159..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactory.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using AWS.Lambda.Powertools.Logging.Internal.Helpers; -using Microsoft.Extensions.Logging; - -namespace AWS.Lambda.Powertools.Logging; - -internal sealed class PowertoolsLoggerFactory : IDisposable -{ - private readonly ILoggerFactory _factory; - - internal PowertoolsLoggerFactory(ILoggerFactory loggerFactory) - { - _factory = loggerFactory; - } - - internal PowertoolsLoggerFactory() : this(LoggerFactory.Create(builder => { builder.AddPowertoolsLogger(); })) - { - } - - internal static PowertoolsLoggerFactory Create(Action configureOptions) - { - var options = new PowertoolsLoggerConfiguration(); - configureOptions(options); - var factory = Create(options); - return new PowertoolsLoggerFactory(factory); - } - - internal static ILoggerFactory Create(PowertoolsLoggerConfiguration options) - { - return LoggerFactoryHelper.CreateAndConfigureFactory(options); - } - - // Add builder pattern support - internal static PowertoolsLoggerBuilder CreateBuilder() - { - return new PowertoolsLoggerBuilder(); - } - - internal ILogger CreateLogger() => CreateLogger(typeof(T).FullName ?? typeof(T).Name); - - internal ILogger CreateLogger(string category) - { - return _factory.CreateLogger(category); - } - - internal ILogger CreatePowertoolsLogger() - { - return _factory.CreatePowertoolsLogger(); - } - - public void Dispose() - { - _factory?.Dispose(); - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactoryExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactoryExtensions.cs deleted file mode 100644 index edec07fd7..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggerFactoryExtensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.Extensions.Logging; - -namespace AWS.Lambda.Powertools.Logging; - -/// -/// Extensions for ILoggerFactory -/// -public static class PowertoolsLoggerFactoryExtensions -{ - /// - /// Creates a new Powertools Logger instance using the Powertools full name. - /// - /// The factory. - /// The that was created. - public static ILogger CreatePowertoolsLogger(this ILoggerFactory factory) - { - return new PowertoolsLoggerFactory(factory).CreateLogger(PowertoolsLoggerConfiguration.ConfigurationSectionName); - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggingBuilderExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggingBuilderExtensions.cs deleted file mode 100644 index 73046197d..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Logging/PowertoolsLoggingBuilderExtensions.cs +++ /dev/null @@ -1,237 +0,0 @@ -using System; -using System.Collections.Concurrent; -using AWS.Lambda.Powertools.Common; -using AWS.Lambda.Powertools.Logging.Internal; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Configuration; - -namespace AWS.Lambda.Powertools.Logging; - -/// -/// Extension methods to configure and add the Powertools logger to an . -/// -/// -/// This class provides methods to integrate the AWS Lambda Powertools logging capabilities -/// with the standard .NET logging framework. -/// -/// -/// Basic usage: -/// -/// builder.Logging.AddPowertoolsLogger(); -/// -/// -public static class PowertoolsLoggingBuilderExtensions -{ - private static readonly ConcurrentBag AllProviders = new(); - private static readonly object Lock = new(); - private static PowertoolsLoggerConfiguration _currentConfig = new(); - - internal static void UpdateConfiguration(PowertoolsLoggerConfiguration config) - { - lock (Lock) - { - // Update the shared configuration - _currentConfig = config; - - // Notify all providers about the change - foreach (var provider in AllProviders) - { - provider.UpdateConfiguration(config); - } - } - } - - internal static PowertoolsLoggerConfiguration GetCurrentConfiguration() - { - lock (Lock) - { - // Return a copy to prevent external modification - return _currentConfig; - } - } - - /// - /// Adds the Powertools logger to the logging builder with default configuration. - /// - /// The logging builder to configure. - /// Opt-in to clear providers for Powertools-only output - /// The logging builder for further configuration. - /// - /// This method registers the Powertools logger with default settings. The logger will output - /// structured JSON logs that integrate well with AWS CloudWatch and other log analysis tools. - /// - /// - /// Add the Powertools logger to your Lambda function: - /// - /// var builder = new HostBuilder() - /// .ConfigureLogging(logging => - /// { - /// logging.AddPowertoolsLogger(); - /// }); - /// - /// - /// Using with minimal API: - /// - /// var builder = WebApplication.CreateBuilder(args); - /// builder.Logging.AddPowertoolsLogger(); - /// - /// - public static ILoggingBuilder AddPowertoolsLogger( - this ILoggingBuilder builder, - bool clearExistingProviders = false) - { - if (clearExistingProviders) - { - builder.ClearProviders(); - } - - builder.AddConfiguration(); - - builder.Services.TryAddSingleton(); - builder.Services.TryAddSingleton(sp => - new PowertoolsConfigurations(sp.GetRequiredService())); - - // automatically register ILogger - builder.Services.TryAddSingleton(provider => - provider.GetRequiredService().CreatePowertoolsLogger()); - - builder.Services.TryAddEnumerable( - ServiceDescriptor.Singleton(provider => - { - var powertoolsConfigurations = provider.GetRequiredService(); - - var loggerProvider = new PowertoolsLoggerProvider( - _currentConfig, - powertoolsConfigurations); - - lock (Lock) - { - AllProviders.Add(loggerProvider); - } - - return loggerProvider; - })); - - return builder; - } - - /// - /// Adds the Powertools logger to the logging builder with default configuration. - /// - /// The logging builder to configure. - /// - /// Opt-in to clear providers for Powertools-only output - /// The logging builder for further configuration. - /// - /// This method registers the Powertools logger with default settings. The logger will output - /// structured JSON logs that integrate well with AWS CloudWatch and other log analysis tools. - /// - /// - /// Add the Powertools logger to your Lambda function: - /// - /// var builder = new HostBuilder() - /// .ConfigureLogging(logging => - /// { - /// logging.AddPowertoolsLogger(); - /// }); - /// - /// - /// Using with minimal API: - /// - /// var builder = WebApplication.CreateBuilder(args); - /// builder.Logging.AddPowertoolsLogger(); - /// - /// With custom configuration: - /// - /// builder.Logging.AddPowertoolsLogger(options => - /// { - /// options.MinimumLogLevel = LogLevel.Information; - /// options.LoggerOutputCase = LoggerOutputCase.PascalCase; - /// options.IncludeLogLevel = true; - /// }); - /// - /// - /// With log buffering: - /// - /// builder.Logging.AddPowertoolsLogger(options => - /// { - /// options.LogBuffering = new LogBufferingOptions - /// { - /// Enabled = true, - /// BufferAtLogLevel = LogLevel.Debug - /// }; - /// }); - /// - /// - public static ILoggingBuilder AddPowertoolsLogger( - this ILoggingBuilder builder, - Action configure, - bool clearExistingProviders = false) - { - // Add configuration - builder.AddPowertoolsLogger(clearExistingProviders); - - // Create initial configuration - var options = new PowertoolsLoggerConfiguration(); - configure(options); - - // IMPORTANT: Set the minimum level directly on the builder - if (options.MinimumLogLevel != LogLevel.None) - { - builder.SetMinimumLevel(options.MinimumLogLevel); - } - - builder.Services.Configure(configure); - - UpdateConfiguration(options); - - // If buffering is enabled, register buffer providers - if (options.LogBuffering != null) - { - // Add a filter for the buffer provider - builder.AddFilter( - null, - LogLevel.Trace); - - // Register the buffer provider as an enumerable service - // Using singleton to ensure it's properly tracked - builder.Services.TryAddEnumerable( - ServiceDescriptor.Singleton(provider => - { - var powertoolsConfigurations = provider.GetRequiredService(); - - var bufferingProvider = new BufferingLoggerProvider( - _currentConfig, powertoolsConfigurations - ); - - lock (Lock) - { - AllProviders.Add(bufferingProvider); - } - - return bufferingProvider; - })); - } - - - return builder; - } - - /// - /// Resets all providers and clears the configuration. - /// This is useful for testing purposes to ensure a clean state. - /// - internal static void ResetAllProviders() - { - lock (Lock) - { - // Clear the provider collection - AllProviders.Clear(); - - // Reset the current configuration to default - _currentConfig = new PowertoolsLoggerConfiguration(); - } - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/CompositeJsonTypeInfoResolver.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/CompositeJsonTypeInfoResolver.cs deleted file mode 100644 index 028c1cb40..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/CompositeJsonTypeInfoResolver.cs +++ /dev/null @@ -1,44 +0,0 @@ -#if NET8_0_OR_GREATER - -using System; -using System.Text.Json; -using System.Text.Json.Serialization.Metadata; - -namespace AWS.Lambda.Powertools.Logging.Serializers -{ - /// - /// Combines multiple IJsonTypeInfoResolver instances into one - /// - internal class CompositeJsonTypeInfoResolver : IJsonTypeInfoResolver - { - private readonly IJsonTypeInfoResolver[] _resolvers; - - /// - /// Creates a new composite resolver from multiple resolvers - /// - /// Array of resolvers to use - public CompositeJsonTypeInfoResolver(IJsonTypeInfoResolver[] resolvers) - { - _resolvers = resolvers ?? throw new ArgumentNullException(nameof(resolvers)); - } - - - /// - /// Gets JSON type info by trying each resolver in order (.NET Standard 2.0 version) - /// - public JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options) - { - foreach (var resolver in _resolvers) - { - var typeInfo = resolver?.GetTypeInfo(type, options); - if (typeInfo != null) - { - return typeInfo; - } - } - - return null; - } - } -} -#endif \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/LoggingSerializationContext.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/LoggingSerializationContext.cs index d4d918e07..459a3b0fa 100644 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/LoggingSerializationContext.cs +++ b/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/LoggingSerializationContext.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.Collections.Generic; using System.IO; @@ -5,8 +20,6 @@ namespace AWS.Lambda.Powertools.Logging.Serializers; -#if NET8_0_OR_GREATER - /// /// Custom JSON serializer context for AWS.Lambda.Powertools.Logging /// @@ -29,5 +42,3 @@ public partial class PowertoolsLoggingSerializationContext : JsonSerializerConte { } - -#endif \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs index 22afec8f8..b13fb6c18 100644 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs +++ b/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsLoggingSerializer.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -10,64 +25,36 @@ using AWS.Lambda.Powertools.Common; using AWS.Lambda.Powertools.Common.Utils; using AWS.Lambda.Powertools.Logging.Internal.Converters; +using Microsoft.Extensions.Logging; namespace AWS.Lambda.Powertools.Logging.Serializers; /// /// Provides serialization functionality for Powertools logging. /// -internal class PowertoolsLoggingSerializer +internal static class PowertoolsLoggingSerializer { - private JsonSerializerOptions _currentOptions; - private LoggerOutputCase _currentOutputCase; - private JsonSerializerOptions _jsonOptions; - private readonly object _lock = new(); + private static LoggerOutputCase _currentOutputCase; + private static JsonSerializerOptions _jsonOptions; -#if NET8_0_OR_GREATER - private readonly ConcurrentBag _additionalContexts = new(); - private static JsonSerializerContext _staticAdditionalContexts; - private IJsonTypeInfoResolver _customTypeInfoResolver; -#endif + private static readonly ConcurrentBag AdditionalContexts = + new ConcurrentBag(); /// /// Gets the JsonSerializerOptions instance. /// - internal JsonSerializerOptions GetSerializerOptions() + internal static JsonSerializerOptions GetSerializerOptions() { - // Double-checked locking pattern for thread safety while ensuring we only build once - if (_jsonOptions == null) - { - lock (_lock) - { - if (_jsonOptions == null) - { - BuildJsonSerializerOptions(_currentOptions); - } - } - } - - return _jsonOptions; + return _jsonOptions ?? BuildJsonSerializerOptions(); } /// /// Configures the naming policy for the serializer. /// /// The case to use for serialization. - internal void ConfigureNamingPolicy(LoggerOutputCase loggerOutputCase) + internal static void ConfigureNamingPolicy(LoggerOutputCase loggerOutputCase) { - if (_currentOutputCase != loggerOutputCase) - { - lock (_lock) - { - _currentOutputCase = loggerOutputCase; - - // Only rebuild options if they already exist - if (_jsonOptions != null) - { - SetOutputCase(); - } - } - } + _currentOutputCase = loggerOutputCase; } /// @@ -77,20 +64,15 @@ internal void ConfigureNamingPolicy(LoggerOutputCase loggerOutputCase) /// The type of the object to serialize. /// A JSON string representation of the object. /// Thrown when the input type is not known to the serializer. - internal string Serialize(object value, Type inputType) + internal static string Serialize(object value, Type inputType) { -#if NET6_0 - var options = GetSerializerOptions(); - return JsonSerializer.Serialize(value, options); -#else if (RuntimeFeatureWrapper.IsDynamicCodeSupported) { - var jsonSerializerOptions = GetSerializerOptions(); + var options = GetSerializerOptions(); #pragma warning disable - return JsonSerializer.Serialize(value, jsonSerializerOptions); + return JsonSerializer.Serialize(value, options); } - // Try to serialize using the configured TypeInfoResolver var typeInfo = GetTypeInfo(inputType); if (typeInfo == null) { @@ -99,149 +81,42 @@ internal string Serialize(object value, Type inputType) } return JsonSerializer.Serialize(value, typeInfo); - -#endif } -#if NET8_0_OR_GREATER - /// /// Adds a JsonSerializerContext to the serializer options. /// /// The JsonSerializerContext to add. /// Thrown when the context is null. - internal void AddSerializerContext(JsonSerializerContext context) + internal static void AddSerializerContext(JsonSerializerContext context) { ArgumentNullException.ThrowIfNull(context); - // Don't add duplicates - if (!_additionalContexts.Contains(context)) + if (!AdditionalContexts.Contains(context)) { - _additionalContexts.Add(context); - - // If we have existing JSON options, update their type resolver - if (_jsonOptions != null && !RuntimeFeatureWrapper.IsDynamicCodeSupported) - { - // Reset the type resolver chain to rebuild it - _jsonOptions.TypeInfoResolver = GetCompositeResolver(); - } + AdditionalContexts.Add(context); } } - internal static void AddStaticSerializerContext(JsonSerializerContext context) - { - ArgumentNullException.ThrowIfNull(context); - - _staticAdditionalContexts = context; - } - - /// - /// Get a composite resolver that includes all configured resolvers - /// - private IJsonTypeInfoResolver GetCompositeResolver() - { - var resolvers = new List(); - - // Add custom resolver if provided - if (_customTypeInfoResolver != null) - { - resolvers.Add(_customTypeInfoResolver); - } - - // add any static resolvers - if (_staticAdditionalContexts != null) - { - resolvers.Add(_staticAdditionalContexts); - } - - // Add default context - resolvers.Add(PowertoolsLoggingSerializationContext.Default); - - // Add additional contexts - foreach (var context in _additionalContexts) - { - resolvers.Add(context); - } - - return new CompositeJsonTypeInfoResolver(resolvers.ToArray()); - } - /// /// Gets the JsonTypeInfo for a given type. /// /// The type to get information for. /// The JsonTypeInfo for the specified type, or null if not found. - private JsonTypeInfo GetTypeInfo(Type type) + internal static JsonTypeInfo GetTypeInfo(Type type) { var options = GetSerializerOptions(); return options.TypeInfoResolver?.GetTypeInfo(type, options); } -#endif - /// /// Builds and configures the JsonSerializerOptions. /// /// A configured JsonSerializerOptions instance. - private void BuildJsonSerializerOptions(JsonSerializerOptions options = null) + private static JsonSerializerOptions BuildJsonSerializerOptions() { - lock (_lock) - { - // Create a completely new options instance regardless - _jsonOptions = new JsonSerializerOptions(); - - // Copy any properties from the original options if provided - if (options != null) - { - // Copy standard properties - _jsonOptions.DefaultIgnoreCondition = options.DefaultIgnoreCondition; - _jsonOptions.PropertyNameCaseInsensitive = options.PropertyNameCaseInsensitive; - _jsonOptions.PropertyNamingPolicy = options.PropertyNamingPolicy; - _jsonOptions.DictionaryKeyPolicy = options.DictionaryKeyPolicy; - _jsonOptions.WriteIndented = options.WriteIndented; - _jsonOptions.ReferenceHandler = options.ReferenceHandler; - _jsonOptions.MaxDepth = options.MaxDepth; - _jsonOptions.IgnoreReadOnlyFields = options.IgnoreReadOnlyFields; - _jsonOptions.IgnoreReadOnlyProperties = options.IgnoreReadOnlyProperties; - _jsonOptions.IncludeFields = options.IncludeFields; - _jsonOptions.NumberHandling = options.NumberHandling; - _jsonOptions.ReadCommentHandling = options.ReadCommentHandling; - _jsonOptions.UnknownTypeHandling = options.UnknownTypeHandling; - _jsonOptions.AllowTrailingCommas = options.AllowTrailingCommas; - -#if NET8_0_OR_GREATER - // Handle type resolver extraction without setting it yet - if (options.TypeInfoResolver != null) - { - _customTypeInfoResolver = options.TypeInfoResolver; - - // If it's a JsonSerializerContext, also add it to our contexts - if (_customTypeInfoResolver is JsonSerializerContext jsonContext) - { - AddSerializerContext(jsonContext); - } - } -#endif - } - - // Set output case and other properties - SetOutputCase(); - AddConverters(); - _jsonOptions.Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping; - _jsonOptions.PropertyNameCaseInsensitive = true; + _jsonOptions = new JsonSerializerOptions(); -#if NET8_0_OR_GREATER - // Set TypeInfoResolver last, as this makes options read-only - if (!RuntimeFeatureWrapper.IsDynamicCodeSupported) - { - _jsonOptions.TypeInfoResolver = GetCompositeResolver(); - } -#endif - } - } - - internal void SetOutputCase() - { switch (_currentOutputCase) { case LoggerOutputCase.CamelCase: @@ -253,20 +128,11 @@ internal void SetOutputCase() _jsonOptions.DictionaryKeyPolicy = PascalCaseNamingPolicy.Instance; break; default: // Snake case -#if NET8_0_OR_GREATER - // If is default (Not Set) and JsonOptions provided with DictionaryKeyPolicy or PropertyNamingPolicy, use it - _jsonOptions.DictionaryKeyPolicy ??= JsonNamingPolicy.SnakeCaseLower; - _jsonOptions.PropertyNamingPolicy ??= JsonNamingPolicy.SnakeCaseLower; -#else - _jsonOptions.PropertyNamingPolicy = SnakeCaseNamingPolicy.Instance; - _jsonOptions.DictionaryKeyPolicy = SnakeCaseNamingPolicy.Instance; -#endif + _jsonOptions.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower; + _jsonOptions.DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower; break; } - } - private void AddConverters() - { _jsonOptions.Converters.Add(new ByteArrayConverter()); _jsonOptions.Converters.Add(new ExceptionConverter()); _jsonOptions.Converters.Add(new MemoryStreamConverter()); @@ -274,15 +140,38 @@ private void AddConverters() _jsonOptions.Converters.Add(new DateOnlyConverter()); _jsonOptions.Converters.Add(new TimeOnlyConverter()); -#if NET8_0_OR_GREATER - _jsonOptions.Converters.Add(new LogLevelJsonConverter()); -#elif NET6_0 _jsonOptions.Converters.Add(new LogLevelJsonConverter()); -#endif + + _jsonOptions.Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping; + _jsonOptions.PropertyNameCaseInsensitive = true; + + // Only add TypeInfoResolver if AOT mode + if (!RuntimeFeatureWrapper.IsDynamicCodeSupported) + { + _jsonOptions.TypeInfoResolverChain.Add(PowertoolsLoggingSerializationContext.Default); + foreach (var context in AdditionalContexts) + { + _jsonOptions.TypeInfoResolverChain.Add(context); + } + } + return _jsonOptions; } - internal void SetOptions(JsonSerializerOptions options) + internal static bool HasContext(JsonSerializerContext customContext) + { + return AdditionalContexts.Contains(customContext); + } + + internal static void ClearContext() + { + AdditionalContexts.Clear(); + } + + /// + /// Clears options for tests + /// + internal static void ClearOptions() { - _currentOptions = options; + _jsonOptions = null; } } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsSourceGeneratorSerializer.cs b/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsSourceGeneratorSerializer.cs index 95bd749ea..8871fb950 100644 --- a/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsSourceGeneratorSerializer.cs +++ b/libraries/src/AWS.Lambda.Powertools.Logging/Serializers/PowertoolsSourceGeneratorSerializer.cs @@ -1,4 +1,17 @@ -#if NET8_0_OR_GREATER +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ using System; using System.Diagnostics.CodeAnalysis; @@ -59,8 +72,6 @@ public PowertoolsSourceGeneratorSerializer( } var jsonSerializerContext = constructor.Invoke(new object[] { options }) as TSgContext; - PowertoolsLoggingSerializer.AddStaticSerializerContext(jsonSerializerContext); + PowertoolsLoggingSerializer.AddSerializerContext(jsonSerializerContext); } } - -#endif \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/AWS.Lambda.Powertools.Metrics.AspNetCore.csproj b/libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/AWS.Lambda.Powertools.Metrics.AspNetCore.csproj deleted file mode 100644 index 976fe8b00..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/AWS.Lambda.Powertools.Metrics.AspNetCore.csproj +++ /dev/null @@ -1,25 +0,0 @@ - - - - - AWS.Lambda.Powertools.Metrics.AspNetCore - Powertools for AWS Lambda (.NET) - Metrics AspNetCore package. - AWS.Lambda.Powertools.Metrics.AspNetCore - AWS.Lambda.Powertools.Metrics.AspNetCore - net8.0 - false - enable - enable - - - - - - - - - - - - - diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/Http/ColdStartTracker.cs b/libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/Http/ColdStartTracker.cs deleted file mode 100644 index 7d6473d75..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/Http/ColdStartTracker.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Amazon.Lambda.Core; -using Microsoft.AspNetCore.Http; - -namespace AWS.Lambda.Powertools.Metrics.AspNetCore.Http; - - -/// -/// Tracks and manages cold start metrics for Lambda functions in ASP.NET Core applications. -/// -/// -/// This class is responsible for detecting and recording the first invocation (cold start) of a Lambda function. -/// It ensures thread-safe tracking of cold starts and proper metric capture using the provided IMetrics implementation. -/// -internal class ColdStartTracker : IDisposable -{ - private readonly IMetrics _metrics; - private static bool _coldStart = true; - private static readonly object _lock = new(); - - /// - /// Initializes a new instance of the class. - /// - /// The metrics implementation to use for capturing cold start metrics. - public ColdStartTracker(IMetrics metrics) - { - _metrics = metrics; - } - - /// - /// Tracks the cold start of the Lambda function. - /// - /// The current HTTP context. - internal void TrackColdStart(HttpContext context) - { - if (!_coldStart) return; - - lock (_lock) - { - if (!_coldStart) return; - _metrics.CaptureColdStartMetric(context.Items["LambdaContext"] as ILambdaContext); - _coldStart = false; - } - } - - /// - /// Resets the cold start tracking state. - /// - internal static void ResetColdStart() - { - lock (_lock) - { - _coldStart = true; - } - } - - /// - public void Dispose() - { - ResetColdStart(); - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/Http/MetricsEndpointExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/Http/MetricsEndpointExtensions.cs deleted file mode 100644 index 2ca74c053..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/Http/MetricsEndpointExtensions.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; - -namespace AWS.Lambda.Powertools.Metrics.AspNetCore.Http; - -/// -/// Provides extension methods for adding metrics to route handlers. -/// -public static class MetricsEndpointExtensions -{ - /// - /// Adds a metrics filter to the specified route handler builder. - /// This will capture cold start (if CaptureColdStart is enabled) metrics and flush metrics on function exit. - /// - /// The route handler builder to add the metrics filter to. - /// The route handler builder with the metrics filter added. - public static RouteHandlerBuilder WithMetrics(this RouteHandlerBuilder builder) - { - builder.AddEndpointFilter(); - return builder; - } -} diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/Http/MetricsFilter.cs b/libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/Http/MetricsFilter.cs deleted file mode 100644 index 8c84836b3..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/Http/MetricsFilter.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Microsoft.AspNetCore.Http; - -namespace AWS.Lambda.Powertools.Metrics.AspNetCore.Http; - -/// -/// Represents a filter that captures and records metrics for HTTP endpoints. -/// -/// -/// This filter is responsible for tracking cold starts and capturing metrics during HTTP request processing. -/// It integrates with the ASP.NET Core endpoint routing system to inject metrics collection at the endpoint level. -/// -/// -/// -public class MetricsFilter : IEndpointFilter, IDisposable -{ - private readonly ColdStartTracker _coldStartTracker; - - /// - /// Initializes a new instance of the class. - /// - public MetricsFilter(IMetrics metrics) - { - _coldStartTracker = new ColdStartTracker(metrics); - } - - /// - /// Invokes the filter asynchronously. - /// - /// The context for the endpoint filter invocation. - /// The delegate to invoke the next filter or endpoint. - /// A task that represents the asynchronous operation, containing the result of the endpoint invocation. - public async ValueTask InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next) - { - try - { - _coldStartTracker.TrackColdStart(context.HttpContext); - } - catch - { - // ignored - } - - return await next(context); - } - - /// - /// Disposes of the resources used by the filter. - /// - public void Dispose() - { - _coldStartTracker.Dispose(); - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/Http/MetricsMiddlewareExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/Http/MetricsMiddlewareExtensions.cs deleted file mode 100644 index 0d74b2fae..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/Http/MetricsMiddlewareExtensions.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.DependencyInjection; - -namespace AWS.Lambda.Powertools.Metrics.AspNetCore.Http; - -/// -/// Provides extension methods for adding metrics middleware to the application pipeline. -/// -public static class MetricsMiddlewareExtensions -{ - /// - /// Adds middleware to capture and record metrics for HTTP requests, including cold start tracking. - /// - /// The application builder instance used to configure the request pipeline. - /// The application builder with the metrics middleware added. - /// - /// This middleware tracks cold starts and captures request metrics. To use this middleware, ensure you have registered - /// the required services using builder.Services.AddSingleton<IMetrics>() in your service configuration. - /// - /// - /// - /// app.UseMetrics(); - /// - /// - public static IApplicationBuilder UseMetrics(this IApplicationBuilder app) - { - return app.Use(async (context, next) => - { - var metrics = context.RequestServices.GetRequiredService(); - using var metricsHelper = new ColdStartTracker(metrics); - metricsHelper.TrackColdStart(context); - await next(); - }); - } -} diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/InternalsVisibleTo.cs b/libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/InternalsVisibleTo.cs deleted file mode 100644 index 7e99bec26..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Metrics.AspNetCore/InternalsVisibleTo.cs +++ /dev/null @@ -1,3 +0,0 @@ -using System.Runtime.CompilerServices; - -[assembly: InternalsVisibleTo("AWS.Lambda.Powertools.Metrics.AspNetCore.Tests")] \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/IMetrics.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/IMetrics.cs index f9b1d2611..6f0b868a0 100644 --- a/libraries/src/AWS.Lambda.Powertools.Metrics/IMetrics.cs +++ b/libraries/src/AWS.Lambda.Powertools.Metrics/IMetrics.cs @@ -1,113 +1,104 @@ -using System.Collections.Generic; -using Amazon.Lambda.Core; +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System; +using System.Collections.Generic; namespace AWS.Lambda.Powertools.Metrics; /// -/// Interface for metrics operations. +/// Interface IMetrics +/// Implements the /// /// -public interface IMetrics +public interface IMetrics { /// - /// Adds a metric to the collection. + /// Adds metric /// - /// The metric key. - /// The metric value. - /// The metric unit. - /// The metric resolution. - void AddMetric(string key, double value, MetricUnit unit = MetricUnit.None, - MetricResolution resolution = MetricResolution.Default); + /// Metric key + /// Metric value + /// Metric unit + /// + void AddMetric(string key, double value, MetricUnit unit, MetricResolution metricResolution); /// - /// Adds a dimension to the collection. + /// Adds a dimension /// - /// The dimension key. - /// The dimension value. + /// Dimension key + /// Dimension value void AddDimension(string key, string value); /// - /// Adds metadata to the collection. - /// - /// The metadata key. - /// The metadata value. - void AddMetadata(string key, object value); - - /// - /// Sets the default dimensions. + /// Sets the default dimensions /// - /// The default dimensions. - void SetDefaultDimensions(Dictionary defaultDimensions); + /// Default dimensions + void SetDefaultDimensions(Dictionary defaultDimension); /// - /// Sets the namespace for the metrics. + /// Adds metadata /// - /// The namespace. - void SetNamespace(string nameSpace); + /// Metadata key + /// Metadata value + void AddMetadata(string key, object value); /// - /// Sets the service name for the metrics. + /// Pushes a single metric with custom namespace, service and dimensions. /// - /// The service name. - void SetService(string service); + /// Name of the metric + /// Metric value + /// Metric unit + /// Metric namespace + /// Metric service + /// Metric default dimensions + /// Metrics resolution + void PushSingleMetric(string metricName, double value, MetricUnit unit, string nameSpace = null, + string service = null, Dictionary defaultDimensions = null, MetricResolution metricResolution = MetricResolution.Default); /// - /// Sets whether to raise an event on empty metrics. + /// Sets the namespace /// - /// If set to true, raises an event on empty metrics. - void SetRaiseOnEmptyMetrics(bool raiseOnEmptyMetrics); + /// Metrics namespace + void SetNamespace(string nameSpace); /// - /// Sets whether to capture cold start metrics. + /// Gets the namespace /// - /// If set to true, captures cold start metrics. - void SetCaptureColdStart(bool captureColdStart); + /// System.String. + string GetNamespace(); /// - /// Pushes a single metric to the collection. + /// Gets the service /// - /// The metric name. - /// The metric value. - /// The metric unit. - /// The namespace. - /// The service name. - /// The default dimensions. - /// The metric resolution. - void PushSingleMetric(string name, double value, MetricUnit unit, string nameSpace = null, string service = null, - Dictionary dimensions = null, MetricResolution resolution = MetricResolution.Default); + /// System.String. + string GetService(); /// - /// Clears the default dimensions. + /// Serializes metrics instance /// - void ClearDefaultDimensions(); + /// System.String. + string Serialize(); /// - /// Flushes the metrics. + /// Flushes metrics to CloudWatch /// - /// If set to true, indicates a metrics overflow. + /// if set to true [metrics overflow]. void Flush(bool metricsOverflow = false); - - /// - /// Gets the metrics options. - /// - /// The metrics options. - public MetricsOptions Options { get; } - - /// - /// Sets the function name. - /// - /// - void SetFunctionName(string functionName); /// - /// Captures the cold start metric. + /// Clears both default dimensions and dimensions lists /// - /// - void CaptureColdStartMetric(ILambdaContext context); - - /// - /// Adds multiple dimensions at once. - /// - /// Array of key-value tuples representing dimensions. - void AddDimensions(params (string key, string value)[] dimensions); -} \ No newline at end of file + void ClearDefaultDimensions(); +} diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/Internal/MetricsAspect.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/Internal/MetricsAspect.cs index ed81742ca..a72e205e0 100644 --- a/libraries/src/AWS.Lambda.Powertools.Metrics/Internal/MetricsAspect.cs +++ b/libraries/src/AWS.Lambda.Powertools.Metrics/Internal/MetricsAspect.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.Collections.Generic; using System.Linq; @@ -5,7 +20,6 @@ using Amazon.Lambda.Core; using AspectInjector.Broker; using AWS.Lambda.Powertools.Common; -using AWS.Lambda.Powertools.Common.Core; namespace AWS.Lambda.Powertools.Metrics; @@ -16,12 +30,22 @@ namespace AWS.Lambda.Powertools.Metrics; [Aspect(Scope.Global)] public class MetricsAspect { + /// + /// The is cold start + /// + private static bool _isColdStart; + /// /// Gets the metrics instance. /// /// The metrics instance. private static IMetrics _metricsInstance; + static MetricsAspect() + { + _isColdStart = true; + } + /// /// Runs before the execution of the method marked with the Metrics Attribute /// @@ -46,13 +70,13 @@ public void Before( var trigger = triggers.OfType().First(); - _metricsInstance ??= Metrics.Configure(options => { - options.Namespace = trigger.Namespace; - options.Service = trigger.Service; - options.RaiseOnEmptyMetrics = trigger.IsRaiseOnEmptyMetricsSet ? trigger.RaiseOnEmptyMetrics : null; - options.CaptureColdStart = trigger.IsCaptureColdStartSet ? trigger.CaptureColdStart : null; - options.FunctionName = trigger.FunctionName; - }); + _metricsInstance ??= new Metrics( + PowertoolsConfigurations.Instance, + trigger.Namespace, + trigger.Service, + trigger.RaiseOnEmptyMetrics, + trigger.CaptureColdStart + ); var eventArgs = new AspectEventArgs { @@ -65,9 +89,32 @@ public void Before( Triggers = triggers }; - if (LambdaLifecycleTracker.IsColdStart) + if (trigger.CaptureColdStart && _isColdStart) { - _metricsInstance.CaptureColdStartMetric(GetContext(eventArgs)); + _isColdStart = false; + + var nameSpace = _metricsInstance.GetNamespace(); + var service = _metricsInstance.GetService(); + Dictionary dimensions = null; + + var context = GetContext(eventArgs); + + if (context is not null) + { + dimensions = new Dictionary + { + { "FunctionName", context.FunctionName } + }; + } + + _metricsInstance.PushSingleMetric( + "ColdStart", + 1.0, + MetricUnit.Count, + nameSpace, + service, + dimensions + ); } } @@ -87,10 +134,10 @@ public void Exit() internal static void ResetForTest() { _metricsInstance = null; - LambdaLifecycleTracker.Reset(); + _isColdStart = true; Metrics.ResetForTest(); } - + /// /// Gets the Lambda context /// @@ -98,7 +145,6 @@ internal static void ResetForTest() /// private static ILambdaContext GetContext(AspectEventArgs args) { - if (args == null || args.Method == null) return null; var index = Array.FindIndex(args.Method.GetParameters(), p => p.ParameterType == typeof(ILambdaContext)); if (index >= 0) { diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/InternalsVisibleTo.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/InternalsVisibleTo.cs index a1b532578..0e44da1ca 100644 --- a/libraries/src/AWS.Lambda.Powertools.Metrics/InternalsVisibleTo.cs +++ b/libraries/src/AWS.Lambda.Powertools.Metrics/InternalsVisibleTo.cs @@ -15,6 +15,4 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("AWS.Lambda.Powertools.Metrics.Tests")] -[assembly: InternalsVisibleTo("AWS.Lambda.Powertools.Metrics.AspNetCore")] -[assembly: InternalsVisibleTo("AWS.Lambda.Powertools.Metrics.AspNetCore.Tests")] \ No newline at end of file +[assembly: InternalsVisibleTo("AWS.Lambda.Powertools.Metrics.Tests")] \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs index 9cf95ec51..51b86e370 100644 --- a/libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs +++ b/libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs @@ -1,7 +1,21 @@ -using System; +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System; using System.Collections.Generic; using System.Linq; -using Amazon.Lambda.Core; using AWS.Lambda.Powertools.Common; namespace AWS.Lambda.Powertools.Metrics; @@ -13,47 +27,11 @@ namespace AWS.Lambda.Powertools.Metrics; /// public class Metrics : IMetrics, IDisposable { - /// - /// Gets or sets the instance. - /// - public static IMetrics Instance - { - get => _instance ?? new Metrics(PowertoolsConfigurations.Instance, consoleWrapper: new ConsoleWrapper()); - private set => _instance = value; - } - - /// - /// Gets DefaultDimensions - /// - public static Dictionary DefaultDimensions => Instance.Options.DefaultDimensions; - - /// - /// Gets Namespace - /// - public static string Namespace => Instance.Options.Namespace; - - /// - /// Gets Service - /// - public static string Service => Instance.Options.Service; - - /// - public MetricsOptions Options => _options ?? - new() - { - CaptureColdStart = _captureColdStartEnabled, - Namespace = GetNamespace(), - Service = GetService(), - RaiseOnEmptyMetrics = _raiseOnEmptyMetrics, - DefaultDimensions = GetDefaultDimensions(), - FunctionName = _functionName - }; - /// /// The instance /// private static IMetrics _instance; - + /// /// The context /// @@ -67,76 +45,17 @@ public static IMetrics Instance /// /// If true, Powertools for AWS Lambda (.NET) will throw an exception on empty metrics when trying to flush /// - private bool _raiseOnEmptyMetrics; - + private readonly bool _raiseOnEmptyMetrics; + /// /// The capture cold start enabled /// - private bool _captureColdStartEnabled; + private readonly bool _captureColdStartEnabled; - /// - /// Shared synchronization object - /// + // + // Shared synchronization object + // private readonly object _lockObj = new(); - - /// - /// Function name is used for metric dimension across all metrics. - /// - private string _functionName; - - /// - /// The options - /// - private readonly MetricsOptions _options; - - /// - /// The console wrapper for console output - /// - private readonly IConsoleWrapper _consoleWrapper; - - /// - /// Gets a value indicating whether metrics are disabled. - /// - private bool _disabled; - - /// - /// Initializes a new instance of the class. - /// - /// - /// - public static IMetrics Configure(Action configure) - { - var options = new MetricsOptions(); - configure(options); - - if (!string.IsNullOrEmpty(options.Namespace)) - SetNamespace(options.Namespace); - - if (!string.IsNullOrEmpty(options.Service)) - Instance.SetService(options.Service); - - if (options.RaiseOnEmptyMetrics.HasValue) - Instance.SetRaiseOnEmptyMetrics(options.RaiseOnEmptyMetrics.Value); - if (options.CaptureColdStart.HasValue) - Instance.SetCaptureColdStart(options.CaptureColdStart.Value); - - if (options.DefaultDimensions != null) - SetDefaultDimensions(options.DefaultDimensions); - - if (!string.IsNullOrEmpty(options.FunctionName)) - Instance.SetFunctionName(options.FunctionName); - - return Instance; - } - - /// - /// Sets the function name. - /// - /// - void IMetrics.SetFunctionName(string functionName) - { - _functionName = functionName; - } /// /// Creates a Metrics object that provides features to send metrics to Amazon Cloudwatch using the Embedded metric @@ -148,99 +67,94 @@ void IMetrics.SetFunctionName(string functionName) /// Metrics Service Name /// Instructs metrics validation to throw exception if no metrics are provided /// Instructs metrics capturing the ColdStart is enabled - /// For console output - /// MetricsOptions internal Metrics(IPowertoolsConfigurations powertoolsConfigurations, string nameSpace = null, string service = null, - bool raiseOnEmptyMetrics = false, bool captureColdStartEnabled = false, IConsoleWrapper consoleWrapper = null, MetricsOptions options = null) + bool raiseOnEmptyMetrics = false, bool captureColdStartEnabled = false) { + _instance ??= this; + _powertoolsConfigurations = powertoolsConfigurations; - _consoleWrapper = consoleWrapper; - _context = new MetricsContext(); _raiseOnEmptyMetrics = raiseOnEmptyMetrics; _captureColdStartEnabled = captureColdStartEnabled; - _options = options; - - _disabled = _powertoolsConfigurations.MetricsDisabled; + _context = InitializeContext(nameSpace, service, null); - Instance = this; _powertoolsConfigurations.SetExecutionEnvironment(this); - - // set namespace and service always - SetNamespace(nameSpace); - SetService(service); + } - /// - void IMetrics.AddMetric(string key, double value, MetricUnit unit, MetricResolution resolution) + /// + /// Implements interface that adds new metric to memory. + /// + /// Metric Key + /// Metric Value + /// Metric Unit + /// Metric resolution + /// + /// 'AddMetric' method requires a valid metrics key. 'Null' or empty values + /// are not allowed. + /// + void IMetrics.AddMetric(string key, double value, MetricUnit unit, MetricResolution metricResolution) { - if(_disabled) - return; + if (string.IsNullOrWhiteSpace(key)) + throw new ArgumentNullException( + nameof(key), "'AddMetric' method requires a valid metrics key. 'Null' or empty values are not allowed."); - if (Instance != null) + if (value < 0) { + throw new ArgumentException( + "'AddMetric' method requires a valid metrics value. Value must be >= 0.", nameof(value)); + } + + lock (_lockObj) { - if (string.IsNullOrWhiteSpace(key)) - throw new ArgumentNullException( - nameof(key), - "'AddMetric' method requires a valid metrics key. 'Null' or empty values are not allowed."); - if (key.Length > 255) - throw new ArgumentOutOfRangeException( - nameof(key), - "'AddMetric' method requires a valid metrics key. Key exceeds the allowed length constraint."); - - if (value < 0) + var metrics = _context.GetMetrics(); + + if (metrics.Count > 0 && + (metrics.Count == PowertoolsConfigurations.MaxMetrics || + metrics.FirstOrDefault(x => x.Name == key) + ?.Values.Count == PowertoolsConfigurations.MaxMetrics)) { - throw new ArgumentException( - "'AddMetric' method requires a valid metrics value. Value must be >= 0.", nameof(value)); + _instance.Flush(true); } - lock (_lockObj) - { - var metrics = _context.GetMetrics(); - - if (metrics.Count > 0 && - (metrics.Count == PowertoolsConfigurations.MaxMetrics || - metrics.FirstOrDefault(x => x.Name == key) - ?.Values.Count == PowertoolsConfigurations.MaxMetrics)) - { - Instance.Flush(true); - } - - _context.AddMetric(key, value, unit, resolution); - } - } - else - { - _consoleWrapper.Debug( - $"##WARNING##: Metrics should be initialized in Handler method before calling {nameof(AddMetric)} method."); + _context.AddMetric(key, value, unit, metricResolution); } } - /// + /// + /// Implements interface that sets metrics namespace identifier. + /// + /// Metrics Namespace Identifier void IMetrics.SetNamespace(string nameSpace) { - _context.SetNamespace(!string.IsNullOrWhiteSpace(nameSpace) - ? nameSpace - : GetNamespace() ?? _powertoolsConfigurations.MetricsNamespace); + _context.SetNamespace(nameSpace); } + /// + /// Implements interface that allows retrieval of namespace identifier. + /// + /// Namespace identifier + string IMetrics.GetNamespace() + { + return _context.GetNamespace(); + } /// /// Implements interface to get service name /// /// System.String. - private string GetService() + string IMetrics.GetService() { - try - { - return _context.GetService(); - } - catch - { - return null; - } + return _context.GetService(); } - /// + /// + /// Implements interface that adds a dimension. + /// + /// Dimension key. Must not be null, empty or whitespace + /// Dimension value + /// + /// 'AddDimension' method requires a valid dimension key. 'Null' or empty + /// values are not allowed. + /// void IMetrics.AddDimension(string key, string value) { if (string.IsNullOrWhiteSpace(key)) @@ -250,7 +164,15 @@ void IMetrics.AddDimension(string key, string value) _context.AddDimension(key, value); } - /// + /// + /// Implements interface that adds metadata. + /// + /// Metadata key. Must not be null, empty or whitespace + /// Metadata value + /// + /// 'AddMetadata' method requires a valid metadata key. 'Null' or empty + /// values are not allowed. + /// void IMetrics.AddMetadata(string key, object value) { if (string.IsNullOrWhiteSpace(key)) @@ -260,23 +182,32 @@ void IMetrics.AddMetadata(string key, object value) _context.AddMetadata(key, value); } - /// - void IMetrics.SetDefaultDimensions(Dictionary defaultDimensions) + /// + /// Implements interface that sets default dimension list + /// + /// Default Dimension List + /// + /// 'SetDefaultDimensions' method requires a valid key pair. 'Null' or empty + /// values are not allowed. + /// + void IMetrics.SetDefaultDimensions(Dictionary defaultDimension) { - foreach (var item in defaultDimensions) + foreach (var item in defaultDimension) if (string.IsNullOrWhiteSpace(item.Key) || string.IsNullOrWhiteSpace(item.Value)) throw new ArgumentNullException(nameof(item.Key), "'SetDefaultDimensions' method requires a valid key pair. 'Null' or empty values are not allowed."); - _context.SetDefaultDimensions(DictionaryToList(defaultDimensions)); + _context.SetDefaultDimensions(DictionaryToList(defaultDimension)); } - /// + /// + /// Flushes metrics in Embedded Metric Format (EMF) to Standard Output. In Lambda, this output is collected + /// automatically and sent to Cloudwatch. + /// + /// If enabled, non-default dimensions are cleared after flushing metrics + /// true void IMetrics.Flush(bool metricsOverflow) { - if(_disabled) - return; - if (_context.GetMetrics().Count == 0 && _raiseOnEmptyMetrics) throw new SchemaValidationException(true); @@ -285,7 +216,7 @@ void IMetrics.Flush(bool metricsOverflow) { var emfPayload = _context.Serialize(); - _consoleWrapper.WriteLine(emfPayload); + Console.WriteLine(emfPayload); _context.ClearMetrics(); @@ -294,91 +225,56 @@ void IMetrics.Flush(bool metricsOverflow) else { if (!_captureColdStartEnabled) - _consoleWrapper.WriteLine( - "##User-WARNING## No application metrics to publish. The cold-start metric may be published if enabled. If application metrics should never be empty, consider using 'RaiseOnEmptyMetrics = true'"); + Console.WriteLine( + "##WARNING## Metrics and Metadata have not been specified. No data will be sent to Cloudwatch Metrics."); } } - - /// + + /// + /// Clears both default dimensions and dimensions lists + /// void IMetrics.ClearDefaultDimensions() { _context.ClearDefaultDimensions(); } - /// - void IMetrics.SetService(string service) - { - // this needs to check if service is set through code or env variables - // the default value service_undefined has to be ignored and return null so it is not added as default - var parsedService = !string.IsNullOrWhiteSpace(service) - ? service - : _powertoolsConfigurations.Service == "service_undefined" - ? null - : _powertoolsConfigurations.Service; - - if (parsedService != null) - { - _context.SetService(parsedService); - _context.SetDefaultDimensions(new List(new[] - { new DimensionSet("Service", GetService()) })); - } - } - - /// - public void SetRaiseOnEmptyMetrics(bool raiseOnEmptyMetrics) - { - _raiseOnEmptyMetrics = raiseOnEmptyMetrics; - } - - /// - public void SetCaptureColdStart(bool captureColdStart) - { - _captureColdStartEnabled = captureColdStart; - } - - private Dictionary GetDefaultDimensions() + /// + /// Serialize global context object + /// + /// Serialized global context object + public string Serialize() { - return ListToDictionary(_context.GetDefaultDimensions()); + return _context.Serialize(); } - /// - void IMetrics.PushSingleMetric(string name, double value, MetricUnit unit, string nameSpace, - string service, Dictionary dimensions, MetricResolution resolution) - { - if(_disabled) - return; - - if (string.IsNullOrWhiteSpace(name)) - throw new ArgumentNullException(nameof(name), + /// + /// Implements the interface that pushes single metric to CloudWatch using Embedded Metric Format. This can be used to + /// push metrics with a different context. + /// + /// Metric Name. Metric key cannot be null, empty or whitespace + /// Metric Value + /// Metric Unit + /// Metric Namespace + /// Service Name + /// Default dimensions list + /// Metrics resolution + /// + /// 'PushSingleMetric' method requires a valid metrics key. 'Null' or empty + /// values are not allowed. + /// + void IMetrics.PushSingleMetric(string metricName, double value, MetricUnit unit, string nameSpace, string service, + Dictionary defaultDimensions, MetricResolution metricResolution) + { + if (string.IsNullOrWhiteSpace(metricName)) + throw new ArgumentNullException(nameof(metricName), "'PushSingleMetric' method requires a valid metrics key. 'Null' or empty values are not allowed."); - var context = new MetricsContext(); - context.SetNamespace(nameSpace ?? GetNamespace()); - - var parsedService = !string.IsNullOrWhiteSpace(service) - ? service - : _powertoolsConfigurations.Service == "service_undefined" - ? null - : _powertoolsConfigurations.Service; - - if (!string.IsNullOrWhiteSpace(parsedService)) - { - context.SetService(parsedService); - context.AddDimension("Service", parsedService); - } - - if (dimensions != null) - { - var dimensionsList = DictionaryToList(dimensions); - context.AddDimensions(dimensionsList); - } - - context.AddMetric(name, value, unit, resolution); + using var context = InitializeContext(nameSpace, service, defaultDimensions); + context.AddMetric(metricName, value, unit, metricResolution); Flush(context); } - /// /// Implementation of IDisposable interface /// @@ -387,7 +283,7 @@ public void Dispose() Dispose(true); GC.SuppressFinalize(this); } - + /// /// /// @@ -397,7 +293,7 @@ protected virtual void Dispose(bool disposing) // Cleanup if (disposing) { - Instance.Flush(); + _instance.Flush(); } } @@ -407,11 +303,11 @@ protected virtual void Dispose(bool disposing) /// Metric Key. Must not be null, empty or whitespace /// Metric Value /// Metric Unit - /// + /// public static void AddMetric(string key, double value, MetricUnit unit = MetricUnit.None, - MetricResolution resolution = MetricResolution.Default) + MetricResolution metricResolution = MetricResolution.Default) { - Instance.AddMetric(key, value, unit, resolution); + _instance.AddMetric(key, value, unit, metricResolution); } /// @@ -420,32 +316,16 @@ public static void AddMetric(string key, double value, MetricUnit unit = MetricU /// Metrics Namespace Identifier public static void SetNamespace(string nameSpace) { - Instance.SetNamespace(nameSpace); - } - - /// - /// Sets the service name for the metrics. - /// - /// The service name. - public static void SetService(string service) - { - Instance.SetService(service); + _instance.SetNamespace(nameSpace); } /// /// Retrieves namespace identifier. /// /// Namespace identifier - public string GetNamespace() + public static string GetNamespace() { - try - { - return _context.GetNamespace() ?? _powertoolsConfigurations.MetricsNamespace; - } - catch - { - return null; - } + return _instance.GetNamespace(); } /// @@ -455,7 +335,7 @@ public string GetNamespace() /// Dimension value public static void AddDimension(string key, string value) { - Instance.AddDimension(key, value); + _instance.AddDimension(key, value); } /// @@ -465,7 +345,7 @@ public static void AddDimension(string key, string value) /// Metadata value public static void AddMetadata(string key, object value) { - Instance.AddMetadata(key, value); + _instance.AddMetadata(key, value); } /// @@ -474,15 +354,15 @@ public static void AddMetadata(string key, object value) /// Default Dimension List public static void SetDefaultDimensions(Dictionary defaultDimensions) { - Instance.SetDefaultDimensions(defaultDimensions); + _instance.SetDefaultDimensions(defaultDimensions); } - + /// /// Clears both default dimensions and dimensions lists /// public static void ClearDefaultDimensions() { - Instance.ClearDefaultDimensions(); + _instance.ClearDefaultDimensions(); } /// @@ -494,134 +374,70 @@ private void Flush(MetricsContext context) { var emfPayload = context.Serialize(); - _consoleWrapper.WriteLine(emfPayload); + Console.WriteLine(emfPayload); } /// /// Pushes single metric to CloudWatch using Embedded Metric Format. This can be used to push metrics with a different /// context. /// - /// Metric Name. Metric key cannot be null, empty or whitespace + /// Metric Name. Metric key cannot be null, empty or whitespace /// Metric Value /// Metric Unit /// Metric Namespace /// Service Name - /// Default dimensions list - /// Metrics resolution - public static void PushSingleMetric(string name, double value, MetricUnit unit, string nameSpace = null, - string service = null, Dictionary dimensions = null, - MetricResolution resolution = MetricResolution.Default) + /// Default dimensions list + /// Metrics resolution + public static void PushSingleMetric(string metricName, double value, MetricUnit unit, string nameSpace = null, + string service = null, Dictionary defaultDimensions = null, MetricResolution metricResolution = MetricResolution.Default) { - Instance.PushSingleMetric(name, value, unit, nameSpace, service, dimensions, - resolution); + _instance.PushSingleMetric(metricName, value, unit, nameSpace, service, defaultDimensions, metricResolution); } /// - /// Helper method to convert default dimensions dictionary to list + /// Sets global namespace, service name and default dimensions list. Service name is automatically added as a default + /// dimension /// - /// Default dimensions dictionary - /// Default dimensions list - private List DictionaryToList(Dictionary defaultDimensions) + /// Metrics namespace + /// Service Name + /// Default Dimensions List + /// MetricsContext. + private MetricsContext InitializeContext(string nameSpace, string service, + Dictionary defaultDimensions) { - var dimensionsList = new List(); - if (defaultDimensions != null) - foreach (var item in defaultDimensions) - dimensionsList.Add(new DimensionSet(item.Key, item.Value)); - - return dimensionsList; - } + var context = new MetricsContext(); - private Dictionary ListToDictionary(List dimensions) - { - var dictionary = new Dictionary(); - try - { - return dimensions != null - ? new Dictionary(dimensions.SelectMany(x => x.Dimensions)) - : dictionary; - } - catch (Exception e) - { - _consoleWrapper.Debug("Error converting list to dictionary: " + e.Message); - return dictionary; - } - } - - /// - /// Captures the cold start metric. - /// - /// The ILambdaContext. - void IMetrics.CaptureColdStartMetric(ILambdaContext context) - { - if (Options.CaptureColdStart == null || !Options.CaptureColdStart.Value) return; - - // bring default dimensions if exist - var dimensions = Options?.DefaultDimensions; - - var functionName = Options?.FunctionName ?? context?.FunctionName ?? ""; - if (!string.IsNullOrWhiteSpace(functionName)) - { - dimensions ??= new Dictionary(); - dimensions.Add("FunctionName", functionName); - } + context.SetNamespace(!string.IsNullOrWhiteSpace(nameSpace) + ? nameSpace + : _powertoolsConfigurations.MetricsNamespace); - PushSingleMetric( - "ColdStart", - 1.0, - MetricUnit.Count, - Options?.Namespace ?? "", - Options?.Service ?? "", - dimensions - ); - } - - /// - void IMetrics.AddDimensions(params (string key, string value)[] dimensions) - { - if (dimensions == null || dimensions.Length == 0) - return; + context.SetService(!string.IsNullOrWhiteSpace(service) + ? service + : _powertoolsConfigurations.Service); - // Validate all dimensions first - foreach (var (key, value) in dimensions) - { - if (string.IsNullOrWhiteSpace(key)) - throw new ArgumentNullException(nameof(dimensions), - "'AddDimensions' method requires valid dimension keys. 'Null' or empty values are not allowed."); + var defaultDimensionsList = DictionaryToList(defaultDimensions); - if (string.IsNullOrWhiteSpace(value)) - throw new ArgumentNullException(nameof(dimensions), - "'AddDimensions' method requires valid dimension values. 'Null' or empty values are not allowed."); - } + // Add service as a default dimension + defaultDimensionsList.Add(new DimensionSet("Service", context.GetService())); - // Create a new dimension set with all dimensions - var dimensionSet = new DimensionSet(dimensions[0].key, dimensions[0].value); - - // Add remaining dimensions to the same set - for (var i = 1; i < dimensions.Length; i++) - { - dimensionSet.Dimensions.Add(dimensions[i].key, dimensions[i].value); - } + context.SetDefaultDimensions(defaultDimensionsList); - // Add the dimensionSet to a list and pass it to AddDimensions - _context.AddDimensions([dimensionSet]); + return context; } - - /// - /// Adds multiple dimensions at once. - /// - /// Array of key-value tuples representing dimensions. - public static void AddDimensions(params (string key, string value)[] dimensions) - { - Instance.AddDimensions(dimensions); - } - + /// - /// Flushes the metrics. + /// Helper method to convert default dimensions dictionary to list /// - /// If set to true, indicates a metrics overflow. - public static void Flush(bool metricsOverflow = false) + /// Default dimensions dictionary + /// Default dimensions list + private List DictionaryToList(Dictionary defaultDimensions) { - Instance.Flush(metricsOverflow); + var defaultDimensionsList = new List(); + if (defaultDimensions != null) + foreach (var item in defaultDimensions) + defaultDimensionsList.Add(new DimensionSet(item.Key, item.Value)); + + return defaultDimensionsList; } /// @@ -629,15 +445,6 @@ public static void Flush(bool metricsOverflow = false) /// internal static void ResetForTest() { - Instance = null; - } - - /// - /// For testing purposes, resets the Instance to the provided metrics instance. - /// - /// - public static void UseMetricsForTests(IMetrics metricsInstance) - { - Instance = metricsInstance; + _instance = null; } -} \ No newline at end of file +} diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/MetricsAttribute.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/MetricsAttribute.cs index 3e47c1e03..761bf7bd0 100644 --- a/libraries/src/AWS.Lambda.Powertools.Metrics/MetricsAttribute.cs +++ b/libraries/src/AWS.Lambda.Powertools.Metrics/MetricsAttribute.cs @@ -66,7 +66,7 @@ namespace AWS.Lambda.Powertools.Metrics; /// /// /// Service -/// string, service name is used for metric dimension +/// string, service name is used for metric dimension across all metrics, by default service_undefined /// /// /// Namespace @@ -112,13 +112,6 @@ public class MetricsAttribute : Attribute /// The namespace. public string Namespace { get; set; } - /// - /// Function name is used for metric dimension across all metrics. - /// This can be also set using the environment variable LAMBDA_FUNCTION_NAME. - /// If not set, the function name will be automatically set to the Lambda function name. - /// - public string FunctionName { get; set; } - /// /// Service name is used for metric dimension across all metrics. /// This can be also set using the environment variable POWERTOOLS_SERVICE_NAME. @@ -126,41 +119,15 @@ public class MetricsAttribute : Attribute /// The service. public string Service { get; set; } - private bool _captureColdStartSet; - private bool _captureColdStart; - /// /// Captures cold start during Lambda execution /// /// true if [capture cold start]; otherwise, false. - public bool CaptureColdStart - { - get => _captureColdStart; - set - { - _captureColdStart = value; - _captureColdStartSet = true; - } - } - - internal bool IsCaptureColdStartSet => _captureColdStartSet; - - private bool _raiseOnEmptyMetricsSet; - private bool _raiseOnEmptyMetrics; + public bool CaptureColdStart { get; set; } /// /// Instructs metrics validation to throw exception if no metrics are provided. /// /// true if [raise on empty metrics]; otherwise, false. - public bool RaiseOnEmptyMetrics - { - get => _raiseOnEmptyMetrics; - set - { - _raiseOnEmptyMetrics = value; - _raiseOnEmptyMetricsSet = true; - } - } - - internal bool IsRaiseOnEmptyMetricsSet => _raiseOnEmptyMetricsSet; + public bool RaiseOnEmptyMetrics { get; set; } } diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/MetricsBuilder.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/MetricsBuilder.cs deleted file mode 100644 index 388226a1b..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Metrics/MetricsBuilder.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System.Collections.Generic; - -namespace AWS.Lambda.Powertools.Metrics; - -/// -/// Provides a builder for configuring metrics. -/// -public class MetricsBuilder -{ - private readonly MetricsOptions _options = new(); - - /// - /// Sets the namespace for the metrics. - /// - /// The namespace identifier. - /// The current instance of . - public MetricsBuilder WithNamespace(string nameSpace) - { - _options.Namespace = nameSpace; - return this; - } - - /// - /// Sets the service name for the metrics. - /// - /// The service name. - /// The current instance of . - public MetricsBuilder WithService(string service) - { - _options.Service = service; - return this; - } - - /// - /// Sets whether to raise an exception if no metrics are captured. - /// - /// If true, raises an exception when no metrics are captured. - /// The current instance of . - public MetricsBuilder WithRaiseOnEmptyMetrics(bool raiseOnEmptyMetrics) - { - _options.RaiseOnEmptyMetrics = raiseOnEmptyMetrics; - return this; - } - - /// - /// Sets whether to capture cold start metrics. - /// - /// If true, captures cold start metrics. - /// The current instance of . - public MetricsBuilder WithCaptureColdStart(bool captureColdStart) - { - _options.CaptureColdStart = captureColdStart; - return this; - } - - /// - /// Sets the default dimensions for the metrics. - /// - /// A dictionary of default dimensions. - /// The current instance of . - public MetricsBuilder WithDefaultDimensions(Dictionary defaultDimensions) - { - _options.DefaultDimensions = defaultDimensions; - return this; - } - - /// - /// Sets the function name for the metrics dimension. - /// - /// - /// - public MetricsBuilder WithFunctionName(string functionName) - { - _options.FunctionName = !string.IsNullOrWhiteSpace(functionName) ? functionName : null; - return this; - } - - /// - /// Builds and configures the metrics instance. - /// - /// An instance of . - public IMetrics Build() - { - return Metrics.Configure(opt => - { - opt.Namespace = _options.Namespace; - opt.Service = _options.Service; - opt.RaiseOnEmptyMetrics = _options.RaiseOnEmptyMetrics; - opt.CaptureColdStart = _options.CaptureColdStart; - opt.DefaultDimensions = _options.DefaultDimensions; - opt.FunctionName = _options.FunctionName; - }); - } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/MetricsOptions.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/MetricsOptions.cs deleted file mode 100644 index 71adc5572..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Metrics/MetricsOptions.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Collections.Generic; - -namespace AWS.Lambda.Powertools.Metrics; - -/// -/// Configuration options for AWS Lambda Powertools Metrics. -/// -public class MetricsOptions -{ - /// - /// Gets or sets the CloudWatch metrics namespace. - /// - public string Namespace { get; set; } - - /// - /// Gets or sets the service name to be used as a metric dimension. - /// - public string Service { get; set; } - - /// - /// Gets or sets whether to throw an exception when no metrics are emitted. - /// - public bool? RaiseOnEmptyMetrics { get; set; } - - /// - /// Gets or sets whether to capture cold start metrics. - /// - public bool? CaptureColdStart { get; set; } - - /// - /// Gets or sets the default dimensions to be added to all metrics. - /// - public Dictionary DefaultDimensions { get; set; } - - /// - /// Gets or sets the function name to be used as a metric dimension. - /// - public string FunctionName { get; set; } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/Model/Metadata.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/Model/Metadata.cs index 847b0dc88..ee3d0605c 100644 --- a/libraries/src/AWS.Lambda.Powertools.Metrics/Model/Metadata.cs +++ b/libraries/src/AWS.Lambda.Powertools.Metrics/Model/Metadata.cs @@ -1,4 +1,19 @@ -using System; +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System; using System.Collections.Generic; using System.Text.Json.Serialization; @@ -114,19 +129,10 @@ internal string GetService() /// Adds new Dimension /// /// Dimension to add - internal void AddDimension(DimensionSet dimension) + internal void AddDimensionSet(DimensionSet dimension) { _metricDirective.AddDimension(dimension); } - - /// - /// Adds new List of Dimensions - /// - /// Dimensions to add - internal void AddDimensionSet(List dimension) - { - _metricDirective.AddDimensionSet(dimension); - } /// /// Sets default dimensions list @@ -178,12 +184,4 @@ internal void ClearDefaultDimensions() { _metricDirective.ClearDefaultDimensions(); } - - /// - /// Retrieves default dimensions list - /// - internal List GetDefaultDimensions() - { - return _metricDirective.DefaultDimensions; - } } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricDirective.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricDirective.cs index 0d300d5e8..f8ccad768 100644 --- a/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricDirective.cs +++ b/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricDirective.cs @@ -105,39 +105,25 @@ private MetricDirective(string nameSpace, List metrics, List /// All dimension keys. [JsonPropertyName("Dimensions")] - public List> AllDimensionKeys + public List AllDimensionKeys { get { - var result = new List>(); - var allDimKeys = new List(); + var defaultKeys = DefaultDimensions + .Where(d => d.DimensionKeys.Any()) + .SelectMany(s => s.DimensionKeys) + .ToList(); - // Add default dimensions keys - if (DefaultDimensions.Any()) - { - foreach (var dimensionSet in DefaultDimensions) - { - foreach (var key in dimensionSet.DimensionKeys.Where(key => !allDimKeys.Contains(key))) - { - allDimKeys.Add(key); - } - } - } + var keys = Dimensions + .Where(d => d.DimensionKeys.Any()) + .SelectMany(s => s.DimensionKeys) + .ToList(); - // Add all regular dimensions to the same array - foreach (var dimensionSet in Dimensions) - { - foreach (var key in dimensionSet.DimensionKeys.Where(key => !allDimKeys.Contains(key))) - { - allDimKeys.Add(key); - } - } + defaultKeys.AddRange(keys); - // Add non-empty dimension arrays - // When no dimensions exist, add an empty array - result.Add(allDimKeys.Any() ? allDimKeys : []); + if (defaultKeys.Count == 0) defaultKeys = new List(); - return result; + return defaultKeys; } } @@ -205,37 +191,19 @@ internal void SetService(string service) /// Dimensions - Cannot add more than 9 dimensions at the same time. internal void AddDimension(DimensionSet dimension) { - // Check if we already have any dimensions - if (Dimensions.Count > 0) + if (Dimensions.Count < PowertoolsConfigurations.MaxDimensions) { - // Get the first dimension set where we now store all dimensions - var firstDimensionSet = Dimensions[0]; - - // Check the actual dimension count inside the first dimension set - if (firstDimensionSet.Dimensions.Count >= PowertoolsConfigurations.MaxDimensions) - { - throw new ArgumentOutOfRangeException(nameof(dimension), - $"Cannot add more than {PowertoolsConfigurations.MaxDimensions} dimensions at the same time."); - } - - // Add to the first dimension set instead of creating a new one - foreach (var pair in dimension.Dimensions) - { - if (!firstDimensionSet.Dimensions.ContainsKey(pair.Key)) - { - firstDimensionSet.Dimensions.Add(pair.Key, pair.Value); - } - else - { - Console.WriteLine( - $"##WARNING##: Failed to Add dimension '{pair.Key}'. Dimension already exists."); - } - } + var matchingKeys = AllDimensionKeys.Where(x => x.Contains(dimension.DimensionKeys[0])); + if (!matchingKeys.Any()) + Dimensions.Add(dimension); + else + Console.WriteLine( + $"##WARNING##: Failed to Add dimension '{dimension.DimensionKeys[0]}'. Dimension already exists."); } else { - // No dimensions yet, add the new one - Dimensions.Add(dimension); + throw new ArgumentOutOfRangeException(nameof(Dimensions), + "Cannot add more than 9 dimensions at the same time."); } } @@ -259,44 +227,18 @@ internal void SetDefaultDimensions(List defaultDimensions) /// Dictionary with dimension and default dimension list appended internal Dictionary ExpandAllDimensionSets() { - // if a key appears multiple times, the last value will be the one that's used in the output. var dimensions = new Dictionary(); foreach (var dimensionSet in DefaultDimensions) foreach (var (key, value) in dimensionSet.Dimensions) - dimensions[key] = value; + dimensions.TryAdd(key, value); foreach (var dimensionSet in Dimensions) foreach (var (key, value) in dimensionSet.Dimensions) - dimensions[key] = value; + dimensions.TryAdd(key, value); return dimensions; } - - /// - /// Adds multiple dimensions as a complete dimension set to memory. - /// - /// List of dimension sets to add - internal void AddDimensionSet(List dimensionSets) - { - if (dimensionSets == null || !dimensionSets.Any()) - return; - - if (Dimensions.Count + dimensionSets.Count <= PowertoolsConfigurations.MaxDimensions) - { - // Simply add the dimension sets without checking for existing keys - // This ensures dimensions added together stay together - foreach (var dimensionSet in dimensionSets.Where(dimensionSet => dimensionSet.DimensionKeys.Any())) - { - Dimensions.Add(dimensionSet); - } - } - else - { - throw new ArgumentOutOfRangeException(nameof(Dimensions), - $"Cannot add more than {PowertoolsConfigurations.MaxDimensions} dimensions at the same time."); - } - } /// /// Clears both default dimensions and dimensions lists diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricUnit.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricUnit.cs index 2fbb389d9..ddf091c5e 100644 --- a/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricUnit.cs +++ b/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricUnit.cs @@ -21,11 +21,7 @@ namespace AWS.Lambda.Powertools.Metrics; /// /// Enum MetricUnit /// -#if NET8_0_OR_GREATER [JsonConverter(typeof(JsonStringEnumConverter))] -#else -[JsonConverter(typeof(JsonStringEnumConverter))] -#endif public enum MetricUnit { /// diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricsContext.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricsContext.cs index d43d059ba..ba77d0edb 100644 --- a/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricsContext.cs +++ b/libraries/src/AWS.Lambda.Powertools.Metrics/Model/MetricsContext.cs @@ -132,17 +132,7 @@ internal string GetService() /// Dimension value public void AddDimension(string key, string value) { - _rootNode.AWS.AddDimension(new DimensionSet(key, value)); - } - - /// - /// Adds new dimensions to memory - /// - /// List of dimensions - public void AddDimensions(List dimensions) - { - // Call the AddDimensionSet method on the MetricDirective to add as a set - _rootNode.AWS.AddDimensionSet(dimensions); + _rootNode.AWS.AddDimensionSet(new DimensionSet(key, value)); } /// @@ -180,12 +170,4 @@ public void ClearDefaultDimensions() { _rootNode.AWS.ClearDefaultDimensions(); } - - /// - /// Retrieves default dimensions list - /// - internal List GetDefaultDimensions() - { - return _rootNode.AWS.GetDefaultDimensions(); - } } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/Model/RootNode.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/Model/RootNode.cs index 496606cd1..91ccdc9f8 100644 --- a/libraries/src/AWS.Lambda.Powertools.Metrics/Model/RootNode.cs +++ b/libraries/src/AWS.Lambda.Powertools.Metrics/Model/RootNode.cs @@ -65,11 +65,6 @@ public string Serialize() { if (string.IsNullOrWhiteSpace(AWS.GetNamespace())) throw new SchemaValidationException("namespace"); -#if NET8_0_OR_GREATER - return JsonSerializer.Serialize(this, typeof(RootNode), MetricsSerializationContext.Default); -#else - return JsonSerializer.Serialize(this); -#endif } } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Metrics/Serializer/MetricsSerializationContext.cs b/libraries/src/AWS.Lambda.Powertools.Metrics/Serializer/MetricsSerializationContext.cs index df77cc583..05317c4a7 100644 --- a/libraries/src/AWS.Lambda.Powertools.Metrics/Serializer/MetricsSerializationContext.cs +++ b/libraries/src/AWS.Lambda.Powertools.Metrics/Serializer/MetricsSerializationContext.cs @@ -1,9 +1,23 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System.Collections.Generic; using System.Text.Json.Serialization; namespace AWS.Lambda.Powertools.Metrics; -#if NET8_0_OR_GREATER /// /// Source generator for Metrics types /// @@ -21,5 +35,4 @@ namespace AWS.Lambda.Powertools.Metrics; public partial class MetricsSerializationContext : JsonSerializerContext { -} -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/Transform/JsonTransformer.cs b/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/Transform/JsonTransformer.cs index 136f889fa..36f19a468 100644 --- a/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/Transform/JsonTransformer.cs +++ b/libraries/src/AWS.Lambda.Powertools.Parameters/Internal/Transform/JsonTransformer.cs @@ -23,31 +23,12 @@ namespace AWS.Lambda.Powertools.Parameters.Internal.Transform; /// internal class JsonTransformer : ITransformer { - private readonly JsonSerializerOptions _options; - - /// - /// Initializes a new instance of the class. - /// - public JsonTransformer() - { - _options = new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true - }; - } - /// /// Deserialize a JSON value from a JSON string. /// /// JSON string. /// JSON value type. /// JSON value. -#if NET6_0_OR_GREATER - [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("AOT", "IL3050:RequiresDynamicCode", - Justification = "Types are expected to be known at compile time")] - [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode", - Justification = "Types are expected to be preserved")] -#endif public T? Transform(string value) { if (typeof(T) == typeof(string)) @@ -56,6 +37,6 @@ public JsonTransformer() if (string.IsNullOrWhiteSpace(value)) return default; - return JsonSerializer.Deserialize(value, _options); + return JsonSerializer.Deserialize(value); } } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingAspect.cs b/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingAspect.cs index 84bf02467..6052bb682 100644 --- a/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingAspect.cs +++ b/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingAspect.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.Linq; using System.Runtime.ExceptionServices; @@ -5,7 +20,6 @@ using System.Threading.Tasks; using AspectInjector.Broker; using AWS.Lambda.Powertools.Common; -using AWS.Lambda.Powertools.Common.Core; using AWS.Lambda.Powertools.Common.Utils; namespace AWS.Lambda.Powertools.Tracing.Internal; @@ -27,6 +41,11 @@ public class TracingAspect /// private readonly IXRayRecorder _xRayRecorder; + /// + /// If true, then is cold start + /// + private static bool _isColdStart = true; + /// /// If true, capture annotations /// @@ -129,7 +148,7 @@ private void BeginSegment(string segmentName, string @namespace) if (_captureAnnotations) { - _xRayRecorder.AddAnnotation("ColdStart", LambdaLifecycleTracker.IsColdStart); + _xRayRecorder.AddAnnotation("ColdStart", _isColdStart); _captureAnnotations = false; _isAnnotationsCaptured = true; @@ -137,6 +156,8 @@ private void BeginSegment(string segmentName, string @namespace) if (_powertoolsConfigurations.IsServiceDefined) _xRayRecorder.AddAnnotation("Service", _powertoolsConfigurations.Service); } + + _isColdStart = false; } private void HandleResponse(string name, object result, TracingCaptureMode captureMode, string @namespace) @@ -147,7 +168,6 @@ private void HandleResponse(string name, object result, TracingCaptureMode captu // Skip if the result is VoidTaskResult if (result.GetType().Name == "VoidTaskResult") return; -#if NET8_0_OR_GREATER if (!RuntimeFeatureWrapper.IsDynamicCodeSupported) // is AOT { _xRayRecorder.AddMetadata( @@ -157,7 +177,6 @@ private void HandleResponse(string name, object result, TracingCaptureMode captu ); return; } -#endif _xRayRecorder.AddMetadata( @namespace, @@ -232,7 +251,7 @@ private bool CaptureError(TracingCaptureMode captureMode) internal static void ResetForTest() { - LambdaLifecycleTracker.Reset(); + _isColdStart = true; _captureAnnotations = true; } } \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingAspectFactory.cs b/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingAspectFactory.cs index f1e17c5c5..b013fde74 100644 --- a/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingAspectFactory.cs +++ b/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingAspectFactory.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using AWS.Lambda.Powertools.Common; diff --git a/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingSubsegment.cs b/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingSubsegment.cs deleted file mode 100644 index 68622858f..000000000 --- a/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingSubsegment.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Amazon.XRay.Recorder.Core.Internal.Entities; - -namespace AWS.Lambda.Powertools.Tracing.Internal; - -/// -/// Class TracingSubsegment. -/// It's a wrapper for Subsegment from Amazon.XRay.Recorder.Core.Internal. -/// -/// -public class TracingSubsegment : Subsegment -{ - /// - /// Wrapper constructor - /// - /// - public TracingSubsegment(string name) : base(name) { } -} \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/XRayRecorder.cs b/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/XRayRecorder.cs index 0d4aa658b..53bface31 100644 --- a/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/XRayRecorder.cs +++ b/libraries/src/AWS.Lambda.Powertools.Tracing/Internal/XRayRecorder.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using Amazon.XRay.Recorder.Core; using Amazon.XRay.Recorder.Core.Internal.Emitters; diff --git a/libraries/src/AWS.Lambda.Powertools.Tracing/Serializers/PowertoolsTracingSerializer.cs b/libraries/src/AWS.Lambda.Powertools.Tracing/Serializers/PowertoolsTracingSerializer.cs index 9f2e9e8f8..fca467259 100644 --- a/libraries/src/AWS.Lambda.Powertools.Tracing/Serializers/PowertoolsTracingSerializer.cs +++ b/libraries/src/AWS.Lambda.Powertools.Tracing/Serializers/PowertoolsTracingSerializer.cs @@ -1,5 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ -#if NET8_0_OR_GREATER using System; using System.Collections.Generic; @@ -89,5 +102,3 @@ private static object ConvertValue(JsonElement element) } } } - -#endif \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Tracing/Serializers/TracingSerializerExtensions.cs b/libraries/src/AWS.Lambda.Powertools.Tracing/Serializers/TracingSerializerExtensions.cs index e18368cb1..9287c6977 100644 --- a/libraries/src/AWS.Lambda.Powertools.Tracing/Serializers/TracingSerializerExtensions.cs +++ b/libraries/src/AWS.Lambda.Powertools.Tracing/Serializers/TracingSerializerExtensions.cs @@ -1,4 +1,17 @@ -#if NET8_0_OR_GREATER +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ using System; using System.Diagnostics.CodeAnalysis; @@ -40,5 +53,3 @@ internal static JsonSerializerOptions GetDefaultOptions() return serializer; } } - -#endif \ No newline at end of file diff --git a/libraries/src/AWS.Lambda.Powertools.Tracing/Tracing.cs b/libraries/src/AWS.Lambda.Powertools.Tracing/Tracing.cs index 3d7d1aa8c..29e5b88a5 100644 --- a/libraries/src/AWS.Lambda.Powertools.Tracing/Tracing.cs +++ b/libraries/src/AWS.Lambda.Powertools.Tracing/Tracing.cs @@ -130,7 +130,7 @@ public static void AddHttpInformation(string key, object value) /// The name of the subsegment. /// The AWS X-Ray subsegment for the wrapped consumer. /// Thrown when the name is not provided. - public static void WithSubsegment(string name, Action subsegment) + public static void WithSubsegment(string name, Action subsegment) { WithSubsegment(null, name, subsegment); } @@ -145,7 +145,7 @@ public static void WithSubsegment(string name, Action subsegm /// The name of the subsegment. /// The AWS X-Ray subsegment for the wrapped consumer. /// name - public static void WithSubsegment(string nameSpace, string name, Action subsegment) + public static void WithSubsegment(string nameSpace, string name, Action subsegment) { if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name)); @@ -154,8 +154,7 @@ public static void WithSubsegment(string nameSpace, string name, ActionThe AWS X-Ray subsegment for the wrapped consumer. /// Thrown when the name is not provided. /// Thrown when the entity is not provided. - public static void WithSubsegment(string name, Entity entity, Action subsegment) + public static void WithSubsegment(string name, Entity entity, Action subsegment) { WithSubsegment(null, name, subsegment); } @@ -192,7 +191,7 @@ public static void WithSubsegment(string name, Entity entity, ActionThe AWS X-Ray subsegment for the wrapped consumer. /// name /// entity - public static void WithSubsegment(string nameSpace, string name, Entity entity, Action subsegment) + public static void WithSubsegment(string nameSpace, string name, Entity entity, Action subsegment) { if (string.IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name)); @@ -200,7 +199,7 @@ public static void WithSubsegment(string nameSpace, string name, Entity entity, if (entity is null) throw new ArgumentNullException(nameof(entity)); - var childSubsegment = new TracingSubsegment($"## {name}"); + var childSubsegment = new Subsegment($"## {name}"); entity.AddSubsegment(childSubsegment); childSubsegment.Sampled = entity.Sampled; childSubsegment.SetStartTimeToNow(); @@ -241,7 +240,7 @@ private static string GetNamespaceOrDefault(string nameSpace) return PowertoolsConfigurations.Instance.Service; } - + /// /// Registers X-Ray for all instances of . /// diff --git a/libraries/src/AWS.Lambda.Powertools.Tracing/TracingAttribute.cs b/libraries/src/AWS.Lambda.Powertools.Tracing/TracingAttribute.cs index 5cbfc4956..c144d0387 100644 --- a/libraries/src/AWS.Lambda.Powertools.Tracing/TracingAttribute.cs +++ b/libraries/src/AWS.Lambda.Powertools.Tracing/TracingAttribute.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using AspectInjector.Broker; using AWS.Lambda.Powertools.Tracing.Internal; diff --git a/libraries/src/Directory.Build.props b/libraries/src/Directory.Build.props index 7d1e38a66..7d7d53060 100644 --- a/libraries/src/Directory.Build.props +++ b/libraries/src/Directory.Build.props @@ -1,9 +1,9 @@ - net6.0;net8.0 + net8.0 default - $(Version) + 0.0.1 Amazon Web Services Amazon.com, Inc Powertools for AWS Lambda (.NET) @@ -16,7 +16,6 @@ AWSLogo128x128.png true true - $(VersionSuffix) diff --git a/libraries/src/Directory.Packages.props b/libraries/src/Directory.Packages.props index be5d56855..56d0fba99 100644 --- a/libraries/src/Directory.Packages.props +++ b/libraries/src/Directory.Packages.props @@ -4,20 +4,14 @@ - - + - + - - - - - - + @@ -26,6 +20,5 @@ - \ No newline at end of file diff --git a/libraries/src/KafkaDependencies.props b/libraries/src/KafkaDependencies.props deleted file mode 100644 index 1034529a1..000000000 --- a/libraries/src/KafkaDependencies.props +++ /dev/null @@ -1,20 +0,0 @@ - - - false - - - - - - - - - Kafka\%(RecursiveDir)%(Filename)%(Extension) - - - Common\%(RecursiveDir)%(Filename)%(Extension) - - - - - \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/AotCompatibilityTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/AotCompatibilityTests.cs deleted file mode 100644 index fb8b976c9..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/AotCompatibilityTests.cs +++ /dev/null @@ -1,385 +0,0 @@ - - -using System; -using System.Runtime.CompilerServices; -using System.Text.Json; -using System.Text.Json.Serialization; -using AWS.Lambda.Powertools.BatchProcessing.Exceptions; -using AWS.Lambda.Powertools.BatchProcessing.Internal; -using Xunit; - -namespace AWS.Lambda.Powertools.BatchProcessing.Tests; - -[Collection("Sequential")] -public class AotCompatibilityTests -{ - #region Test Models - - public class TestAotModel - { - public int Id { get; set; } - public string Name { get; set; } - public DateTime CreatedAt { get; set; } - } - - public class UnregisteredModel - { - public string Value { get; set; } - } - - #endregion - - #region AotCompatibilityHelper Tests - - [Fact] - public void IsAotMode_ReturnsExpectedValue() - { - // Act - var isAotMode = AotCompatibilityHelper.IsAotMode(); - - // Assert - // In test environment, this should return false (not AOT compiled) - // The actual value depends on the runtime, but we can test that it returns a boolean - Assert.IsType(isAotMode); - } - - [Fact] - public void ValidateTypeInContext_WithValidType_ReturnsTrue() - { - // Arrange - var context = TestAotJsonSerializerContext.Default; - - // Act - var result = AotCompatibilityHelper.ValidateTypeInContext(context, false); - - // Assert - Assert.True(result); - } - - [Fact] - public void ValidateTypeInContext_WithUnregisteredType_ReturnsFalse() - { - // Arrange - var context = TestAotJsonSerializerContext.Default; - - // Act - var result = AotCompatibilityHelper.ValidateTypeInContext(context, false); - - // Assert - Assert.False(result); - } - - [Fact] - public void ValidateTypeInContext_WithUnregisteredTypeAndThrow_ThrowsException() - { - // Arrange - var context = TestAotJsonSerializerContext.Default; - - // Act & Assert - var exception = Assert.Throws(() => - AotCompatibilityHelper.ValidateTypeInContext(context, true)); - - Assert.Equal(typeof(UnregisteredModel), exception.TargetType); - Assert.Contains("UnregisteredModel", exception.Message); - Assert.Contains("JsonSerializable", exception.Message); - } - - [Fact] - public void ValidateTypeInContext_WithNullContext_ThrowsException() - { - // Act & Assert - var exception = Assert.Throws(() => - AotCompatibilityHelper.ValidateTypeInContext(null, true)); - - Assert.Equal(typeof(TestAotModel), exception.TargetType); - Assert.Contains("JsonSerializerContext is null", exception.Message); - } - - [Fact] - public void ValidateTypeInContext_WithNullContextAndNoThrow_ReturnsFalse() - { - // Act - var result = AotCompatibilityHelper.ValidateTypeInContext(null, false); - - // Assert - Assert.False(result); - } - - [Fact] - public void GetAotCompatibilityErrorMessage_WithoutContext_ReturnsCorrectMessage() - { - // Act - var message = AotCompatibilityHelper.GetAotCompatibilityErrorMessage(typeof(TestAotModel), false); - - // Assert - Assert.Contains("AOT compilation requires a JsonSerializerContext", message); - Assert.Contains("TestAotModel", message); - Assert.Contains("JsonSerializable", message); - } - - [Fact] - public void GetAotCompatibilityErrorMessage_WithContext_ReturnsCorrectMessage() - { - // Act - var message = AotCompatibilityHelper.GetAotCompatibilityErrorMessage(typeof(TestAotModel), true); - - // Assert - Assert.Contains("does not contain type information", message); - Assert.Contains("TestAotModel", message); - Assert.Contains("JsonSerializable", message); - } - - [Fact] - public void ValidateAotCompatibility_InNonAotMode_DoesNotThrow() - { - // Arrange - var options = new DeserializationOptions(); - - // Act & Assert - Should not throw in non-AOT mode - AotCompatibilityHelper.ValidateAotCompatibility(options); - } - - [Fact] - public void ValidateAotCompatibility_WithJsonSerializerContext_ValidatesType() - { - // Arrange - var options = new DeserializationOptions(TestAotJsonSerializerContext.Default); - - // Act & Assert - Should not throw for registered type - AotCompatibilityHelper.ValidateAotCompatibility(options); - } - - [Fact] - public void ValidateAotCompatibility_WithUnregisteredType_ThrowsException() - { - // Arrange - var options = new DeserializationOptions(TestAotJsonSerializerContext.Default); - - // Act & Assert - var exception = Assert.Throws(() => - AotCompatibilityHelper.ValidateAotCompatibility(options)); - - Assert.Equal(typeof(UnregisteredModel), exception.TargetType); - Assert.Contains("UnregisteredModel", exception.Message); - } - - [Fact] - public void FallbackDeserialize_WithJsonSerializerOptions_Succeeds() - { - // Arrange - var json = """{"Id":1,"Name":"Test","CreatedAt":"2023-01-01T00:00:00Z"}"""; - var options = new DeserializationOptions - { - JsonSerializerOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true } - }; - - // Act - var result = AotCompatibilityHelper.FallbackDeserialize(json, options); - - // Assert - Assert.NotNull(result); - Assert.Equal(1, result.Id); - Assert.Equal("Test", result.Name); - } - - [Fact] - public void FallbackDeserialize_WithNullOptions_Succeeds() - { - // Arrange - var json = """{"Id":1,"Name":"Test","CreatedAt":"2023-01-01T00:00:00Z"}"""; - - // Act - var result = AotCompatibilityHelper.FallbackDeserialize(json, null); - - // Assert - Assert.NotNull(result); - Assert.Equal(1, result.Id); - Assert.Equal("Test", result.Name); - } - - #endregion - - #region JsonDeserializationService AOT Tests - - [Fact] - public void JsonDeserializationService_WithValidJsonSerializerContext_Succeeds() - { - // Arrange - var service = new JsonDeserializationService(); - var json = """{"Id":1,"Name":"Test","CreatedAt":"2023-01-01T00:00:00Z"}"""; - var options = new DeserializationOptions(TestAotJsonSerializerContext.Default); - - // Act - var result = service.Deserialize(json, options); - - // Assert - Assert.NotNull(result); - Assert.Equal(1, result.Id); - Assert.Equal("Test", result.Name); - Assert.Equal(new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc), result.CreatedAt); - } - - [Fact] - public void JsonDeserializationService_WithUnregisteredTypeInContext_ThrowsException() - { - // Arrange - var service = new JsonDeserializationService(); - var json = """{"Value":"test"}"""; - var options = new DeserializationOptions(TestAotJsonSerializerContext.Default); - - // Act & Assert - var exception = Assert.Throws(() => - service.Deserialize(json, options)); - - Assert.Equal(typeof(UnregisteredModel), exception.TargetType); - Assert.Contains("UnregisteredModel", exception.Message); - } - - [Fact] - public void JsonDeserializationService_TryDeserialize_WithUnregisteredType_ReturnsFalse() - { - // Arrange - var service = new JsonDeserializationService(); - var json = """{"Value":"test"}"""; - var options = new DeserializationOptions(TestAotJsonSerializerContext.Default); - - // Act - var success = service.TryDeserialize(json, out var result, out var exception, options); - - // Assert - Assert.False(success); - Assert.Null(result); - Assert.NotNull(exception); - Assert.IsType(exception); - } - - [Fact] - public void JsonDeserializationService_WithPrimitiveTypes_WorksWithContext() - { - // Arrange - var service = new JsonDeserializationService(); - var options = new DeserializationOptions(TestAotJsonSerializerContext.Default); - - // Act & Assert - Assert.Equal(42, service.Deserialize("42", options)); - Assert.Equal("test", service.Deserialize("\"test\"", options)); - } - - #endregion - - #region Exception Tests - - [Fact] - public void AotCompatibilityException_ConstructorWithMessage_SetsProperties() - { - // Arrange - var targetType = typeof(TestAotModel); - var message = "Test message"; - - // Act - var exception = new AotCompatibilityException(targetType, message); - - // Assert - Assert.Equal(targetType, exception.TargetType); - Assert.Equal(message, exception.Message); - Assert.Null(exception.InnerException); - } - - [Fact] - public void AotCompatibilityException_ConstructorWithInnerException_SetsProperties() - { - // Arrange - var targetType = typeof(TestAotModel); - var message = "Test message"; - var innerException = new InvalidOperationException("Inner"); - - // Act - var exception = new AotCompatibilityException(targetType, message, innerException); - - // Assert - Assert.Equal(targetType, exception.TargetType); - Assert.Equal(message, exception.Message); - Assert.Equal(innerException, exception.InnerException); - } - - [Fact] - public void AotTypeValidationException_ConstructorWithMessage_SetsProperties() - { - // Arrange - var targetType = typeof(TestAotModel); - var message = "Test message"; - - // Act - var exception = new AotTypeValidationException(targetType, message); - - // Assert - Assert.Equal(targetType, exception.TargetType); - Assert.Equal(message, exception.Message); - Assert.Null(exception.InnerException); - } - - [Fact] - public void AotTypeValidationException_ConstructorWithInnerException_SetsProperties() - { - // Arrange - var targetType = typeof(TestAotModel); - var message = "Test message"; - var innerException = new NotSupportedException("Inner"); - - // Act - var exception = new AotTypeValidationException(targetType, message, innerException); - - // Assert - Assert.Equal(targetType, exception.TargetType); - Assert.Equal(message, exception.Message); - Assert.Equal(innerException, exception.InnerException); - } - - #endregion - - #region Integration Tests - - [Fact] - public void DeserializationOptions_WithJsonSerializerContext_PreservesContext() - { - // Arrange - var context = TestAotJsonSerializerContext.Default; - - // Act - var options = new DeserializationOptions(context); - - // Assert - Assert.Equal(context, options.JsonSerializerContext); - Assert.Null(options.JsonSerializerOptions); - } - - [Fact] - public void JsonDeserializationService_ContextTakesPrecedenceOverOptions() - { - // Arrange - var service = new JsonDeserializationService(); - var json = """{"Id":1,"Name":"Test","CreatedAt":"2023-01-01T00:00:00Z"}"""; - var options = new DeserializationOptions - { - JsonSerializerContext = TestAotJsonSerializerContext.Default, - JsonSerializerOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = false } - }; - - // Act - var result = service.Deserialize(json, options); - - // Assert - Should succeed because JsonSerializerContext is used instead of JsonSerializerOptions - Assert.NotNull(result); - Assert.Equal(1, result.Id); - } - - #endregion -} - -// JsonSerializerContext needs to be outside the test class and partial for source generation -[JsonSerializable(typeof(AotCompatibilityTests.TestAotModel))] -[JsonSerializable(typeof(string))] -[JsonSerializable(typeof(int))] -public partial class TestAotJsonSerializerContext : JsonSerializerContext -{ -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/BackwardCompatibilityIntegrationTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/BackwardCompatibilityIntegrationTests.cs deleted file mode 100644 index a0f9ac852..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/BackwardCompatibilityIntegrationTests.cs +++ /dev/null @@ -1,549 +0,0 @@ - - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Text.Json; -using System.Threading; -using System.Threading.Tasks; -using Amazon.Lambda.Core; -using Amazon.Lambda.DynamoDBEvents; -using Amazon.Lambda.KinesisEvents; -using Amazon.Lambda.SQSEvents; -using AWS.Lambda.Powertools.BatchProcessing.DynamoDb; -using AWS.Lambda.Powertools.BatchProcessing.Exceptions; -using AWS.Lambda.Powertools.BatchProcessing.Kinesis; -using AWS.Lambda.Powertools.BatchProcessing.Sqs; -using AWS.Lambda.Powertools.BatchProcessing.Tests.Handlers.SQS.Custom; -using Xunit; - -namespace AWS.Lambda.Powertools.BatchProcessing.Tests; - -/// -/// Integration tests to verify backward compatibility with existing batch processing functionality. -/// These tests ensure that all existing IRecordHandler implementations, BatchProcessorAttribute usage, -/// static access patterns, and ProcessingResult structure remain unchanged. -/// -[Collection("Sequential")] -public class BackwardCompatibilityIntegrationTests -{ - #region Static Access Pattern Tests - - [Fact] - public void SqsBatchProcessor_StaticInstance_ShouldBeAccessible() - { - // Arrange & Act - var instance = SqsBatchProcessor.Instance; - - // Assert - Assert.NotNull(instance); - Assert.IsAssignableFrom(instance); - Assert.IsAssignableFrom>(instance); - } - - [Fact] - public void KinesisEventBatchProcessor_StaticInstance_ShouldBeAccessible() - { - // Arrange & Act - var instance = KinesisEventBatchProcessor.Instance; - - // Assert - Assert.NotNull(instance); - Assert.IsAssignableFrom(instance); - Assert.IsAssignableFrom>(instance); - } - - [Fact] - public void DynamoDbStreamBatchProcessor_StaticInstance_ShouldBeAccessible() - { - // Arrange & Act - var instance = DynamoDbStreamBatchProcessor.Instance; - - // Assert - Assert.NotNull(instance); - Assert.IsAssignableFrom(instance); - Assert.IsAssignableFrom>(instance); - } - - [Fact] - public void SqsBatchProcessor_StaticResult_ShouldBeAccessible() - { - // Arrange & Act - var result = SqsBatchProcessor.Result; - - // Assert - Assert.NotNull(result); - Assert.IsType>(result); - } - - [Fact] - public async Task KinesisEventBatchProcessor_StaticResult_ShouldBeAccessible() - { - // Arrange - First use the processor to initialize the result - var processor = KinesisEventBatchProcessor.Instance; - var handler = new TraditionalKinesisRecordHandler(); - var kinesisEvent = CreateSampleKinesisEvent(); - - // Act - Process to initialize the static result - await processor.ProcessAsync(kinesisEvent, handler); - var result = KinesisEventBatchProcessor.Result; - - // Assert - Assert.NotNull(result); - Assert.IsType>(result); - } - - [Fact] - public void DynamoDbStreamBatchProcessor_StaticResult_ShouldBeAccessible() - { - // Arrange & Act - var result = DynamoDbStreamBatchProcessor.Result; - - // Assert - Assert.NotNull(result); - Assert.IsType>(result); - } - - #endregion - - #region Traditional IRecordHandler Tests - - [Fact] - public async Task SqsBatchProcessor_WithTraditionalRecordHandler_ShouldProcessSuccessfully() - { - // Arrange - var processor = SqsBatchProcessor.Instance; - var handler = new TraditionalSqsRecordHandler(); - var sqsEvent = CreateSampleSqsEvent(); - - // Act - var result = await processor.ProcessAsync(sqsEvent, handler); - - // Assert - Assert.NotNull(result); - Assert.IsType>(result); - Assert.Equal(2, result.BatchRecords.Count); - Assert.Equal(2, result.SuccessRecords.Count); - Assert.Empty(result.FailureRecords); - Assert.Empty(result.BatchItemFailuresResponse.BatchItemFailures); - } - - [Fact] - public async Task KinesisEventBatchProcessor_WithTraditionalRecordHandler_ShouldProcessSuccessfully() - { - // Arrange - var processor = KinesisEventBatchProcessor.Instance; - var handler = new TraditionalKinesisRecordHandler(); - var kinesisEvent = CreateSampleKinesisEvent(); - - // Act - var result = await processor.ProcessAsync(kinesisEvent, handler); - - // Assert - Assert.NotNull(result); - Assert.IsType>(result); - Assert.Equal(2, result.BatchRecords.Count); - Assert.Equal(2, result.SuccessRecords.Count); - Assert.Empty(result.FailureRecords); - Assert.Empty(result.BatchItemFailuresResponse.BatchItemFailures); - } - - [Fact] - public async Task DynamoDbStreamBatchProcessor_WithTraditionalRecordHandler_ShouldProcessSuccessfully() - { - // Arrange - var processor = DynamoDbStreamBatchProcessor.Instance; - var handler = new TraditionalDynamoDbRecordHandler(); - var dynamoDbEvent = CreateSampleDynamoDbEvent(); - - // Act - var result = await processor.ProcessAsync(dynamoDbEvent, handler); - - // Assert - Assert.NotNull(result); - Assert.IsType>(result); - Assert.Equal(2, result.BatchRecords.Count); - Assert.Equal(2, result.SuccessRecords.Count); - Assert.Empty(result.FailureRecords); - Assert.Empty(result.BatchItemFailuresResponse.BatchItemFailures); - } - - #endregion - - #region ProcessingResult Structure Compatibility Tests - - [Fact] - public async Task ProcessingResult_Structure_ShouldRemainUnchanged() - { - // Arrange - var processor = SqsBatchProcessor.Instance; - var handler = new TraditionalSqsRecordHandler(); - var sqsEvent = CreateSampleSqsEvent(); - - // Act - var result = await processor.ProcessAsync(sqsEvent, handler); - - // Assert - Verify all expected properties exist and have correct types - Assert.NotNull(result.BatchItemFailuresResponse); - Assert.IsType(result.BatchItemFailuresResponse); - - Assert.NotNull(result.BatchRecords); - Assert.IsType>(result.BatchRecords); - - Assert.NotNull(result.SuccessRecords); - Assert.IsType>>(result.SuccessRecords); - - Assert.NotNull(result.FailureRecords); - Assert.IsType>>(result.FailureRecords); - - // Verify BatchItemFailuresResponse structure - Assert.NotNull(result.BatchItemFailuresResponse.BatchItemFailures); - Assert.IsType>(result.BatchItemFailuresResponse.BatchItemFailures); - } - - [Fact] - public async Task ProcessingResult_WithFailures_ShouldMaintainStructure() - { - // Arrange - var processor = SqsBatchProcessor.Instance; - var handler = new FailingTraditionalSqsRecordHandler(); - var sqsEvent = CreateSampleSqsEvent(); - var processingOptions = new ProcessingOptions - { - ThrowOnFullBatchFailure = false // Disable throwing on full batch failure for this test - }; - - // Act - var result = await processor.ProcessAsync(sqsEvent, handler, processingOptions); - - // Assert - Assert.NotNull(result); - Assert.Equal(2, result.BatchRecords.Count); - Assert.Empty(result.SuccessRecords); - Assert.Equal(2, result.FailureRecords.Count); - Assert.Equal(2, result.BatchItemFailuresResponse.BatchItemFailures.Count); - - // Verify failure record structure - var failureRecord = result.FailureRecords.First(); - Assert.NotNull(failureRecord.Record); - Assert.NotNull(failureRecord.Exception); - Assert.NotNull(failureRecord.RecordId); - - // Verify batch item failure structure - var batchItemFailure = result.BatchItemFailuresResponse.BatchItemFailures.First(); - Assert.NotNull(batchItemFailure.ItemIdentifier); - } - - #endregion - - #region BatchProcessorAttribute Compatibility Tests - - [Fact] - public void BatchProcessorAttribute_WithTraditionalHandler_ShouldCreateAspectHandler() - { - // Arrange - var attribute = new BatchProcessorAttribute - { - RecordHandler = typeof(TraditionalSqsRecordHandler) - }; - - // Act - var handler = attribute.CreateAspectHandler(new object[] { new SQSEvent() }); - - // Assert - Assert.NotNull(handler); - } - - [Fact] - public void BatchProcessorAttribute_WithRecordHandlerProvider_ShouldCreateAspectHandler() - { - // Arrange - var attribute = new BatchProcessorAttribute - { - RecordHandlerProvider = typeof(TraditionalSqsRecordHandlerProvider) - }; - - // Act - var handler = attribute.CreateAspectHandler(new object[] { new SQSEvent() }); - - // Assert - Assert.NotNull(handler); - } - - [Fact] - public void BatchProcessorAttribute_WithCustomBatchProcessor_ShouldCreateAspectHandler() - { - // Arrange - var attribute = new BatchProcessorAttribute - { - BatchProcessor = typeof(CustomSqsBatchProcessor), - RecordHandler = typeof(TraditionalSqsRecordHandler) - }; - - // Act - var handler = attribute.CreateAspectHandler(new object[] { new SQSEvent() }); - - // Assert - Assert.NotNull(handler); - } - - [Fact] - public void BatchProcessorAttribute_WithBatchProcessorProvider_ShouldCreateAspectHandler() - { - // Arrange - var attribute = new BatchProcessorAttribute - { - BatchProcessorProvider = typeof(CustomSqsBatchProcessorProvider), - RecordHandler = typeof(TraditionalSqsRecordHandler) - }; - - // Act - var handler = attribute.CreateAspectHandler(new object[] { new SQSEvent() }); - - // Assert - Assert.NotNull(handler); - } - - #endregion - - #region Mixed Usage Compatibility Tests - - [Fact] - public async Task BatchProcessor_MixedUsage_TraditionalAndDirectCalls_ShouldWork() - { - // Arrange - var processor = SqsBatchProcessor.Instance; - var traditionalHandler = new TraditionalSqsRecordHandler(); - var sqsEvent = CreateSampleSqsEvent(); - - // Act - First use traditional handler - var result1 = await processor.ProcessAsync(sqsEvent, traditionalHandler); - - // Then use direct processor call (simulating mixed usage) - var result2 = await processor.ProcessAsync(sqsEvent, traditionalHandler, CancellationToken.None); - - // Assert - Assert.NotNull(result1); - Assert.NotNull(result2); - Assert.Equal(result1.BatchRecords.Count, result2.BatchRecords.Count); - Assert.Equal(result1.SuccessRecords.Count, result2.SuccessRecords.Count); - } - - #endregion - - #region Error Handling Compatibility Tests - - [Fact] - public async Task BatchProcessor_ErrorHandling_ShouldMaintainExistingBehavior() - { - // Arrange - var processor = SqsBatchProcessor.Instance; - var handler = new PartiallyFailingTraditionalSqsRecordHandler(); - var sqsEvent = CreateSampleSqsEvent(); - - // Act - var result = await processor.ProcessAsync(sqsEvent, handler); - - // Assert - Assert.NotNull(result); - Assert.Equal(2, result.BatchRecords.Count); - Assert.Single(result.SuccessRecords); - Assert.Single(result.FailureRecords); - Assert.Single(result.BatchItemFailuresResponse.BatchItemFailures); - - // Verify error structure remains the same - var failureRecord = result.FailureRecords.First(); - Assert.Contains("Failed processing record", failureRecord.Exception.Message); - Assert.IsType(failureRecord.Exception); - } - - #endregion - - #region Performance Compatibility Tests - - [Fact] - public async Task BatchProcessor_ParallelProcessing_ShouldMaintainCompatibility() - { - // Arrange - var processor = SqsBatchProcessor.Instance; - var handler = new TraditionalSqsRecordHandler(); - var sqsEvent = CreateLargeSampleSqsEvent(10); - var processingOptions = new ProcessingOptions - { - BatchParallelProcessingEnabled = true, - MaxDegreeOfParallelism = 4 - }; - - // Act - var result = await processor.ProcessAsync(sqsEvent, handler, processingOptions); - - // Assert - Assert.NotNull(result); - Assert.Equal(10, result.BatchRecords.Count); - Assert.Equal(10, result.SuccessRecords.Count); - Assert.Empty(result.FailureRecords); - } - - #endregion - - #region Helper Methods and Classes - - private static SQSEvent CreateSampleSqsEvent() - { - return new SQSEvent - { - Records = new List - { - new() - { - MessageId = "msg-1", - Body = JsonSerializer.Serialize(new { Id = 1, Name = "Product 1" }), - EventSourceArn = "arn:aws:sqs:us-east-1:123456789012:test-queue" - }, - new() - { - MessageId = "msg-2", - Body = JsonSerializer.Serialize(new { Id = 2, Name = "Product 2" }), - EventSourceArn = "arn:aws:sqs:us-east-1:123456789012:test-queue" - } - } - }; - } - - private static SQSEvent CreateLargeSampleSqsEvent(int recordCount) - { - var records = new List(); - for (int i = 1; i <= recordCount; i++) - { - records.Add(new SQSEvent.SQSMessage - { - MessageId = $"msg-{i}", - Body = JsonSerializer.Serialize(new { Id = i, Name = $"Product {i}" }), - EventSourceArn = "arn:aws:sqs:us-east-1:123456789012:test-queue" - }); - } - - return new SQSEvent { Records = records }; - } - - private static KinesisEvent CreateSampleKinesisEvent() - { - return new KinesisEvent - { - Records = new List - { - new() - { - Kinesis = new KinesisEvent.Record - { - SequenceNumber = "1", - Data = new MemoryStream(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(new { Id = 1, Name = "Event 1" }))) - } - }, - new() - { - Kinesis = new KinesisEvent.Record - { - SequenceNumber = "2", - Data = new MemoryStream(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(new { Id = 2, Name = "Event 2" }))) - } - } - } - }; - } - - private static DynamoDBEvent CreateSampleDynamoDbEvent() - { - return new DynamoDBEvent - { - Records = new List - { - new() - { - Dynamodb = new DynamoDBEvent.StreamRecord - { - SequenceNumber = "seq-1" - } - }, - new() - { - Dynamodb = new DynamoDBEvent.StreamRecord - { - SequenceNumber = "seq-2" - } - } - } - }; - } - - // Traditional record handlers for testing - public class TraditionalSqsRecordHandler : IRecordHandler - { - public Task HandleAsync(SQSEvent.SQSMessage record, CancellationToken cancellationToken) - { - return Task.FromResult(RecordHandlerResult.None); - } - } - - public class TraditionalKinesisRecordHandler : IRecordHandler - { - public Task HandleAsync(KinesisEvent.KinesisEventRecord record, CancellationToken cancellationToken) - { - return Task.FromResult(RecordHandlerResult.None); - } - } - - public class TraditionalDynamoDbRecordHandler : IRecordHandler - { - public Task HandleAsync(DynamoDBEvent.DynamodbStreamRecord record, CancellationToken cancellationToken) - { - return Task.FromResult(RecordHandlerResult.None); - } - } - - public class FailingTraditionalSqsRecordHandler : IRecordHandler - { - public Task HandleAsync(SQSEvent.SQSMessage record, CancellationToken cancellationToken) - { - throw new InvalidOperationException("Simulated failure"); - } - } - - public class PartiallyFailingTraditionalSqsRecordHandler : IRecordHandler - { - public Task HandleAsync(SQSEvent.SQSMessage record, CancellationToken cancellationToken) - { - if (record.MessageId == "msg-1") - { - throw new InvalidOperationException("Simulated failure for first record"); - } - return Task.FromResult(RecordHandlerResult.None); - } - } - - // Provider classes for testing - public class TraditionalSqsRecordHandlerProvider : IRecordHandlerProvider - { - public IRecordHandler Create() - { - return new TraditionalSqsRecordHandler(); - } - } - - public class CustomSqsBatchProcessor : SqsBatchProcessor - { - // Custom implementation for testing - } - - public class CustomSqsBatchProcessorProvider : IBatchProcessorProvider - { - public IBatchProcessor Create() - { - return new CustomSqsBatchProcessor(); - } - } - - #endregion -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/BatchProcessingAttributeTest.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/BatchProcessingAttributeTest.cs index d50782345..db2bee36f 100644 --- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/BatchProcessingAttributeTest.cs +++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/BatchProcessingAttributeTest.cs @@ -1,224 +1,31 @@ - +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ -using System; -using System.Text.Json.Serialization; -using System.Threading; -using System.Threading.Tasks; -using Amazon.Lambda.Core; -using Amazon.Lambda.SQSEvents; -using AWS.Lambda.Powertools.BatchProcessing.Tests.Handlers.SQS.Custom; using Xunit; namespace AWS.Lambda.Powertools.BatchProcessing.Tests { [Collection("Sequential")] - public partial class BatchProcessingAttributeTest + public class BatchProcessingAttributeTest { [Fact] - public void BatchProcessorAttribute_WithTypedRecordHandler_ThrowsNotSupportedException() + public void OnEntry_Test() { // Arrange - var attribute = new BatchProcessorAttribute - { - TypedRecordHandler = typeof(TestTypedRecordHandler) - }; - - // Act & Assert - var exception = Assert.Throws(() => - attribute.CreateAspectHandler(new object[] { new SQSEvent() })); - - Assert.Contains("Typed record handlers are not yet fully supported with BatchProcessorAttribute", exception.Message); - } - - [Fact] - public void BatchProcessorAttribute_WithTypedRecordHandlerProvider_ThrowsNotSupportedException() - { - // Arrange - var attribute = new BatchProcessorAttribute - { - TypedRecordHandlerProvider = typeof(TestTypedRecordHandlerProvider) - }; - - // Act & Assert - var exception = Assert.Throws(() => - attribute.CreateAspectHandler(new object[] { new SQSEvent() })); - - Assert.Contains("Typed record handlers are not yet fully supported with BatchProcessorAttribute", exception.Message); - } - - [Fact] - public void BatchProcessorAttribute_WithTypedRecordHandlerWithContext_ThrowsNotSupportedException() - { - // Arrange - var attribute = new BatchProcessorAttribute - { - TypedRecordHandlerWithContext = typeof(TestTypedRecordHandlerWithContext) - }; - - // Act & Assert - var exception = Assert.Throws(() => - attribute.CreateAspectHandler(new object[] { new SQSEvent() })); - - Assert.Contains("Typed record handlers are not yet fully supported with BatchProcessorAttribute", exception.Message); - } - - [Fact] - public void BatchProcessorAttribute_WithTypedRecordHandlerWithContextProvider_ThrowsNotSupportedException() - { - // Arrange - var attribute = new BatchProcessorAttribute - { - TypedRecordHandlerWithContextProvider = typeof(TestTypedRecordHandlerWithContextProvider) - }; - - // Act & Assert - var exception = Assert.Throws(() => - attribute.CreateAspectHandler(new object[] { new SQSEvent() })); - - Assert.Contains("Typed record handlers are not yet fully supported with BatchProcessorAttribute", exception.Message); - } - - [Fact] - public void BatchProcessorAttribute_WithMultipleHandlerTypes_ThrowsInvalidOperationException() - { - // Arrange - var attribute = new BatchProcessorAttribute - { - RecordHandler = typeof(CustomSqsRecordHandler), - TypedRecordHandler = typeof(TestTypedRecordHandler) - }; - - // Act & Assert - var exception = Assert.Throws(() => - attribute.CreateAspectHandler(new object[] { new SQSEvent() })); - - Assert.Contains("Only one type of handler (traditional or typed) can be configured at a time", exception.Message); - } - - [Fact] - public void BatchProcessorAttribute_WithNoHandlers_ThrowsInvalidOperationException() - { - // Arrange - var attribute = new BatchProcessorAttribute(); - - // Act & Assert - var exception = Assert.Throws(() => - attribute.CreateAspectHandler(new object[] { new SQSEvent() })); - - Assert.Contains("A record handler, record handler provider, typed record handler, or typed record handler provider is required", exception.Message); - } - - [Fact] - public void BatchProcessorAttribute_WithInvalidJsonSerializerContext_ThrowsInvalidOperationException() - { - // Arrange - var attribute = new BatchProcessorAttribute - { - RecordHandler = typeof(CustomSqsRecordHandler), - JsonSerializerContext = typeof(string) // Invalid type - }; - - // Act & Assert - var exception = Assert.Throws(() => - attribute.CreateAspectHandler(new object[] { new SQSEvent() })); - - Assert.Contains("The provided JsonSerializerContext must inherit from", exception.Message); - } - - [Fact] - public void BatchProcessorAttribute_WithValidJsonSerializerContext_DoesNotThrow() - { - // Arrange - Use a mock type that inherits from JsonSerializerContext for validation - var attribute = new BatchProcessorAttribute - { - RecordHandler = typeof(CustomSqsRecordHandler), - // We'll skip this test since it requires complex source generation setup - // The validation logic is tested in the invalid case above - }; - - // Act & Assert - Should not throw during validation - // The actual processing would still work with traditional handlers - Assert.NotNull(attribute); - } - - [Fact] - public void BatchProcessorAttribute_DeserializationErrorPolicy_DefaultValue() - { - // Arrange & Act - var attribute = new BatchProcessorAttribute(); - - // Assert - Assert.Equal(DeserializationErrorPolicy.FailRecord, attribute.DeserializationErrorPolicy); - } - - [Fact] - public void BatchProcessorAttribute_DeserializationErrorPolicy_CanBeSet() - { - // Arrange - var attribute = new BatchProcessorAttribute - { - DeserializationErrorPolicy = DeserializationErrorPolicy.IgnoreRecord - }; - - // Act & Assert - Assert.Equal(DeserializationErrorPolicy.IgnoreRecord, attribute.DeserializationErrorPolicy); - } - - [Fact] - public void BatchProcessorAttribute_BackwardCompatibility_WithTraditionalHandler() - { - // Arrange - var attribute = new BatchProcessorAttribute - { - RecordHandler = typeof(CustomSqsRecordHandler) - }; - - // Act - This should work as before (traditional processing) - var handler = attribute.CreateAspectHandler(new object[] { new SQSEvent() }); - + // Act // Assert - Assert.NotNull(handler); - } - - // Test helper classes - private class TestTypedRecordHandler : ITypedRecordHandler - { - public Task HandleAsync(TestData data, CancellationToken cancellationToken) - { - return Task.FromResult(RecordHandlerResult.None); - } } - - private class TestTypedRecordHandlerProvider : ITypedRecordHandlerProvider - { - public ITypedRecordHandler Create() - { - return new TestTypedRecordHandler(); - } - } - - private class TestTypedRecordHandlerWithContext : ITypedRecordHandlerWithContext - { - public Task HandleAsync(TestData data, ILambdaContext context, CancellationToken cancellationToken) - { - return Task.FromResult(RecordHandlerResult.None); - } - } - - private class TestTypedRecordHandlerWithContextProvider : ITypedRecordHandlerWithContextProvider - { - public ITypedRecordHandlerWithContext Create() - { - return new TestTypedRecordHandlerWithContext(); - } - } - - private class TestData - { - public string Message { get; set; } - public int Id { get; set; } - } - - // Removed TestJsonSerializerContext to avoid source generation conflicts } } diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/BatchProcessingTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/BatchProcessingTests.cs index 6d2040522..a59866e76 100644 --- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/BatchProcessingTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/BatchProcessingTests.cs @@ -1,4 +1,17 @@ - +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ using System; using System.Collections.Generic; diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/DeserializationErrorHandlingTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/DeserializationErrorHandlingTests.cs deleted file mode 100644 index 755e7712c..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/DeserializationErrorHandlingTests.cs +++ /dev/null @@ -1,225 +0,0 @@ - - -using System; -using System.Threading; -using System.Threading.Tasks; -using AWS.Lambda.Powertools.BatchProcessing; -using AWS.Lambda.Powertools.BatchProcessing.Exceptions; -using Xunit; - -namespace AWS.Lambda.Powertools.BatchProcessing.Tests; - -public class DeserializationErrorHandlingTests -{ - [Fact] - public void DeserializationException_WithAllParameters_SetsPropertiesCorrectly() - { - // Arrange - var recordData = "{\"invalid\": json}"; - var targetType = typeof(TestModel); - var recordId = "test-record-123"; - var innerException = new ArgumentException("Invalid JSON"); - - // Act - var exception = new DeserializationException(recordData, targetType, recordId, innerException); - - // Assert - Assert.Equal(recordData, exception.RecordData); - Assert.Equal(targetType, exception.TargetType); - Assert.Equal(recordId, exception.RecordId); - Assert.Equal(innerException, exception.InnerException); - Assert.Contains("Failed to deserialize record 'test-record-123' to type 'TestModel'", exception.Message); - } - - [Fact] - public void DeserializationException_WithoutRecordId_UsesUnknownAsDefault() - { - // Arrange - var recordData = "{\"invalid\": json}"; - var targetType = typeof(TestModel); - var innerException = new ArgumentException("Invalid JSON"); - - // Act - var exception = new DeserializationException(recordData, targetType, innerException); - - // Assert - Assert.Equal(recordData, exception.RecordData); - Assert.Equal(targetType, exception.TargetType); - Assert.Equal("Unknown", exception.RecordId); - Assert.Equal(innerException, exception.InnerException); - Assert.Contains("Failed to deserialize record 'Unknown' to type 'TestModel'", exception.Message); - } - - [Fact] - public void DeserializationException_WithNullTargetType_HandlesGracefully() - { - // Arrange - var recordData = "{\"invalid\": json}"; - Type targetType = null; - var recordId = "test-record-123"; - var innerException = new ArgumentException("Invalid JSON"); - - // Act - var exception = new DeserializationException(recordData, targetType, recordId, innerException); - - // Assert - Assert.Equal(recordData, exception.RecordData); - Assert.Null(exception.TargetType); - Assert.Equal(recordId, exception.RecordId); - Assert.Contains("Failed to deserialize record 'test-record-123' to type 'Unknown'", exception.Message); - } - - [Fact] - public void DeserializationException_WithMessageOnly_CreatesCorrectly() - { - // Arrange - var message = "Custom error message"; - - // Act - var exception = new DeserializationException(message); - - // Assert - Assert.Equal(message, exception.Message); - Assert.Null(exception.InnerException); - } - - [Fact] - public void DeserializationException_WithMessageAndInnerException_CreatesCorrectly() - { - // Arrange - var message = "Custom error message"; - var innerException = new ArgumentException("Inner error"); - - // Act - var exception = new DeserializationException(message, innerException); - - // Assert - Assert.Equal(message, exception.Message); - Assert.Equal(innerException, exception.InnerException); - } - - [Theory] - [InlineData(DeserializationErrorPolicy.FailRecord)] - [InlineData(DeserializationErrorPolicy.IgnoreRecord)] - [InlineData(DeserializationErrorPolicy.CustomHandler)] - public void DeserializationErrorPolicy_AllValuesAreDefined(DeserializationErrorPolicy policy) - { - // Act & Assert - Should not throw - var policyName = policy.ToString(); - Assert.NotNull(policyName); - Assert.NotEmpty(policyName); - } - - [Fact] - public void DeserializationOptions_DefaultErrorPolicy_IsFailRecord() - { - // Act - var options = new DeserializationOptions(); - - // Assert - Assert.Equal(DeserializationErrorPolicy.FailRecord, options.ErrorPolicy); - } - - [Fact] - public void DeserializationOptions_CanSetErrorPolicy() - { - // Arrange - var options = new DeserializationOptions(); - - // Act - options.ErrorPolicy = DeserializationErrorPolicy.IgnoreRecord; - - // Assert - Assert.Equal(DeserializationErrorPolicy.IgnoreRecord, options.ErrorPolicy); - } - - [Fact] - public void DeserializationOptions_BackwardCompatibility_IgnoreDeserializationErrorsStillWorks() - { - // Arrange - var options = new DeserializationOptions(); - - // Act - #pragma warning disable CS0618 // Type or member is obsolete - options.IgnoreDeserializationErrors = true; - #pragma warning restore CS0618 // Type or member is obsolete - - // Assert - #pragma warning disable CS0618 // Type or member is obsolete - Assert.True(options.IgnoreDeserializationErrors); - #pragma warning restore CS0618 // Type or member is obsolete - } - - [Fact] - public async Task TestDeserializationErrorHandler_HandleDeserializationError_ReturnsExpectedResult() - { - // Arrange - var handler = new TestDeserializationErrorHandler(); - var record = new TestRecord { Id = "test-123", Data = "test-data" }; - var exception = new DeserializationException("Test error"); - var cancellationToken = CancellationToken.None; - - // Act - var result = await handler.HandleDeserializationError(record, exception, cancellationToken); - - // Assert - Assert.NotNull(result); - Assert.Equal("Error handling record: test-123", result.Data); - } - - [Fact] - public async Task TestDeserializationErrorHandler_WithNullRecord_HandlesGracefully() - { - // Arrange - var handler = new TestDeserializationErrorHandler(); - TestRecord record = null; - var exception = new DeserializationException("Test error"); - var cancellationToken = CancellationToken.None; - - // Act - var result = await handler.HandleDeserializationError(record, exception, cancellationToken); - - // Assert - Assert.NotNull(result); - Assert.Equal("Error handling record: unknown", result.Data); - } - - [Fact] - public async Task TestDeserializationErrorHandler_WithCancellation_RespectsCancellationToken() - { - // Arrange - var handler = new TestDeserializationErrorHandler(); - var record = new TestRecord { Id = "test-123", Data = "test-data" }; - var exception = new DeserializationException("Test error"); - var cancellationTokenSource = new CancellationTokenSource(); - cancellationTokenSource.Cancel(); - - // Act & Assert - await Assert.ThrowsAsync( - () => handler.HandleDeserializationError(record, exception, cancellationTokenSource.Token)); - } - - // Test models and helpers - public class TestModel - { - public string Name { get; set; } - public int Value { get; set; } - } - - public class TestRecord - { - public string Id { get; set; } - public string Data { get; set; } - } - - public class TestDeserializationErrorHandler : IDeserializationErrorHandler - { - public Task HandleDeserializationError(TestRecord record, Exception exception, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var recordId = record?.Id ?? "unknown"; - return Task.FromResult(RecordHandlerResult.FromData($"Error handling record: {recordId}")); - } - } -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/Custom/CustomDynamoDbStreamBatchProcessors.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/Custom/CustomDynamoDbStreamBatchProcessors.cs index ca84f0a36..1abf56589 100644 --- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/Custom/CustomDynamoDbStreamBatchProcessors.cs +++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/Custom/CustomDynamoDbStreamBatchProcessors.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.Collections.Generic; using System.Linq; diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/Custom/CustomDynamoDbStreamHandlers.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/Custom/CustomDynamoDbStreamHandlers.cs index 033f59415..767c3b1da 100644 --- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/Custom/CustomDynamoDbStreamHandlers.cs +++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/Custom/CustomDynamoDbStreamHandlers.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.Text.Json; using System.Threading; diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/CustomProcessorTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/CustomProcessorTests.cs index e2b4d8ac7..bce9ed57b 100644 --- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/CustomProcessorTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/CustomProcessorTests.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using Xunit; using TestHelper = AWS.Lambda.Powertools.BatchProcessing.Tests.Helpers.Helpers; using System.Threading.Tasks; diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/Handler/HandlerFunction.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/Handler/HandlerFunction.cs index abce8320d..52d6f0871 100644 --- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/Handler/HandlerFunction.cs +++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/Handler/HandlerFunction.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.Text.Json; using System.Threading.Tasks; diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/HandlerTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/HandlerTests.cs index f7b3b1936..68d6d0837 100644 --- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/HandlerTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/HandlerTests.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.Threading.Tasks; using Amazon.Lambda.DynamoDBEvents; diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/HandlerValidationTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/HandlerValidationTests.cs index c7d79ab4f..9152ab162 100644 --- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/HandlerValidationTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/HandlerValidationTests.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using TestHelper = AWS.Lambda.Powertools.BatchProcessing.Tests.Helpers.Helpers; using Xunit; using System; diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/Services.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/Services.cs index 9475b9d55..224a2eecd 100644 --- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/Services.cs +++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/DynamoDB/Services.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using AWS.Lambda.Powertools.BatchProcessing.DynamoDb; using AWS.Lambda.Powertools.BatchProcessing.Tests.Handlers.DynamoDB.Custom; diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/Custom/CustomKinesisBatchProcessors.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/Custom/CustomKinesisBatchProcessors.cs index f0082a211..473bef733 100644 --- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/Custom/CustomKinesisBatchProcessors.cs +++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/Custom/CustomKinesisBatchProcessors.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.Collections.Generic; using System.Linq; diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/Custom/CustomKinesisHandlers.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/Custom/CustomKinesisHandlers.cs index 1dde72b81..27a517e9a 100644 --- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/Custom/CustomKinesisHandlers.cs +++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/Custom/CustomKinesisHandlers.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.Text.Json; using System.Threading; diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/CustomProcessorTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/CustomProcessorTests.cs index 07284bc3e..fe094737b 100644 --- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/CustomProcessorTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/CustomProcessorTests.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System.Threading.Tasks; using Amazon.Lambda.KinesisEvents; using AWS.Lambda.Powertools.BatchProcessing.Kinesis; diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/Handler/HandlerFunction.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/Handler/HandlerFunction.cs index 8d9864ef4..233b52def 100644 --- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/Handler/HandlerFunction.cs +++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/Handler/HandlerFunction.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.Text.Json; using System.Threading.Tasks; diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/HandlerTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/HandlerTests.cs index c0f0f7c68..07758fd38 100644 --- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/HandlerTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/HandlerTests.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.Threading.Tasks; using Amazon.Lambda.KinesisEvents; diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/HandlerValidationTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/HandlerValidationTests.cs index 88cee6d12..9f9398e6b 100644 --- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/HandlerValidationTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/HandlerValidationTests.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.Threading.Tasks; using Amazon.Lambda.KinesisEvents; diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/Services.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/Services.cs index 613d076fc..0c7fbc79c 100644 --- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/Services.cs +++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/Kinesis/Services.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using AWS.Lambda.Powertools.BatchProcessing.Kinesis; using AWS.Lambda.Powertools.BatchProcessing.Tests.Handlers.Kinesis.Custom; diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/Custom/CustomSqsBatchProcessors.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/Custom/CustomSqsBatchProcessors.cs index 19e95d839..af477753b 100644 --- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/Custom/CustomSqsBatchProcessors.cs +++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/Custom/CustomSqsBatchProcessors.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.Collections.Generic; using System.Linq; diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/Custom/CustomSqsHandlers.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/Custom/CustomSqsHandlers.cs index 1479b08de..06c87acdc 100644 --- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/Custom/CustomSqsHandlers.cs +++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/Custom/CustomSqsHandlers.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.Text.Json; using System.Threading; diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/CustomProcessorTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/CustomProcessorTests.cs index 5cf5ee91a..aba71da0e 100644 --- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/CustomProcessorTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/CustomProcessorTests.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System.Threading.Tasks; using Amazon.Lambda.SQSEvents; using AWS.Lambda.Powertools.BatchProcessing.Sqs; diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/Handler/HandlerFunction.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/Handler/HandlerFunction.cs index f7a73de30..9bfbf90bb 100644 --- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/Handler/HandlerFunction.cs +++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/Handler/HandlerFunction.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.Text.Json; using System.Threading.Tasks; @@ -132,7 +147,7 @@ public BatchItemFailuresResponse HandlerUsingAttributeAllFail_ThrowOnFullBatchFa public BatchItemFailuresResponse HandlerUsingAttributeAllFail_ThrowOnFullBatchFailureFalseEnv(SQSEvent _) { return SqsBatchProcessor.Result.BatchItemFailuresResponse; - } + } public async Task HandlerUsingUtilityAllFail_ThrowOnFullBatchFailureFalseOption(SQSEvent sqsEvent) { @@ -160,7 +175,7 @@ public BatchItemFailuresResponse HandlerUsingAttributeFailAll_StopOnFirstErrorAt public BatchItemFailuresResponse HandlerUsingAttributeFailAll_StopOnFirstErrorAttr_ThrowOnFullBatchFailureFalseEnv(SQSEvent _) { return SqsBatchProcessor.Result.BatchItemFailuresResponse; - } + } public async Task HandlerUsingUtility_StopOnFirstErrorOption_ThrowOnFullBatchFailureFalseOption(SQSEvent sqsEvent) { diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/HandlerTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/HandlerTests.cs index 2c31eb2ff..04bd0b214 100644 --- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/HandlerTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/HandlerTests.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.Threading.Tasks; using Amazon.Lambda.SQSEvents; @@ -28,8 +43,8 @@ public Task Sqs_Handler_Using_Attribute() Assert.Equal("4", response.BatchItemFailures[1].ItemIdentifier); return Task.CompletedTask; - } - + } + [Fact] public Task Sqs_Handler_All_Fail_Using_Attribute_Should_Throw_BatchProcessingException() { @@ -194,7 +209,7 @@ public Task Sqs_Handler_Using_Attribute_All_Fail_Should_Not_Throw_BatchProcessin Assert.Equal("5", response.BatchItemFailures[4].ItemIdentifier); return Task.CompletedTask; - } + } [Fact] public Task Sqs_Handler_Using_Attribute_All_Fail_Should_Not_Throw_BatchProcessingException_With_Throw_On_Full_Batch_Failure_False_Env() diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/HandlerValidationTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/HandlerValidationTests.cs index 075742146..6996a769c 100644 --- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/HandlerValidationTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/HandlerValidationTests.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.Threading.Tasks; using Amazon.Lambda.SQSEvents; diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/Services.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/Services.cs index 9339824d1..f6b4227bc 100644 --- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/Services.cs +++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Handlers/SQS/Services.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using AWS.Lambda.Powertools.BatchProcessing.Sqs; using AWS.Lambda.Powertools.BatchProcessing.Tests.Handlers.SQS.Custom; diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Helpers/Helpers.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Helpers/Helpers.cs index 792314996..da090c51d 100644 --- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Helpers/Helpers.cs +++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Helpers/Helpers.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System.Collections.Generic; using System.IO; using System.Linq; diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/ITypedBatchProcessorIntegrationTest.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/ITypedBatchProcessorIntegrationTest.cs deleted file mode 100644 index d139bdf90..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/ITypedBatchProcessorIntegrationTest.cs +++ /dev/null @@ -1,192 +0,0 @@ - - -using System; -using System.Threading; -using System.Threading.Tasks; -using Amazon.Lambda.Core; -using Amazon.Lambda.SQSEvents; -using Xunit; - -namespace AWS.Lambda.Powertools.BatchProcessing.Tests; - -/// -/// Integration tests to verify ITypedBatchProcessor interface accessibility and usage. -/// -public class ITypedBatchProcessorIntegrationTest -{ - [Fact] - public void ITypedBatchProcessor_ShouldBeAccessibleFromNamespace() - { - // Arrange & Act - var interfaceType = typeof(ITypedBatchProcessor); - - // Assert - Assert.NotNull(interfaceType); - Assert.True(interfaceType.IsInterface); - Assert.Equal("AWS.Lambda.Powertools.BatchProcessing", interfaceType.Namespace); - } - - [Fact] - public void ITypedBatchProcessor_ShouldHaveCorrectGenericParameters() - { - // Arrange & Act - var interfaceType = typeof(ITypedBatchProcessor<,>); - var genericParameters = interfaceType.GetGenericArguments(); - - // Assert - Assert.Equal(2, genericParameters.Length); - Assert.Equal("TEvent", genericParameters[0].Name); - Assert.Equal("TRecord", genericParameters[1].Name); - } - - [Fact] - public void ITypedBatchProcessor_ShouldBeImplementableByConcreteClass() - { - // Arrange & Act - var concreteType = typeof(TestTypedBatchProcessor); - var interfaceType = typeof(ITypedBatchProcessor); - - // Assert - Assert.True(interfaceType.IsAssignableFrom(concreteType)); - } - - [Fact] - public async Task ITypedBatchProcessor_ShouldSupportTypedRecordHandlers() - { - // Arrange - var processor = new TestTypedBatchProcessor(); - var handler = new TestTypedRecordHandler(); - var sqsEvent = new SQSEvent(); - - // Act - var result = await processor.ProcessAsync(sqsEvent, handler); - - // Assert - Assert.NotNull(result); - Assert.IsType>(result); - } - - [Fact] - public async Task ITypedBatchProcessor_ShouldSupportTypedRecordHandlersWithContext() - { - // Arrange - var processor = new TestTypedBatchProcessor(); - var handler = new TestTypedRecordHandlerWithContext(); - var sqsEvent = new SQSEvent(); - var context = new TestLambdaContext(); - - // Act - var result = await processor.ProcessAsync(sqsEvent, handler, context); - - // Assert - Assert.NotNull(result); - Assert.IsType>(result); - } - - /// - /// Test implementation of ITypedBatchProcessor for integration testing. - /// - public class TestTypedBatchProcessor : ITypedBatchProcessor - { - public ProcessingResult ProcessingResult { get; } = new ProcessingResult(); - - public Task> ProcessAsync(SQSEvent @event, ITypedRecordHandler recordHandler) - { - return Task.FromResult(ProcessingResult); - } - - public Task> ProcessAsync(SQSEvent @event, ITypedRecordHandler recordHandler, DeserializationOptions deserializationOptions) - { - return Task.FromResult(ProcessingResult); - } - - public Task> ProcessAsync(SQSEvent @event, ITypedRecordHandler recordHandler, CancellationToken cancellationToken) - { - return Task.FromResult(ProcessingResult); - } - - public Task> ProcessAsync(SQSEvent @event, ITypedRecordHandler recordHandler, DeserializationOptions deserializationOptions, CancellationToken cancellationToken) - { - return Task.FromResult(ProcessingResult); - } - - public Task> ProcessAsync(SQSEvent @event, ITypedRecordHandler recordHandler, DeserializationOptions deserializationOptions, ProcessingOptions processingOptions) - { - return Task.FromResult(ProcessingResult); - } - - public Task> ProcessAsync(SQSEvent @event, ITypedRecordHandlerWithContext recordHandler, ILambdaContext context) - { - return Task.FromResult(ProcessingResult); - } - - public Task> ProcessAsync(SQSEvent @event, ITypedRecordHandlerWithContext recordHandler, ILambdaContext context, DeserializationOptions deserializationOptions) - { - return Task.FromResult(ProcessingResult); - } - - public Task> ProcessAsync(SQSEvent @event, ITypedRecordHandlerWithContext recordHandler, ILambdaContext context, CancellationToken cancellationToken) - { - return Task.FromResult(ProcessingResult); - } - - public Task> ProcessAsync(SQSEvent @event, ITypedRecordHandlerWithContext recordHandler, ILambdaContext context, DeserializationOptions deserializationOptions, CancellationToken cancellationToken) - { - return Task.FromResult(ProcessingResult); - } - - public Task> ProcessAsync(SQSEvent @event, ITypedRecordHandlerWithContext recordHandler, ILambdaContext context, DeserializationOptions deserializationOptions, ProcessingOptions processingOptions) - { - return Task.FromResult(ProcessingResult); - } - } - - /// - /// Test typed record handler. - /// - public class TestTypedRecordHandler : ITypedRecordHandler - { - public Task HandleAsync(TestData data, CancellationToken cancellationToken) - { - return Task.FromResult(RecordHandlerResult.FromData("Success")); - } - } - - /// - /// Test typed record handler with context. - /// - public class TestTypedRecordHandlerWithContext : ITypedRecordHandlerWithContext - { - public Task HandleAsync(TestData data, ILambdaContext context, CancellationToken cancellationToken) - { - return Task.FromResult(RecordHandlerResult.FromData("Success with context")); - } - } - - /// - /// Test data class. - /// - public class TestData - { - public string Id { get; set; } - public string Name { get; set; } - } - - /// - /// Test Lambda context. - /// - public class TestLambdaContext : ILambdaContext - { - public string AwsRequestId { get; set; } = "test-request-id"; - public IClientContext ClientContext { get; set; } - public string FunctionName { get; set; } = "test-function"; - public string FunctionVersion { get; set; } = "1.0"; - public ICognitoIdentity Identity { get; set; } - public string InvokedFunctionArn { get; set; } = "arn:aws:lambda:us-east-1:123456789012:function:test-function"; - public ILambdaLogger Logger { get; set; } - public string LogGroupName { get; set; } = "/aws/lambda/test-function"; - public string LogStreamName { get; set; } = "2023/01/01/[$LATEST]abcdef123456"; - public int MemoryLimitInMB { get; set; } = 128; - public TimeSpan RemainingTime { get; set; } = TimeSpan.FromMinutes(5); - } -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/ITypedBatchProcessorTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/ITypedBatchProcessorTests.cs deleted file mode 100644 index 5b68dfe78..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/ITypedBatchProcessorTests.cs +++ /dev/null @@ -1,382 +0,0 @@ - - -using System; -using System.Threading; -using System.Threading.Tasks; -using Amazon.Lambda.Core; -using Amazon.Lambda.SQSEvents; -using Xunit; - -namespace AWS.Lambda.Powertools.BatchProcessing.Tests; - -/// -/// Tests for ITypedBatchProcessor interface contracts. -/// -public class ITypedBatchProcessorTests -{ - /// - /// Test data class for testing typed record handlers. - /// - public class TestData - { - public string Id { get; set; } - public string Name { get; set; } - public int Value { get; set; } - } - - /// - /// Mock implementation of ITypedBatchProcessor for testing interface contracts. - /// - public class MockTypedBatchProcessor : ITypedBatchProcessor - { - public ProcessingResult ProcessingResult { get; private set; } - - public MockTypedBatchProcessor() - { - ProcessingResult = new ProcessingResult(); - } - - public Task> ProcessAsync(SQSEvent @event, ITypedRecordHandler recordHandler) - { - return Task.FromResult(ProcessingResult); - } - - public Task> ProcessAsync(SQSEvent @event, ITypedRecordHandler recordHandler, DeserializationOptions deserializationOptions) - { - return Task.FromResult(ProcessingResult); - } - - public Task> ProcessAsync(SQSEvent @event, ITypedRecordHandler recordHandler, CancellationToken cancellationToken) - { - return Task.FromResult(ProcessingResult); - } - - public Task> ProcessAsync(SQSEvent @event, ITypedRecordHandler recordHandler, DeserializationOptions deserializationOptions, CancellationToken cancellationToken) - { - return Task.FromResult(ProcessingResult); - } - - public Task> ProcessAsync(SQSEvent @event, ITypedRecordHandler recordHandler, DeserializationOptions deserializationOptions, ProcessingOptions processingOptions) - { - return Task.FromResult(ProcessingResult); - } - - public Task> ProcessAsync(SQSEvent @event, ITypedRecordHandlerWithContext recordHandler, ILambdaContext context) - { - return Task.FromResult(ProcessingResult); - } - - public Task> ProcessAsync(SQSEvent @event, ITypedRecordHandlerWithContext recordHandler, ILambdaContext context, DeserializationOptions deserializationOptions) - { - return Task.FromResult(ProcessingResult); - } - - public Task> ProcessAsync(SQSEvent @event, ITypedRecordHandlerWithContext recordHandler, ILambdaContext context, CancellationToken cancellationToken) - { - return Task.FromResult(ProcessingResult); - } - - public Task> ProcessAsync(SQSEvent @event, ITypedRecordHandlerWithContext recordHandler, ILambdaContext context, DeserializationOptions deserializationOptions, CancellationToken cancellationToken) - { - return Task.FromResult(ProcessingResult); - } - - public Task> ProcessAsync(SQSEvent @event, ITypedRecordHandlerWithContext recordHandler, ILambdaContext context, DeserializationOptions deserializationOptions, ProcessingOptions processingOptions) - { - return Task.FromResult(ProcessingResult); - } - } - - /// - /// Mock typed record handler for testing. - /// - public class MockTypedRecordHandler : ITypedRecordHandler - { - public Task HandleAsync(TestData data, CancellationToken cancellationToken) - { - return Task.FromResult(RecordHandlerResult.FromData($"Processed: {data?.Name}")); - } - } - - /// - /// Mock typed record handler with context for testing. - /// - public class MockTypedRecordHandlerWithContext : ITypedRecordHandlerWithContext - { - public Task HandleAsync(TestData data, ILambdaContext context, CancellationToken cancellationToken) - { - return Task.FromResult(RecordHandlerResult.FromData($"Processed: {data?.Name} with context")); - } - } - - /// - /// Mock Lambda context for testing. - /// - public class MockLambdaContext : ILambdaContext - { - public string AwsRequestId { get; set; } = "test-request-id"; - public IClientContext ClientContext { get; set; } - public string FunctionName { get; set; } = "test-function"; - public string FunctionVersion { get; set; } = "1.0"; - public ICognitoIdentity Identity { get; set; } - public string InvokedFunctionArn { get; set; } = "arn:aws:lambda:us-east-1:123456789012:function:test-function"; - public ILambdaLogger Logger { get; set; } - public string LogGroupName { get; set; } = "/aws/lambda/test-function"; - public string LogStreamName { get; set; } = "2023/01/01/[$LATEST]abcdef123456"; - public int MemoryLimitInMB { get; set; } = 128; - public TimeSpan RemainingTime { get; set; } = TimeSpan.FromMinutes(5); - } - - [Fact] - public void ProcessingResult_Property_ShouldBeAccessible() - { - // Arrange - var processor = new MockTypedBatchProcessor(); - - // Act - var result = processor.ProcessingResult; - - // Assert - Assert.NotNull(result); - Assert.IsType>(result); - } - - [Fact] - public async Task ProcessAsync_WithTypedHandler_ShouldReturnProcessingResult() - { - // Arrange - var processor = new MockTypedBatchProcessor(); - var handler = new MockTypedRecordHandler(); - var testEvent = new SQSEvent(); - - // Act - var result = await processor.ProcessAsync(testEvent, handler); - - // Assert - Assert.NotNull(result); - Assert.IsType>(result); - } - - [Fact] - public async Task ProcessAsync_WithTypedHandlerAndDeserializationOptions_ShouldReturnProcessingResult() - { - // Arrange - var processor = new MockTypedBatchProcessor(); - var handler = new MockTypedRecordHandler(); - var testEvent = new SQSEvent(); - var deserializationOptions = new DeserializationOptions(); - - // Act - var result = await processor.ProcessAsync(testEvent, handler, deserializationOptions); - - // Assert - Assert.NotNull(result); - Assert.IsType>(result); - } - - [Fact] - public async Task ProcessAsync_WithTypedHandlerAndCancellationToken_ShouldReturnProcessingResult() - { - // Arrange - var processor = new MockTypedBatchProcessor(); - var handler = new MockTypedRecordHandler(); - var testEvent = new SQSEvent(); - var cancellationToken = new CancellationToken(); - - // Act - var result = await processor.ProcessAsync(testEvent, handler, cancellationToken); - - // Assert - Assert.NotNull(result); - Assert.IsType>(result); - } - - [Fact] - public async Task ProcessAsync_WithTypedHandlerDeserializationOptionsAndCancellationToken_ShouldReturnProcessingResult() - { - // Arrange - var processor = new MockTypedBatchProcessor(); - var handler = new MockTypedRecordHandler(); - var testEvent = new SQSEvent(); - var deserializationOptions = new DeserializationOptions(); - var cancellationToken = new CancellationToken(); - - // Act - var result = await processor.ProcessAsync(testEvent, handler, deserializationOptions, cancellationToken); - - // Assert - Assert.NotNull(result); - Assert.IsType>(result); - } - - [Fact] - public async Task ProcessAsync_WithTypedHandlerDeserializationOptionsAndProcessingOptions_ShouldReturnProcessingResult() - { - // Arrange - var processor = new MockTypedBatchProcessor(); - var handler = new MockTypedRecordHandler(); - var testEvent = new SQSEvent(); - var deserializationOptions = new DeserializationOptions(); - var processingOptions = new ProcessingOptions(); - - // Act - var result = await processor.ProcessAsync(testEvent, handler, deserializationOptions, processingOptions); - - // Assert - Assert.NotNull(result); - Assert.IsType>(result); - } - - [Fact] - public async Task ProcessAsync_WithTypedHandlerWithContext_ShouldReturnProcessingResult() - { - // Arrange - var processor = new MockTypedBatchProcessor(); - var handler = new MockTypedRecordHandlerWithContext(); - var testEvent = new SQSEvent(); - var context = new MockLambdaContext(); - - // Act - var result = await processor.ProcessAsync(testEvent, handler, context); - - // Assert - Assert.NotNull(result); - Assert.IsType>(result); - } - - [Fact] - public async Task ProcessAsync_WithTypedHandlerWithContextAndDeserializationOptions_ShouldReturnProcessingResult() - { - // Arrange - var processor = new MockTypedBatchProcessor(); - var handler = new MockTypedRecordHandlerWithContext(); - var testEvent = new SQSEvent(); - var context = new MockLambdaContext(); - var deserializationOptions = new DeserializationOptions(); - - // Act - var result = await processor.ProcessAsync(testEvent, handler, context, deserializationOptions); - - // Assert - Assert.NotNull(result); - Assert.IsType>(result); - } - - [Fact] - public async Task ProcessAsync_WithTypedHandlerWithContextAndCancellationToken_ShouldReturnProcessingResult() - { - // Arrange - var processor = new MockTypedBatchProcessor(); - var handler = new MockTypedRecordHandlerWithContext(); - var testEvent = new SQSEvent(); - var context = new MockLambdaContext(); - var cancellationToken = new CancellationToken(); - - // Act - var result = await processor.ProcessAsync(testEvent, handler, context, cancellationToken); - - // Assert - Assert.NotNull(result); - Assert.IsType>(result); - } - - [Fact] - public async Task ProcessAsync_WithTypedHandlerWithContextDeserializationOptionsAndCancellationToken_ShouldReturnProcessingResult() - { - // Arrange - var processor = new MockTypedBatchProcessor(); - var handler = new MockTypedRecordHandlerWithContext(); - var testEvent = new SQSEvent(); - var context = new MockLambdaContext(); - var deserializationOptions = new DeserializationOptions(); - var cancellationToken = new CancellationToken(); - - // Act - var result = await processor.ProcessAsync(testEvent, handler, context, deserializationOptions, cancellationToken); - - // Assert - Assert.NotNull(result); - Assert.IsType>(result); - } - - [Fact] - public async Task ProcessAsync_WithTypedHandlerWithContextDeserializationOptionsAndProcessingOptions_ShouldReturnProcessingResult() - { - // Arrange - var processor = new MockTypedBatchProcessor(); - var handler = new MockTypedRecordHandlerWithContext(); - var testEvent = new SQSEvent(); - var context = new MockLambdaContext(); - var deserializationOptions = new DeserializationOptions(); - var processingOptions = new ProcessingOptions(); - - // Act - var result = await processor.ProcessAsync(testEvent, handler, context, deserializationOptions, processingOptions); - - // Assert - Assert.NotNull(result); - Assert.IsType>(result); - } - - [Fact] - public void Interface_ShouldHaveCorrectGenericConstraints() - { - // Arrange & Act - var interfaceType = typeof(ITypedBatchProcessor<,>); - var genericParameters = interfaceType.GetGenericArguments(); - - // Assert - Assert.Equal(2, genericParameters.Length); - - // TEvent should be contravariant (in) - Assert.True(genericParameters[0].GenericParameterAttributes.HasFlag(System.Reflection.GenericParameterAttributes.Contravariant)); - - // TRecord should be invariant (no variance) - Assert.False(genericParameters[1].GenericParameterAttributes.HasFlag(System.Reflection.GenericParameterAttributes.Contravariant)); - Assert.False(genericParameters[1].GenericParameterAttributes.HasFlag(System.Reflection.GenericParameterAttributes.Covariant)); - } - - [Fact] - public void Interface_ShouldInheritFromCorrectNamespace() - { - // Arrange & Act - var interfaceType = typeof(ITypedBatchProcessor<,>); - - // Assert - Assert.Equal("AWS.Lambda.Powertools.BatchProcessing", interfaceType.Namespace); - } - - [Fact] - public void Interface_ShouldHaveCorrectMethodSignatures() - { - // Arrange & Act - var interfaceType = typeof(ITypedBatchProcessor<,>); - var methods = interfaceType.GetMethods(); - - // Assert - Assert.True(methods.Length >= 10); // Should have at least 10 ProcessAsync overloads plus ProcessingResult property getter - - // Check that all ProcessAsync methods are generic - var processAsyncMethods = Array.FindAll(methods, m => m.Name == "ProcessAsync"); - Assert.True(processAsyncMethods.Length >= 10); - - foreach (var method in processAsyncMethods) - { - Assert.True(method.IsGenericMethodDefinition); - Assert.Equal(1, method.GetGenericArguments().Length); // Should have one generic parameter T - } - } - - [Fact] - public void Interface_ProcessingResult_Property_ShouldBeReadOnly() - { - // Arrange & Act - var interfaceType = typeof(ITypedBatchProcessor<,>); - var property = interfaceType.GetProperty("ProcessingResult"); - - // Assert - Assert.NotNull(property); - Assert.True(property.CanRead); - Assert.False(property.CanWrite); // Should be read-only - } -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Internal/BatchProcessingInternalTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Internal/BatchProcessingInternalTests.cs index ed12994a4..299956ec0 100644 --- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Internal/BatchProcessingInternalTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/Internal/BatchProcessingInternalTests.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using AWS.Lambda.Powertools.BatchProcessing.DynamoDb; using AWS.Lambda.Powertools.BatchProcessing.Kinesis; using AWS.Lambda.Powertools.BatchProcessing.Sqs; @@ -13,15 +28,25 @@ public class BatchProcessingInternalTests public void BatchProcessing_Set_Execution_Environment_Context_SQS() { // Arrange - var env = new PowertoolsEnvironment(); - var conf = new PowertoolsConfigurations(env); + var assemblyName = "AWS.Lambda.Powertools.BatchProcessing"; + var assemblyVersion = "1.0.0"; + + var env = Substitute.For(); + env.GetAssemblyName(Arg.Any()).Returns(assemblyName); + env.GetAssemblyVersion(Arg.Any()).ReturnsForAnyArgs(assemblyVersion); + + var conf = new PowertoolsConfigurations(new SystemWrapper(env)); // Act var sqsBatchProcessor = new SqsBatchProcessor(conf); // Assert - Assert.Contains($"{Constants.FeatureContextIdentifier}/BatchProcessing/", - env.GetEnvironmentVariable("AWS_EXECUTION_ENV")); + env.Received(1).SetEnvironmentVariable( + "AWS_EXECUTION_ENV", + $"{Constants.FeatureContextIdentifier}/BatchProcessing/{assemblyVersion}" + ); + + env.Received(1).GetEnvironmentVariable("AWS_EXECUTION_ENV"); Assert.NotNull(sqsBatchProcessor); } @@ -30,15 +55,25 @@ public void BatchProcessing_Set_Execution_Environment_Context_SQS() public void BatchProcessing_Set_Execution_Environment_Context_Kinesis() { // Arrange - var env = new PowertoolsEnvironment(); - var conf = new PowertoolsConfigurations(env); + var assemblyName = "AWS.Lambda.Powertools.BatchProcessing"; + var assemblyVersion = "1.0.0"; + + var env = Substitute.For(); + env.GetAssemblyName(Arg.Any()).Returns(assemblyName); + env.GetAssemblyVersion(Arg.Any()).ReturnsForAnyArgs(assemblyVersion); + + var conf = new PowertoolsConfigurations(new SystemWrapper(env)); // Act var KinesisEventBatchProcessor = new KinesisEventBatchProcessor(conf); // Assert - Assert.Contains($"{Constants.FeatureContextIdentifier}/BatchProcessing/", - env.GetEnvironmentVariable("AWS_EXECUTION_ENV")); + env.Received(1).SetEnvironmentVariable( + "AWS_EXECUTION_ENV", + $"{Constants.FeatureContextIdentifier}/BatchProcessing/{assemblyVersion}" + ); + + env.Received(1).GetEnvironmentVariable("AWS_EXECUTION_ENV"); Assert.NotNull(KinesisEventBatchProcessor); } @@ -47,15 +82,25 @@ public void BatchProcessing_Set_Execution_Environment_Context_Kinesis() public void BatchProcessing_Set_Execution_Environment_Context_DynamoDB() { // Arrange - var env = new PowertoolsEnvironment(); - var conf = new PowertoolsConfigurations(env); + var assemblyName = "AWS.Lambda.Powertools.BatchProcessing"; + var assemblyVersion = "1.0.0"; + + var env = Substitute.For(); + env.GetAssemblyName(Arg.Any()).Returns(assemblyName); + env.GetAssemblyVersion(Arg.Any()).ReturnsForAnyArgs(assemblyVersion); + + var conf = new PowertoolsConfigurations(new SystemWrapper(env)); // Act var dynamoDbStreamBatchProcessor = new DynamoDbStreamBatchProcessor(conf); // Assert - Assert.Contains($"{Constants.FeatureContextIdentifier}/BatchProcessing/", - env.GetEnvironmentVariable("AWS_EXECUTION_ENV")); + env.Received(1).SetEnvironmentVariable( + "AWS_EXECUTION_ENV", + $"{Constants.FeatureContextIdentifier}/BatchProcessing/{assemblyVersion}" + ); + + env.Received(1).GetEnvironmentVariable("AWS_EXECUTION_ENV"); Assert.NotNull(dynamoDbStreamBatchProcessor); } diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/JsonDeserializationServiceTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/JsonDeserializationServiceTests.cs deleted file mode 100644 index feed52549..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/JsonDeserializationServiceTests.cs +++ /dev/null @@ -1,357 +0,0 @@ - - -using System; -using System.Text.Json; -using System.Text.Json.Serialization; -using AWS.Lambda.Powertools.BatchProcessing.Exceptions; -using Xunit; - -namespace AWS.Lambda.Powertools.BatchProcessing.Tests; - -[Collection("Sequential")] -public partial class JsonDeserializationServiceTests -{ - private readonly JsonDeserializationService _service; - - public JsonDeserializationServiceTests() - { - _service = new JsonDeserializationService(); - } - - #region Test Models - - public class TestProduct - { - public int Id { get; set; } - public string Name { get; set; } - public decimal Price { get; set; } - } - - public class TestOrder - { - public string OrderId { get; set; } - public DateTime OrderDate { get; set; } - public TestProduct[] Items { get; set; } - } - - [JsonSerializable(typeof(TestProduct))] - [JsonSerializable(typeof(TestOrder))] - [JsonSerializable(typeof(string))] - [JsonSerializable(typeof(int))] - public partial class TestJsonSerializerContext : JsonSerializerContext - { - } - - #endregion - - #region Deserialize Tests - - [Fact] - public void Deserialize_ValidJson_ReturnsDeserializedObject() - { - // Arrange - var json = """{"Id":1,"Name":"Test Product","Price":99.99}"""; - - // Act - var result = _service.Deserialize(json); - - // Assert - Assert.NotNull(result); - Assert.Equal(1, result.Id); - Assert.Equal("Test Product", result.Name); - Assert.Equal(99.99m, result.Price); - } - - [Fact] - public void Deserialize_WithJsonSerializerOptions_ReturnsDeserializedObject() - { - // Arrange - var json = """{"id":1,"name":"Test Product","price":99.99}"""; - var options = new DeserializationOptions(new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true - }); - - // Act - var result = _service.Deserialize(json, options); - - // Assert - Assert.NotNull(result); - Assert.Equal(1, result.Id); - Assert.Equal("Test Product", result.Name); - Assert.Equal(99.99m, result.Price); - } - - [Fact] - public void Deserialize_WithJsonSerializerContext_ReturnsDeserializedObject() - { - // Arrange - var json = """{"Id":1,"Name":"Test Product","Price":99.99}"""; - var options = new DeserializationOptions(TestJsonSerializerContext.Default); - - // Act - var result = _service.Deserialize(json, options); - - // Assert - Assert.NotNull(result); - Assert.Equal(1, result.Id); - Assert.Equal("Test Product", result.Name); - Assert.Equal(99.99m, result.Price); - } - - [Fact] - public void Deserialize_ComplexObject_ReturnsDeserializedObject() - { - // Arrange - var json = """{"OrderId":"ORD-123","OrderDate":"2023-01-01T00:00:00Z","Items":[{"Id":1,"Name":"Product 1","Price":10.00},{"Id":2,"Name":"Product 2","Price":20.00}]}"""; - - // Act - var result = _service.Deserialize(json); - - // Assert - Assert.NotNull(result); - Assert.Equal("ORD-123", result.OrderId); - Assert.Equal(new DateTime(2023, 1, 1, 0, 0, 0, DateTimeKind.Utc), result.OrderDate); - Assert.NotNull(result.Items); - Assert.Equal(2, result.Items.Length); - Assert.Equal("Product 1", result.Items[0].Name); - Assert.Equal("Product 2", result.Items[1].Name); - } - - [Fact] - public void Deserialize_PrimitiveTypes_ReturnsDeserializedValues() - { - // Arrange & Act & Assert - Assert.Equal(42, _service.Deserialize("42")); - Assert.Equal("test", _service.Deserialize("\"test\"")); - Assert.True(_service.Deserialize("true")); - Assert.Equal(3.14, _service.Deserialize("3.14")); - } - - [Fact] - public void Deserialize_NullData_ThrowsDeserializationException() - { - // Act & Assert - var exception = Assert.Throws(() => _service.Deserialize(null)); - Assert.Contains("Data cannot be null or empty", exception.Message); - Assert.IsType(exception.InnerException); - } - - [Fact] - public void Deserialize_EmptyData_ThrowsDeserializationException() - { - // Act & Assert - var exception = Assert.Throws(() => _service.Deserialize("")); - Assert.Contains("Data cannot be null or empty", exception.Message); - Assert.IsType(exception.InnerException); - } - - [Fact] - public void Deserialize_InvalidJson_ThrowsDeserializationException() - { - // Arrange - var invalidJson = "{invalid json}"; - - // Act & Assert - var exception = Assert.Throws(() => _service.Deserialize(invalidJson)); - Assert.Equal(invalidJson, exception.RecordData); - Assert.Equal(typeof(TestProduct), exception.TargetType); - Assert.IsType(exception.InnerException); - } - - [Fact] - public void Deserialize_InvalidJsonWithIgnoreErrors_ReturnsDefault() - { - // Arrange - var invalidJson = "{invalid json}"; - var options = new DeserializationOptions { IgnoreDeserializationErrors = true }; - - // Act - var result = _service.Deserialize(invalidJson, options); - - // Assert - Assert.Null(result); - } - - #endregion - - #region TryDeserialize Tests - - [Fact] - public void TryDeserialize_ValidJson_ReturnsTrue() - { - // Arrange - var json = """{"Id":1,"Name":"Test Product","Price":99.99}"""; - - // Act - var success = _service.TryDeserialize(json, out var result); - - // Assert - Assert.True(success); - Assert.NotNull(result); - Assert.Equal(1, result.Id); - Assert.Equal("Test Product", result.Name); - Assert.Equal(99.99m, result.Price); - } - - [Fact] - public void TryDeserialize_InvalidJson_ReturnsFalse() - { - // Arrange - var invalidJson = "{invalid json}"; - - // Act - var success = _service.TryDeserialize(invalidJson, out var result); - - // Assert - Assert.False(success); - Assert.Null(result); - } - - [Fact] - public void TryDeserialize_NullData_ReturnsFalse() - { - // Act - var success = _service.TryDeserialize(null, out var result); - - // Assert - Assert.False(success); - Assert.Null(result); - } - - [Fact] - public void TryDeserialize_WithException_ReturnsFalseAndException() - { - // Arrange - var invalidJson = "{invalid json}"; - - // Act - var success = _service.TryDeserialize(invalidJson, out var result, out var exception); - - // Assert - Assert.False(success); - Assert.Null(result); - Assert.NotNull(exception); - Assert.IsType(exception); - } - - [Fact] - public void TryDeserialize_WithJsonSerializerContext_ReturnsTrue() - { - // Arrange - var json = """{"Id":1,"Name":"Test Product","Price":99.99}"""; - var options = new DeserializationOptions(TestJsonSerializerContext.Default); - - // Act - var success = _service.TryDeserialize(json, out var result, options); - - // Assert - Assert.True(success); - Assert.NotNull(result); - Assert.Equal(1, result.Id); - } - - #endregion - - #region DeserializationOptions Tests - - [Fact] - public void DeserializationOptions_DefaultConstructor_SetsDefaults() - { - // Act - var options = new DeserializationOptions(); - - // Assert - Assert.Null(options.JsonSerializerContext); - Assert.Null(options.JsonSerializerOptions); - Assert.False(options.IgnoreDeserializationErrors); - } - - [Fact] - public void DeserializationOptions_JsonSerializerContextConstructor_SetsContext() - { - // Arrange - var context = TestJsonSerializerContext.Default; - - // Act - var options = new DeserializationOptions(context); - - // Assert - Assert.Equal(context, options.JsonSerializerContext); - Assert.Null(options.JsonSerializerOptions); - Assert.False(options.IgnoreDeserializationErrors); - } - - [Fact] - public void DeserializationOptions_JsonSerializerOptionsConstructor_SetsOptions() - { - // Arrange - var jsonOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; - - // Act - var options = new DeserializationOptions(jsonOptions); - - // Assert - Assert.Null(options.JsonSerializerContext); - Assert.Equal(jsonOptions, options.JsonSerializerOptions); - Assert.False(options.IgnoreDeserializationErrors); - } - - #endregion - - #region Singleton Tests - - [Fact] - public void Instance_ReturnsSameInstance() - { - // Act - var instance1 = JsonDeserializationService.Instance; - var instance2 = JsonDeserializationService.Instance; - - // Assert - Assert.Same(instance1, instance2); - } - - [Fact] - public void Instance_IsNotNull() - { - // Act - var instance = JsonDeserializationService.Instance; - - // Assert - Assert.NotNull(instance); - } - - #endregion - - #region Edge Cases - - [Fact] - public void Deserialize_JsonSerializerContextTakesPrecedence_OverJsonSerializerOptions() - { - // Arrange - var json = """{"Id":1,"Name":"Test Product","Price":99.99}"""; - var options = new DeserializationOptions - { - JsonSerializerContext = TestJsonSerializerContext.Default, - JsonSerializerOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = false } - }; - - // Act - var result = _service.Deserialize(json, options); - - // Assert - Should succeed because JsonSerializerContext is used instead of JsonSerializerOptions - Assert.NotNull(result); - Assert.Equal(1, result.Id); - } - - [Fact] - public void Deserialize_WhitespaceOnlyData_ThrowsDeserializationException() - { - // Act & Assert - var exception = Assert.Throws(() => _service.Deserialize(" ")); - Assert.Contains("Data cannot be null or empty", exception.Message); - } - - #endregion -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/RecordDataExtractorTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/RecordDataExtractorTests.cs deleted file mode 100644 index 3735e1012..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/RecordDataExtractorTests.cs +++ /dev/null @@ -1,317 +0,0 @@ - - -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Text.Json; -using Amazon.Lambda.DynamoDBEvents; -using Amazon.Lambda.KinesisEvents; -using Amazon.Lambda.SQSEvents; -using AWS.Lambda.Powertools.BatchProcessing.DynamoDb; -using AWS.Lambda.Powertools.BatchProcessing.Kinesis; -using AWS.Lambda.Powertools.BatchProcessing.Sqs; -using Xunit; - -namespace AWS.Lambda.Powertools.BatchProcessing.Tests; - -public class RecordDataExtractorTests -{ - #region SQS Record Data Extractor Tests - - [Fact] - public void SqsRecordDataExtractor_ExtractData_ReturnsMessageBody() - { - // Arrange - var extractor = SqsRecordDataExtractor.Instance; - var messageBody = "{\"orderId\": \"12345\", \"amount\": 99.99}"; - var sqsMessage = new SQSEvent.SQSMessage - { - Body = messageBody, - MessageId = "test-message-id" - }; - - // Act - var result = extractor.ExtractData(sqsMessage); - - // Assert - Assert.Equal(messageBody, result); - } - - [Fact] - public void SqsRecordDataExtractor_ExtractData_WithNullRecord_ReturnsEmptyString() - { - // Arrange - var extractor = SqsRecordDataExtractor.Instance; - - // Act - var result = extractor.ExtractData(null); - - // Assert - Assert.Equal(string.Empty, result); - } - - [Fact] - public void SqsRecordDataExtractor_ExtractData_WithNullBody_ReturnsEmptyString() - { - // Arrange - var extractor = SqsRecordDataExtractor.Instance; - var sqsMessage = new SQSEvent.SQSMessage - { - Body = null, - MessageId = "test-message-id" - }; - - // Act - var result = extractor.ExtractData(sqsMessage); - - // Assert - Assert.Equal(string.Empty, result); - } - - [Fact] - public void SqsRecordDataExtractor_Instance_IsSingleton() - { - // Arrange & Act - var instance1 = SqsRecordDataExtractor.Instance; - var instance2 = SqsRecordDataExtractor.Instance; - - // Assert - Assert.Same(instance1, instance2); - } - - #endregion - - #region Kinesis Record Data Extractor Tests - - [Fact] - public void KinesisRecordDataExtractor_ExtractData_ReadsFromMemoryStream() - { - // Arrange - var extractor = KinesisRecordDataExtractor.Instance; - var originalData = "{\"userId\": \"user123\", \"action\": \"login\"}"; - var dataStream = new MemoryStream(Encoding.UTF8.GetBytes(originalData)); - - var kinesisRecord = new KinesisEvent.KinesisEventRecord - { - Kinesis = new KinesisEvent.Record - { - Data = dataStream, - SequenceNumber = "12345" - } - }; - - // Act - var result = extractor.ExtractData(kinesisRecord); - - // Assert - Assert.Equal(originalData, result); - } - - [Fact] - public void KinesisRecordDataExtractor_ExtractData_WithEmptyStream_ReturnsEmptyString() - { - // Arrange - var extractor = KinesisRecordDataExtractor.Instance; - var emptyStream = new MemoryStream(); - - var kinesisRecord = new KinesisEvent.KinesisEventRecord - { - Kinesis = new KinesisEvent.Record - { - Data = emptyStream, - SequenceNumber = "12345" - } - }; - - // Act - var result = extractor.ExtractData(kinesisRecord); - - // Assert - Assert.Equal(string.Empty, result); - } - - [Fact] - public void KinesisRecordDataExtractor_ExtractData_WithNullRecord_ReturnsEmptyString() - { - // Arrange - var extractor = KinesisRecordDataExtractor.Instance; - - // Act - var result = extractor.ExtractData(null); - - // Assert - Assert.Equal(string.Empty, result); - } - - [Fact] - public void KinesisRecordDataExtractor_ExtractData_WithNullKinesisData_ReturnsEmptyString() - { - // Arrange - var extractor = KinesisRecordDataExtractor.Instance; - var kinesisRecord = new KinesisEvent.KinesisEventRecord - { - Kinesis = new KinesisEvent.Record - { - Data = null, - SequenceNumber = "12345" - } - }; - - // Act - var result = extractor.ExtractData(kinesisRecord); - - // Assert - Assert.Equal(string.Empty, result); - } - - [Fact] - public void KinesisRecordDataExtractor_ExtractData_WithNullKinesis_ReturnsEmptyString() - { - // Arrange - var extractor = KinesisRecordDataExtractor.Instance; - var kinesisRecord = new KinesisEvent.KinesisEventRecord - { - Kinesis = null - }; - - // Act - var result = extractor.ExtractData(kinesisRecord); - - // Assert - Assert.Equal(string.Empty, result); - } - - [Fact] - public void KinesisRecordDataExtractor_Instance_IsSingleton() - { - // Arrange & Act - var instance1 = KinesisRecordDataExtractor.Instance; - var instance2 = KinesisRecordDataExtractor.Instance; - - // Assert - Assert.Same(instance1, instance2); - } - - #endregion - - #region DynamoDB Record Data Extractor Tests - - [Fact] - public void DynamoDbRecordDataExtractor_ExtractData_SerializesDynamoDbRecord() - { - // Arrange - var extractor = DynamoDbRecordDataExtractor.Instance; - var dynamoDbRecord = new DynamoDBEvent.DynamodbStreamRecord - { - EventName = "INSERT", - Dynamodb = new DynamoDBEvent.StreamRecord - { - Keys = new Dictionary - { - ["id"] = new DynamoDBEvent.AttributeValue { S = "123" } - }, - NewImage = new Dictionary - { - ["id"] = new DynamoDBEvent.AttributeValue { S = "123" }, - ["name"] = new DynamoDBEvent.AttributeValue { S = "Test Item" } - }, - SequenceNumber = "12345", - SizeBytes = 100, - StreamViewType = "NEW_AND_OLD_IMAGES" - } - }; - - // Act - var result = extractor.ExtractData(dynamoDbRecord); - - // Assert - Assert.NotEmpty(result); - - // Verify the result is valid JSON - var deserializedResult = JsonSerializer.Deserialize(result); - Assert.Equal("INSERT", deserializedResult.GetProperty("EventName").GetString()); - Assert.Equal("12345", deserializedResult.GetProperty("SequenceNumber").GetString()); - Assert.Equal(100, deserializedResult.GetProperty("SizeBytes").GetInt32()); - } - - [Fact] - public void DynamoDbRecordDataExtractor_ExtractData_WithRemoveEvent_IncludesOldImage() - { - // Arrange - var extractor = DynamoDbRecordDataExtractor.Instance; - var dynamoDbRecord = new DynamoDBEvent.DynamodbStreamRecord - { - EventName = "REMOVE", - Dynamodb = new DynamoDBEvent.StreamRecord - { - Keys = new Dictionary - { - ["id"] = new DynamoDBEvent.AttributeValue { S = "123" } - }, - OldImage = new Dictionary - { - ["id"] = new DynamoDBEvent.AttributeValue { S = "123" }, - ["name"] = new DynamoDBEvent.AttributeValue { S = "Deleted Item" } - }, - SequenceNumber = "12345", - StreamViewType = "NEW_AND_OLD_IMAGES" - } - }; - - // Act - var result = extractor.ExtractData(dynamoDbRecord); - - // Assert - Assert.NotEmpty(result); - - // Verify the result contains the old image - var deserializedResult = JsonSerializer.Deserialize(result); - Assert.Equal("REMOVE", deserializedResult.GetProperty("EventName").GetString()); - Assert.True(deserializedResult.GetProperty("OldImage").ValueKind != JsonValueKind.Null); - } - - [Fact] - public void DynamoDbRecordDataExtractor_ExtractData_WithNullRecord_ReturnsEmptyString() - { - // Arrange - var extractor = DynamoDbRecordDataExtractor.Instance; - - // Act - var result = extractor.ExtractData(null); - - // Assert - Assert.Equal(string.Empty, result); - } - - [Fact] - public void DynamoDbRecordDataExtractor_ExtractData_WithNullDynamoDb_ReturnsEmptyString() - { - // Arrange - var extractor = DynamoDbRecordDataExtractor.Instance; - var dynamoDbRecord = new DynamoDBEvent.DynamodbStreamRecord - { - EventName = "INSERT", - Dynamodb = null - }; - - // Act - var result = extractor.ExtractData(dynamoDbRecord); - - // Assert - Assert.Equal(string.Empty, result); - } - - [Fact] - public void DynamoDbRecordDataExtractor_Instance_IsSingleton() - { - // Arrange & Act - var instance1 = DynamoDbRecordDataExtractor.Instance; - var instance2 = DynamoDbRecordDataExtractor.Instance; - - // Assert - Assert.Same(instance1, instance2); - } - - #endregion -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/TypedDynamoDbStreamBatchProcessorTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/TypedDynamoDbStreamBatchProcessorTests.cs deleted file mode 100644 index bf79930b2..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/TypedDynamoDbStreamBatchProcessorTests.cs +++ /dev/null @@ -1,443 +0,0 @@ - - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.Json; -using System.Text.Json.Serialization; -using System.Threading; -using System.Threading.Tasks; -using Amazon.Lambda.Core; -using Amazon.Lambda.DynamoDBEvents; -using AWS.Lambda.Powertools.BatchProcessing.DynamoDb; -using AWS.Lambda.Powertools.BatchProcessing.Exceptions; -using AWS.Lambda.Powertools.Common; -using NSubstitute; -using Xunit; - -namespace AWS.Lambda.Powertools.BatchProcessing.Tests; - -[Collection("Sequential")] -public class TypedDynamoDbStreamBatchProcessorTests -{ - private readonly IPowertoolsConfigurations _mockConfigurations; - private readonly TypedDynamoDbStreamBatchProcessor _processor; - - public TypedDynamoDbStreamBatchProcessorTests() - { - _mockConfigurations = Substitute.For(); - _processor = new TypedDynamoDbStreamBatchProcessor(_mockConfigurations); - } - - [Fact] - public async Task ProcessAsync_WithTypedHandler_DeserializesAndProcessesSuccessfully() - { - // Arrange - var @event = CreateDynamoDbEvent("INSERT", "seq-1", "1", "Test Record"); - - TestDynamoDbRecord capturedRecord = null; - var handler = Substitute.For>(); - handler.HandleAsync(Arg.Any(), Arg.Any()) - .Returns(callInfo => - { - capturedRecord = callInfo.Arg(); - return RecordHandlerResult.None; - }); - - // Act - var result = await _processor.ProcessAsync(@event, handler); - - // Assert - Assert.Single(result.SuccessRecords); - Assert.Empty(result.FailureRecords); - Assert.Empty(result.BatchItemFailuresResponse.BatchItemFailures); - Assert.NotNull(capturedRecord); - Assert.Equal("INSERT", capturedRecord.EventName); - Assert.Equal("seq-1", capturedRecord.SequenceNumber); - - await handler.Received(1).HandleAsync(Arg.Any(), Arg.Any()); - } - - [Fact] - public async Task ProcessAsync_WithTypedHandlerWithContext_PassesContextCorrectly() - { - // Arrange - var context = Substitute.For(); - context.AwsRequestId.Returns("test-request-id"); - - var @event = CreateDynamoDbEvent("MODIFY", "seq-2", "2", "Context Test"); - - TestDynamoDbRecord capturedRecord = null; - ILambdaContext capturedContext = null; - var handler = Substitute.For>(); - handler.HandleAsync(Arg.Any(), Arg.Any(), Arg.Any()) - .Returns(callInfo => - { - capturedRecord = callInfo.Arg(); - capturedContext = callInfo.Arg(); - return RecordHandlerResult.None; - }); - - // Act - var result = await _processor.ProcessAsync(@event, handler, context); - - // Assert - Assert.Single(result.SuccessRecords); - Assert.Empty(result.FailureRecords); - Assert.NotNull(capturedRecord); - Assert.Equal("MODIFY", capturedRecord.EventName); - Assert.Equal("seq-2", capturedRecord.SequenceNumber); - Assert.NotNull(capturedContext); - Assert.Equal("test-request-id", capturedContext.AwsRequestId); - - await handler.Received(1).HandleAsync(Arg.Any(), Arg.Any(), Arg.Any()); - } - - [Fact] - public async Task ProcessAsync_WithDeserializationOptions_UsesCustomOptions() - { - // Arrange - var @event = CreateDynamoDbEvent("INSERT", "seq-3", "3", "Custom Options Test"); - - var deserializationOptions = new DeserializationOptions - { - JsonSerializerOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true } - }; - - TestDynamoDbRecord capturedRecord = null; - var handler = Substitute.For>(); - handler.HandleAsync(Arg.Any(), Arg.Any()) - .Returns(callInfo => - { - capturedRecord = callInfo.Arg(); - return RecordHandlerResult.None; - }); - - // Act - var result = await _processor.ProcessAsync(@event, handler, deserializationOptions); - - // Assert - Assert.Single(result.SuccessRecords); - Assert.Empty(result.FailureRecords); - Assert.NotNull(capturedRecord); - Assert.Equal("INSERT", capturedRecord.EventName); - Assert.Equal("seq-3", capturedRecord.SequenceNumber); - Assert.NotNull(capturedRecord.Keys); - Assert.NotNull(capturedRecord.NewImage); - - await handler.Received(1).HandleAsync(Arg.Any(), Arg.Any()); - } - - [Fact] - public async Task ProcessAsync_WithJsonSerializerContext_UsesAOTCompatibleDeserialization() - { - // Arrange - var @event = CreateDynamoDbEvent("INSERT", "seq-4", "4", "AOT Test"); - - var deserializationOptions = new DeserializationOptions(TypedDynamoDbTestJsonSerializerContext.Default); - - TestDynamoDbRecord capturedRecord = null; - var handler = Substitute.For>(); - handler.HandleAsync(Arg.Any(), Arg.Any()) - .Returns(callInfo => - { - capturedRecord = callInfo.Arg(); - return RecordHandlerResult.None; - }); - - // Act - var result = await _processor.ProcessAsync(@event, handler, deserializationOptions); - - // Assert - Assert.Single(result.SuccessRecords); - Assert.Empty(result.FailureRecords); - Assert.NotNull(capturedRecord); - Assert.Equal("INSERT", capturedRecord.EventName); - Assert.Equal("seq-4", capturedRecord.SequenceNumber); - - await handler.Received(1).HandleAsync(Arg.Any(), Arg.Any()); - } - - [Fact] - public async Task ProcessAsync_WithInvalidJson_FailsRecordByDefault() - { - // Arrange - Create a record that will produce invalid JSON for our TestDynamoDbRecord type - // We'll use a custom deserializer that always throws to simulate deserialization failure - var @event = CreateDynamoDbEvent("INSERT", "seq-5", "5", "Invalid Test"); - - var mockDeserializationService = Substitute.For(); - mockDeserializationService.Deserialize(Arg.Any(), Arg.Any()) - .Returns(callInfo => throw new DeserializationException("Test deserialization failure", typeof(TestDynamoDbRecord), "seq-5", new JsonException("Invalid JSON"))); - - var processor = new TypedDynamoDbStreamBatchProcessor(_mockConfigurations, mockDeserializationService); - var handler = Substitute.For>(); - - // Act & Assert - var exception = await Assert.ThrowsAsync(() => processor.ProcessAsync(@event, handler)); - - // Verify the exception contains the expected details - Assert.Contains("Failed processing record: 'seq-5'", exception.Message); - Assert.Single(exception.InnerExceptions); - - await handler.DidNotReceive().HandleAsync(Arg.Any(), Arg.Any()); - } - - [Fact] - public async Task ProcessAsync_WithInvalidJsonAndIgnorePolicy_IgnoresRecord() - { - // Arrange - Create a record that will produce invalid JSON for our TestDynamoDbRecord type - // We'll use a custom deserializer that always throws to simulate deserialization failure - var @event = CreateDynamoDbEvent("INSERT", "seq-6", "6", "Invalid Test"); - - var mockDeserializationService = Substitute.For(); - mockDeserializationService.Deserialize(Arg.Any(), Arg.Any()) - .Returns(callInfo => throw new DeserializationException("Test deserialization failure", typeof(TestDynamoDbRecord), "seq-6", new JsonException("Invalid JSON"))); - - var processor = new TypedDynamoDbStreamBatchProcessor(_mockConfigurations, mockDeserializationService); - - var deserializationOptions = new DeserializationOptions - { - ErrorPolicy = DeserializationErrorPolicy.IgnoreRecord - }; - - var handler = Substitute.For>(); - - // Act - var result = await processor.ProcessAsync(@event, handler, deserializationOptions); - - // Assert - Assert.Single(result.SuccessRecords); - Assert.Empty(result.FailureRecords); - Assert.Empty(result.BatchItemFailuresResponse.BatchItemFailures); - - await handler.DidNotReceive().HandleAsync(Arg.Any(), Arg.Any()); - } - - [Fact] - public async Task ProcessAsync_WithMultipleRecords_ProcessesAllSuccessfully() - { - // Arrange - var @event = new DynamoDBEvent - { - Records = new List - { - CreateDynamoDbRecord("INSERT", "seq-7", "7", "Record 1"), - CreateDynamoDbRecord("MODIFY", "seq-8", "8", "Record 2") - } - }; - - var handler = Substitute.For>(); - handler.HandleAsync(Arg.Any(), Arg.Any()) - .Returns(RecordHandlerResult.None); - - // Act - var result = await _processor.ProcessAsync(@event, handler); - - // Assert - Assert.Equal(2, result.SuccessRecords.Count); - Assert.Empty(result.FailureRecords); - Assert.Empty(result.BatchItemFailuresResponse.BatchItemFailures); - - await handler.Received(2).HandleAsync(Arg.Any(), Arg.Any()); - } - - [Fact] - public async Task ProcessAsync_WithHandlerException_FailsRecord() - { - // Arrange - var @event = CreateDynamoDbEvent("INSERT", "seq-9", "9", "Exception Test"); - - var handler = Substitute.For>(); - handler.WhenForAnyArgs(x => x.HandleAsync(Arg.Any(), Arg.Any())) - .Do(callInfo => throw new InvalidOperationException("Handler failed")); - - // Act & Assert - var exception = await Assert.ThrowsAsync(() => _processor.ProcessAsync(@event, handler)); - - // Verify the exception contains the expected details - Assert.Contains("Failed processing record: 'seq-9'", exception.Message); - Assert.Single(exception.InnerExceptions); - } - - [Fact] - public async Task ProcessAsync_WithCancellationToken_PropagatesCancellation() - { - // Arrange - var @event = CreateDynamoDbEvent("INSERT", "seq-10", "10", "Cancellation Test"); - - var handler = Substitute.For>(); - var cancellationTokenSource = new CancellationTokenSource(); - cancellationTokenSource.Cancel(); - - // Act & Assert - var exception = await Assert.ThrowsAsync(() => - _processor.ProcessAsync(@event, handler, cancellationTokenSource.Token)); - - // Verify the cancellation was the root cause - Assert.Contains("Failed processing record: 'seq-10'", exception.Message); - Assert.Single(exception.InnerExceptions); - Assert.IsType(exception.InnerExceptions.First()); - Assert.IsType(exception.InnerExceptions.First().InnerException); - } - - - - [Fact] - public async Task ProcessAsync_WithNullContext_HandlesGracefully() - { - // Arrange - var @event = CreateDynamoDbEvent("INSERT", "seq-11", "11", "Null Context Test"); - - TestDynamoDbRecord capturedRecord = null; - ILambdaContext capturedContext = null; - var handler = Substitute.For>(); - handler.HandleAsync(Arg.Any(), Arg.Any(), Arg.Any()) - .Returns(callInfo => - { - capturedRecord = callInfo.Arg(); - capturedContext = callInfo.Arg(); - return RecordHandlerResult.None; - }); - - // Act - var result = await _processor.ProcessAsync(@event, handler, context: null); - - // Assert - Assert.Single(result.SuccessRecords); - Assert.Empty(result.FailureRecords); - Assert.NotNull(capturedRecord); - Assert.Equal("INSERT", capturedRecord.EventName); - Assert.Equal("seq-11", capturedRecord.SequenceNumber); - Assert.Null(capturedContext); - - await handler.Received(1).HandleAsync(Arg.Any(), Arg.Any(), Arg.Any()); - } - - [Fact] - public async Task ProcessAsync_WithRemoveEvent_ProcessesOldImageCorrectly() - { - // Arrange - var @event = CreateDynamoDbEvent("REMOVE", "seq-12", "12", "Remove Test"); - - TestDynamoDbRecord capturedRecord = null; - var handler = Substitute.For>(); - handler.HandleAsync(Arg.Any(), Arg.Any()) - .Returns(callInfo => - { - capturedRecord = callInfo.Arg(); - return RecordHandlerResult.None; - }); - - // Act - var result = await _processor.ProcessAsync(@event, handler); - - // Assert - Assert.Single(result.SuccessRecords); - Assert.Empty(result.FailureRecords); - Assert.NotNull(capturedRecord); - Assert.Equal("REMOVE", capturedRecord.EventName); - Assert.Equal("seq-12", capturedRecord.SequenceNumber); - - await handler.Received(1).HandleAsync(Arg.Any(), Arg.Any()); - } - - [Fact] - public async Task ProcessAsync_WithMixedEventTypes_ProcessesAllCorrectly() - { - // Arrange - var @event = new DynamoDBEvent - { - Records = new List - { - CreateDynamoDbRecord("INSERT", "seq-13", "13", "Insert Record"), - CreateDynamoDbRecord("MODIFY", "seq-14", "14", "Modify Record"), - CreateDynamoDbRecord("REMOVE", "seq-15", "15", "Remove Record") - } - }; - - var handler = Substitute.For>(); - handler.HandleAsync(Arg.Any(), Arg.Any()) - .Returns(RecordHandlerResult.None); - - // Act - var result = await _processor.ProcessAsync(@event, handler); - - // Assert - Assert.Equal(3, result.SuccessRecords.Count); - Assert.Empty(result.FailureRecords); - Assert.Empty(result.BatchItemFailuresResponse.BatchItemFailures); - - await handler.Received(3).HandleAsync(Arg.Any(), Arg.Any()); - } - - // Helper methods - private DynamoDBEvent CreateDynamoDbEvent(string eventName, string sequenceNumber, string id, string name) - { - return new DynamoDBEvent - { - Records = new List - { - CreateDynamoDbRecord(eventName, sequenceNumber, id, name) - } - }; - } - - private DynamoDBEvent.DynamodbStreamRecord CreateDynamoDbRecord(string eventName, string sequenceNumber, string id, string name) - { - var record = new DynamoDBEvent.DynamodbStreamRecord - { - EventName = eventName, - Dynamodb = new DynamoDBEvent.StreamRecord - { - SequenceNumber = sequenceNumber, - Keys = new Dictionary - { - ["Id"] = new DynamoDBEvent.AttributeValue { N = id } - }, - StreamViewType = "NEW_AND_OLD_IMAGES" - } - }; - - // For INSERT and MODIFY events, include NewImage - if (eventName == "INSERT" || eventName == "MODIFY") - { - record.Dynamodb.NewImage = new Dictionary - { - ["Id"] = new DynamoDBEvent.AttributeValue { N = id }, - ["Name"] = new DynamoDBEvent.AttributeValue { S = name } - }; - } - - // For REMOVE and MODIFY events, include OldImage - if (eventName == "REMOVE" || eventName == "MODIFY") - { - record.Dynamodb.OldImage = new Dictionary - { - ["Id"] = new DynamoDBEvent.AttributeValue { N = id }, - ["Name"] = new DynamoDBEvent.AttributeValue { S = name } - }; - } - - return record; - } - - - - // Test data classes that match the DynamoDbRecordDataExtractor output structure - public class TestDynamoDbRecord - { - public string EventName { get; set; } - public Dictionary Keys { get; set; } - public Dictionary NewImage { get; set; } - public Dictionary OldImage { get; set; } - public string SequenceNumber { get; set; } - public long SizeBytes { get; set; } - public string StreamViewType { get; set; } - } -} - -// JsonSerializerContext needs to be outside the test class and partial for source generation -[JsonSerializable(typeof(TypedDynamoDbStreamBatchProcessorTests.TestDynamoDbRecord))] -public partial class TypedDynamoDbTestJsonSerializerContext : JsonSerializerContext -{ -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/TypedKinesisEventBatchProcessorTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/TypedKinesisEventBatchProcessorTests.cs deleted file mode 100644 index e38aba969..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/TypedKinesisEventBatchProcessorTests.cs +++ /dev/null @@ -1,526 +0,0 @@ - - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Text.Json; -using System.Text.Json.Serialization; -using System.Threading; -using System.Threading.Tasks; -using Amazon.Lambda.Core; -using Amazon.Lambda.KinesisEvents; -using AWS.Lambda.Powertools.BatchProcessing.Exceptions; -using AWS.Lambda.Powertools.BatchProcessing.Kinesis; -using AWS.Lambda.Powertools.Common; -using NSubstitute; -using Xunit; - -namespace AWS.Lambda.Powertools.BatchProcessing.Tests; - -[Collection("Sequential")] -public class TypedKinesisEventBatchProcessorTests -{ - private readonly IPowertoolsConfigurations _mockConfigurations; - private readonly TypedKinesisEventBatchProcessor _processor; - - public TypedKinesisEventBatchProcessorTests() - { - _mockConfigurations = Substitute.For(); - _processor = new TypedKinesisEventBatchProcessor(_mockConfigurations); - } - - [Fact] - public async Task ProcessAsync_WithTypedHandler_DeserializesAndProcessesSuccessfully() - { - // Arrange - var testData = new TestKinesisMessage { Id = 1, Name = "Test Kinesis Message" }; - var messageBody = JsonSerializer.Serialize(testData); - var dataStream = new MemoryStream(Encoding.UTF8.GetBytes(messageBody)); - - var @event = new KinesisEvent - { - Records = new List - { - new() - { - Kinesis = new KinesisEvent.Record - { - SequenceNumber = "12345", - Data = dataStream, - PartitionKey = "partition-1" - } - } - } - }; - - var handler = Substitute.For>(); - handler.HandleAsync(Arg.Any(), Arg.Any()) - .Returns(RecordHandlerResult.None); - - // Act - var result = await _processor.ProcessAsync(@event, handler); - - // Assert - Assert.Single(result.SuccessRecords); - Assert.Empty(result.FailureRecords); - Assert.Empty(result.BatchItemFailuresResponse.BatchItemFailures); - - await handler.Received(1).HandleAsync( - Arg.Is(m => m.Id == testData.Id && m.Name == testData.Name), - Arg.Any()); - } - - [Fact] - public async Task ProcessAsync_WithTypedHandlerWithContext_PassesContextCorrectly() - { - // Arrange - var testData = new TestKinesisMessage { Id = 2, Name = "Context Test" }; - var messageBody = JsonSerializer.Serialize(testData); - var dataStream = new MemoryStream(Encoding.UTF8.GetBytes(messageBody)); - var context = Substitute.For(); - context.AwsRequestId.Returns("test-request-id"); - - var @event = new KinesisEvent - { - Records = new List - { - new() - { - Kinesis = new KinesisEvent.Record - { - SequenceNumber = "12346", - Data = dataStream, - PartitionKey = "partition-2" - } - } - } - }; - - var handler = Substitute.For>(); - handler.HandleAsync(Arg.Any(), Arg.Any(), Arg.Any()) - .Returns(RecordHandlerResult.None); - - // Act - var result = await _processor.ProcessAsync(@event, handler, context); - - // Assert - Assert.Single(result.SuccessRecords); - Assert.Empty(result.FailureRecords); - - await handler.Received(1).HandleAsync( - Arg.Is(m => m.Id == testData.Id && m.Name == testData.Name), - Arg.Is(c => c.AwsRequestId == "test-request-id"), - Arg.Any()); - } - - [Fact] - public async Task ProcessAsync_WithDeserializationOptions_UsesCustomOptions() - { - // Arrange - var testData = new TestKinesisMessage { Id = 3, Name = "Custom Options Test" }; - var messageBody = JsonSerializer.Serialize(testData, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); - var dataStream = new MemoryStream(Encoding.UTF8.GetBytes(messageBody)); - - var @event = new KinesisEvent - { - Records = new List - { - new() - { - Kinesis = new KinesisEvent.Record - { - SequenceNumber = "12347", - Data = dataStream, - PartitionKey = "partition-3" - } - } - } - }; - - var deserializationOptions = new DeserializationOptions - { - JsonSerializerOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase } - }; - - var handler = Substitute.For>(); - handler.HandleAsync(Arg.Any(), Arg.Any()) - .Returns(RecordHandlerResult.None); - - // Act - var result = await _processor.ProcessAsync(@event, handler, deserializationOptions); - - // Assert - Assert.Single(result.SuccessRecords); - Assert.Empty(result.FailureRecords); - - await handler.Received(1).HandleAsync( - Arg.Is(m => m.Id == testData.Id && m.Name == testData.Name), - Arg.Any()); - } - - [Fact] - public async Task ProcessAsync_WithJsonSerializerContext_UsesAOTCompatibleDeserialization() - { - // Arrange - var testData = new TestKinesisMessage { Id = 4, Name = "AOT Test" }; - var messageBody = JsonSerializer.Serialize(testData, TypedKinesisTestJsonSerializerContext.Default.TestKinesisMessage); - var dataStream = new MemoryStream(Encoding.UTF8.GetBytes(messageBody)); - - var @event = new KinesisEvent - { - Records = new List - { - new() - { - Kinesis = new KinesisEvent.Record - { - SequenceNumber = "12348", - Data = dataStream, - PartitionKey = "partition-4" - } - } - } - }; - - var deserializationOptions = new DeserializationOptions(TypedKinesisTestJsonSerializerContext.Default); - - var handler = Substitute.For>(); - handler.HandleAsync(Arg.Any(), Arg.Any()) - .Returns(RecordHandlerResult.None); - - // Act - var result = await _processor.ProcessAsync(@event, handler, deserializationOptions); - - // Assert - Assert.Single(result.SuccessRecords); - Assert.Empty(result.FailureRecords); - - await handler.Received(1).HandleAsync( - Arg.Is(m => m.Id == testData.Id && m.Name == testData.Name), - Arg.Any()); - } - - [Fact] - public async Task ProcessAsync_WithInvalidJson_FailsRecordByDefault() - { - // Arrange - var invalidJson = "invalid json"; - var dataStream = new MemoryStream(Encoding.UTF8.GetBytes(invalidJson)); - - var @event = new KinesisEvent - { - Records = new List - { - new() - { - Kinesis = new KinesisEvent.Record - { - SequenceNumber = "12349", - Data = dataStream, - PartitionKey = "partition-5" - } - } - } - }; - - var handler = Substitute.For>(); - - // Act & Assert - var exception = await Assert.ThrowsAsync(() => _processor.ProcessAsync(@event, handler)); - - // Verify the exception contains the expected details - Assert.Contains("Failed processing record: '12349'", exception.Message); - Assert.Single(exception.InnerExceptions); - - await handler.DidNotReceive().HandleAsync(Arg.Any(), Arg.Any()); - } - - [Fact] - public async Task ProcessAsync_WithInvalidJsonAndIgnorePolicy_IgnoresRecord() - { - // Arrange - var invalidJson = "invalid json"; - var dataStream = new MemoryStream(Encoding.UTF8.GetBytes(invalidJson)); - - var @event = new KinesisEvent - { - Records = new List - { - new() - { - Kinesis = new KinesisEvent.Record - { - SequenceNumber = "12350", - Data = dataStream, - PartitionKey = "partition-6" - } - } - } - }; - - var deserializationOptions = new DeserializationOptions - { - ErrorPolicy = DeserializationErrorPolicy.IgnoreRecord - }; - - var handler = Substitute.For>(); - - // Act - var result = await _processor.ProcessAsync(@event, handler, deserializationOptions); - - // Assert - Assert.Single(result.SuccessRecords); - Assert.Empty(result.FailureRecords); - Assert.Empty(result.BatchItemFailuresResponse.BatchItemFailures); - - await handler.DidNotReceive().HandleAsync(Arg.Any(), Arg.Any()); - } - - [Fact] - public async Task ProcessAsync_WithMultipleRecords_ProcessesAllSuccessfully() - { - // Arrange - var testData1 = new TestKinesisMessage { Id = 7, Name = "Message 1" }; - var testData2 = new TestKinesisMessage { Id = 8, Name = "Message 2" }; - var dataStream1 = new MemoryStream(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(testData1))); - var dataStream2 = new MemoryStream(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(testData2))); - - var @event = new KinesisEvent - { - Records = new List - { - new() - { - Kinesis = new KinesisEvent.Record - { - SequenceNumber = "12351", - Data = dataStream1, - PartitionKey = "partition-7" - } - }, - new() - { - Kinesis = new KinesisEvent.Record - { - SequenceNumber = "12352", - Data = dataStream2, - PartitionKey = "partition-8" - } - } - } - }; - - var handler = Substitute.For>(); - handler.HandleAsync(Arg.Any(), Arg.Any()) - .Returns(RecordHandlerResult.None); - - // Act - var result = await _processor.ProcessAsync(@event, handler); - - // Assert - Assert.Equal(2, result.SuccessRecords.Count); - Assert.Empty(result.FailureRecords); - Assert.Empty(result.BatchItemFailuresResponse.BatchItemFailures); - - await handler.Received(2).HandleAsync(Arg.Any(), Arg.Any()); - } - - [Fact] - public async Task ProcessAsync_WithHandlerException_FailsRecord() - { - // Arrange - var testData = new TestKinesisMessage { Id = 9, Name = "Exception Test" }; - var messageBody = JsonSerializer.Serialize(testData); - var dataStream = new MemoryStream(Encoding.UTF8.GetBytes(messageBody)); - - var @event = new KinesisEvent - { - Records = new List - { - new() - { - Kinesis = new KinesisEvent.Record - { - SequenceNumber = "12353", - Data = dataStream, - PartitionKey = "partition-9" - } - } - } - }; - - var handler = Substitute.For>(); - handler.WhenForAnyArgs(x => x.HandleAsync(Arg.Any(), Arg.Any())) - .Do(callInfo => throw new InvalidOperationException("Handler failed")); - - // Act & Assert - var exception = await Assert.ThrowsAsync(() => _processor.ProcessAsync(@event, handler)); - - // Verify the exception contains the expected details - Assert.Contains("Failed processing record: '12353'", exception.Message); - Assert.Single(exception.InnerExceptions); - } - - [Fact] - public async Task ProcessAsync_WithCancellationToken_PropagatesCancellation() - { - // Arrange - var testData = new TestKinesisMessage { Id = 12, Name = "Cancellation Test" }; - var messageBody = JsonSerializer.Serialize(testData); - var dataStream = new MemoryStream(Encoding.UTF8.GetBytes(messageBody)); - - var @event = new KinesisEvent - { - Records = new List - { - new() - { - Kinesis = new KinesisEvent.Record - { - SequenceNumber = "12354", - Data = dataStream, - PartitionKey = "partition-12" - } - } - } - }; - - var handler = Substitute.For>(); - var cancellationTokenSource = new CancellationTokenSource(); - cancellationTokenSource.Cancel(); - - // Act & Assert - var exception = await Assert.ThrowsAsync(() => - _processor.ProcessAsync(@event, handler, cancellationTokenSource.Token)); - - // Verify the cancellation was the root cause - Assert.Contains("Failed processing record: '12354'", exception.Message); - Assert.Single(exception.InnerExceptions); - Assert.IsType(exception.InnerExceptions.First()); - Assert.IsType(exception.InnerExceptions.First().InnerException); - } - - - - [Fact] - public async Task ProcessAsync_WithNullContext_HandlesGracefully() - { - // Arrange - var testData = new TestKinesisMessage { Id = 13, Name = "Null Context Test" }; - var messageBody = JsonSerializer.Serialize(testData); - var dataStream = new MemoryStream(Encoding.UTF8.GetBytes(messageBody)); - - var @event = new KinesisEvent - { - Records = new List - { - new() - { - Kinesis = new KinesisEvent.Record - { - SequenceNumber = "12355", - Data = dataStream, - PartitionKey = "partition-13" - } - } - } - }; - - var handler = Substitute.For>(); - handler.HandleAsync(Arg.Any(), Arg.Any(), Arg.Any()) - .Returns(RecordHandlerResult.None); - - // Act - var result = await _processor.ProcessAsync(@event, handler, context: null); - - // Assert - Assert.Single(result.SuccessRecords); - Assert.Empty(result.FailureRecords); - - await handler.Received(1).HandleAsync( - Arg.Any(), - Arg.Is(c => c == null), - Arg.Any()); - } - - [Fact] - public async Task ProcessAsync_WithEmptyKinesisData_HandlesGracefully() - { - // Arrange - var dataStream = new MemoryStream(Encoding.UTF8.GetBytes(string.Empty)); - - var @event = new KinesisEvent - { - Records = new List - { - new() - { - Kinesis = new KinesisEvent.Record - { - SequenceNumber = "12356", - Data = dataStream, - PartitionKey = "partition-14" - } - } - } - }; - - var handler = Substitute.For>(); - - // Act & Assert - var exception = await Assert.ThrowsAsync(() => _processor.ProcessAsync(@event, handler)); - - // Verify the exception contains the expected details - Assert.Contains("Failed processing record: '12356'", exception.Message); - Assert.Single(exception.InnerExceptions); - - await handler.DidNotReceive().HandleAsync(Arg.Any(), Arg.Any()); - } - - [Fact] - public async Task ProcessAsync_WithNullKinesisData_HandlesGracefully() - { - // Arrange - var @event = new KinesisEvent - { - Records = new List - { - new() - { - Kinesis = new KinesisEvent.Record - { - SequenceNumber = "12357", - Data = null, - PartitionKey = "partition-15" - } - } - } - }; - - var handler = Substitute.For>(); - - // Act & Assert - var exception = await Assert.ThrowsAsync(() => _processor.ProcessAsync(@event, handler)); - - // Verify the exception contains the expected details - Assert.Contains("Failed processing record: '12357'", exception.Message); - Assert.Single(exception.InnerExceptions); - - await handler.DidNotReceive().HandleAsync(Arg.Any(), Arg.Any()); - } - - // Test data classes - public class TestKinesisMessage - { - public int Id { get; set; } - public string Name { get; set; } - } -} - -// JsonSerializerContext needs to be outside the test class and partial for source generation -[JsonSerializable(typeof(TypedKinesisEventBatchProcessorTests.TestKinesisMessage))] -public partial class TypedKinesisTestJsonSerializerContext : JsonSerializerContext -{ -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/TypedSqsBatchProcessorTests.cs b/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/TypedSqsBatchProcessorTests.cs deleted file mode 100644 index 4a067f378..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.BatchProcessing.Tests/TypedSqsBatchProcessorTests.cs +++ /dev/null @@ -1,672 +0,0 @@ - - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.Json; -using System.Text.Json.Serialization; -using System.Threading; -using System.Threading.Tasks; -using Amazon.Lambda.Core; -using Amazon.Lambda.SQSEvents; -using AWS.Lambda.Powertools.BatchProcessing.Exceptions; -using AWS.Lambda.Powertools.BatchProcessing.Sqs; -using AWS.Lambda.Powertools.Common; -using NSubstitute; -using Xunit; - -namespace AWS.Lambda.Powertools.BatchProcessing.Tests; - -[Collection("Sequential")] -public class TypedSqsBatchProcessorTests -{ - private readonly IPowertoolsConfigurations _mockConfigurations; - private readonly TypedSqsBatchProcessor _processor; - - public TypedSqsBatchProcessorTests() - { - _mockConfigurations = Substitute.For(); - _processor = new TypedSqsBatchProcessor(_mockConfigurations); - } - - [Fact] - public async Task ProcessAsync_WithTypedHandler_DeserializesAndProcessesSuccessfully() - { - // Arrange - var testData = new TestMessage { Id = 1, Name = "Test Message" }; - var messageBody = JsonSerializer.Serialize(testData); - - var @event = new SQSEvent - { - Records = new List - { - new() - { - MessageId = "msg-1", - Body = messageBody, - EventSourceArn = "arn:aws:sqs:us-east-1:123456789012:test-queue" - } - } - }; - - var handler = Substitute.For>(); - handler.HandleAsync(Arg.Any(), Arg.Any()) - .Returns(RecordHandlerResult.None); - - // Act - var result = await _processor.ProcessAsync(@event, handler); - - // Assert - Assert.Single(result.SuccessRecords); - Assert.Empty(result.FailureRecords); - Assert.Empty(result.BatchItemFailuresResponse.BatchItemFailures); - - await handler.Received(1).HandleAsync( - Arg.Is(m => m.Id == testData.Id && m.Name == testData.Name), - Arg.Any()); - } - - [Fact] - public async Task ProcessAsync_WithTypedHandlerWithContext_PassesContextCorrectly() - { - // Arrange - var testData = new TestMessage { Id = 2, Name = "Context Test" }; - var messageBody = JsonSerializer.Serialize(testData); - var context = Substitute.For(); - context.AwsRequestId.Returns("test-request-id"); - - var @event = new SQSEvent - { - Records = new List - { - new() - { - MessageId = "msg-2", - Body = messageBody, - EventSourceArn = "arn:aws:sqs:us-east-1:123456789012:test-queue" - } - } - }; - - var handler = Substitute.For>(); - handler.HandleAsync(Arg.Any(), Arg.Any(), Arg.Any()) - .Returns(RecordHandlerResult.None); - - // Act - var result = await _processor.ProcessAsync(@event, handler, context); - - // Assert - Assert.Single(result.SuccessRecords); - Assert.Empty(result.FailureRecords); - - await handler.Received(1).HandleAsync( - Arg.Is(m => m.Id == testData.Id && m.Name == testData.Name), - Arg.Is(c => c.AwsRequestId == "test-request-id"), - Arg.Any()); - } - - [Fact] - public async Task ProcessAsync_WithDeserializationOptions_UsesCustomOptions() - { - // Arrange - var testData = new TestMessage { Id = 3, Name = "Custom Options Test" }; - var messageBody = JsonSerializer.Serialize(testData, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); - - var @event = new SQSEvent - { - Records = new List - { - new() - { - MessageId = "msg-3", - Body = messageBody, - EventSourceArn = "arn:aws:sqs:us-east-1:123456789012:test-queue" - } - } - }; - - var deserializationOptions = new DeserializationOptions - { - JsonSerializerOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase } - }; - - var handler = Substitute.For>(); - handler.HandleAsync(Arg.Any(), Arg.Any()) - .Returns(RecordHandlerResult.None); - - // Act - var result = await _processor.ProcessAsync(@event, handler, deserializationOptions); - - // Assert - Assert.Single(result.SuccessRecords); - Assert.Empty(result.FailureRecords); - - await handler.Received(1).HandleAsync( - Arg.Is(m => m.Id == testData.Id && m.Name == testData.Name), - Arg.Any()); - } - - [Fact] - public async Task ProcessAsync_WithJsonSerializerContext_UsesAOTCompatibleDeserialization() - { - // Arrange - var testData = new TestMessage { Id = 4, Name = "AOT Test" }; - var messageBody = JsonSerializer.Serialize(testData, TypedSqsTestJsonSerializerContext.Default.TestMessage); - - var @event = new SQSEvent - { - Records = new List - { - new() - { - MessageId = "msg-4", - Body = messageBody, - EventSourceArn = "arn:aws:sqs:us-east-1:123456789012:test-queue" - } - } - }; - - var deserializationOptions = new DeserializationOptions(TypedSqsTestJsonSerializerContext.Default); - - var handler = Substitute.For>(); - handler.HandleAsync(Arg.Any(), Arg.Any()) - .Returns(RecordHandlerResult.None); - - // Act - var result = await _processor.ProcessAsync(@event, handler, deserializationOptions); - - // Assert - Assert.Single(result.SuccessRecords); - Assert.Empty(result.FailureRecords); - - await handler.Received(1).HandleAsync( - Arg.Is(m => m.Id == testData.Id && m.Name == testData.Name), - Arg.Any()); - } - - [Fact] - public async Task ProcessAsync_WithInvalidJson_FailsRecordByDefault() - { - // Arrange - var @event = new SQSEvent - { - Records = new List - { - new() - { - MessageId = "msg-5", - Body = "invalid json", - EventSourceArn = "arn:aws:sqs:us-east-1:123456789012:test-queue" - } - } - }; - - var handler = Substitute.For>(); - - // Act & Assert - var exception = await Assert.ThrowsAsync(() => _processor.ProcessAsync(@event, handler)); - - // Verify the exception contains the expected details - Assert.Contains("Failed processing record: 'msg-5'", exception.Message); - Assert.Single(exception.InnerExceptions); - - await handler.DidNotReceive().HandleAsync(Arg.Any(), Arg.Any()); - } - - [Fact] - public async Task ProcessAsync_WithInvalidJsonAndIgnorePolicy_IgnoresRecord() - { - // Arrange - var @event = new SQSEvent - { - Records = new List - { - new() - { - MessageId = "msg-6", - Body = "invalid json", - EventSourceArn = "arn:aws:sqs:us-east-1:123456789012:test-queue" - } - } - }; - - var deserializationOptions = new DeserializationOptions - { - ErrorPolicy = DeserializationErrorPolicy.IgnoreRecord - }; - - var handler = Substitute.For>(); - - // Act - var result = await _processor.ProcessAsync(@event, handler, deserializationOptions); - - // Assert - Assert.Single(result.SuccessRecords); - Assert.Empty(result.FailureRecords); - Assert.Empty(result.BatchItemFailuresResponse.BatchItemFailures); - - await handler.DidNotReceive().HandleAsync(Arg.Any(), Arg.Any()); - } - - [Fact] - public async Task ProcessAsync_WithMultipleRecords_ProcessesAllSuccessfully() - { - // Arrange - var testData1 = new TestMessage { Id = 7, Name = "Message 1" }; - var testData2 = new TestMessage { Id = 8, Name = "Message 2" }; - - var @event = new SQSEvent - { - Records = new List - { - new() - { - MessageId = "msg-7", - Body = JsonSerializer.Serialize(testData1), - EventSourceArn = "arn:aws:sqs:us-east-1:123456789012:test-queue" - }, - new() - { - MessageId = "msg-8", - Body = JsonSerializer.Serialize(testData2), - EventSourceArn = "arn:aws:sqs:us-east-1:123456789012:test-queue" - } - } - }; - - var handler = Substitute.For>(); - handler.HandleAsync(Arg.Any(), Arg.Any()) - .Returns(RecordHandlerResult.None); - - // Act - var result = await _processor.ProcessAsync(@event, handler); - - // Assert - Assert.Equal(2, result.SuccessRecords.Count); - Assert.Empty(result.FailureRecords); - Assert.Empty(result.BatchItemFailuresResponse.BatchItemFailures); - - await handler.Received(2).HandleAsync(Arg.Any(), Arg.Any()); - } - - [Fact] - public async Task ProcessAsync_WithHandlerException_FailsRecord() - { - // Arrange - var testData = new TestMessage { Id = 9, Name = "Exception Test" }; - var messageBody = JsonSerializer.Serialize(testData); - - var @event = new SQSEvent - { - Records = new List - { - new() - { - MessageId = "msg-9", - Body = messageBody, - EventSourceArn = "arn:aws:sqs:us-east-1:123456789012:test-queue" - } - } - }; - - var handler = Substitute.For>(); - handler.WhenForAnyArgs(x => x.HandleAsync(Arg.Any(), Arg.Any())) - .Do(callInfo => throw new InvalidOperationException("Handler failed")); - - // Act & Assert - var exception = await Assert.ThrowsAsync(() => _processor.ProcessAsync(@event, handler)); - - // Verify the exception contains the expected details - Assert.Contains("Failed processing record: 'msg-9'", exception.Message); - Assert.Single(exception.InnerExceptions); - } - - [Fact] - public async Task ProcessAsync_WithFifoQueue_UsesStopOnFirstFailurePolicy() - { - // Arrange - var testData1 = new TestMessage { Id = 10, Name = "FIFO Message 1" }; - var testData2 = new TestMessage { Id = 11, Name = "FIFO Message 2" }; - - var @event = new SQSEvent - { - Records = new List - { - new() - { - MessageId = "msg-10", - Body = JsonSerializer.Serialize(testData1), - EventSourceArn = "arn:aws:sqs:us-east-1:123456789012:test-queue.fifo" - }, - new() - { - MessageId = "msg-11", - Body = JsonSerializer.Serialize(testData2), - EventSourceArn = "arn:aws:sqs:us-east-1:123456789012:test-queue.fifo" - } - } - }; - - var handler = Substitute.For>(); - handler.WhenForAnyArgs(x => x.HandleAsync(Arg.Any(), Arg.Any())) - .Do(callInfo => - { - var data = callInfo.Arg(); - if (data.Id == 10) - { - throw new InvalidOperationException("First record failed"); - } - }); - handler.HandleAsync(Arg.Is(m => m.Id == 11), Arg.Any()) - .Returns(RecordHandlerResult.None); - - // Act & Assert - var exception = await Assert.ThrowsAsync(() => _processor.ProcessAsync(@event, handler)); - - // Verify that the first record failed and the second was not processed - Assert.Contains("Failed processing record: 'msg-10'", exception.Message); - Assert.Contains("Record: 'msg-11' has not been processed", exception.Message); - Assert.Equal(2, exception.InnerExceptions.Count); - } - - [Fact] - public async Task ProcessAsync_WithCancellationToken_PropagatesCancellation() - { - // Arrange - var testData = new TestMessage { Id = 12, Name = "Cancellation Test" }; - var messageBody = JsonSerializer.Serialize(testData); - - var @event = new SQSEvent - { - Records = new List - { - new() - { - MessageId = "msg-12", - Body = messageBody, - EventSourceArn = "arn:aws:sqs:us-east-1:123456789012:test-queue" - } - } - }; - - var handler = Substitute.For>(); - var cancellationTokenSource = new CancellationTokenSource(); - cancellationTokenSource.Cancel(); - - // Act & Assert - var exception = await Assert.ThrowsAsync(() => - _processor.ProcessAsync(@event, handler, cancellationTokenSource.Token)); - - // Verify the cancellation was the root cause - Assert.Contains("Failed processing record: 'msg-12'", exception.Message); - Assert.Single(exception.InnerExceptions); - Assert.IsType(exception.InnerExceptions.First()); - Assert.IsType(exception.InnerExceptions.First().InnerException); - } - - - - [Fact] - public async Task ProcessAsync_WithNullContext_HandlesGracefully() - { - // Arrange - var testData = new TestMessage { Id = 13, Name = "Null Context Test" }; - var messageBody = JsonSerializer.Serialize(testData); - - var @event = new SQSEvent - { - Records = new List - { - new() - { - MessageId = "msg-13", - Body = messageBody, - EventSourceArn = "arn:aws:sqs:us-east-1:123456789012:test-queue" - } - } - }; - - var handler = Substitute.For>(); - handler.HandleAsync(Arg.Any(), Arg.Any(), Arg.Any()) - .Returns(RecordHandlerResult.None); - - // Act - var result = await _processor.ProcessAsync(@event, handler, context: null); - - // Assert - Assert.Single(result.SuccessRecords); - Assert.Empty(result.FailureRecords); - - await handler.Received(1).HandleAsync( - Arg.Any(), - Arg.Is(c => c == null), - Arg.Any()); - } - - // Test data classes - public class TestMessage - { - public int Id { get; set; } - public string Name { get; set; } - } - - public class UnregisteredSqsMessage - { - public string Value { get; set; } - } - - #region AOT Compatibility Tests - - [Fact] - public async Task ProcessAsync_WithUnregisteredTypeInContext_ThrowsAotTypeValidationException() - { - // Arrange - var @event = new SQSEvent - { - Records = new List - { - new() - { - MessageId = "msg-1", - Body = """{"Value":"test"}""" - } - } - }; - - var deserializationOptions = new DeserializationOptions(TypedSqsTestJsonSerializerContext.Default); - var handler = Substitute.For>(); - - // Act & Assert - var exception = await Assert.ThrowsAsync(() => - _processor.ProcessAsync(@event, handler, deserializationOptions)); - - Assert.Equal(typeof(UnregisteredSqsMessage), exception.TargetType); - Assert.Contains("UnregisteredSqsMessage", exception.Message); - Assert.Contains("JsonSerializable", exception.Message); - } - - [Fact] - public async Task ProcessAsync_WithContextHandler_ValidatesAotCompatibility() - { - // Arrange - var @event = new SQSEvent - { - Records = new List - { - new() - { - MessageId = "msg-1", - Body = """{"Value":"test"}""" - } - } - }; - - var context = Substitute.For(); - var deserializationOptions = new DeserializationOptions(TypedSqsTestJsonSerializerContext.Default); - var handler = Substitute.For>(); - - // Act & Assert - var exception = await Assert.ThrowsAsync(() => - _processor.ProcessAsync(@event, handler, context, deserializationOptions)); - - Assert.Equal(typeof(UnregisteredSqsMessage), exception.TargetType); - } - - [Fact] - public async Task ProcessAsync_WithValidAotContext_ProcessesSuccessfully() - { - // Arrange - var testData = new TestMessage { Id = 5, Name = "AOT Valid Test" }; - var messageBody = JsonSerializer.Serialize(testData, TypedSqsTestJsonSerializerContext.Default.TestMessage); - - var @event = new SQSEvent - { - Records = new List - { - new() - { - MessageId = "msg-1", - Body = messageBody - } - } - }; - - var deserializationOptions = new DeserializationOptions(TypedSqsTestJsonSerializerContext.Default); - var handler = Substitute.For>(); - handler.HandleAsync(Arg.Any(), Arg.Any()) - .Returns(RecordHandlerResult.None); - - // Act - var result = await _processor.ProcessAsync(@event, handler, deserializationOptions); - - // Assert - Assert.NotNull(result); - Assert.Empty(result.BatchItemFailuresResponse.BatchItemFailures); - await handler.Received(1).HandleAsync( - Arg.Is(m => m.Id == 5 && m.Name == "AOT Valid Test"), - Arg.Any()); - } - - #endregion - - #region Constructor and Instance Tests - - - - [Fact] - public void Constructor_WithCustomServices_InitializesCorrectly() - { - // Arrange - var mockDeserializationService = Substitute.For(); - var mockRecordDataExtractor = Substitute.For>(); - - // Act - var processor = new TypedSqsBatchProcessor( - _mockConfigurations, - mockDeserializationService, - mockRecordDataExtractor); - - // Assert - Assert.NotNull(processor); - } - - [Fact] - public void DefaultConstructor_InitializesCorrectly() - { - // Act & Assert - Should not throw - var processor = new TestableTypedSqsBatchProcessor(); - Assert.NotNull(processor); - } - - // Helper class to test protected constructor - private class TestableTypedSqsBatchProcessor : TypedSqsBatchProcessor - { - public TestableTypedSqsBatchProcessor() : base() - { - } - } - - #endregion - - #region Wrapper Class Coverage Tests - - [Fact] - public async Task ProcessAsync_WithIgnoreErrorPolicy_SkipsInvalidRecords() - { - // Arrange - var @event = new SQSEvent - { - Records = new List - { - new() - { - MessageId = "msg-1", - Body = "invalid-json" - }, - new() - { - MessageId = "msg-2", - Body = JsonSerializer.Serialize(new TestMessage { Id = 1, Name = "Valid" }) - } - } - }; - - var deserializationOptions = new DeserializationOptions - { - ErrorPolicy = DeserializationErrorPolicy.IgnoreRecord - }; - - var handler = Substitute.For>(); - handler.HandleAsync(Arg.Any(), Arg.Any()) - .Returns(RecordHandlerResult.None); - - // Act - var result = await _processor.ProcessAsync(@event, handler, deserializationOptions); - - // Assert - Assert.NotNull(result); - // Should only process the valid record - await handler.Received(1).HandleAsync(Arg.Any(), Arg.Any()); - } - - [Fact] - public async Task ProcessAsync_WithContextHandlerAndIgnoreErrorPolicy_SkipsInvalidRecords() - { - // Arrange - var @event = new SQSEvent - { - Records = new List - { - new() - { - MessageId = "msg-1", - Body = "invalid-json" - } - } - }; - - var context = Substitute.For(); - var deserializationOptions = new DeserializationOptions - { - ErrorPolicy = DeserializationErrorPolicy.IgnoreRecord - }; - - var handler = Substitute.For>(); - - // Act - var result = await _processor.ProcessAsync(@event, handler, context, deserializationOptions); - - // Assert - Assert.NotNull(result); - // Should not call handler for invalid record - await handler.DidNotReceive().HandleAsync(Arg.Any(), Arg.Any(), Arg.Any()); - } - - #endregion -} - -// JsonSerializerContext needs to be outside the test class and partial for source generation -[JsonSerializable(typeof(TypedSqsBatchProcessorTests.TestMessage))] -public partial class TypedSqsTestJsonSerializerContext : JsonSerializerContext -{ -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Common.Tests/ConsoleWrapperTests.cs b/libraries/tests/AWS.Lambda.Powertools.Common.Tests/ConsoleWrapperTests.cs deleted file mode 100644 index 25cea21d4..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Common.Tests/ConsoleWrapperTests.cs +++ /dev/null @@ -1,335 +0,0 @@ -using System; -using System.IO; -using NSubstitute; -using Xunit; - -namespace AWS.Lambda.Powertools.Common.Tests; - -public class ConsoleWrapperTests : IDisposable -{ - private readonly TextWriter _originalOut; - private readonly TextWriter _originalError; - private readonly StringWriter _testWriter; - - public ConsoleWrapperTests() - { - // Store original console outputs - _originalOut = Console.Out; - _originalError = Console.Error; - - // Setup test writer - _testWriter = new StringWriter(); - - // Reset ConsoleWrapper state before each test - ConsoleWrapper.ResetForTest(); - - // Clear any Lambda environment variables - Environment.SetEnvironmentVariable("AWS_LAMBDA_FUNCTION_NAME", null); - } - - public void Dispose() - { - // Restore original console outputs - Console.SetOut(_originalOut); - Console.SetError(_originalError); - - // Reset ConsoleWrapper state after each test - ConsoleWrapper.ResetForTest(); - - // Clear any test environment variables - Environment.SetEnvironmentVariable("AWS_LAMBDA_FUNCTION_NAME", null); - - _testWriter?.Dispose(); - } - - [Fact] - public void WriteLine_GivenInTestMode_WhenCalled_ThenWritesToTestOutputStream() - { - // Given - ConsoleWrapper.SetOut(_testWriter); - var wrapper = new ConsoleWrapper(); - const string message = "test message"; - - // When - wrapper.WriteLine(message); - - // Then - Assert.Equal($"{message}{Environment.NewLine}", _testWriter.ToString()); - } - - [Fact] - public void WriteLine_GivenNotInLambdaEnvironment_WhenCalled_ThenWritesToConsoleDirectly() - { - // Given - var wrapper = new ConsoleWrapper(); - var consoleOutput = new StringWriter(); - Console.SetOut(consoleOutput); - const string message = "test message"; - - // When - wrapper.WriteLine(message); - - // Then - Assert.Equal($"{message}{Environment.NewLine}", consoleOutput.ToString()); - consoleOutput.Dispose(); - } - - [Fact] - public void WriteLine_GivenInLambdaEnvironment_WhenCalled_ThenOverridesConsoleOutput() - { - // Given - Environment.SetEnvironmentVariable("AWS_LAMBDA_FUNCTION_NAME", "test-function"); - var wrapper = new ConsoleWrapper(); - const string message = "test message"; - - // When - wrapper.WriteLine(message); - - // Then - // Should not throw and should have attempted to override console - Assert.NotNull(Console.Out); - } - - [Fact] - public void WriteLine_GivenMultipleCallsInLambda_WhenConsoleIsReIntercepted_ThenReOverridesConsole() - { - // Given - Environment.SetEnvironmentVariable("AWS_LAMBDA_FUNCTION_NAME", "test-function"); - var wrapper = new ConsoleWrapper(); - - // When - First call should override console - wrapper.WriteLine("First message"); - - // Simulate Lambda re-intercepting console by setting it to a wrapped writer - var lambdaInterceptedWriter = new StringWriter(); - Console.SetOut(lambdaInterceptedWriter); - - // Second call should detect and re-override - wrapper.WriteLine("Second message"); - - // Then - // Should not throw and console should be overridden again - Assert.NotNull(Console.Out); - lambdaInterceptedWriter.Dispose(); - } - - [Fact] - public void WriteLine_GivenLambdaEnvironmentWithConsoleOverrideFailing_WhenCalled_ThenDoesNotThrow() - { - // Given - Environment.SetEnvironmentVariable("AWS_LAMBDA_FUNCTION_NAME", "test-function"); - var wrapper = new ConsoleWrapper(); - - // When & Then - Should not throw even if console override fails - var exception = Record.Exception(() => wrapper.WriteLine("Test message")); - Assert.Null(exception); - } - - [Fact] - public void Debug_GivenInTestMode_WhenCalled_ThenWritesToTestOutputStream() - { - // Given - ConsoleWrapper.SetOut(_testWriter); - var wrapper = new ConsoleWrapper(); - const string message = "debug message"; - - // When - wrapper.Debug(message); - - // Then - Assert.Equal($"{message}{Environment.NewLine}", _testWriter.ToString()); - } - - [Fact] - public void Debug_GivenNotInTestMode_WhenCalled_ThenDoesNotThrow() - { - // Given - var wrapper = new ConsoleWrapper(); - ConsoleWrapper.ResetForTest(); // Ensure we're not in test mode - - // When & Then - Just verify it doesn't throw - var exception = Record.Exception(() => wrapper.Debug("debug message")); - Assert.Null(exception); - } - - [Fact] - public void Error_GivenInTestMode_WhenCalled_ThenWritesToTestOutputStream() - { - // Given - ConsoleWrapper.SetOut(_testWriter); - var wrapper = new ConsoleWrapper(); - const string message = "error message"; - - // When - wrapper.Error(message); - - // Then - Assert.Equal($"{message}{Environment.NewLine}", _testWriter.ToString()); - } - - [Fact] - public void Error_GivenNotInTestMode_WhenCalled_ThenDoesNotThrow() - { - // Given - var wrapper = new ConsoleWrapper(); - ConsoleWrapper.ResetForTest(); // Ensure we're not in test mode - - // When & Then - The Error method creates its own StreamWriter, - // so we just verify it doesn't throw - var exception = Record.Exception(() => wrapper.Error("error message")); - Assert.Null(exception); - } - - [Fact] - public void Error_GivenNotOverridden_WhenCalled_ThenDoesNotThrow() - { - // Given - var wrapper = new ConsoleWrapper(); - ConsoleWrapper.ResetForTest(); // Reset to ensure _override is false - - // When & Then - Just verify it doesn't throw - var exception = Record.Exception(() => wrapper.Error("error without override")); - Assert.Null(exception); - } - - [Fact] - public void SetOut_GivenTextWriter_WhenCalled_ThenEnablesTestMode() - { - // Given - var testOutput = new StringWriter(); - - // When - ConsoleWrapper.SetOut(testOutput); - - // Then - var wrapper = new ConsoleWrapper(); - wrapper.WriteLine("test"); - Assert.Equal($"test{Environment.NewLine}", testOutput.ToString()); - testOutput.Dispose(); - } - - [Fact] - public void ResetForTest_GivenTestModeEnabled_WhenCalled_ThenResetsToNormalMode() - { - // Given - var testOutput = new StringWriter(); - ConsoleWrapper.SetOut(testOutput); - - // When - ConsoleWrapper.ResetForTest(); - - // Then - var wrapper = new ConsoleWrapper(); - var consoleOutput = new StringWriter(); - Console.SetOut(consoleOutput); - wrapper.WriteLine("test"); - Assert.Equal($"test{Environment.NewLine}", consoleOutput.ToString()); - Assert.Empty(testOutput.ToString()); - testOutput.Dispose(); - consoleOutput.Dispose(); - } - - [Fact] - public void WriteLineStatic_GivenLogLevelAndMessage_WhenCalled_ThenFormatsWithTimestamp() - { - // Given - ConsoleWrapper.SetOut(_testWriter); - const string logLevel = "INFO"; - const string message = "Test log message"; - - try - { - // When - Using reflection to call internal static method - var method = typeof(ConsoleWrapper) - .GetMethod("WriteLine", - System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); - - if (method == null) - { - // Fall back if the method signature has changed - Assert.True(true, "StaticWriteLine method not available or has changed signature"); - return; - } - - method.Invoke(null, new object[] { logLevel, message }); - - // Then - var output = _testWriter.ToString(); - Assert.Contains(logLevel, output); - Assert.Contains(message, output); - - var lines = output.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries); - Assert.True(lines.Length > 0, "Output should contain at least one line"); - - var parts = lines[0].Split('\t'); - Assert.True(parts.Length >= 3, "Output should contain at least 3 tab-separated parts"); - - // Check that parts[0] contains a timestamp-like string - Assert.Matches(@"[\d\-:TZ.]", parts[0]); - Assert.Equal(logLevel, parts[1]); - Assert.Equal(message, parts[2]); - } - catch (Exception ex) - { - Console.WriteLine($"Test exception: {ex}"); - Assert.True(true, "Skipping test due to reflection error"); - } - } - - [Fact] - public void ClearOutputResetFlag_GivenAnyState_WhenCalled_ThenDoesNotThrow() - { - // Given - any state - - // When & Then - Should not throw (kept for backward compatibility) - var exception = Record.Exception(() => ConsoleWrapper.ClearOutputResetFlag()); - Assert.Null(exception); - } - - [Fact] - public void ClearOutputResetFlag_GivenMultipleCalls_WhenCalled_ThenAllowsRepeatedWrites() - { - // Given - var wrapper = new ConsoleWrapper(); - ConsoleWrapper.SetOut(_testWriter); - - // When - wrapper.WriteLine("First message"); - ConsoleWrapper.ClearOutputResetFlag(); - wrapper.WriteLine("Second message"); - - // Then - Assert.Equal($"First message{Environment.NewLine}Second message{Environment.NewLine}", _testWriter.ToString()); - } - - // from here - - [Fact] - public void HasLambdaReInterceptedConsole_WhenConsoleOutAccessThrows_ThenReturnsTrueFromCatchBlock() - { - // Given - A function that throws when called (simulating Console.Out access failure) - Func throwingAccessor = () => throw new InvalidOperationException("Console.Out access failed"); - - // When - Call the internal method with the throwing accessor - var result = ConsoleWrapper.HasLambdaReInterceptedConsole(throwingAccessor); - - // Then - Should return true from the catch block (lines 102-105) - Assert.True(result); - } - - [Fact] - public void OverrideLambdaLogger_WhenOpenStandardOutputThrows_ThenSetsOverrideToFalse() - { - // Given - ConsoleWrapper.ResetForTest(); - - // A function that throws when called (simulating Console.OpenStandardOutput failure) - Func throwingOpener = () => throw new UnauthorizedAccessException("Cannot open standard output"); - - // When - Call the internal method with the throwing opener - var exception = Record.Exception(() => ConsoleWrapper.OverrideLambdaLogger(throwingOpener)); - - // Then - Should not throw (catch block handles it on lines 120-123) - Assert.Null(exception); - } -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Common.Tests/Core/LambdaLifecycleTrackerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Common.Tests/Core/LambdaLifecycleTrackerTests.cs deleted file mode 100644 index 5b69f4578..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Common.Tests/Core/LambdaLifecycleTrackerTests.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System; -using AWS.Lambda.Powertools.Common.Core; -using Xunit; - -namespace AWS.Lambda.Powertools.Common.Tests; - -public class LambdaLifecycleTrackerTests : IDisposable - { - public LambdaLifecycleTrackerTests() - { - // Reset before each test to ensure clean state - LambdaLifecycleTracker.Reset(); - Environment.SetEnvironmentVariable(Constants.AWSInitializationTypeEnv, null); - } - - public void Dispose() - { - // Reset after each test - LambdaLifecycleTracker.Reset(); - Environment.SetEnvironmentVariable(Constants.AWSInitializationTypeEnv, null); - } - - [Fact] - public void IsColdStart_FirstInvocation_ReturnsTrue() - { - // Act - var result = LambdaLifecycleTracker.IsColdStart; - - // Assert - Assert.True(result); - } - - [Fact] - public void IsColdStart_SecondInvocation_ReturnsFalse() - { - // Arrange - first access to trigger cold start - _ = LambdaLifecycleTracker.IsColdStart; - - // Clear just the AsyncLocal value to simulate new invocation in same container - LambdaLifecycleTracker.Reset(resetContainer: false); - - // Act - second invocation on same container - var result = LambdaLifecycleTracker.IsColdStart; - - // Assert - Assert.False(result); - } - - [Fact] - public void IsColdStart_WithProvisionedConcurrency_ReturnsFalse() - { - // Arrange - Environment.SetEnvironmentVariable(Constants.AWSInitializationTypeEnv, "provisioned-concurrency"); - - // Act - var result = LambdaLifecycleTracker.IsColdStart; - - // Assert - Assert.False(result); - } - - [Fact] - public void IsColdStart_ReturnsSameValueWithinInvocation() - { - // Act - access multiple times in the same invocation - var firstAccess = LambdaLifecycleTracker.IsColdStart; - var secondAccess = LambdaLifecycleTracker.IsColdStart; - var thirdAccess = LambdaLifecycleTracker.IsColdStart; - - // Assert - Assert.True(firstAccess); - Assert.Equal(firstAccess, secondAccess); - Assert.Equal(firstAccess, thirdAccess); - } - - [Fact] - public void Reset_ResetsState() - { - // Arrange - _ = LambdaLifecycleTracker.IsColdStart; // First invocation - - // Act - LambdaLifecycleTracker.Reset(); - var result = LambdaLifecycleTracker.IsColdStart; - - // Assert - Assert.True(result); // Should be true again after reset - } - - [Fact] - public void Reset_ClearsEnvironmentSetting() - { - // Arrange - Environment.SetEnvironmentVariable(Constants.AWSInitializationTypeEnv, "provisioned-concurrency"); - _ = LambdaLifecycleTracker.IsColdStart; // Load the environment variable - - // Act - LambdaLifecycleTracker.Reset(); - Environment.SetEnvironmentVariable(Constants.AWSInitializationTypeEnv, null); // Clear the environment - var result = LambdaLifecycleTracker.IsColdStart; - - // Assert - Assert.True(result); // Should be true when env var is cleared - } - } \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Common.Tests/Core/PowertoolsConfigurationsTest.cs b/libraries/tests/AWS.Lambda.Powertools.Common.Tests/Core/PowertoolsConfigurationsTest.cs index 934a162cd..e154acd95 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Common.Tests/Core/PowertoolsConfigurationsTest.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Common.Tests/Core/PowertoolsConfigurationsTest.cs @@ -29,17 +29,17 @@ public void GetEnvironmentVariableOrDefault_WhenEnvironmentVariableIsNull_Return // Arrange var key = Guid.NewGuid().ToString(); var defaultValue = Guid.NewGuid().ToString(); - var environment = Substitute.For(); + var systemWrapper = Substitute.For(); - environment.GetEnvironmentVariable(key).Returns(string.Empty); + systemWrapper.GetEnvironmentVariable(key).Returns(string.Empty); - var configurations = new PowertoolsConfigurations(environment); + var configurations = new PowertoolsConfigurations(systemWrapper); // Act var result = configurations.GetEnvironmentVariableOrDefault(key, defaultValue); // Assert - environment.Received(1).GetEnvironmentVariable(key); + systemWrapper.Received(1).GetEnvironmentVariable(key); Assert.Equal(result, defaultValue); } @@ -49,17 +49,17 @@ public void GetEnvironmentVariableOrDefault_WhenEnvironmentVariableIsNull_Return { // Arrange var key = Guid.NewGuid().ToString(); - var environment = Substitute.For(); + var systemWrapper = Substitute.For(); - environment.GetEnvironmentVariable(key).Returns(string.Empty); + systemWrapper.GetEnvironmentVariable(key).Returns(string.Empty); - var configurations = new PowertoolsConfigurations(environment); + var configurations = new PowertoolsConfigurations(systemWrapper); // Act var result = configurations.GetEnvironmentVariableOrDefault(key, false); // Assert - environment.Received(1).GetEnvironmentVariable(key); + systemWrapper.Received(1).GetEnvironmentVariable(key); Assert.False(result); } @@ -69,17 +69,17 @@ public void GetEnvironmentVariableOrDefault_WhenEnvironmentVariableIsNull_Return { // Arrange var key = Guid.NewGuid().ToString(); - var environment = Substitute.For(); + var systemWrapper = Substitute.For(); - environment.GetEnvironmentVariable(key).Returns(string.Empty); + systemWrapper.GetEnvironmentVariable(key).Returns(string.Empty); - var configurations = new PowertoolsConfigurations(environment); + var configurations = new PowertoolsConfigurations(systemWrapper); // Act var result = configurations.GetEnvironmentVariableOrDefault(key, true); // Assert - environment.Received(1).GetEnvironmentVariable(Arg.Is(i => i == key)); + systemWrapper.Received(1).GetEnvironmentVariable(Arg.Is(i => i == key)); Assert.True(result); } @@ -91,17 +91,17 @@ public void GetEnvironmentVariableOrDefault_WhenEnvironmentVariableHasValue_Retu var key = Guid.NewGuid().ToString(); var defaultValue = Guid.NewGuid().ToString(); var value = Guid.NewGuid().ToString(); - var environment = Substitute.For(); + var systemWrapper = Substitute.For(); - environment.GetEnvironmentVariable(key).Returns(value); + systemWrapper.GetEnvironmentVariable(key).Returns(value); - var configurations = new PowertoolsConfigurations(environment); + var configurations = new PowertoolsConfigurations(systemWrapper); // Act var result = configurations.GetEnvironmentVariableOrDefault(key, defaultValue); // Assert - environment.Received(1).GetEnvironmentVariable(Arg.Is(i => i == key)); + systemWrapper.Received(1).GetEnvironmentVariable(Arg.Is(i => i == key)); Assert.Equal(result, value); } @@ -111,17 +111,17 @@ public void GetEnvironmentVariableOrDefault_WhenEnvironmentVariableHasValue_Retu { // Arrange var key = Guid.NewGuid().ToString(); - var environment = Substitute.For(); + var systemWrapper = Substitute.For(); - environment.GetEnvironmentVariable(key).Returns("true"); + systemWrapper.GetEnvironmentVariable(key).Returns("true"); - var configurations = new PowertoolsConfigurations(environment); + var configurations = new PowertoolsConfigurations(systemWrapper); // Act var result = configurations.GetEnvironmentVariableOrDefault(key, false); // Assert - environment.Received(1).GetEnvironmentVariable(Arg.Is(i => i == key)); + systemWrapper.Received(1).GetEnvironmentVariable(Arg.Is(i => i == key)); Assert.True(result); } @@ -131,17 +131,17 @@ public void GetEnvironmentVariableOrDefault_WhenEnvironmentVariableHasValue_Retu { // Arrange var key = Guid.NewGuid().ToString(); - var environment = Substitute.For(); + var systemWrapper = Substitute.For(); - environment.GetEnvironmentVariable(key).Returns("false"); + systemWrapper.GetEnvironmentVariable(key).Returns("false"); - var configurations = new PowertoolsConfigurations(environment); + var configurations = new PowertoolsConfigurations(systemWrapper); // Act var result = configurations.GetEnvironmentVariableOrDefault(key, true); // Assert - environment.Received(1).GetEnvironmentVariable(Arg.Is(i => i == key)); + systemWrapper.Received(1).GetEnvironmentVariable(Arg.Is(i => i == key)); Assert.False(result); } @@ -155,17 +155,17 @@ public void Service_WhenEnvironmentIsNull_ReturnsDefaultValue() { // Arrange var defaultService = "service_undefined"; - var environment = Substitute.For(); + var systemWrapper = Substitute.For(); - environment.GetEnvironmentVariable(Constants.ServiceNameEnv).Returns(string.Empty); + systemWrapper.GetEnvironmentVariable(Constants.ServiceNameEnv).Returns(string.Empty); - var configurations = new PowertoolsConfigurations(environment); + var configurations = new PowertoolsConfigurations(systemWrapper); // Act var result = configurations.Service; // Assert - environment.Received(1).GetEnvironmentVariable(Arg.Is(i => i == Constants.ServiceNameEnv)); + systemWrapper.Received(1).GetEnvironmentVariable(Arg.Is(i => i == Constants.ServiceNameEnv)); Assert.Equal(result, defaultService); } @@ -175,17 +175,17 @@ public void Service_WhenEnvironmentHasValue_ReturnsValue() { // Arrange var service = Guid.NewGuid().ToString(); - var environment = Substitute.For(); + var systemWrapper = Substitute.For(); - environment.GetEnvironmentVariable(Constants.ServiceNameEnv).Returns(service); + systemWrapper.GetEnvironmentVariable(Constants.ServiceNameEnv).Returns(service); - var configurations = new PowertoolsConfigurations(environment); + var configurations = new PowertoolsConfigurations(systemWrapper); // Act var result = configurations.Service; // Assert - environment.Received(1).GetEnvironmentVariable(Arg.Is(i => i == Constants.ServiceNameEnv)); + systemWrapper.Received(1).GetEnvironmentVariable(Arg.Is(i => i == Constants.ServiceNameEnv)); Assert.Equal(result, service); } @@ -199,17 +199,17 @@ public void IsServiceDefined_WhenEnvironmentHasValue_ReturnsTrue() { // Arrange var service = Guid.NewGuid().ToString(); - var environment = Substitute.For(); + var systemWrapper = Substitute.For(); - environment.GetEnvironmentVariable(Constants.ServiceNameEnv).Returns(service); + systemWrapper.GetEnvironmentVariable(Constants.ServiceNameEnv).Returns(service); - var configurations = new PowertoolsConfigurations(environment); + var configurations = new PowertoolsConfigurations(systemWrapper); // Act var result = configurations.IsServiceDefined; // Assert - environment.Received(1).GetEnvironmentVariable(Arg.Is(i => i == Constants.ServiceNameEnv)); + systemWrapper.Received(1).GetEnvironmentVariable(Arg.Is(i => i == Constants.ServiceNameEnv)); Assert.True(result); } @@ -218,17 +218,17 @@ public void IsServiceDefined_WhenEnvironmentHasValue_ReturnsTrue() public void IsServiceDefined_WhenEnvironmentDoesNotHaveValue_ReturnsFalse() { // Arrange - var environment = Substitute.For(); + var systemWrapper = Substitute.For(); - environment.GetEnvironmentVariable(Constants.ServiceNameEnv).Returns(string.Empty); + systemWrapper.GetEnvironmentVariable(Constants.ServiceNameEnv).Returns(string.Empty); - var configurations = new PowertoolsConfigurations(environment); + var configurations = new PowertoolsConfigurations(systemWrapper); // Act var result = configurations.IsServiceDefined; // Assert - environment.Received(1).GetEnvironmentVariable(Arg.Is(i => i == Constants.ServiceNameEnv)); + systemWrapper.Received(1).GetEnvironmentVariable(Arg.Is(i => i == Constants.ServiceNameEnv)); Assert.False(result); } @@ -241,17 +241,17 @@ public void IsServiceDefined_WhenEnvironmentDoesNotHaveValue_ReturnsFalse() public void TracerCaptureResponse_WhenEnvironmentIsNull_ReturnsDefaultValue() { // Arrange - var environment = Substitute.For(); + var systemWrapper = Substitute.For(); - environment.GetEnvironmentVariable(Constants.TracerCaptureResponseEnv).Returns(string.Empty); + systemWrapper.GetEnvironmentVariable(Constants.TracerCaptureResponseEnv).Returns(string.Empty); - var configurations = new PowertoolsConfigurations(environment); + var configurations = new PowertoolsConfigurations(systemWrapper); // Act var result = configurations.TracerCaptureResponse; // Assert - environment.Received(1) + systemWrapper.Received(1) .GetEnvironmentVariable(Arg.Is(i => i == Constants.TracerCaptureResponseEnv)); Assert.True(result); @@ -261,17 +261,17 @@ public void TracerCaptureResponse_WhenEnvironmentIsNull_ReturnsDefaultValue() public void TracerCaptureResponse_WhenEnvironmentHasValue_ReturnsValueFalse() { // Arrange - var environment = Substitute.For(); + var systemWrapper = Substitute.For(); - environment.GetEnvironmentVariable(Constants.TracerCaptureResponseEnv).Returns("false"); + systemWrapper.GetEnvironmentVariable(Constants.TracerCaptureResponseEnv).Returns("false"); - var configurations = new PowertoolsConfigurations(environment); + var configurations = new PowertoolsConfigurations(systemWrapper); // Act var result = configurations.TracerCaptureResponse; // Assert - environment.Received(1) + systemWrapper.Received(1) .GetEnvironmentVariable(Arg.Is(i => i == Constants.TracerCaptureResponseEnv)); Assert.False(result); @@ -281,17 +281,17 @@ public void TracerCaptureResponse_WhenEnvironmentHasValue_ReturnsValueFalse() public void TracerCaptureResponse_WhenEnvironmentHasValue_ReturnsValueTrue() { // Arrange - var environment = Substitute.For(); + var systemWrapper = Substitute.For(); - environment.GetEnvironmentVariable(Constants.TracerCaptureResponseEnv).Returns("true"); + systemWrapper.GetEnvironmentVariable(Constants.TracerCaptureResponseEnv).Returns("true"); - var configurations = new PowertoolsConfigurations(environment); + var configurations = new PowertoolsConfigurations(systemWrapper); // Act var result = configurations.TracerCaptureResponse; // Assert - environment.Received(1) + systemWrapper.Received(1) .GetEnvironmentVariable(Arg.Is(i => i == Constants.TracerCaptureResponseEnv)); Assert.True(result); @@ -305,17 +305,17 @@ public void TracerCaptureResponse_WhenEnvironmentHasValue_ReturnsValueTrue() public void TracerCaptureError_WhenEnvironmentIsNull_ReturnsDefaultValue() { // Arrange - var environment = Substitute.For(); + var systemWrapper = Substitute.For(); - environment.GetEnvironmentVariable(Constants.TracerCaptureErrorEnv).Returns(string.Empty); + systemWrapper.GetEnvironmentVariable(Constants.TracerCaptureErrorEnv).Returns(string.Empty); - var configurations = new PowertoolsConfigurations(environment); + var configurations = new PowertoolsConfigurations(systemWrapper); // Act var result = configurations.TracerCaptureError; // Assert - environment.Received(1) + systemWrapper.Received(1) .GetEnvironmentVariable(Arg.Is(i => i == Constants.TracerCaptureErrorEnv)); Assert.True(result); @@ -325,17 +325,17 @@ public void TracerCaptureError_WhenEnvironmentIsNull_ReturnsDefaultValue() public void TracerCaptureError_WhenEnvironmentHasValue_ReturnsValueFalse() { // Arrange - var environment = Substitute.For(); + var systemWrapper = Substitute.For(); - environment.GetEnvironmentVariable(Constants.TracerCaptureErrorEnv).Returns("false"); + systemWrapper.GetEnvironmentVariable(Constants.TracerCaptureErrorEnv).Returns("false"); - var configurations = new PowertoolsConfigurations(environment); + var configurations = new PowertoolsConfigurations(systemWrapper); // Act var result = configurations.TracerCaptureError; // Assert - environment.Received(1) + systemWrapper.Received(1) .GetEnvironmentVariable(Arg.Is(i => i == Constants.TracerCaptureErrorEnv)); Assert.False(result); @@ -345,17 +345,17 @@ public void TracerCaptureError_WhenEnvironmentHasValue_ReturnsValueFalse() public void TracerCaptureError_WhenEnvironmentHasValue_ReturnsValueTrue() { // Arrange - var environment = Substitute.For(); + var systemWrapper = Substitute.For(); - environment.GetEnvironmentVariable(Constants.TracerCaptureErrorEnv).Returns("true"); + systemWrapper.GetEnvironmentVariable(Constants.TracerCaptureErrorEnv).Returns("true"); - var configurations = new PowertoolsConfigurations(environment); + var configurations = new PowertoolsConfigurations(systemWrapper); // Act var result = configurations.TracerCaptureError; // Assert - environment.Received(1) + systemWrapper.Received(1) .GetEnvironmentVariable(Arg.Is(i => i == Constants.TracerCaptureErrorEnv)); Assert.True(result); @@ -369,17 +369,17 @@ public void TracerCaptureError_WhenEnvironmentHasValue_ReturnsValueTrue() public void IsSamLocal_WhenEnvironmentIsNull_ReturnsDefaultValue() { // Arrange - var environment = Substitute.For(); + var systemWrapper = Substitute.For(); - environment.GetEnvironmentVariable(Constants.SamLocalEnv).Returns(string.Empty); + systemWrapper.GetEnvironmentVariable(Constants.SamLocalEnv).Returns(string.Empty); - var configurations = new PowertoolsConfigurations(environment); + var configurations = new PowertoolsConfigurations(systemWrapper); // Act var result = configurations.IsSamLocal; // Assert - environment.Received(1) + systemWrapper.Received(1) .GetEnvironmentVariable(Arg.Is(i => i == Constants.SamLocalEnv)); Assert.False(result); @@ -389,17 +389,17 @@ public void IsSamLocal_WhenEnvironmentIsNull_ReturnsDefaultValue() public void IsSamLocal_WhenEnvironmentHasValue_ReturnsValueFalse() { // Arrange - var environment = Substitute.For(); + var systemWrapper = Substitute.For(); - environment.GetEnvironmentVariable(Constants.SamLocalEnv).Returns("false"); + systemWrapper.GetEnvironmentVariable(Constants.SamLocalEnv).Returns("false"); - var configurations = new PowertoolsConfigurations(environment); + var configurations = new PowertoolsConfigurations(systemWrapper); // Act var result = configurations.IsSamLocal; // Assert - environment.Received(1) + systemWrapper.Received(1) .GetEnvironmentVariable(Arg.Is(i => i == Constants.SamLocalEnv)); Assert.False(result); @@ -409,17 +409,17 @@ public void IsSamLocal_WhenEnvironmentHasValue_ReturnsValueFalse() public void IsSamLocal_WhenEnvironmentHasValue_ReturnsValueTrue() { // Arrange - var environment = Substitute.For(); + var systemWrapper = Substitute.For(); - environment.GetEnvironmentVariable(Constants.SamLocalEnv).Returns("true"); + systemWrapper.GetEnvironmentVariable(Constants.SamLocalEnv).Returns("true"); - var configurations = new PowertoolsConfigurations(environment); + var configurations = new PowertoolsConfigurations(systemWrapper); // Act var result = configurations.IsSamLocal; // Assert - environment.Received(1) + systemWrapper.Received(1) .GetEnvironmentVariable(Arg.Is(i => i == Constants.SamLocalEnv)); Assert.True(result); @@ -433,17 +433,17 @@ public void IsSamLocal_WhenEnvironmentHasValue_ReturnsValueTrue() public void TracingDisabled_WhenEnvironmentIsNull_ReturnsDefaultValue() { // Arrange - var environment = Substitute.For(); + var systemWrapper = Substitute.For(); - environment.GetEnvironmentVariable(Constants.TracingDisabledEnv).Returns(string.Empty); + systemWrapper.GetEnvironmentVariable(Constants.TracingDisabledEnv).Returns(string.Empty); - var configurations = new PowertoolsConfigurations(environment); + var configurations = new PowertoolsConfigurations(systemWrapper); // Act var result = configurations.TracingDisabled; // Assert - environment.Received(1) + systemWrapper.Received(1) .GetEnvironmentVariable(Arg.Is(i => i == Constants.TracingDisabledEnv)); Assert.False(result); @@ -453,17 +453,17 @@ public void TracingDisabled_WhenEnvironmentIsNull_ReturnsDefaultValue() public void TracingDisabled_WhenEnvironmentHasValue_ReturnsValueFalse() { // Arrange - var environment = Substitute.For(); + var systemWrapper = Substitute.For(); - environment.GetEnvironmentVariable(Constants.TracingDisabledEnv).Returns("false"); + systemWrapper.GetEnvironmentVariable(Constants.TracingDisabledEnv).Returns("false"); - var configurations = new PowertoolsConfigurations(environment); + var configurations = new PowertoolsConfigurations(systemWrapper); // Act var result = configurations.TracingDisabled; // Assert - environment.Received(1) + systemWrapper.Received(1) .GetEnvironmentVariable(Arg.Is(i => i == Constants.TracingDisabledEnv)); Assert.False(result); @@ -473,17 +473,17 @@ public void TracingDisabled_WhenEnvironmentHasValue_ReturnsValueFalse() public void TracingDisabled_WhenEnvironmentHasValue_ReturnsValueTrue() { // Arrange - var environment = Substitute.For(); + var systemWrapper = Substitute.For(); - environment.GetEnvironmentVariable(Constants.TracingDisabledEnv).Returns("true"); + systemWrapper.GetEnvironmentVariable(Constants.TracingDisabledEnv).Returns("true"); - var configurations = new PowertoolsConfigurations(environment); + var configurations = new PowertoolsConfigurations(systemWrapper); // Act var result = configurations.TracingDisabled; // Assert - environment.Received(1) + systemWrapper.Received(1) .GetEnvironmentVariable(Arg.Is(i => i == Constants.TracingDisabledEnv)); Assert.True(result); @@ -497,17 +497,17 @@ public void TracingDisabled_WhenEnvironmentHasValue_ReturnsValueTrue() public void IsLambdaEnvironment_WhenEnvironmentIsNull_ReturnsFalse() { // Arrange - var environment = Substitute.For(); + var systemWrapper = Substitute.For(); - environment.GetEnvironmentVariable(Constants.LambdaTaskRoot).Returns((string)null); + systemWrapper.GetEnvironmentVariable(Constants.LambdaTaskRoot).Returns((string)null); - var configurations = new PowertoolsConfigurations(environment); + var configurations = new PowertoolsConfigurations(systemWrapper); // Act var result = configurations.IsLambdaEnvironment; // Assert - environment.Received(1) + systemWrapper.Received(1) .GetEnvironmentVariable(Arg.Is(i => i == Constants.LambdaTaskRoot)); Assert.False(result); @@ -517,17 +517,17 @@ public void IsLambdaEnvironment_WhenEnvironmentIsNull_ReturnsFalse() public void IsLambdaEnvironment_WhenEnvironmentHasValue_ReturnsTrue() { // Arrange - var environment = Substitute.For(); + var systemWrapper = Substitute.For(); - environment.GetEnvironmentVariable(Constants.TracingDisabledEnv).Returns(Guid.NewGuid().ToString()); + systemWrapper.GetEnvironmentVariable(Constants.TracingDisabledEnv).Returns(Guid.NewGuid().ToString()); - var configurations = new PowertoolsConfigurations(environment); + var configurations = new PowertoolsConfigurations(systemWrapper); // Act var result = configurations.IsLambdaEnvironment; // Assert - environment.Received(1) + systemWrapper.Received(1) .GetEnvironmentVariable(Arg.Is(i => i == Constants.LambdaTaskRoot)); Assert.True(result); @@ -537,20 +537,20 @@ public void IsLambdaEnvironment_WhenEnvironmentHasValue_ReturnsTrue() public void Set_Lambda_Execution_Context() { // Arrange - var environment = Substitute.For(); + var systemWrapper = Substitute.For(); - // environment.Setup(c => + // systemWrapper.Setup(c => // c.SetExecutionEnvironment(GetType()) // ); - var configurations = new PowertoolsConfigurations(environment); + var configurations = new PowertoolsConfigurations(systemWrapper); // Act configurations.SetExecutionEnvironment(typeof(PowertoolsConfigurations)); // Assert // method with correct type was called - environment.Received(1) + systemWrapper.Received(1) .SetExecutionEnvironment(Arg.Is(i => i == typeof(PowertoolsConfigurations))); } diff --git a/libraries/tests/AWS.Lambda.Powertools.Common.Tests/Core/PowertoolsEnvironmentTest.cs b/libraries/tests/AWS.Lambda.Powertools.Common.Tests/Core/PowertoolsEnvironmentTest.cs index 9f9e153cb..df41e2538 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Common.Tests/Core/PowertoolsEnvironmentTest.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Common.Tests/Core/PowertoolsEnvironmentTest.cs @@ -1,9 +1,9 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Xml.Linq; using System.Xml.XPath; -using NSubstitute; using Xunit; namespace AWS.Lambda.Powertools.Common.Tests; @@ -14,57 +14,54 @@ public class PowertoolsEnvironmentTest : IDisposable public void Set_Execution_Environment() { // Arrange - var powertoolsEnv = new PowertoolsEnvironment(); + var systemWrapper = new SystemWrapper(new MockEnvironment()); // Act - powertoolsEnv.SetExecutionEnvironment(this); + systemWrapper.SetExecutionEnvironment(this); // Assert - Assert.Equal($"{Constants.FeatureContextIdentifier}/Tests/1.0.0 PTENV/AWS_LAMBDA_DOTNET{Environment.Version.Major}", powertoolsEnv.GetEnvironmentVariable("AWS_EXECUTION_ENV")); + Assert.Equal($"{Constants.FeatureContextIdentifier}/Fake/1.0.0", systemWrapper.GetEnvironmentVariable("AWS_EXECUTION_ENV")); } [Fact] public void Set_Execution_Environment_WhenEnvironmentHasValue() { // Arrange - var powertoolsEnv = new PowertoolsEnvironment(); + var systemWrapper = new SystemWrapper(new MockEnvironment()); - powertoolsEnv.SetEnvironmentVariable("AWS_EXECUTION_ENV", "ExistingValuesInUserAgent"); + systemWrapper.SetEnvironmentVariable("AWS_EXECUTION_ENV", "ExistingValuesInUserAgent"); // Act - powertoolsEnv.SetExecutionEnvironment(this); + systemWrapper.SetExecutionEnvironment(this); // Assert - Assert.Equal($"ExistingValuesInUserAgent {Constants.FeatureContextIdentifier}/Tests/1.0.0 PTENV/AWS_LAMBDA_DOTNET{Environment.Version.Major}", powertoolsEnv.GetEnvironmentVariable("AWS_EXECUTION_ENV")); + Assert.Equal($"ExistingValuesInUserAgent {Constants.FeatureContextIdentifier}/Fake/1.0.0", systemWrapper.GetEnvironmentVariable("AWS_EXECUTION_ENV")); } [Fact] - public void Set_Same_Execution_Environment_Multiple_Times_Should_Only_Set_Once() + public void Set_Multiple_Execution_Environment() { // Arrange - var powertoolsEnv = new PowertoolsEnvironment(); + var systemWrapper = new SystemWrapper(new MockEnvironment()); // Act - powertoolsEnv.SetExecutionEnvironment(this); - powertoolsEnv.SetExecutionEnvironment(this); + systemWrapper.SetExecutionEnvironment(this); // Assert - Assert.Equal($"{Constants.FeatureContextIdentifier}/Tests/1.0.0 PTENV/AWS_LAMBDA_DOTNET{Environment.Version.Major}", powertoolsEnv.GetEnvironmentVariable("AWS_EXECUTION_ENV")); + Assert.Equal($"{Constants.FeatureContextIdentifier}/Fake/1.0.0", systemWrapper.GetEnvironmentVariable("AWS_EXECUTION_ENV")); } [Fact] - public void Set_Multiple_Execution_Environment() + public void Set_Execution_Real_Environment() { // Arrange - var powertoolsEnv = new PowertoolsEnvironment(); + var systemWrapper = new SystemWrapper(new PowertoolsEnvironment()); // Act - powertoolsEnv.SetExecutionEnvironment(this); - powertoolsEnv.SetExecutionEnvironment(powertoolsEnv.GetType()); + systemWrapper.SetExecutionEnvironment(this); // Assert - Assert.Equal($"{Constants.FeatureContextIdentifier}/Tests/1.0.0 PTENV/AWS_LAMBDA_DOTNET{Environment.Version.Major} {Constants.FeatureContextIdentifier}/Common/1.0.0", - powertoolsEnv.GetEnvironmentVariable("AWS_EXECUTION_ENV")); + Assert.Equal($"{Constants.FeatureContextIdentifier}/Tests/1.0.0", systemWrapper.GetEnvironmentVariable("AWS_EXECUTION_ENV")); } [Fact] @@ -86,251 +83,39 @@ public void Should_Use_Aspect_Injector_281() Assert.Equal("2.8.1", packageReference.Version.ToString()); } - [Fact] - public void SetExecutionEnvironment_Should_Format_Strings_Correctly_With_Mocked_Environment() - { - // Arrange - var mockEnvironment = Substitute.For(); - - // Mock the dependencies to return controlled values - mockEnvironment.GetAssemblyName(Arg.Any()).Returns("AWS.Lambda.Powertools.Common.Tests"); - mockEnvironment.GetAssemblyVersion(Arg.Any()).Returns("1.2.3"); - mockEnvironment.GetEnvironmentVariable("AWS_EXECUTION_ENV").Returns((string)null); - - // Setup the actual method call to use real implementation logic - mockEnvironment.When(x => x.SetExecutionEnvironment(Arg.Any())) - .Do(_ => - { - var assemblyName = "PT/Tests"; // Parsed name - var assemblyVersion = "1.2.3"; - var runtimeEnv = "PTENV/AWS_LAMBDA_DOTNET8"; // Assuming .NET 8 - var expectedValue = $"{assemblyName}/{assemblyVersion} {runtimeEnv}"; - - mockEnvironment.SetEnvironmentVariable("AWS_EXECUTION_ENV", expectedValue); - }); - - // Act - mockEnvironment.SetExecutionEnvironment(this); - - // Assert - mockEnvironment.Received(1).SetEnvironmentVariable("AWS_EXECUTION_ENV", "PT/Tests/1.2.3 PTENV/AWS_LAMBDA_DOTNET8"); - } - - [Fact] - public void SetExecutionEnvironment_Should_Append_To_Existing_Environment_With_Mocked_Values() - { - // Arrange - var mockEnvironment = Substitute.For(); - - // Mock existing environment value - mockEnvironment.GetEnvironmentVariable("AWS_EXECUTION_ENV").Returns("ExistingValue"); - mockEnvironment.GetAssemblyName(Arg.Any()).Returns("AWS.Lambda.Powertools.Logging"); - mockEnvironment.GetAssemblyVersion(Arg.Any()).Returns("2.1.0"); - - // Setup the method call - mockEnvironment.When(x => x.SetExecutionEnvironment(Arg.Any())) - .Do(_ => - { - var currentEnv = "ExistingValue"; - var assemblyName = "PT/Logging"; - var assemblyVersion = "2.1.0"; - var runtimeEnv = "PTENV/AWS_LAMBDA_DOTNET8"; - var expectedValue = $"{currentEnv} {assemblyName}/{assemblyVersion} {runtimeEnv}"; - - mockEnvironment.SetEnvironmentVariable("AWS_EXECUTION_ENV", expectedValue); - }); - - // Act - mockEnvironment.SetExecutionEnvironment(this); - - // Assert - mockEnvironment.Received(1).SetEnvironmentVariable("AWS_EXECUTION_ENV", "ExistingValue PT/Logging/2.1.0 PTENV/AWS_LAMBDA_DOTNET8"); - } - - [Fact] - public void SetExecutionEnvironment_Should_Not_Add_PTENV_Twice_With_Mocked_Values() - { - // Arrange - var mockEnvironment = Substitute.For(); - - // Mock existing environment value that already contains PTENV - mockEnvironment.GetEnvironmentVariable("AWS_EXECUTION_ENV").Returns("PT/Metrics/1.0.0 PTENV/AWS_LAMBDA_DOTNET8"); - mockEnvironment.GetAssemblyName(Arg.Any()).Returns("AWS.Lambda.Powertools.Tracing"); - mockEnvironment.GetAssemblyVersion(Arg.Any()).Returns("1.5.0"); - - // Setup the method call - should not add PTENV again - mockEnvironment.When(x => x.SetExecutionEnvironment(Arg.Any())) - .Do(_ => - { - var currentEnv = "PT/Metrics/1.0.0 PTENV/AWS_LAMBDA_DOTNET8"; - var assemblyName = "PT/Tracing"; - var assemblyVersion = "1.5.0"; - // No PTENV added since it already exists - var expectedValue = $"{currentEnv} {assemblyName}/{assemblyVersion}"; - - mockEnvironment.SetEnvironmentVariable("AWS_EXECUTION_ENV", expectedValue); - }); - - // Act - mockEnvironment.SetExecutionEnvironment(this); - - // Assert - mockEnvironment.Received(1).SetEnvironmentVariable("AWS_EXECUTION_ENV", "PT/Metrics/1.0.0 PTENV/AWS_LAMBDA_DOTNET8 PT/Tracing/1.5.0"); - } - - [Fact] - public void GetAssemblyName_Should_Handle_Type_Object() - { - // Arrange - var powertoolsEnv = new PowertoolsEnvironment(); - var typeObject = typeof(PowertoolsEnvironment); - - // Act - var result = powertoolsEnv.GetAssemblyName(typeObject); - - // Assert - Assert.Equal("AWS.Lambda.Powertools.Common", result); - } - - [Fact] - public void GetAssemblyName_Should_Handle_Regular_Object() - { - // Arrange - var powertoolsEnv = new PowertoolsEnvironment(); - - // Act - var result = powertoolsEnv.GetAssemblyName(this); - - // Assert - Assert.Equal("AWS.Lambda.Powertools.Common.Tests", result); - } - - [Fact] - public void GetAssemblyVersion_Should_Handle_Type_Object() - { - // Arrange - var powertoolsEnv = new PowertoolsEnvironment(); - var typeObject = typeof(PowertoolsEnvironment); - - // Act - var result = powertoolsEnv.GetAssemblyVersion(typeObject); - - // Assert - Assert.Matches(@"\d+\.\d+\.\d+", result); // Should match version pattern like "1.0.0" - } - - [Fact] - public void GetAssemblyVersion_Should_Handle_Regular_Object() - { - // Arrange - var powertoolsEnv = new PowertoolsEnvironment(); - - // Act - var result = powertoolsEnv.GetAssemblyVersion(this); - - // Assert - Assert.Matches(@"\d+\.\d+\.\d+", result); // Should match version pattern like "1.0.0" - } - - [Fact] - public void ParseAssemblyName_Should_Handle_Assembly_Without_Dots() - { - // Act - var result = PowertoolsEnvironment.ParseAssemblyName("SimpleAssemblyName"); - - // Assert - Assert.Equal($"{Constants.FeatureContextIdentifier}/SimpleAssemblyName", result); - } - - [Fact] - public void ParseAssemblyName_Should_Handle_Assembly_With_Dots() - { - // Act - var result = PowertoolsEnvironment.ParseAssemblyName("AWS.Lambda.Powertools.Common"); - - // Assert - Assert.Equal($"{Constants.FeatureContextIdentifier}/Common", result); - } - - [Fact] - public void ParseAssemblyName_Should_Use_Cache_For_Same_Assembly_Name() - { - // Act - Call twice with same assembly name - var result1 = PowertoolsEnvironment.ParseAssemblyName("AWS.Lambda.Powertools.Tests"); - var result2 = PowertoolsEnvironment.ParseAssemblyName("AWS.Lambda.Powertools.Tests"); - - // Assert - Should return same result (cached) - Assert.Equal(result1, result2); - Assert.Equal($"{Constants.FeatureContextIdentifier}/Tests", result1); - } - - [Fact] - public void ParseAssemblyName_Null_Return_Empty() + public void Dispose() { - // Act - Call twice with same assembly name - var result = PowertoolsEnvironment.ParseAssemblyName(null); + //Do cleanup actions here - // Assert - Should return null - Assert.Empty(result); + Environment.SetEnvironmentVariable("AWS_EXECUTION_ENV", null); } +} + +/// +/// Fake Environment for testing +/// +class MockEnvironment : IPowertoolsEnvironment +{ + private readonly Dictionary _mockEnvironment = new(); - [Fact] - public void SetExecutionEnvironment_Should_Handle_Empty_Current_Environment() + public string GetEnvironmentVariable(string variableName) { - // Arrange - var powertoolsEnv = new PowertoolsEnvironment(); - Environment.SetEnvironmentVariable("AWS_EXECUTION_ENV", ""); - - // Act - powertoolsEnv.SetExecutionEnvironment(this); - - // Assert - var result = powertoolsEnv.GetEnvironmentVariable("AWS_EXECUTION_ENV"); - Assert.Contains($"{Constants.FeatureContextIdentifier}/Tests/", result); - Assert.Contains("PTENV/AWS_LAMBDA_DOTNET", result); + return _mockEnvironment.TryGetValue(variableName, out var value) ? value : null; } - - [Fact] - public void SetExecutionEnvironment_Should_Add_PTENV_When_Not_Present() + + public void SetEnvironmentVariable(string variableName, string value) { - // Arrange - var powertoolsEnv = new PowertoolsEnvironment(); - powertoolsEnv.SetEnvironmentVariable("AWS_EXECUTION_ENV", "SomeExistingValue"); - - // Act - powertoolsEnv.SetExecutionEnvironment(this); - - // Assert - var result = powertoolsEnv.GetEnvironmentVariable("AWS_EXECUTION_ENV"); - Assert.StartsWith("SomeExistingValue", result); - Assert.Contains("PTENV/AWS_LAMBDA_DOTNET", result); + // Check for entry not existing and add to dictionary + _mockEnvironment[variableName] = value; } - - [Fact] - public void SetExecutionEnvironment_Should_Not_Add_PTENV_When_Already_Present() + + public string GetAssemblyName(T type) { - // Arrange - var powertoolsEnv = new PowertoolsEnvironment(); - var existingValue = $"ExistingValue PTENV/AWS_LAMBDA_DOTNET{Environment.Version.Major}"; - powertoolsEnv.SetEnvironmentVariable("AWS_EXECUTION_ENV", existingValue); - - // Act - powertoolsEnv.SetExecutionEnvironment(this); - - // Assert - var result = powertoolsEnv.GetEnvironmentVariable("AWS_EXECUTION_ENV"); - var ptenvCount = result.Split("PTENV/").Length - 1; - Assert.Equal(1, ptenvCount); // Should only have one PTENV entry + return "AWS.Lambda.Powertools.Fake"; } - public void Dispose() + public string GetAssemblyVersion(T type) { - //Do cleanup actions here - Environment.SetEnvironmentVariable("AWS_EXECUTION_ENV", null); - - // Clear the singleton instance to ensure fresh state for each test - var instanceField = typeof(PowertoolsEnvironment).GetField("_instance", - System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); - instanceField?.SetValue(null, null); + return "1.0.0"; } } diff --git a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/AWS.Lambda.Powertools.EventHandler.Tests.csproj b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/AWS.Lambda.Powertools.EventHandler.Tests.csproj deleted file mode 100644 index 2d37bab65..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/AWS.Lambda.Powertools.EventHandler.Tests.csproj +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - AWS.Lambda.Powertools.EventHandler.Tests - AWS.Lambda.Powertools.EventHandler.Tests - net8.0 - enable - enable - - false - true - - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - - - - - - - - - PreserveNewest - - - - - PreserveNewest - - - - diff --git a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/BedrockAgentFunctionResolverAdditionalTests.cs b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/BedrockAgentFunctionResolverAdditionalTests.cs deleted file mode 100644 index 3f73c6867..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/BedrockAgentFunctionResolverAdditionalTests.cs +++ /dev/null @@ -1,340 +0,0 @@ -using Amazon.Lambda.Core; -using Amazon.Lambda.TestUtilities; -using AWS.Lambda.Powertools.EventHandler.Resolvers; -using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; - -namespace AWS.Lambda.Powertools.EventHandler.BedrockAgentFunction -{ - public class BedrockAgentFunctionResolverAdditionalTests - { - [Fact] - public async Task ResolveAsync_WithValidInput_ReturnsResult() - { - // Arrange - var resolver = new BedrockAgentFunctionResolver(); - resolver.Tool("AsyncTest", () => "Async result"); - - var input = new BedrockFunctionRequest { Function = "AsyncTest" }; - var context = new TestLambdaContext(); - - // Act - var result = await resolver.ResolveAsync(input, context); - - // Assert - Assert.Equal("Async result", result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - [Fact] - public void Tool_WithNullHandler_ThrowsException() - { - // Arrange - var resolver = new BedrockAgentFunctionResolver(); - Func nullHandler = null!; - - // Act/Assert - Assert.Throws(() => resolver.Tool("NullTest", nullHandler)); - } - - [Fact] - public void Resolve_WithNullFunction_ReturnsErrorResponse() - { - // Arrange - var resolver = new BedrockAgentFunctionResolver(); - var input = new BedrockFunctionRequest { Function = null }; - - // Act - var result = resolver.Resolve(input); - - // Assert - Assert.Equal("No tool specified in the request", result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - [Fact] - public void Resolve_WithEmptyFunction_ReturnsErrorResponse() - { - // Arrange - var resolver = new BedrockAgentFunctionResolver(); - var input = new BedrockFunctionRequest { Function = "" }; - - // Act - var result = resolver.Resolve(input); - - // Assert - Assert.Equal("No tool specified in the request", result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - [Fact] - public void Tool_WithHandlerThrowingException_ReturnsErrorResponse() - { - // Arrange - var resolver = new BedrockAgentFunctionResolver(); - resolver.Tool("ExceptionTest", (BedrockFunctionRequest input, ILambdaContext ctx) => { - throw new InvalidOperationException("Handler exception"); - return new BedrockFunctionResponse(); - }); - - var input = new BedrockFunctionRequest { Function = "ExceptionTest" }; - - // Act - var result = resolver.Resolve(input); - - // Assert - Assert.Equal("Error when invoking tool: Handler exception", result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - [Fact] - public void Tool_WithDynamicInvokeException_ReturnsErrorResponse() - { - // Arrange - var resolver = new BedrockAgentFunctionResolver(); - resolver.Tool("ExceptionTest", (Func)(() => { - throw new InvalidOperationException("Dynamic invoke exception"); - })); - - var input = new BedrockFunctionRequest { Function = "ExceptionTest" }; - - // Act - var result = resolver.Resolve(input); - - // Assert - Assert.Contains("Error when invoking tool", result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - [Fact] - public void Tool_ObjectFunctionRegistration_ReturnsObjectAsString() - { - // Arrange - var testObject = new TestObject { Id = 123, Name = "Test" }; - var resolver = new BedrockAgentFunctionResolver(); - resolver.Tool("ObjectTest", () => testObject); - - var input = new BedrockFunctionRequest { Function = "ObjectTest" }; - - // Act - var result = resolver.Resolve(input); - - // Assert - Assert.Equal(testObject.ToString(), result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - [Fact] - public async Task Resolve_WithAsyncTask_HandlesCorrectly() - { - // Arrange - var resolver = new BedrockAgentFunctionResolver(); - resolver.Tool("AsyncTaskTest", async (string message) => { - await Task.Delay(10); // Simulate async work - return $"Processed: {message}"; - }); - - var input = new BedrockFunctionRequest { - Function = "AsyncTaskTest", - Parameters = new List { - new Parameter { Name = "message", Value = "hello", Type = "String" } - } - }; - - // Act - var result = resolver.Resolve(input); - - // Assert - Assert.Equal("Processed: hello", result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - [Fact] - public void Tool_WithBedrockFunctionResponseHandlerNoContext_MapsCorrectly() - { - // Arrange - var resolver = new BedrockAgentFunctionResolver(); - resolver.Tool("NoContextTest", (BedrockFunctionRequest request) => new BedrockFunctionResponse - { - Response = new Response - { - ActionGroup = "TestGroup", - Function = "NoContextTest", - FunctionResponse = new FunctionResponse - { - ResponseBody = new ResponseBody - { - Text = new TextBody { Body = "No context needed" } - } - } - } - }); - - var input = new BedrockFunctionRequest { Function = "NoContextTest" }; - - // Act - var result = resolver.Resolve(input); - - // Assert - Assert.Equal("No context needed", result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - [Fact] - public void Tool_WithBedrockFunctionResponseHandler_MapsCorrectly() - { - // Arrange - var resolver = new BedrockAgentFunctionResolver(); - resolver.Tool("ResponseTest", () => new BedrockFunctionResponse - { - Response = new Response - { - ActionGroup = "TestGroup", - Function = "ResponseTest", - FunctionResponse = new FunctionResponse - { - ResponseBody = new ResponseBody - { - Text = new TextBody { Body = "Direct response" } - } - } - } - }); - - var input = new BedrockFunctionRequest { Function = "ResponseTest" }; - - // Act - var result = resolver.Resolve(input); - - // Assert - Assert.Equal("Direct response", result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - [Fact] - public void Tool_WithCustomFailureResponse_ReturnsFailureState() - { - // Arrange - var resolver = new BedrockAgentFunctionResolver(); - resolver.Tool("CustomFailure", () => - { - // Return a custom FAILURE response - return new BedrockFunctionResponse - { - Response = new Response - { - ActionGroup = "TestGroup", - Function = "CustomFailure", - FunctionResponse = new FunctionResponse - { - ResponseBody = new ResponseBody - { - Text = new TextBody - { - Body = "Critical error occurred: Database unavailable" - } - }, - ResponseState = ResponseState.FAILURE // Mark as FAILURE to abort the conversation - } - } - }; - }); - - var input = new BedrockFunctionRequest { Function = "CustomFailure" }; - var context = new TestLambdaContext(); - - // Act - var result = resolver.Resolve(input, context); - - // Assert - Assert.Equal("Critical error occurred: Database unavailable", result.Response.FunctionResponse.ResponseBody.Text.Body); - Assert.Equal("FAILURE", result.Response.FunctionResponse.ResponseState.ToString()); - } - - [Fact] - public void Tool_WithSessionAttributesPersistence_MaintainsStateAcrossInvocations() - { - // Arrange - var resolver = new BedrockAgentFunctionResolver(); - - // Create a counter tool that reads and updates session attributes - resolver.Tool("CounterTool", (BedrockFunctionRequest request) => - { - // Read the current count from session attributes - int currentCount = 0; - if (request.SessionAttributes != null && - request.SessionAttributes.TryGetValue("counter", out var countStr) && - int.TryParse(countStr, out var count)) - { - currentCount = count; - } - - // Increment the counter - currentCount++; - - // Create a new dictionary with updated counter - var updatedSessionAttributes = new Dictionary(request.SessionAttributes ?? new Dictionary()) - { - ["counter"] = currentCount.ToString(), - ["lastAccessed"] = DateTime.UtcNow.ToString("o") - }; - - // Return response with updated session attributes - return new BedrockFunctionResponse - { - Response = new Response - { - ActionGroup = request.ActionGroup, - Function = request.Function, - FunctionResponse = new FunctionResponse - { - ResponseBody = new ResponseBody - { - Text = new TextBody { Body = $"Current count: {currentCount}" } - } - } - }, - SessionAttributes = updatedSessionAttributes, - PromptSessionAttributes = request.PromptSessionAttributes - }; - }); - - // First invocation - should start with 0 and increment to 1 - var firstInput = new BedrockFunctionRequest - { - Function = "CounterTool", - SessionAttributes = new Dictionary(), - PromptSessionAttributes = new Dictionary { ["prompt"] = "initial" } - }; - - // Second invocation - should use the session attributes from first response - var secondInput = new BedrockFunctionRequest { Function = "CounterTool" }; - - // Act - var firstResult = resolver.Resolve(firstInput); - // In a real scenario, the agent would pass the updated session attributes back to us - secondInput.SessionAttributes = firstResult.SessionAttributes; - secondInput.PromptSessionAttributes = firstResult.PromptSessionAttributes; - var secondResult = resolver.Resolve(secondInput); - - // Now a third invocation to verify the counter keeps incrementing - var thirdInput = new BedrockFunctionRequest { Function = "CounterTool" }; - thirdInput.SessionAttributes = secondResult.SessionAttributes; - thirdInput.PromptSessionAttributes = secondResult.PromptSessionAttributes; - var thirdResult = resolver.Resolve(thirdInput); - - // Assert - Assert.Equal("Current count: 1", firstResult.Response.FunctionResponse.ResponseBody.Text.Body); - Assert.Equal("Current count: 2", secondResult.Response.FunctionResponse.ResponseBody.Text.Body); - Assert.Equal("Current count: 3", thirdResult.Response.FunctionResponse.ResponseBody.Text.Body); - - // Verify session attributes are maintained - Assert.Equal("1", firstResult.SessionAttributes["counter"]); - Assert.Equal("2", secondResult.SessionAttributes["counter"]); - Assert.Equal("3", thirdResult.SessionAttributes["counter"]); - - // Verify prompt attributes are preserved - Assert.Equal("initial", firstResult.PromptSessionAttributes["prompt"]); - Assert.Equal("initial", secondResult.PromptSessionAttributes["prompt"]); - Assert.Equal("initial", thirdResult.PromptSessionAttributes["prompt"]); - } - - private class TestObject - { - public int Id { get; set; } - public string Name { get; set; } = ""; - - public override string ToString() => $"{Name} (ID: {Id})"; - } - } -} diff --git a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/BedrockAgentFunctionResolverExceptionTests.cs b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/BedrockAgentFunctionResolverExceptionTests.cs deleted file mode 100644 index b05c3f426..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/BedrockAgentFunctionResolverExceptionTests.cs +++ /dev/null @@ -1,68 +0,0 @@ -using Amazon.Lambda.TestUtilities; -using AWS.Lambda.Powertools.EventHandler.Resolvers; -using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; - -namespace AWS.Lambda.Powertools.EventHandler.BedrockAgentFunction -{ - public class BedrockAgentFunctionResolverExceptionTests - { - [Fact] - public void RegisterToolHandler_WithParameterMappingException_ReturnsErrorResponse() - { - // Arrange - var resolver = new BedrockAgentFunctionResolver(); - - // Register a tool that requires a complex parameter that can't be mapped automatically - resolver.Tool("ComplexTest", (TestComplexType complex) => $"Name: {complex.Name}"); - - var input = new BedrockFunctionRequest - { - Function = "ComplexTest", - Parameters = new List - { - // This parameter can't be automatically mapped to the complex type - new Parameter { Name = "complex", Value = "{\"name\":\"Test\"}", Type = "String" } - } - }; - var context = new TestLambdaContext(); - - // Act - var result = resolver.Resolve(input, context); - - // Assert - // This should trigger the parameter mapping exception path - Assert.Contains("Error when invoking tool:", result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - [Fact] - public void RegisterToolHandler_WithNestedExceptionInDelegateInvoke_HandlesCorrectly() - { - // Arrange - var resolver = new BedrockAgentFunctionResolver(); - - // Register a tool with a delegate that will throw an exception with inner exception - resolver.Tool("NestedExceptionTest", () => { - throw new AggregateException("Outer exception", - new ApplicationException("Inner exception message")); - return "Should not reach here"; - }); - - var input = new BedrockFunctionRequest { Function = "NestedExceptionTest" }; - var context = new TestLambdaContext(); - - // Act - var result = resolver.Resolve(input, context); - - // Assert - // The error should contain the inner exception message - Assert.Contains("Inner exception message", result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - // A test complex type that can't be automatically mapped from parameters - private class TestComplexType - { - public string Name { get; set; } = ""; - public int Value { get; set; } - } - } -} diff --git a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/BedrockAgentFunctionResolverTests.cs b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/BedrockAgentFunctionResolverTests.cs deleted file mode 100644 index 1090a2484..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/BedrockAgentFunctionResolverTests.cs +++ /dev/null @@ -1,943 +0,0 @@ -using System.Globalization; -using System.Text; -using System.Text.Json.Serialization; -using Amazon.Lambda.Core; -using Amazon.Lambda.TestUtilities; -using AWS.Lambda.Powertools.EventHandler.Resolvers; -using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; -using Microsoft.Extensions.DependencyInjection; - -#pragma warning disable CS0162 // Unreachable code detected - - -namespace AWS.Lambda.Powertools.EventHandler.BedrockAgentFunction; - -public class BedrockAgentFunctionResolverTests -{ - [Fact] - public void TestFunctionHandlerWithNoParameters() - { - // Arrange - var resolver = new BedrockAgentFunctionResolver(); - resolver.Tool("TestFunction", () => new BedrockFunctionResponse - { - Response = new Response - { - ActionGroup = "TestGroup", - Function = "TestFunction", - FunctionResponse = new FunctionResponse - { - ResponseBody = new ResponseBody - { - Text = new TextBody { Body = "Hello, World!" } - } - } - } - }); - - var input = new BedrockFunctionRequest { Function = "TestFunction" }; - var context = new TestLambdaContext(); - - // Act - var result = resolver.Resolve(input, context); - - // Assert - Assert.Equal("Hello, World!", result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - [Fact] - public void TestFunctionHandlerWithDescription() - { - // Arrange - var resolver = new BedrockAgentFunctionResolver(); - resolver.Tool("TestFunction", () => new BedrockFunctionResponse - { - Response = new Response - { - ActionGroup = "TestGroup", - Function = "TestFunction", - FunctionResponse = new FunctionResponse - { - ResponseBody = new ResponseBody - { - Text = new TextBody { Body = "Hello, World!" } - } - } - } - }, - "This is a test function"); - - var input = new BedrockFunctionRequest { Function = "TestFunction" }; - var context = new TestLambdaContext(); - - // Act - var result = resolver.Resolve(input, context); - - // Assert - Assert.Equal("Hello, World!", result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - [Fact] - public void TestFunctionHandlerWithMultiplTools() - { - // Arrange - var resolver = new BedrockAgentFunctionResolver(); - - resolver.Tool("TestFunction1", () => new BedrockFunctionResponse - { - Response = new Response - { - ActionGroup = "TestGroup", - Function = "TestFunction", - FunctionResponse = new FunctionResponse - { - ResponseBody = new ResponseBody - { - Text = new TextBody { Body = "Hello from Function 1!" } - } - } - } - }); - resolver.Tool("TestFunction2", () => new BedrockFunctionResponse - { - Response = new Response - { - ActionGroup = "TestGroup", - Function = "TestFunction", - FunctionResponse = new FunctionResponse - { - ResponseBody = new ResponseBody - { - Text = new TextBody { Body = "Hello from Function 2!" } - } - } - } - }); - - var input1 = new BedrockFunctionRequest { Function = "TestFunction1" }; - var input2 = new BedrockFunctionRequest { Function = "TestFunction2" }; - var context = new TestLambdaContext(); - - // Act - var result1 = resolver.Resolve(input1, context); - var result2 = resolver.Resolve(input2, context); - - // Assert - Assert.Equal("Hello from Function 1!", result1.Response.FunctionResponse.ResponseBody.Text.Body); - Assert.Equal("Hello from Function 2!", result2.Response.FunctionResponse.ResponseBody.Text.Body); - } - - [Fact] - public void TestFunctionHandlerWithMultiplToolsDuplicate() - { - // Arrange - var resolver = new BedrockAgentFunctionResolver(); - resolver.Tool("TestFunction1", () => new BedrockFunctionResponse - { - Response = new Response - { - ActionGroup = "TestGroup", - Function = "TestFunction", - FunctionResponse = new FunctionResponse - { - ResponseBody = new ResponseBody - { - Text = new TextBody { Body = "Hello from Function 1!" } - } - } - } - }); - resolver.Tool("TestFunction1", () => new BedrockFunctionResponse - { - Response = new Response - { - ActionGroup = "TestGroup", - Function = "TestFunction", - FunctionResponse = new FunctionResponse - { - ResponseBody = new ResponseBody - { - Text = new TextBody { Body = "Hello from Function 2!" } - } - } - } - }); - - var input1 = new BedrockFunctionRequest { Function = "TestFunction1" }; - var input2 = new BedrockFunctionRequest { Function = "TestFunction1" }; - var context = new TestLambdaContext(); - - // Act - var result1 = resolver.Resolve(input1, context); - var result2 = resolver.Resolve(input2, context); - - // Assert - Assert.Equal("Hello from Function 2!", result1.Response.FunctionResponse.ResponseBody.Text.Body); - Assert.Equal("Hello from Function 2!", result2.Response.FunctionResponse.ResponseBody.Text.Body); - } - - - [Fact] - public void TestFunctionHandlerWithInput() - { - // Arrange - var resolver = new BedrockAgentFunctionResolver(); - resolver.Tool("TestFunction", - (input, context) => new BedrockFunctionResponse - { - Response = new Response - { - ActionGroup = "TestGroup", - Function = "TestFunction", - FunctionResponse = new FunctionResponse - { - ResponseBody = new ResponseBody - { - Text = new TextBody { Body = $"Hello, {input.Function}!" } - } - } - } - }); - - var input = new BedrockFunctionRequest { Function = "TestFunction" }; - var context = new TestLambdaContext(); - - // Act - var result = resolver.Resolve(input, context); - - // Assert - Assert.Equal("Hello, TestFunction!", result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - [Fact] - public void TestFunctionHandlerNoToolMatch() - { - // Arrange - var resolver = new BedrockAgentFunctionResolver(); - resolver.Tool("TestFunction", () => new BedrockFunctionResponse - { - Response = new Response - { - ActionGroup = "TestGroup", - Function = "TestFunction", - FunctionResponse = new FunctionResponse - { - ResponseBody = new ResponseBody - { - Text = new TextBody { Body = "Hello, World!" } - } - } - } - }); - - var input = new BedrockFunctionRequest { Function = "NonExistentFunction" }; - var context = new TestLambdaContext(); - - // Act - var result = resolver.Resolve(input, context); - - // Assert - Assert.Equal($"Error: Tool {input.Function} has not been registered in handler", - result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - [Fact] - public void TestFunctionHandlerWithEvent() - { - // Arrange - var resolver = new BedrockAgentFunctionResolver(); - resolver.Tool( - name: "GetCustomForecast", - description: "Get detailed forecast for a location", - handler: (string location, int days, ILambdaContext ctx) => - { - ctx.Logger.LogLine($"Getting forecast for {location}"); - return $"{days}-day forecast for {location}"; - } - ); - - resolver.Tool( - name: "Greet", - description: "Greet a user", - handler: (string name) => { return $"Hello {name}"; } - ); - - resolver.Tool( - name: "Simple", - description: "Greet a user", - handler: () => { return "Hello"; } - ); - - var input = new BedrockFunctionRequest - { - Function = "GetCustomForecast", - Parameters = new List - { - new Parameter - { - Name = "location", - Value = "Lisbon", - Type = "String" - }, - new Parameter - { - Name = "days", - Value = "1", - Type = "Number" - } - } - }; - - var context = new TestLambdaContext(); - - // Act - var result = resolver.Resolve(input, context); - - // Assert - Assert.Equal("1-day forecast for Lisbon", result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - [Fact] - public void TestFunctionHandlerWithEventAndServices() - { - // Setup DI - var services = new ServiceCollection(); - services.AddSingleton(new MyImplementation()); - services.AddBedrockResolver(); - - var serviceProvider = services.BuildServiceProvider(); - var resolver = serviceProvider.GetRequiredService(); - - resolver.Tool( - name: "GetCustomForecast", - description: "Get detailed forecast for a location", - handler: async (string location, int days, IMyInterface client, ILambdaContext ctx) => - { - var resp = await client.DoSomething(location, days); - return resp; - } - ); - - var input = new BedrockFunctionRequest - { - Function = "GetCustomForecast", - Parameters = new List - { - new Parameter - { - Name = "location", - Value = "Lisbon", - Type = "String" - }, - new Parameter - { - Name = "days", - Value = "1", - Type = "Number" - } - } - }; - - var context = new TestLambdaContext(); - - // Act - var result = resolver.Resolve(input, context); - - // Assert - Assert.Equal("Forecast for Lisbon for 1 days", result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - [Fact] - public void TestFunctionHandlerWithBooleanParameter() - { - // Arrange - var resolver = new BedrockAgentFunctionResolver(); - resolver.Tool( - name: "TestBool", - description: "Test boolean parameter", - handler: (bool isEnabled) => { return $"Feature is {(isEnabled ? "enabled" : "disabled")}"; } - ); - - var input = new BedrockFunctionRequest - { - Function = "TestBool", - Parameters = new List - { - new Parameter - { - Name = "isEnabled", - Value = "true", - Type = "Boolean" - } - } - }; - - // Act - var result = resolver.Resolve(input); - - // Assert - Assert.Equal("Feature is enabled", result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - [Fact] - public void TestFunctionHandlerWithMissingRequiredParameter() - { - // Arrange - var resolver = new BedrockAgentFunctionResolver(); - resolver.Tool( - name: "RequiredParam", - description: "Function with required parameter", - handler: (string name) => $"Hello, {name}!" - ); - - var input = new BedrockFunctionRequest - { - Function = "RequiredParam", - Parameters = new List() // Empty parameters - }; - - // Act - var result = resolver.Resolve(input); - - // Assert - Assert.Contains("Hello, !", result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - [Fact] - public void TestFunctionHandlerWithMultipleParameterTypes() - { - // Arrange - var resolver = new BedrockAgentFunctionResolver(); - resolver.Tool( - name: "ComplexFunction", - description: "Test multiple parameter types", - handler: (string name, int count, bool isActive) => - { - return $"Name: {name}, Count: {count}, Active: {isActive}"; - } - ); - - var input = new BedrockFunctionRequest - { - Function = "ComplexFunction", - Parameters = new List - { - new Parameter { Name = "name", Value = "Test", Type = "String" }, - new Parameter { Name = "count", Value = "5", Type = "Integer" }, - new Parameter { Name = "isActive", Value = "true", Type = "Boolean" } - } - }; - - // Act - var result = resolver.Resolve(input); - - // Assert - Assert.Equal("Name: Test, Count: 5, Active: True", result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - public enum TestEnum - { - Option1, - Option2, - Option3 - } - - [Fact] - public void TestFunctionHandlerWithEnumParameter() - { - // Arrange - var resolver = new BedrockAgentFunctionResolver(); - resolver.Tool( - name: "EnumTest", - description: "Test enum parameter", - handler: (TestEnum option) => { return $"Selected option: {option}"; } - ); - - var input = new BedrockFunctionRequest - { - Function = "EnumTest", - Parameters = new List - { - new Parameter - { - Name = "option", - Value = "Option2", - Type = "String" // Enums come as strings - } - } - }; - - // Act - var result = resolver.Resolve(input); - - // Assert - Assert.Equal("Selected option: Option2", result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - [Fact] - public void TestParameterNameCaseSensitivity() - { - // Arrange - var resolver = new BedrockAgentFunctionResolver(); - resolver.Tool( - name: "CaseTest", - description: "Test case sensitivity", - handler: (string userName) => $"Hello, {userName}!" - ); - - var input = new BedrockFunctionRequest - { - Function = "CaseTest", - Parameters = new List - { - new Parameter - { - Name = "UserName", // Different case than parameter - Value = "John", - Type = "String" - } - } - }; - - // Act - var result = resolver.Resolve(input); - - // Assert - Assert.Equal("Hello, John!", result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - [Fact] - public void TestParameterOrderIndependence() - { - // Arrange - var resolver = new BedrockAgentFunctionResolver(); - resolver.Tool( - name: "OrderTest", - description: "Test parameter order independence", - handler: (string firstName, string lastName) => { return $"Name: {firstName} {lastName}"; } - ); - - var input = new BedrockFunctionRequest - { - Function = "OrderTest", - Parameters = new List - { - // Parameters in reverse order of handler parameters - new Parameter { Name = "lastName", Value = "Smith", Type = "String" }, - new Parameter { Name = "firstName", Value = "John", Type = "String" } - } - }; - - // Act - var result = resolver.Resolve(input); - - // Assert - Assert.Equal("Name: John Smith", result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - [Fact] - public void TestFunctionHandlerWithDecimalParameter() - { - // Arrange - var resolver = new BedrockAgentFunctionResolver(); - resolver.Tool( - name: "PriceCalculator", - description: "Calculate total price with tax", - handler: (decimal price) => - { - var withTax = price * 1.2m; - return $"Total price with tax: {withTax.ToString("F2", CultureInfo.InvariantCulture)}"; - } - ); - - var input = new BedrockFunctionRequest - { - Function = "PriceCalculator", - Parameters = new List - { - new Parameter - { - Name = "price", - Value = "29.99", - Type = "Number" - } - } - }; - - // Act - var result = resolver.Resolve(input); - - // Assert - Assert.Contains("35.99", result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - [Fact] - public void TestFunctionHandlerWithStringArrayParameter() - { - // Arrange - var resolver = new BedrockAgentFunctionResolver(); - resolver.Tool( - name: "ProcessWorkout", - description: "Process workout exercises", - handler: (string[] exercises) => - { - var result = new StringBuilder(); - result.AppendLine("Your workout plan:"); - - for (int i = 0; i < exercises.Length; i++) - { - result.AppendLine($" {i + 1}. {exercises[i]}"); - } - - return result.ToString(); - } - ); - - var input = new BedrockFunctionRequest - { - Function = "ProcessWorkout", - Parameters = new List - { - new Parameter - { - Name = "exercises", - Value = - "[\"Squats, 3 sets of 10 reps\",\"Push-ups, 3 sets of 10 reps\",\"Plank, 3 sets of 30 seconds\"]", - Type = "String" // The type is String since it contains JSON - } - } - }; - - // Act - var result = resolver.Resolve(input); - - // Assert - Assert.Contains("Your workout plan:", result.Response.FunctionResponse.ResponseBody.Text.Body); - Assert.Contains("1. Squats, 3 sets of 10 reps", result.Response.FunctionResponse.ResponseBody.Text.Body); - Assert.Contains("2. Push-ups, 3 sets of 10 reps", result.Response.FunctionResponse.ResponseBody.Text.Body); - Assert.Contains("3. Plank, 3 sets of 30 seconds", result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - [Fact] - public void TestFunctionHandlerWithExceptionInHandler() - { - // Arrange - var resolver = new BedrockAgentFunctionResolver(); - resolver.Tool( - name: "ThrowingFunction", - description: "Function that throws exception", - handler: () => - { - throw new InvalidOperationException("Test error"); - return "This will not run:"; - } - ); - - var input = new BedrockFunctionRequest { Function = "ThrowingFunction" }; - - // Act - var result = resolver.Resolve(input); - - // Assert - Assert.Contains("Error when invoking tool: Test error", result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - [Fact] - public void TestSessionAttributesPreservation() - { - // Arrange - var resolver = new BedrockAgentFunctionResolver(); - resolver.Tool( - name: "SessionTest", - description: "Test session attributes preservation", - handler: (string message) => message - ); - - var input = new BedrockFunctionRequest - { - Function = "SessionTest", - ActionGroup = "TestGroup", - Parameters = new List - { - new Parameter { Name = "message", Value = "Hello", Type = "String" } - }, - SessionAttributes = new Dictionary - { - { "userId", "12345" }, - { "preferredLanguage", "en-US" } - }, - PromptSessionAttributes = new Dictionary - { - { "context", "customer_support" }, - { "previousQuestion", "How do I reset my password?" } - } - }; - - // Act - var result = resolver.Resolve(input); - - // Assert - Assert.Equal("Hello", result.Response.FunctionResponse.ResponseBody.Text.Body); - Assert.Equal(2, result.SessionAttributes.Count); - Assert.Equal("12345", result.SessionAttributes["userId"]); - Assert.Equal("en-US", result.SessionAttributes["preferredLanguage"]); - Assert.Equal(2, result.PromptSessionAttributes.Count); - Assert.Equal("customer_support", result.PromptSessionAttributes["context"]); - Assert.Equal("How do I reset my password?", result.PromptSessionAttributes["previousQuestion"]); - } - - [Fact] - public void TestSessionAttributesPreservationWithErrorHandling() - { - // Arrange - var resolver = new BedrockAgentFunctionResolver(); - resolver.Tool( - name: "ErrorTest", - description: "Test session attributes preservation with error", - handler: () => { throw new Exception("Test error"); return "This will not run"; } - ); - - var input = new BedrockFunctionRequest - { - Function = "ErrorTest", - ActionGroup = "TestGroup", - SessionAttributes = new Dictionary - { - { "userId", "12345" }, - { "session", "active" } - }, - PromptSessionAttributes = new Dictionary - { - { "lastAction", "login" } - } - }; - - // Act - var result = resolver.Resolve(input); - - // Assert - Assert.Contains("Error when invoking tool: Test error", result.Response.FunctionResponse.ResponseBody.Text.Body); - Assert.Equal(2, result.SessionAttributes.Count); - Assert.Equal("12345", result.SessionAttributes["userId"]); - Assert.Equal("active", result.SessionAttributes["session"]); - Assert.Equal(1, result.PromptSessionAttributes?.Count); - Assert.Equal("login", result.PromptSessionAttributes?["lastAction"]); - } - - [Fact] - public void TestSessionAttributesPreservationWithNoToolMatch() - { - // Arrange - var resolver = new BedrockAgentFunctionResolver(); - - var input = new BedrockFunctionRequest - { - Function = "NonExistentTool", - SessionAttributes = new Dictionary - { - { "preferredTheme", "dark" } - }, - PromptSessionAttributes = new Dictionary - { - { "lastVisited", "homepage" } - } - }; - - // Act - var result = resolver.Resolve(input); - - // Assert - Assert.Contains($"Error: Tool {input.Function} has not been registered in handler", result.Response.FunctionResponse.ResponseBody.Text.Body); - Assert.Equal(1, result.SessionAttributes?.Count); - Assert.Equal("dark", result.SessionAttributes?["preferredTheme"]); - Assert.Equal(1, result.PromptSessionAttributes?.Count); - Assert.Equal("homepage", result.PromptSessionAttributes?["lastVisited"]); - } - - [Fact] - public void TestSReturningNull() - { - // Arrange - var resolver = new BedrockAgentFunctionResolver(); - resolver.Tool( - name: "NullTest", - description: "Test session attributes preservation with error", - handler: () => - { - string test = null!; - return test; - } - ); - - var input = new BedrockFunctionRequest - { - Function = "NullTest", - }; - - // Act - var result = resolver.Resolve(input); - - // Assert - Assert.Equal("", result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - [Fact] - public void TestToolOverrideWithWarning() - { - // Arrange - var resolver = new BedrockAgentFunctionResolver(); - - // Register a tool - resolver.Tool("Calculator", () => "Original Calculator"); - - // Register same tool again with different implementation - resolver.Tool("Calculator", () => "New Calculator"); - - // Verify the tool was overridden - var input = new BedrockFunctionRequest { Function = "Calculator" }; - var result = resolver.Resolve(input); - - // The second registration should have overwritten the first - Assert.Equal("New Calculator", result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - [Fact] - public void TestFunctionHandlerWithCustomType() - { - // Arrange - var resolver = new BedrockAgentFunctionResolver(); - resolver.Tool( - name: "PriceCalculator", - description: "Calculate total price with tax", - handler: (MyCustomType myCustomType) => - { - var withTax = myCustomType.Price * 1.2m; - return $"Total price with tax: {withTax.ToString("F2", CultureInfo.InvariantCulture)}"; - } - ); - - var input = new BedrockFunctionRequest - { - Function = "PriceCalculator", - InputText = "{\"Price\": 29.99}", // JSON representation of MyCustomType - }; - - // Act - var result = resolver.Resolve(input); - - // Assert - Assert.Contains("35.99", result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - [Fact] - public void TestFunctionHandlerWithCustomTypeWithTypeInfoResolver() - { - // Arrange - var resolver = new BedrockAgentFunctionResolver(MycustomSerializationContext.Default); - resolver.Tool( - name: "PriceCalculator", - description: "Calculate total price with tax", - handler: (MyCustomType myCustomType) => - { - var withTax = myCustomType.Price * 1.2m; - return $"Total price with tax: {withTax.ToString("F2", CultureInfo.InvariantCulture)}"; - } - ); - - var input = new BedrockFunctionRequest - { - Function = "PriceCalculator", - InputText = "{\"Price\": 29.99}", // JSON representation of MyCustomType - }; - - // Act - var result = resolver.Resolve(input); - - // Assert - Assert.Contains("35.99", result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - [Fact] - public void TestAttributeBasedToolRegistration() - { - // Arrange - - var services = new ServiceCollection(); - services.AddSingleton(new MyImplementation()); - services.AddBedrockResolver(); - - var serviceProvider = services.BuildServiceProvider(); - var resolver = serviceProvider.GetRequiredService() - .RegisterTool(); - - // Create test input for echo function - var echoInput = new BedrockFunctionRequest - { - Function = "Echo", - Parameters = new List - { - new Parameter { Name = "message", Value = "Hello world", Type = "String" } - } - }; - - // Create test input for calculate function - var calcInput = new BedrockFunctionRequest - { - Function = "Calculate", - Parameters = new List - { - new Parameter { Name = "x", Value = "5", Type = "Number" }, - new Parameter { Name = "y", Value = "3", Type = "Number" } - } - }; - - // Act - var echoResult = resolver.Resolve(echoInput); - var calcResult = resolver.Resolve(calcInput); - - // Assert - Assert.Equal("You asked: Forecast for Lisbon for 1 days", echoResult.Response.FunctionResponse.ResponseBody.Text.Body); - Assert.Equal("Result: 8", calcResult.Response.FunctionResponse.ResponseBody.Text.Body); - } - - // Example tool class using attributes - [BedrockFunctionType] - public class AttributeBasedTool - { - [BedrockFunctionTool(Name = "Echo", Description = "Echoes back the input message")] - public static string EchoMessage(string message, IMyInterface myInterface, ILambdaContext context) - { - return $"You asked: {myInterface.DoSomething("Lisbon", 1).Result}"; - } - - [BedrockFunctionTool(Name = "Calculate", Description = "Adds two numbers together")] - public static string Calculate(int x, int y) - { - return $"Result: {x + y}"; - } - } -} - -public interface IMyInterface -{ - Task DoSomething(string location, int days); -} - -public class MyImplementation : IMyInterface -{ - public async Task DoSomething(string location, int days) - { - return await Task.FromResult($"Forecast for {location} for {days} days"); - } -} - -public class MyCustomType -{ - public decimal Price { get; set; } -} - - -[JsonSerializable(typeof(MyCustomType))] -public partial class MycustomSerializationContext : JsonSerializerContext -{ -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/Helpers/ParameterAccessorTests.cs b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/Helpers/ParameterAccessorTests.cs deleted file mode 100644 index b5d54046a..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/Helpers/ParameterAccessorTests.cs +++ /dev/null @@ -1,338 +0,0 @@ -using AWS.Lambda.Powertools.EventHandler.Resolvers; -using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Helpers; - -namespace AWS.Lambda.Powertools.EventHandler.BedrockAgentFunction.Helpers -{ - public class ParameterAccessorTests - { - [Fact] - public void Get_WithStringParameter_ReturnsValue() - { - // Arrange - var parameters = new List - { - new Parameter { Name = "name", Value = "TestValue", Type = "String" } - }; - var accessor = new ParameterAccessor(parameters); - - // Act - var result = accessor.Get("name"); - - // Assert - Assert.Equal("TestValue", result); - } - - [Fact] - public void Get_WithIntParameter_ReturnsValue() - { - // Arrange - var parameters = new List - { - new Parameter { Name = "age", Value = "30", Type = "Number" } - }; - var accessor = new ParameterAccessor(parameters); - - // Act - var result = accessor.Get("age"); - - // Assert - Assert.Equal(30, result); - } - - [Fact] - public void Get_WithBoolParameter_ReturnsValue() - { - // Arrange - var parameters = new List - { - new Parameter { Name = "active", Value = "true", Type = "Boolean" } - }; - var accessor = new ParameterAccessor(parameters); - - // Act - var result = accessor.Get("active"); - - // Assert - Assert.True(result); - } - - [Fact] - public void Get_WithLongParameter_ReturnsValue() - { - // Arrange - var parameters = new List - { - new Parameter { Name = "bigNumber", Value = "9223372036854775807", Type = "Number" } - }; - var accessor = new ParameterAccessor(parameters); - - // Act - var result = accessor.Get("bigNumber"); - - // Assert - Assert.Equal(9223372036854775807, result); - } - - [Fact] - public void Get_WithDoubleParameter_ReturnsValue() - { - // Arrange - var parameters = new List - { - new Parameter { Name = "price", Value = "99.99", Type = "Number" } - }; - var accessor = new ParameterAccessor(parameters); - - // Act - var result = accessor.Get("price"); - - // Assert - Assert.Equal(99.99, result); - } - - [Fact] - public void Get_WithDecimalParameter_ReturnsValue() - { - // Arrange - var parameters = new List - { - new Parameter { Name = "amount", Value = "123.456", Type = "Number" } - }; - var accessor = new ParameterAccessor(parameters); - - // Act - var result = accessor.Get("amount"); - - // Assert - Assert.Equal(123.456m, result); - } - - [Fact] - public void Get_WithNonExistentParameter_ReturnsDefault() - { - // Arrange - var parameters = new List - { - new Parameter { Name = "existing", Value = "value", Type = "String" } - }; - var accessor = new ParameterAccessor(parameters); - - // Act - var stringResult = accessor.Get("nonExistent"); - var intResult = accessor.Get("nonExistent"); - var boolResult = accessor.Get("nonExistent"); - - // Assert - Assert.Null(stringResult); - Assert.Equal(0, intResult); - Assert.False(boolResult); - } - - [Fact] - public void Get_WithCaseSensitivity_WorksCaseInsensitively() - { - // Arrange - var parameters = new List - { - new Parameter { Name = "userName", Value = "John", Type = "String" } - }; - var accessor = new ParameterAccessor(parameters); - - // Act - var result1 = accessor.Get("userName"); - var result2 = accessor.Get("UserName"); - var result3 = accessor.Get("USERNAME"); - - // Assert - Assert.Equal("John", result1); - Assert.Equal("John", result2); - Assert.Equal("John", result3); - } - - [Fact] - public void Get_WithNullParameters_ReturnsDefault() - { - // Arrange - var accessor = new ParameterAccessor(null); - - // Act - var stringResult = accessor.Get("any"); - var intResult = accessor.Get("any"); - - // Assert - Assert.Null(stringResult); - Assert.Equal(0, intResult); - } - - [Fact] - public void Get_WithInvalidType_ReturnsDefault() - { - // Arrange - var parameters = new List - { - new Parameter { Name = "number", Value = "not-a-number", Type = "Number" } - }; - var accessor = new ParameterAccessor(parameters); - - // Act - var result = accessor.Get("number"); - - // Assert - Assert.Equal(0, result); - } - - [Fact] - public void Get_WithEmptyParameters_ReturnsDefault() - { - // Arrange - var parameters = new List(); - var accessor = new ParameterAccessor(parameters); - - // Act - var result = accessor.Get("anything"); - - // Assert - Assert.Null(result); - } - - [Fact] - public void GetAt_WithValidIndex_ReturnsValue() - { - // Arrange - var parameters = new List - { - new Parameter { Name = "first", Value = "Value1", Type = "String" }, - new Parameter { Name = "second", Value = "42", Type = "Number" }, - new Parameter { Name = "third", Value = "true", Type = "Boolean" } - }; - var accessor = new ParameterAccessor(parameters); - - // Act - var stringResult = accessor.GetAt(0); - var intResult = accessor.GetAt(1); - var boolResult = accessor.GetAt(2); - - // Assert - Assert.Equal("Value1", stringResult); - Assert.Equal(42, intResult); - Assert.True(boolResult); - } - - [Fact] - public void GetAt_WithInvalidIndex_ReturnsDefaultValue() - { - // Arrange - var parameters = new List - { - new Parameter { Name = "param", Value = "Value", Type = "String" } - }; - var accessor = new ParameterAccessor(parameters); - - // Act - var negativeIndexResult = accessor.GetAt(-1); - var tooLargeIndexResult = accessor.GetAt(1); - - // Assert - Assert.Null(negativeIndexResult); - Assert.Null(tooLargeIndexResult); - } - - [Fact] - public void GetAt_WithNullParameters_ReturnsDefaultValue() - { - // Arrange - var accessor = new ParameterAccessor(null); - - // Act - var result = accessor.GetAt(0); - - // Assert - Assert.Null(result); - } - - [Fact] - public void GetAt_WithNullValue_ReturnsDefaultValue() - { - // Arrange - var parameters = new List - { - new Parameter { Name = "param", Value = null, Type = "String" } - }; - var accessor = new ParameterAccessor(parameters); - - // Act - var result = accessor.GetAt(0); - - // Assert - Assert.Null(result); - } - - [Fact] - public void GetOrDefault_WithExistingParameter_ReturnsValue() - { - // Arrange - var parameters = new List - { - new Parameter { Name = "name", Value = "TestValue", Type = "String" } - }; - var accessor = new ParameterAccessor(parameters); - - // Act - var result = accessor.GetOrDefault("name", "DefaultValue"); - - // Assert - Assert.Equal("TestValue", result); - } - - [Fact] - public void GetOrDefault_WithNonExistentParameter_ReturnsDefaultValue() - { - // Arrange - var parameters = new List - { - new Parameter { Name = "existing", Value = "value", Type = "String" } - }; - var accessor = new ParameterAccessor(parameters); - - // Act - var result = accessor.GetOrDefault("nonExistent", "DefaultValue"); - - // Assert - Assert.Equal("DefaultValue", result); - } - - [Fact] - public void GetOrDefault_WithNullValue_ReturnsDefaultValue() - { - // Arrange - var parameters = new List - { - new Parameter { Name = "param", Value = null, Type = "String" } - }; - var accessor = new ParameterAccessor(parameters); - - // Act - var result = accessor.GetOrDefault("param", "DefaultValue"); - - // Assert - Assert.Equal("DefaultValue", result); - } - - [Fact] - public void GetOrDefault_WithInvalidConversion_ReturnsDefaultValue() - { - // Arrange - var parameters = new List - { - new Parameter { Name = "invalidNumber", Value = "not-a-number", Type = "Number" } - }; - var accessor = new ParameterAccessor(parameters); - - // Act - var result = accessor.GetOrDefault("invalidNumber", 999); - - // Assert - Assert.Equal(999, result); - } - } -} diff --git a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/Helpers/ParameterMapperTests.cs b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/Helpers/ParameterMapperTests.cs deleted file mode 100644 index b4cd5705e..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/Helpers/ParameterMapperTests.cs +++ /dev/null @@ -1,311 +0,0 @@ -using Amazon.Lambda.Core; -using Amazon.Lambda.TestUtilities; -using AWS.Lambda.Powertools.EventHandler.Resolvers; -using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Helpers; -using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; -using NSubstitute; - -namespace AWS.Lambda.Powertools.EventHandler.BedrockAgentFunction.Helpers -{ - public class ParameterMapperTests - { - private readonly ParameterMapper _mapper = new(); - - [Fact] - public void MapParameters_WithNoParameters_ReturnsEmptyArray() - { - // Arrange - var methodInfo = typeof(TestMethodsClass).GetMethod(nameof(TestMethodsClass.NoParameters))!; - var input = new BedrockFunctionRequest(); - var context = new TestLambdaContext(); - - // Act - var result = _mapper.MapParameters(methodInfo, input, context, null); - - // Assert - Assert.Empty(result); - } - - [Fact] - public void MapParameters_WithLambdaContext_MapsCorrectly() - { - // Arrange - var methodInfo = typeof(TestMethodsClass).GetMethod(nameof(TestMethodsClass.WithLambdaContext))!; - var input = new BedrockFunctionRequest(); - var context = new TestLambdaContext(); - - // Act - var result = _mapper.MapParameters(methodInfo, input, context, null); - - // Assert - Assert.Single(result); - Assert.Same(context, result[0]); - } - - [Fact] - public void MapParameters_WithBedrockFunctionRequest_MapsCorrectly() - { - // Arrange - var methodInfo = typeof(TestMethodsClass).GetMethod(nameof(TestMethodsClass.WithBedrockFunctionRequest))!; - var input = new BedrockFunctionRequest(); - var context = new TestLambdaContext(); - - // Act - var result = _mapper.MapParameters(methodInfo, input, context, null); - - // Assert - Assert.Single(result); - Assert.Same(input, result[0]); - } - - [Fact] - public void MapParameters_WithStringParameter_MapsCorrectly() - { - // Arrange - var methodInfo = typeof(TestMethodsClass).GetMethod(nameof(TestMethodsClass.WithStringParameter))!; - var input = new BedrockFunctionRequest - { - Parameters = new List - { - new() { Name = "name", Value = "TestValue", Type = "String" } - } - }; - var context = new TestLambdaContext(); - - // Act - var result = _mapper.MapParameters(methodInfo, input, context, null); - - // Assert - Assert.Single(result); - Assert.Equal("TestValue", result[0]); - } - - [Fact] - public void MapParameters_WithIntParameter_MapsCorrectly() - { - // Arrange - var methodInfo = typeof(TestMethodsClass).GetMethod(nameof(TestMethodsClass.WithIntParameter))!; - var input = new BedrockFunctionRequest - { - Parameters = new List - { - new() { Name = "value", Value = "42", Type = "Number" } - } - }; - var context = new TestLambdaContext(); - - // Act - var result = _mapper.MapParameters(methodInfo, input, context, null); - - // Assert - Assert.Single(result); - Assert.Equal(42, result[0]); - } - - [Fact] - public void MapParameters_WithBoolParameter_MapsCorrectly() - { - // Arrange - var methodInfo = typeof(TestMethodsClass).GetMethod(nameof(TestMethodsClass.WithBoolParameter))!; - var input = new BedrockFunctionRequest - { - Parameters = new List - { - new() { Name = "flag", Value = "true", Type = "Boolean" } - } - }; - var context = new TestLambdaContext(); - - // Act - var result = _mapper.MapParameters(methodInfo, input, context, null); - - // Assert - Assert.Single(result); - Assert.True((bool)result[0]!); - } - - [Fact] - public void MapParameters_WithEnumParameter_MapsCorrectly() - { - // Arrange - var methodInfo = typeof(TestMethodsClass).GetMethod(nameof(TestMethodsClass.WithEnumParameter))!; - var input = new BedrockFunctionRequest - { - Parameters = new List - { - new() { Name = "testEnum", Value = "Option2", Type = "String" } - } - }; - var context = new TestLambdaContext(); - - // Act - var result = _mapper.MapParameters(methodInfo, input, context, null); - - // Assert - Assert.Single(result); - Assert.Equal(TestEnum.Option2, result[0]); - } - - [Fact] - public void MapParameters_WithStringArrayParameter_MapsCorrectly() - { - // Arrange - var methodInfo = typeof(TestMethodsClass).GetMethod(nameof(TestMethodsClass.WithStringArrayParameter))!; - var input = new BedrockFunctionRequest - { - Parameters = new List - { - new() { Name = "values", Value = "[\"one\",\"two\",\"three\"]", Type = "String" } - } - }; - var context = new TestLambdaContext(); - - // Act - var result = _mapper.MapParameters(methodInfo, input, context, null); - - // Assert - Assert.Single(result); - var array = (string[])result[0]!; - Assert.Equal(3, array.Length); - Assert.Equal("one", array[0]); - Assert.Equal("two", array[1]); - Assert.Equal("three", array[2]); - } - - [Fact] - public void MapParameters_WithIntArrayParameter_MapsCorrectly() - { - // Arrange - var methodInfo = typeof(TestMethodsClass).GetMethod(nameof(TestMethodsClass.WithIntArrayParameter))!; - var input = new BedrockFunctionRequest - { - Parameters = new List - { - new() { Name = "values", Value = "[1,2,3]", Type = "String" } - } - }; - var context = new TestLambdaContext(); - - // Act - var result = _mapper.MapParameters(methodInfo, input, context, null); - - // Assert - Assert.Single(result); - var array = (int[])result[0]!; - Assert.Equal(3, array.Length); - Assert.Equal(1, array[0]); - Assert.Equal(2, array[1]); - Assert.Equal(3, array[2]); - } - - [Fact] - public void MapParameters_WithInvalidJsonArray_ReturnsNull() - { - // Arrange - var methodInfo = typeof(TestMethodsClass).GetMethod(nameof(TestMethodsClass.WithStringArrayParameter))!; - var input = new BedrockFunctionRequest - { - Parameters = new List - { - new() { Name = "values", Value = "[invalid json]", Type = "String" } - } - }; - var context = new TestLambdaContext(); - - // Act - var result = _mapper.MapParameters(methodInfo, input, context, null); - - // Assert - Assert.Single(result); - Assert.Null(result[0]); - } - - [Fact] - public void MapParameters_WithServiceProvider_ResolvesService() - { - // Arrange - var methodInfo = typeof(TestMethodsClass).GetMethod(nameof(TestMethodsClass.WithDependencyInjection))!; - var input = new BedrockFunctionRequest(); - var context = new TestLambdaContext(); - - // Create a test service - var testService = new TestService(); - - // Setup service provider - var serviceProvider = Substitute.For(); - serviceProvider.GetService(typeof(ITestService)).Returns(testService); - - // Act - var result = _mapper.MapParameters(methodInfo, input, context, serviceProvider); - - // Assert - Assert.Equal(3, result.Length); - Assert.Same(context, result[0]); - Assert.Same(input, result[1]); - Assert.Same(testService, result[2]); - } - - [Fact] - public void MapParameters_WithMultipleParameterTypes_MapsAllCorrectly() - { - // Arrange - var methodInfo = typeof(TestMethodsClass).GetMethod(nameof(TestMethodsClass.WithMultipleParameterTypes))!; - var input = new BedrockFunctionRequest - { - Parameters = new List - { - new() { Name = "name", Value = "TestUser", Type = "String" }, - new() { Name = "age", Value = "30", Type = "Number" }, - new() { Name = "isActive", Value = "true", Type = "Boolean" } - } - }; - var context = new TestLambdaContext(); - - // Act - var result = _mapper.MapParameters(methodInfo, input, context, null); - - // Assert - Assert.Equal(4, result.Length); - Assert.Equal("TestUser", result[0]); - Assert.Equal(30, result[1]); - Assert.True((bool)result[2]!); - Assert.Same(context, result[3]); - } - - public class TestMethodsClass - { - public void NoParameters() { } - - public void WithLambdaContext(ILambdaContext context) { } - - public void WithBedrockFunctionRequest(BedrockFunctionRequest request) { } - - public void WithStringParameter(string name) { } - - public void WithIntParameter(int value) { } - - public void WithBoolParameter(bool flag) { } - - public void WithEnumParameter(TestEnum testEnum) { } - - public void WithStringArrayParameter(string[] values) { } - - public void WithIntArrayParameter(int[] values) { } - - public void WithDependencyInjection(ILambdaContext context, BedrockFunctionRequest request, ITestService service) { } - - public void WithMultipleParameterTypes(string name, int age, bool isActive, ILambdaContext context) { } - } - - public interface ITestService { } - - public class TestService : ITestService { } - - public enum TestEnum - { - Option1, - Option2, - Option3 - } - } -} diff --git a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/Helpers/ParameterTypeValidatorTests.cs b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/Helpers/ParameterTypeValidatorTests.cs deleted file mode 100644 index b8ec33530..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/Helpers/ParameterTypeValidatorTests.cs +++ /dev/null @@ -1,49 +0,0 @@ -using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Helpers; - -namespace AWS.Lambda.Powertools.EventHandler.BedrockAgentFunction.Helpers -{ - public class ParameterTypeValidatorTests - { - private readonly ParameterTypeValidator _validator = new(); - - [Theory] - [InlineData(typeof(string), true)] - [InlineData(typeof(int), true)] - [InlineData(typeof(long), true)] - [InlineData(typeof(double), true)] - [InlineData(typeof(bool), true)] - [InlineData(typeof(decimal), true)] - [InlineData(typeof(DateTime), true)] - [InlineData(typeof(Guid), true)] - [InlineData(typeof(string[]), true)] - [InlineData(typeof(int[]), true)] - [InlineData(typeof(long[]), true)] - [InlineData(typeof(double[]), true)] - [InlineData(typeof(bool[]), true)] - [InlineData(typeof(decimal[]), true)] - [InlineData(typeof(TestEnum), true)] // Enum should be valid - [InlineData(typeof(object), false)] - [InlineData(typeof(Dictionary), false)] - [InlineData(typeof(List), false)] - [InlineData(typeof(float), false)] - [InlineData(typeof(char), false)] - [InlineData(typeof(byte), false)] - [InlineData(typeof(float[]), false)] - [InlineData(typeof(object[]), false)] - public void IsBedrockParameter_WithVariousTypes_ReturnsExpectedResult(Type type, bool expected) - { - // Act - var result = _validator.IsBedrockParameter(type); - - // Assert - Assert.Equal(expected, result); - } - - private enum TestEnum - { - One, - Two, - Three - } - } -} diff --git a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/Helpers/ResultConverterTests.cs b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/Helpers/ResultConverterTests.cs deleted file mode 100644 index 437118e5d..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/Helpers/ResultConverterTests.cs +++ /dev/null @@ -1,276 +0,0 @@ -using Amazon.Lambda.Core; -using Amazon.Lambda.TestUtilities; -using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Helpers; -using AWS.Lambda.Powertools.EventHandler.Resolvers.BedrockAgentFunction.Models; - -namespace AWS.Lambda.Powertools.EventHandler.BedrockAgentFunction.Helpers -{ - public class ResultConverterTests - { - private readonly ResultConverter _converter = new(); - private readonly BedrockFunctionRequest _defaultInput = new() - { - Function = "TestFunction", - ActionGroup = "TestGroup", - SessionAttributes = new Dictionary { { "testKey", "testValue" } }, - PromptSessionAttributes = new Dictionary { { "promptKey", "promptValue" } } - }; - private readonly string _functionName = "TestFunction"; - private readonly ILambdaContext _context = new TestLambdaContext(); - - [Fact] - public void ProcessResult_WithBedrockFunctionResponse_ReturnsUnchanged() - { - // Arrange - var response = BedrockFunctionResponse.WithText( - "Test response", - "TestGroup", - "TestFunction", - new Dictionary(), - new Dictionary(), - new Dictionary()); - - // Act - var result = _converter.ProcessResult(response, _defaultInput, _functionName, _context); - - // Assert - Assert.Same(response, result); - } - - [Fact] - public void ProcessResult_WithNullValue_ReturnsEmptyResponse() - { - // Arrange - object? nullValue = null; - - // Act - var result = _converter.ProcessResult(nullValue, _defaultInput, _functionName, _context); - - // Assert - Assert.Equal(string.Empty, result.Response.FunctionResponse.ResponseBody.Text.Body); - Assert.Equal(_defaultInput.ActionGroup, result.Response.ActionGroup); - Assert.Equal(_defaultInput.Function, result.Response.Function); - } - - [Fact] - public void ProcessResult_WithStringValue_ReturnsTextResponse() - { - // Arrange - var stringValue = "Hello, world!"; - - // Act - var result = _converter.ProcessResult(stringValue, _defaultInput, _functionName, _context); - - // Assert - Assert.Equal(stringValue, result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - [Fact] - public void ProcessResult_WithIntValue_ReturnsTextResponse() - { - // Arrange - var intValue = 42; - - // Act - var result = _converter.ProcessResult(intValue, _defaultInput, _functionName, _context); - - // Assert - Assert.Equal("42", result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - [Fact] - public void ProcessResult_WithDecimalValue_ReturnsTextResponse() - { - // Arrange - var decimalValue = 42.5m; - - // Act - var result = _converter.ProcessResult(decimalValue, _defaultInput, _functionName, _context); - - // Assert - Assert.Equal("42.5", result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - [Fact] - public void ProcessResult_WithBoolValue_ReturnsTextResponse() - { - // Arrange - var boolValue = true; - - // Act - var result = _converter.ProcessResult(boolValue, _defaultInput, _functionName, _context); - - // Assert - Assert.Equal("True", result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - [Fact] - public void ProcessResult_WithObjectValue_ReturnsToString() - { - // Arrange - var testObject = new TestObject { Name = "Test", Value = 42 }; - - // Act - var result = _converter.ProcessResult(testObject, _defaultInput, _functionName, _context); - - // Assert - Assert.Equal(testObject.ToString(), result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - [Fact] - public async Task ProcessResult_WithTaskStringResult_ReturnsTextResponse() - { - // Arrange - Task task = Task.FromResult("Async result"); - - // Act - var result = _converter.ProcessResult(task, _defaultInput, _functionName, _context); - - // Assert - Assert.Equal("Async result", result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - [Fact] - public async Task ProcessResult_WithTaskIntResult_ReturnsTextResponse() - { - // Arrange - Task task = Task.FromResult(42); - - // Act - var result = _converter.ProcessResult(task, _defaultInput, _functionName, _context); - - // Assert - Assert.Equal("42", result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - [Fact] - public async Task ProcessResult_WithTaskBoolResult_ReturnsTextResponse() - { - // Arrange - Task task = Task.FromResult(true); - - // Act - var result = _converter.ProcessResult(task, _defaultInput, _functionName, _context); - - // Assert - Assert.Equal("True", result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - [Fact] - public async Task ProcessResult_WithVoidTask_ReturnsEmptyResponse() - { - // Arrange - Task task = Task.CompletedTask; - - // Act - var result = _converter.ProcessResult(task, _defaultInput, _functionName, _context); - - // Assert - Assert.Equal(string.Empty, result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - [Fact] - public async Task ProcessResult_WithTaskBedrockResponse_ReturnsResponse() - { - // Arrange - var response = BedrockFunctionResponse.WithText( - "Async response", - "AsyncGroup", - "AsyncFunction", - new Dictionary(), - new Dictionary(), - new Dictionary()); - - Task task = Task.FromResult(response); - - // Act - var result = _converter.ProcessResult(task, _defaultInput, _functionName, _context); - - // Assert - Assert.Equal("Async response", result.Response.FunctionResponse.ResponseBody.Text.Body); - Assert.Equal("AsyncGroup", result.Response.ActionGroup); - Assert.Equal("AsyncFunction", result.Response.Function); - } - - [Fact] - public void EnsureResponseMetadata_WithEmptyMetadata_FillsFromInput() - { - // Arrange - var response = BedrockFunctionResponse.WithText( - "Test response", - "", // Empty action group - "", // Empty function name - _defaultInput.SessionAttributes, - _defaultInput.PromptSessionAttributes, - new Dictionary()); - - // Act - var result = _converter.ConvertToOutput(response, _defaultInput); - - // Assert - Assert.Equal("Test response", result.Response.FunctionResponse.ResponseBody.Text.Body); - Assert.Equal(_defaultInput.ActionGroup, result.Response.ActionGroup); // Filled from input - Assert.Equal(_defaultInput.Function, result.Response.Function); // Filled from input - } - - [Fact] - public void ConvertToOutput_PreservesSessionAttributes() - { - // Arrange - var sessionAttributes = new Dictionary { { "userID", "test123" } }; - var promptAttributes = new Dictionary { { "context", "testing" } }; - - var input = new BedrockFunctionRequest - { - Function = "TestFunction", - ActionGroup = "TestGroup", - SessionAttributes = sessionAttributes, - PromptSessionAttributes = promptAttributes - }; - - // Act - var result = _converter.ConvertToOutput("Test response", input); - - // Assert - Assert.Equal(sessionAttributes, result.SessionAttributes); - Assert.Equal(promptAttributes, result.PromptSessionAttributes); - } - - [Fact] - public void ProcessResult_WithLongValue_ReturnsTextResponse() - { - // Arrange - long longValue = 9223372036854775807; - - // Act - var result = _converter.ProcessResult(longValue, _defaultInput, _functionName, _context); - - // Assert - Assert.Equal("9223372036854775807", result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - [Fact] - public void ProcessResult_WithDoubleValue_ReturnsTextResponse() - { - // Arrange - double doubleValue = 123.456; - - // Act - var result = _converter.ProcessResult(doubleValue, _defaultInput, _functionName, _context); - - // Assert - Assert.Equal("123.456", result.Response.FunctionResponse.ResponseBody.Text.Body); - } - - private class TestObject - { - public string Name { get; set; } = ""; - public int Value { get; set; } - - public override string ToString() - { - return $"{Name}:{Value}"; - } - } - } -} diff --git a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/bedrockFunctionEvent.json b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/bedrockFunctionEvent.json deleted file mode 100644 index f2cedeb19..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/BedrockAgentFunction/bedrockFunctionEvent.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "messageVersion": "1.0", - "function": "sum_numbers", - "sessionId": "455081292773641", - "agent": { - "name": "powertools-test", - "version": "DRAFT", - "id": "WPMRGAPAPJ", - "alias": "TSTALIASID" - }, - "parameters": [ - { - "name": "a", - "type": "number", - "value": "1" - }, - { - "name": "b", - "type": "number", - "value": "1" - } - ], - "actionGroup": "utility-tasks", - "sessionAttributes": {}, - "promptSessionAttributes": {}, - "inputText": "Sum 1 and 1" -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/EventHandler/AppSyncEventsTests.cs b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/EventHandler/AppSyncEventsTests.cs deleted file mode 100644 index 07c0e9fa0..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/EventHandler/AppSyncEventsTests.cs +++ /dev/null @@ -1,881 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Serialization; -using Amazon.Lambda.Core; -using Amazon.Lambda.TestUtilities; -using AWS.Lambda.Powertools.EventHandler.AppSyncEvents; -#pragma warning disable CS8604 // Possible null reference argument. -#pragma warning disable CS8602 // Dereference of a possibly null reference. - -namespace AWS.Lambda.Powertools.EventHandler; - -public class AppSyncEventsTests -{ - private readonly AppSyncEventsRequest _appSyncEvent; - - public AppSyncEventsTests() - { - _appSyncEvent = JsonSerializer.Deserialize( - File.ReadAllText("appSyncEventsEvent.json"), - new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true, - Converters = { new JsonStringEnumConverter() } - })!; - } - - [Fact] - public void Should_Return_Unchanged_Payload_No_Handlers() - { - // Arrange - var lambdaContext = new TestLambdaContext(); - var app = new AppSyncEventsResolver(); - - // Act - var result = app.Resolve(_appSyncEvent, lambdaContext); - - // Assert - Assert.Equal(3, result.Events.Count); - Assert.Equal("1", result.Events[0].Id); - Assert.Equal("data_1", result.Events[0].Payload?["event_1"].ToString()); - Assert.Equal("2", result.Events[1].Id); - Assert.Equal("data_2", result.Events[1].Payload?["event_2"].ToString()); - Assert.Equal("3", result.Events[2].Id); - Assert.Equal("data_3", result.Events[2].Payload?["event_3"].ToString()); - } - - [Fact] - public void Should_Return_Unchanged_Payload() - { - // Arrange - var lambdaContext = new TestLambdaContext(); - var app = new AppSyncEventsResolver(); - - app.OnPublish("/default/channel", payload => - { - // Handle channel1 events - return payload; - }); - - // Act - var result = app.Resolve(_appSyncEvent, lambdaContext); - - // Assert - Assert.Equal(3, result.Events.Count); - Assert.Equal("1", result.Events[0].Id); - Assert.Equal("data_1", result.Events[0].Payload?["event_1"].ToString()); - Assert.Equal("2", result.Events[1].Id); - Assert.Equal("data_2", result.Events[1].Payload?["event_2"].ToString()); - Assert.Equal("3", result.Events[2].Id); - Assert.Equal("data_3", result.Events[2].Payload?["event_3"].ToString()); - } - - [Fact] - public async Task Should_Return_Unchanged_Payload_Async() - { - // Arrange - var lambdaContext = new TestLambdaContext(); - var app = new AppSyncEventsResolver(); - - app.OnPublishAsync("/default/channel", payload => - { - // Handle channel1 events - return Task.FromResult(payload); - }); - - // Act - var result = - await app.ResolveAsync(_appSyncEvent, lambdaContext); - - // Assert - Assert.Equal(3, result.Events.Count); - Assert.Equal("1", result.Events[0].Id); - Assert.Equal("data_1", result.Events[0].Payload?["event_1"].ToString()); - Assert.Equal("2", result.Events[1].Id); - Assert.Equal("data_2", result.Events[1].Payload?["event_2"].ToString()); - Assert.Equal("3", result.Events[2].Id); - Assert.Equal("data_3", result.Events[2].Payload?["event_3"].ToString()); - } - - [Fact] - public async Task Should_Handle_Error_In_Event_Processing() - { - // Arrange - var lambdaContext = new TestLambdaContext(); - var app = new AppSyncEventsResolver(); - - app.OnPublishAsync("/default/channel", (payload) => - { - // Throw exception for second event - if (payload.ContainsKey("event_2")) - { - throw new InvalidOperationException("Test error"); - } - - return Task.FromResult(payload); - }); - - // Act - var result = await app.ResolveAsync(_appSyncEvent, lambdaContext); - - // Assert - if (result.Events != null) - { - Assert.Equal(3, result.Events.Count); - Assert.Equal("1", result.Events[0].Id); - Assert.Equal("data_1", result.Events[0].Payload?["event_1"].ToString()); - Assert.Equal("2", result.Events[1].Id); - Assert.NotNull(result.Events[1].Error); - Assert.Contains("Test error", result.Events[1].Error); - Assert.Equal("3", result.Events[2].Id); - Assert.Equal("data_3", result.Events[2].Payload?["event_3"].ToString()); - } - } - - [Fact] - public async Task Should_Match_Path_With_Wildcard() - { - // Arrange - var lambdaContext = new TestLambdaContext(); - var app = new AppSyncEventsResolver(); - - int callCount = 0; - app.OnPublishAsync("/default/*", (payload) => - { - callCount++; - return Task.FromResult(new Dictionary { ["wildcard_matched"] = true }); - }); - - // Act - var result = await app.ResolveAsync(_appSyncEvent, lambdaContext); - - // Assert - if (result.Events != null) - { - Assert.Equal(3, result.Events.Count); - Assert.Equal(3, callCount); - Assert.True((bool)(result.Events[0].Payload?["wildcard_matched"] ?? false)); - } - } - - [Fact] - public async Task Should_Authorize_Subscription() - { - // Arrange - var lambdaContext = new TestLambdaContext(); - var app = new AppSyncEventsResolver(); - - app.OnPublishAsync("/default/channel", (payload) => Task.FromResult(payload)); - - app.OnSubscribeAsync("/default/*", (info) => Task.FromResult(true)); - var subscribeEvent = new AppSyncEventsRequest - { - Info = new Information - { - Channel = new Channel - { - Path = "/default/channel", - Segments = ["default", "channel"] - }, - Operation = AppSyncEventsOperation.Subscribe, - ChannelNamespace = new ChannelNamespace { Name = "default" } - } - }; - // Act - var result = await app.ResolveAsync(subscribeEvent, lambdaContext); - - // Assert - Assert.Null(result); - } - - [Fact] - public void Should_Deny_Subscription() - { - // Arrange - var lambdaContext = new TestLambdaContext(); - var app = new AppSyncEventsResolver(); - - app.OnPublish("/default/channel", (payload) => payload); - - app.OnSubscribe("/default/*", (info) => false); - var subscribeEvent = new AppSyncEventsRequest - { - Info = new Information - { - Channel = new Channel { Path = "/default/channel", Segments = ["default", "channel"] }, - Operation = AppSyncEventsOperation.Subscribe, - ChannelNamespace = new ChannelNamespace { Name = "default" } - } - }; - // Act - var result = app.Resolve(subscribeEvent, lambdaContext); - - // Assert - Assert.NotNull(result.Error); - } - - [Fact] - public void Should_Deny_Subscription_On_Exception() - { - // Arrange - var lambdaContext = new TestLambdaContext(); - var app = new AppSyncEventsResolver(); - - app.OnPublish("/default/channel", (payload) => payload); - - app.OnSubscribe("/default/*", (info) => { throw new Exception("Authorization error"); }); - - var subscribeEvent = new AppSyncEventsRequest - { - Info = new Information - { - Channel = new Channel { Path = "/default/channel", Segments = ["default", "channel"] }, - Operation = AppSyncEventsOperation.Subscribe, - ChannelNamespace = new ChannelNamespace { Name = "default" } - } - }; - - // Act - var result = app.Resolve(subscribeEvent, lambdaContext); - - // Assert - Assert.Equal("Authorization error", result.Error); - } - - [Fact] - public void Should_Handle_Error_In_Aggregate_Mode() - { - // Arrange - var lambdaContext = new TestLambdaContext(); - var app = new AppSyncEventsResolver(); - - app.OnPublishAggregate("/default/channel", - (evt, ctx) => { throw new InvalidOperationException("Aggregate error"); }); - - // Act - var result = app.Resolve(_appSyncEvent, lambdaContext); - - // Assert - Assert.Contains("Aggregate error", result.Error); - } - - [Fact] - public async Task Should_Handle_Error_In_Aggregate_Mode_Async() - { - // Arrange - var lambdaContext = new TestLambdaContext(); - var app = new AppSyncEventsResolver(); - - app.OnPublishAggregateAsync("/default/channel", (evt, ctx) => { throw new InvalidOperationException("Aggregate error"); }); - - // Act - var result = await app.ResolveAsync(_appSyncEvent, lambdaContext); - - // Assert - Assert.Contains("Aggregate error", result.Error); - } - - [Fact] - public void Should_Handle_TransformingPayload() - { - // Arrange - var lambdaContext = new TestLambdaContext(); - var app = new AppSyncEventsResolver(); - - app.OnPublish("/default/channel", (payload) => - { - // Transform each event payload - var transformedPayload = new Dictionary(); - foreach (var key in payload.Keys) - { - transformedPayload[$"transformed_{key}"] = $"transformed_{payload[key]}"; - } - - return transformedPayload; - }); - - // Act - var result = app.Resolve(_appSyncEvent, lambdaContext); - - // Assert - if (result.Events != null) - { - Assert.Equal(3, result.Events.Count); - Assert.Equal("transformed_event_1", result.Events[0].Payload?.Keys.First()); - Assert.Equal("transformed_data_1", result.Events[0].Payload?["transformed_event_1"].ToString()); - } - } - - [Fact] - public async Task Should_Handle_TransformingPayload_Async() - { - // Arrange - var lambdaContext = new TestLambdaContext(); - var app = new AppSyncEventsResolver(); - - app.OnPublishAsync("/default/channel", (payload) => - { - // Transform each event payload - var transformedPayload = new Dictionary(); - foreach (var key in payload.Keys) - { - transformedPayload[$"transformed_{key}"] = $"transformed_{payload[key]}"; - } - - return Task.FromResult(transformedPayload); - }); - - // Act - var result = await app.ResolveAsync(_appSyncEvent, lambdaContext); - - // Assert - if (result.Events != null) - { - Assert.Equal(3, result.Events.Count); - Assert.Equal("transformed_event_1", result.Events[0].Payload?.Keys.First()); - Assert.Equal("transformed_data_1", result.Events[0].Payload?["transformed_event_1"].ToString()); - } - } - - [Fact] - public async Task Should_Throw_For_Unknown_EventType_Async() - { - // Arrange - var lambdaContext = new TestLambdaContext(); - var app = new AppSyncEventsResolver(); - - var unknownEvent = new AppSyncEventsRequest - { - Info = new Information - { - Channel = new Channel { Path = "/default/channel", Segments = ["default", "channel"] }, - Operation = (AppSyncEventsOperation)999, // Unknown operation - ChannelNamespace = new ChannelNamespace { Name = "default" } - } - }; - - // Act & Assert - await Assert.ThrowsAsync(() => - app.ResolveAsync(unknownEvent, lambdaContext)); - } - - [Fact] - public void Should_Throw_For_Unknown_EventType() - { - // Arrange - var lambdaContext = new TestLambdaContext(); - var app = new AppSyncEventsResolver(); - - var unknownEvent = new AppSyncEventsRequest - { - Info = new Information - { - Channel = new Channel { Path = "/default/channel", Segments = ["default", "channel"] }, - Operation = (AppSyncEventsOperation)999, // Unknown operation - ChannelNamespace = new ChannelNamespace { Name = "default" } - } - }; - - // Act & Assert - Assert.Throws(() => - app.Resolve(unknownEvent, lambdaContext)); - } - - [Fact] - public void Should_Return_NonDictionary_Values_Wrapped_In_Data() - { - // Arrange - var lambdaContext = new TestLambdaContext(); - var app = new AppSyncEventsResolver(); - - app.OnPublish("/default/channel", (payload) => - { - // Return a non-dictionary value - return "string value"; - }); - - // Act - var result = app.Resolve(_appSyncEvent, lambdaContext); - - // Assert - if (result.Events != null) - { - Assert.Equal(3, result.Events.Count); - Assert.Equal("string value", result.Events[0].Payload?["data"].ToString()); - } - } - - [Fact] - public void Should_Skip_Invalid_Path_Registration() - { - // Arrange - var lambdaContext = new TestLambdaContext(); - var app = new AppSyncEventsResolver(); - var handlerCalled = false; - - // Register with invalid path - app.OnPublish("/invalid/*/path", (payload) => - { - handlerCalled = true; - return payload; - }); - - // Act - var result = app.Resolve(_appSyncEvent, lambdaContext); - - // Assert - Should return original payload, handler not called - if (result.Events != null) - { - Assert.Equal(3, result.Events.Count); - Assert.Equal("data_1", result.Events[0].Payload?["event_1"].ToString()); - } - - Assert.False(handlerCalled); - } - - [Fact] - public void Should_Replace_Handler_When_RegisteringTwice() - { - // Arrange - var lambdaContext = new TestLambdaContext(); - var app = new AppSyncEventsResolver(); - - app.OnPublish("/default/channel", - (payload) => { return new Dictionary { ["handler"] = "first" }; }); - - app.OnPublish("/default/channel", - (payload) => { return new Dictionary { ["handler"] = "second" }; }); - - // Act - var result = app.Resolve(_appSyncEvent, lambdaContext); - - // Assert - Only second handler should be used - if (result.Events != null) - { - Assert.Equal(3, result.Events.Count); - Assert.Equal("second", result.Events[0].Payload?["handler"].ToString()); - } - } - - [Fact] - public async Task Should_Replace_Handler_When_RegisteringTwice_Async() - { - // Arrange - var lambdaContext = new TestLambdaContext(); - var app = new AppSyncEventsResolver(); - - app.OnPublishAsync("/default/channel", (payload) => { return Task.FromResult(new Dictionary { ["handler"] = "first" }); }); - - app.OnPublishAsync("/default/channel", (payload) => { return Task.FromResult(new Dictionary { ["handler"] = "second" }); }); - - // Act - var result = await app.ResolveAsync(_appSyncEvent, lambdaContext); - - // Assert - Only second handler should be used - if (result.Events != null) - { - Assert.Equal(3, result.Events.Count); - Assert.Equal("second", result.Events[0].Payload?["handler"].ToString()); - } - } - - [Fact] - public void Should_Maintain_EventIds_When_Processing() - { - // Arrange - var lambdaContext = new TestLambdaContext(); - var app = new AppSyncEventsResolver(); - - app.OnPublish("/default/channel", - (payload) => { return new Dictionary { ["processed"] = true }; }); - - // Act - var result = app.Resolve(_appSyncEvent, lambdaContext); - - // Assert - if (result.Events != null) - { - Assert.Equal(3, result.Events.Count); - Assert.Equal("1", result.Events[0].Id); - Assert.Equal("2", result.Events[1].Id); - Assert.Equal("3", result.Events[2].Id); - } - } - - [Fact] - public async Task Aggregate_Handler_Can_Return_Individual_Results_With_Ids() - { - // Arrange - var lambdaContext = new TestLambdaContext(); - var app = new AppSyncEventsResolver(); - - app.OnPublishAggregateAsync("/default/channel13", (payload) => { throw new Exception("My custom exception"); }); - - app.OnPublishAsync("/default/channel12", (payload) => { throw new Exception("My custom exception"); }); - - app.OnPublishAggregateAsync("/default/channel", (evt) => - { - // Iterate through events and return individual results with IDs - var results = new List(); - - foreach (var eventItem in evt.Events) - { - try - { - if (eventItem.Payload.ContainsKey("event_2")) - { - // Create an error for the second event - results.Add(new AppSyncEvent - { - Id = eventItem.Id, - Error = "Intentional error for event 2" - }); - } - else - { - // Process normally - results.Add(new AppSyncEvent - { - Id = eventItem.Id, - Payload = new Dictionary - { - ["processed"] = true, - ["originalData"] = eventItem.Payload - } - }); - } - } - catch (Exception ex) - { - results.Add(new AppSyncEvent - { - Id = eventItem.Id, - Error = $"{ex.GetType().Name} - {ex.Message}" - }); - } - } - - return Task.FromResult(new AppSyncEventsResponse { Events = results }); - }); - - // Act - var result = await app.ResolveAsync(_appSyncEvent, lambdaContext); - - // Assert - if (result.Events != null) - { - Assert.Equal(3, result.Events.Count); - Assert.Equal("1", result.Events[0].Id); - Assert.True((bool)(result.Events[0].Payload?["processed"] ?? false)); - Assert.Equal("2", result.Events[1].Id); - Assert.NotNull(result.Events[1].Error); - Assert.Contains("Intentional error for event 2", result.Events[1].Error); - Assert.Equal("3", result.Events[2].Id); - Assert.True((bool)(result.Events[2].Payload?["processed"] ?? false)); - } - } - - [Fact] - public async Task Should_Verify_Ids_Are_Preserved_In_Error_Case() - { - // Arrange - var lambdaContext = new TestLambdaContext(); - var app = new AppSyncEventsResolver(); - - // Create handlers that throw exceptions for specific events - app.OnPublishAsync("/default/channel", (payload) => - { - if (payload.ContainsKey("event_1")) - throw new InvalidOperationException("Error for event 1"); - if (payload.ContainsKey("event_3")) - throw new ArgumentException("Error for event 3"); - return Task.FromResult(payload); - }); - - // Act - var result = await app.ResolveAsync(_appSyncEvent, lambdaContext); - - // Assert - Assert.Equal(3, result.Events.Count); - Assert.Equal("1", result.Events[0].Id); - Assert.Contains("Error for event 1", result.Events[0].Error); - Assert.Equal("2", result.Events[1].Id); - Assert.Null(result.Events[1].Error); - Assert.Equal("3", result.Events[2].Id); - Assert.Contains("Error for event 3", result.Events[2].Error); - } - - [Fact] - public async Task Should_Match_Most_Specific_Handler_Only() - { - // Arrange - var lambdaContext = new TestLambdaContext(); - var app = new AppSyncEventsResolver(); - - int firstHandlerCalls = 0; - int secondHandlerCalls = 0; - - app.OnPublishAsync("/default/channel", (payload) => - { - firstHandlerCalls++; - return Task.FromResult(new Dictionary { ["handler"] = "first" }); - }); - - app.OnPublishAsync("/default/*", (payload) => - { - secondHandlerCalls++; - return Task.FromResult(new Dictionary { ["handler"] = "second" }); - }); - - // Act - var result = await app.ResolveAsync(_appSyncEvent, lambdaContext); - - // Assert - Only the first (most specific) handler should be called - Assert.Equal(3, result.Events.Count); - Assert.Equal("first", result.Events[0].Payload["handler"].ToString()); - Assert.Equal(3, firstHandlerCalls); - Assert.Equal(0, secondHandlerCalls); - } - - [Fact] - public async Task Should_Handle_Multiple_Keys_In_Payload() - { - // Arrange - var lambdaContext = new TestLambdaContext(); - var app = new AppSyncEventsResolver(); - - // Create an event with multiple keys in the payload - var multiKeyEvent = new AppSyncEventsRequest - { - Info = new Information - { - Channel = new Channel { Path = "/default/channel", Segments = ["default", "channel"] }, - Operation = AppSyncEventsOperation.Publish, - ChannelNamespace = new ChannelNamespace { Name = "default" } - }, - Events = - [ - new AppSyncEvent - { - Id = "1", - Payload = new Dictionary - { - ["event_1"] = "data_1", - ["event_1a"] = "data_1a" - } - } - ] - }; - - app.OnPublishAsync("/default/channel", (payload) => - { - // Check that both keys are present - Assert.Equal("data_1", payload["event_1"]); - Assert.Equal("data_1a", payload["event_1a"]); - - // Return a processed result with both keys - return Task.FromResult(new Dictionary - { - ["processed_1"] = payload["event_1"], - ["processed_1a"] = payload["event_1a"] - }); - }); - - // Act - var result = await app.ResolveAsync(multiKeyEvent, lambdaContext); - - // Assert - Assert.Single(result.Events); - Assert.Equal("1", result.Events[0].Id); - Assert.Equal("data_1", result.Events[0].Payload["processed_1"]); - Assert.Equal("data_1a", result.Events[0].Payload["processed_1a"]); - } - - [Fact] - public async Task Should_Only_Use_First_Matching_Handler_By_Specificity() - { - // Arrange - var lambdaContext = new TestLambdaContext(); - var app = new AppSyncEventsResolver(); - - // Register handlers with different specificity - app.OnPublishAsync("/*", (payload) => Task.FromResult(new Dictionary { ["handler"] = "least-specific" })); - - app.OnPublishAsync("/default/*", (payload) => Task.FromResult(new Dictionary { ["handler"] = "more-specific" })); - - app.OnPublishAsync("/default/channel", (payload) => Task.FromResult(new Dictionary { ["handler"] = "most-specific" })); - - // Act - var result = await app.ResolveAsync(_appSyncEvent, lambdaContext); - - // Assert - Only the most specific handler should be called - Assert.Equal(3, result.Events.Count); - Assert.Equal("most-specific", result.Events[0].Payload["handler"].ToString()); - Assert.Equal("most-specific", result.Events[1].Payload["handler"].ToString()); - Assert.Equal("most-specific", result.Events[2].Payload["handler"].ToString()); - } - - [Fact] - public async Task Should_Fallback_To_Less_Specific_Handler_If_No_Exact_Match() - { - // Arrange - var lambdaContext = new TestLambdaContext(); - var app = new AppSyncEventsResolver(); - - // Create an event with a path that has no exact match - var fallbackEvent = new AppSyncEventsRequest - { - Info = new Information - { - Channel = new Channel { Path = "/default/specific/path", Segments = ["default", "specific", "path"] }, - Operation = AppSyncEventsOperation.Publish, - ChannelNamespace = new ChannelNamespace { Name = "default" } - }, - Events = - [ - new AppSyncEvent - { - Id = "1", - Payload = new Dictionary { ["key"] = "value" } - } - ] - }; - - app.OnPublishAsync("/default/*", (payload) => Task.FromResult(new Dictionary { ["handler"] = "wildcard-handler" })); - - // Act - var result = await app.ResolveAsync(fallbackEvent, lambdaContext); - - // Assert - Assert.Single(result.Events); - Assert.Equal("wildcard-handler", result.Events[0].Payload["handler"].ToString()); - } - - [Fact] - public async Task Should_Return_Null_When_Subscribing_To_Path_Without_Publish_Handler() - { - // Arrange - var lambdaContext = new TestLambdaContext(); - var app = new AppSyncEventsResolver(); - - // Only set up a subscribe handler without corresponding publish handler - app.OnSubscribeAsync("/subscribe-only", (info) => Task.FromResult(true)); - - var subscribeEvent = new AppSyncEventsRequest - { - Info = new Information - { - Channel = new Channel { Path = "/subscribe-only", Segments = ["subscribe-only"] }, - Operation = AppSyncEventsOperation.Subscribe, - ChannelNamespace = new ChannelNamespace { Name = "default" } - } - }; - - // Act - var result = await app.ResolveAsync(subscribeEvent, lambdaContext); - - // Assert - Assert.Null(result); - } - - [Theory] - [InlineData("/default/channel", "/default/channel1")] - [InlineData("/default/channel3", "/default/channel")] - public void Should_Return_Null_When_Subscribing_To_Path_With_No_Match_Publish_Handler(string publishPath, - string subscribePath) - { - // Arrange - var lambdaContext = new TestLambdaContext(); - var app = new AppSyncEventsResolver(); - - app.OnPublish(publishPath, (payload) => payload); - app.OnSubscribe(subscribePath, (info) => true); - - var subscribeEvent = new AppSyncEventsRequest - { - Info = new Information - { - Channel = new Channel { Path = subscribePath, Segments = ["default", "channel"] }, - Operation = AppSyncEventsOperation.Subscribe, - ChannelNamespace = new ChannelNamespace { Name = "default" } - } - }; - - // Act - var result = app.Resolve(subscribeEvent, lambdaContext); - - // Assert - Assert.Null(result); - } - - [Theory] - [InlineData("/default/channel", "/default/channel")] - [InlineData("/default/channel", "/default/*")] - [InlineData("/default/test", "/default/*")] - [InlineData("/default/*", "/default/*")] - public async Task Should_Return_UnauthorizedException_When_Throwing_UnauthorizedException(string publishPath, - string subscribePath) - { - // Arrange - var lambdaContext = new TestLambdaContext(); - var app = new AppSyncEventsResolver(); - - app.OnPublishAsync(publishPath, (payload) => Task.FromResult(payload)); - app.OnSubscribeAsync(subscribePath, - (info, lambdaContext) => { throw new UnauthorizedException("OOPS"); }); - - var subscribeEvent = new AppSyncEventsRequest - { - Info = new Information - { - Channel = new Channel { Path = subscribePath, Segments = ["default", "channel"] }, - Operation = AppSyncEventsOperation.Subscribe, - ChannelNamespace = new ChannelNamespace { Name = "default" } - } - }; - - // Act && Assert - await Assert.ThrowsAsync(() => - app.ResolveAsync(subscribeEvent, lambdaContext)); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task Should_Return_UnauthorizedException_When_Throwing_UnauthorizedException_Publish(bool aggreate) - { - // Arrange - var lambdaContext = new TestLambdaContext(); - var app = new AppSyncEventsResolver(); - - if (aggreate) - { - app.OnPublishAggregateAsync("/default/channel", (payload) => throw new UnauthorizedException("OOPS")); - } - else - { - app.OnPublishAsync("/default/channel", (payload) => throw new UnauthorizedException("OOPS")); - } - - var subscribeEvent = new AppSyncEventsRequest - { - Info = new Information - { - Channel = new Channel { Path = "/default/channel", Segments = ["default", "channel"] }, - Operation = AppSyncEventsOperation.Publish, - ChannelNamespace = new ChannelNamespace { Name = "default" } - }, - Events = - [ - new AppSyncEvent - { - Id = "1", - Payload = new Dictionary { ["key"] = "value" } - } - ] - }; - - // Act && Assert - await Assert.ThrowsAsync(() => - app.ResolveAsync(subscribeEvent, lambdaContext)); - } -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/EventHandler/RouteHandlerRegistryTests.cs b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/EventHandler/RouteHandlerRegistryTests.cs deleted file mode 100644 index ac712da62..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/EventHandler/RouteHandlerRegistryTests.cs +++ /dev/null @@ -1,232 +0,0 @@ -using System.Diagnostics.CodeAnalysis; -using AWS.Lambda.Powertools.EventHandler.Internal; -#pragma warning disable CS8605 // Unboxing a possibly null value. -#pragma warning disable CS8601 // Possible null reference assignment. -#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. -#pragma warning disable CS8602 // Dereference of a possibly null reference. - -namespace AWS.Lambda.Powertools.EventHandler; - -[SuppressMessage("Usage", "xUnit1031:Do not use blocking task operations in test method")] -public class RouteHandlerRegistryTests -{ - [Theory] - [InlineData("/default/channel", true)] - [InlineData("/default/*", true)] - [InlineData("/*", true)] - [InlineData("/a/b/c", true)] - [InlineData("/a/*/c", false)] // Wildcard in the middle is invalid - [InlineData("*/default", false)] // Wildcard at the beginning is invalid - [InlineData("default/*", false)] // Not starting with slash - [InlineData("", false)] // Empty path - [InlineData(null, false)] // Null path - public void IsValidPath_ShouldValidateCorrectly(string? path, bool expected) - { - // Create a private method accessor to test private IsValidPath method - var registry = new RouteHandlerRegistry(); - var isValidPathMethod = typeof(RouteHandlerRegistry) - .GetMethod("IsValidPath", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); - - // Act - var result = (bool)isValidPathMethod.Invoke(null, new object[] { path }); - - // Assert - Assert.Equal(expected, result); - } - - [Fact] - public void Register_ShouldNotAddInvalidPath() - { - // Arrange - var registry = new RouteHandlerRegistry(); - - // Act - registry.Register(new RouteHandlerOptions - { - Path = "/invalid/*/path", // Invalid path with wildcard in the middle - Handler = (_, _) => Task.FromResult(null) - }); - - // Assert - Try to resolve an invalid path - var result = registry.ResolveFirst("/invalid/test/path"); - Assert.Null(result); // Should not find any handler - } - - [Fact] - public void Register_ShouldReplaceExistingHandler() - { - // Arrange - var registry = new RouteHandlerRegistry(); - int firstHandlerCalled = 0; - int secondHandlerCalled = 0; - - // Act - registry.Register(new RouteHandlerOptions - { - Path = "/test/path", - Handler = (_, _) => { - firstHandlerCalled++; - return Task.FromResult("first"); - } - }); - - registry.Register(new RouteHandlerOptions - { - Path = "/test/path", // Same path, should replace first handler - Handler = (_, _) => { - secondHandlerCalled++; - return Task.FromResult("second"); - } - }); - - // Assert - var handler = registry.ResolveFirst("/test/path"); - Assert.NotNull(handler); - var result = handler.Handler(null, null).Result; - Assert.Equal("second", result); - Assert.Equal(0, firstHandlerCalled); - Assert.Equal(1, secondHandlerCalled); - } - - [Fact] - public async Task ResolveFirst_ShouldReturnMostSpecificHandler() - { - // Arrange - var registry = new RouteHandlerRegistry(); - - registry.Register(new RouteHandlerOptions - { - Path = "/*", - Handler = (_, _) => Task.FromResult("least-specific") - }); - - registry.Register(new RouteHandlerOptions - { - Path = "/default/*", - Handler = (_, _) => Task.FromResult("more-specific") - }); - - registry.Register(new RouteHandlerOptions - { - Path = "/default/channel", - Handler = (_, _) => Task.FromResult("most-specific") - }); - - // Act - Test various paths - var exactMatch = registry.ResolveFirst("/default/channel"); - var wildcardMatch = registry.ResolveFirst("/default/something"); - var rootMatch = registry.ResolveFirst("/something"); - - // Assert - Assert.NotNull(exactMatch); - Assert.Equal("most-specific", await exactMatch.Handler(null, null)); - - Assert.NotNull(wildcardMatch); - Assert.Equal("more-specific", await wildcardMatch.Handler(null, null)); - - Assert.NotNull(rootMatch); - Assert.Equal("least-specific", await rootMatch.Handler(null, null)); - } - - [Fact] - public void ResolveFirst_ShouldReturnNullWhenNoMatch() - { - // Arrange - var registry = new RouteHandlerRegistry(); - - registry.Register(new RouteHandlerOptions - { - Path = "/default/*", - Handler = (_, _) => Task.FromResult("test") - }); - - // Act - var result = registry.ResolveFirst("/other/path"); - - // Assert - Assert.Null(result); - } - - [Fact] - public void ResolveFirst_ShouldUseCacheForRepeatedPaths() - { - // Arrange - var registry = new RouteHandlerRegistry(); - int handlerCallCount = 0; - - registry.Register(new RouteHandlerOptions - { - Path = "/test/*", - Handler = (_, _) => { - handlerCallCount++; - return Task.FromResult("cached"); - } - }); - - // Act - Resolve the same path multiple times - var first = registry.ResolveFirst("/test/path"); - var firstResult = first.Handler(null, null).Result; - - // Should use cached result - var second = registry.ResolveFirst("/test/path"); - var secondResult = second.Handler(null, null).Result; - - // Assert - Assert.Equal("cached", firstResult); - Assert.Equal("cached", secondResult); - Assert.Equal(2, handlerCallCount); // Handler should be called twice because handlers are executed - // even though the path resolution is cached - - // The objects should be the same instance - Assert.Same(first, second); - } - - [Fact] - public void LRUCache_ShouldEvictOldestItemsWhenFull() - { - // Arrange - Create a cache with size 2 - var cache = new LruCache(2); - - // Act - cache.Set("key1", "value1"); - cache.Set("key2", "value2"); - cache.Set("key3", "value3"); // Should evict key1 - - // Assert - Assert.False(cache.TryGet("key1", out _)); // Should be evicted - Assert.True(cache.TryGet("key2", out var value2)); - Assert.Equal("value2", value2); - Assert.True(cache.TryGet("key3", out var value3)); - Assert.Equal("value3", value3); - } - - [Fact] - public void IsWildcardMatch_ShouldMatchPathsCorrectly() - { - // Arrange - var registry = new RouteHandlerRegistry(); - var isWildcardMatchMethod = typeof(RouteHandlerRegistry) - .GetMethod("IsWildcardMatch", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); - - // Test cases - var testCases = new[] - { - (pattern: "/default/*", path: "/default/channel", expected: true), - (pattern: "/default/*", path: "/default/other", expected: true), - (pattern: "/default/*", path: "/default/nested/path", expected: true), - (pattern: "/default/channel", path: "/default/channel", expected: true), - (pattern: "/default/channel", path: "/default/other", expected: false), - (pattern: "/*", path: "/anything", expected: true), - (pattern: "/*", path: "/default/nested/deep", expected: true) - }; - - foreach (var (pattern, path, expected) in testCases) - { - // Act - var result = (bool)isWildcardMatchMethod.Invoke(registry, new object[] { pattern, path }); - - // Assert - Assert.Equal(expected, result); - } - } -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/appSyncEventsEvent.json b/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/appSyncEventsEvent.json deleted file mode 100644 index 1334b5ac3..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.EventHandler.Tests/appSyncEventsEvent.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "identity":"None", - "result":"None", - "request":{ - "headers": { - "x-forwarded-for": "1.1.1.1, 2.2.2.2", - "cloudfront-viewer-country": "US", - "cloudfront-is-tablet-viewer": "false", - "via": "2.0 xxxxxxxxxxxxxxxx.cloudfront.net (CloudFront)", - "cloudfront-forwarded-proto": "https", - "origin": "https://us-west-1.console.aws.amazon.com", - "content-length": "217", - "accept-language": "en-US,en;q=0.9", - "host": "xxxxxxxxxxxxxxxx.appsync-api.us-west-1.amazonaws.com", - "x-forwarded-proto": "https", - "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36", - "accept": "*/*", - "cloudfront-is-mobile-viewer": "false", - "cloudfront-is-smarttv-viewer": "false", - "accept-encoding": "gzip, deflate, br", - "referer": "https://us-west-1.console.aws.amazon.com/appsync/home?region=us-west-1", - "content-type": "application/json", - "sec-fetch-mode": "cors", - "x-amz-cf-id": "3aykhqlUwQeANU-HGY7E_guV5EkNeMMtwyOgiA==", - "x-amzn-trace-id": "Root=1-5f512f51-fac632066c5e848ae714", - "authorization": "eyJraWQiOiJScWFCSlJqYVJlM0hrSnBTUFpIcVRXazNOW...", - "sec-fetch-dest": "empty", - "x-amz-user-agent": "AWS-Console-AppSync/", - "cloudfront-is-desktop-viewer": "true", - "sec-fetch-site": "cross-site", - "x-forwarded-port": "443" - }, - "domainName":"None" - }, - "info":{ - "channel":{ - "path":"/default/channel", - "segments":[ - "default", - "channel" - ] - }, - "channelNamespace":{ - "name":"default" - }, - "operation":"PUBLISH" - }, - "error":"None", - "prev":"None", - "stash":{ - - }, - "outErrors":[ - - ], - "events":[ - { - "payload":{ - "event_1":"data_1" - }, - "id":"1" - }, - { - "payload":{ - "event_2":"data_2" - }, - "id":"2" - }, - { - "payload":{ - "event_3":"data_3" - }, - "id":"3" - } - ] -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/IdempotencyTest.cs b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/IdempotencyTest.cs index 13dd5a7a3..8e85d6165 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/IdempotencyTest.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/IdempotencyTest.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.Collections.Generic; using System.IO; diff --git a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotencySerializerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotencySerializerTests.cs index 065c985cb..819b01308 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotencySerializerTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotencySerializerTests.cs @@ -14,9 +14,7 @@ public class IdempotencySerializerTests { public IdempotencySerializerTests() { -#if NET8_0_OR_GREATER IdempotencySerializer.AddTypeInfoResolver(TestJsonSerializerContext.Default); -#endif } [Fact] @@ -59,8 +57,6 @@ public void BuildDefaultOptions_SetsCorrectProperties() Assert.True(options.PropertyNameCaseInsensitive); } -#if NET8_0_OR_GREATER - [Fact] public void GetTypeInfo_UnknownType_ThrowsException() { @@ -155,5 +151,4 @@ public void SetJsonOptions_UpdatesOptionsCorrectly() Assert.Same(newOptions, options); } -#endif } \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotentAspectTests.cs b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotentAspectTests.cs index 324ccd5c4..f91ee7b83 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotentAspectTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Internal/IdempotentAspectTests.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.Linq; using System.Text.Json; @@ -26,9 +41,7 @@ public async Task Handle_WhenFirstCall_ShouldPutInStore(Type type) var store = Substitute.For(); Idempotency.Configure(builder => builder -#if NET8_0_OR_GREATER .WithJsonSerializationContext(TestJsonSerializerContext.Default) -#endif .WithPersistenceStore(store) .WithOptions(optionsBuilder => optionsBuilder.WithEventKeyJmesPath("Id")) ); @@ -74,9 +87,7 @@ public async Task Handle_WhenSecondCall_AndNotExpired_ShouldGetFromStore(Type ty // GIVEN Idempotency.Configure(builder => builder -#if NET8_0_OR_GREATER .WithJsonSerializationContext(TestJsonSerializerContext.Default) -#endif .WithPersistenceStore(store) .WithOptions(optionsBuilder => optionsBuilder.WithEventKeyJmesPath("Id")) ); @@ -113,9 +124,7 @@ public async Task Handle_WhenSecondCall_AndStatusInProgress_ShouldThrowIdempoten Idempotency.Configure(builder => builder .WithPersistenceStore(store) -#if NET8_0_OR_GREATER .WithJsonSerializationContext(TestJsonSerializerContext.Default) -#endif .WithOptions(optionsBuilder => optionsBuilder.WithEventKeyJmesPath("Id")) ); @@ -154,9 +163,7 @@ public async Task Idempotency.Configure(builder => builder .WithPersistenceStore(store) -#if NET8_0_OR_GREATER .WithJsonSerializationContext(TestJsonSerializerContext.Default) -#endif .WithOptions(optionsBuilder => optionsBuilder.WithEventKeyJmesPath("Id")) ); @@ -197,9 +204,7 @@ public async Task Handle_WhenThrowException_ShouldDeleteRecord_AndThrowFunctionE Idempotency.Configure(builder => builder .WithPersistenceStore(store) -#if NET8_0_OR_GREATER .WithJsonSerializationContext(TestJsonSerializerContext.Default) -#endif .WithOptions(optionsBuilder => optionsBuilder.WithEventKeyJmesPath("Id")) ); @@ -227,9 +232,7 @@ public async Task Handle_WhenIdempotencyDisabled_ShouldJustRunTheFunction(Type t Idempotency.Configure(builder => builder .WithPersistenceStore(store) -#if NET8_0_OR_GREATER .WithJsonSerializationContext(TestJsonSerializerContext.Default) -#endif .WithOptions(optionsBuilder => optionsBuilder.WithEventKeyJmesPath("Id")) ); @@ -249,16 +252,25 @@ public async Task Handle_WhenIdempotencyDisabled_ShouldJustRunTheFunction(Type t public void Idempotency_Set_Execution_Environment_Context() { // Arrange + var assemblyName = "AWS.Lambda.Powertools.Idempotency"; + var assemblyVersion = "1.0.0"; + + var env = Substitute.For(); + env.GetAssemblyName(Arg.Any()).Returns(assemblyName); + env.GetAssemblyVersion(Arg.Any()).Returns(assemblyVersion); - var env = new PowertoolsEnvironment(); - var conf = new PowertoolsConfigurations(env); + var conf = new PowertoolsConfigurations(new SystemWrapper(env)); // Act var xRayRecorder = new Idempotency(conf); // Assert - Assert.Contains($"{Constants.FeatureContextIdentifier}/Idempotency/", - env.GetEnvironmentVariable("AWS_EXECUTION_ENV")); + env.Received(1).SetEnvironmentVariable( + "AWS_EXECUTION_ENV", + $"{Constants.FeatureContextIdentifier}/Idempotency/{assemblyVersion}" + ); + + env.Received(1).GetEnvironmentVariable("AWS_EXECUTION_ENV"); Assert.NotNull(xRayRecorder); } @@ -336,9 +348,7 @@ public async Task Handle_WhenIdempotencyOnSubMethodAnnotated_AndKeyJMESPath_Shou Idempotency.Configure(builder => builder .WithPersistenceStore(store) -#if NET8_0_OR_GREATER .WithJsonSerializationContext(TestJsonSerializerContext.Default) -#endif .WithOptions(optionsBuilder => optionsBuilder.WithEventKeyJmesPath("Id")) ); diff --git a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Persistence/BasePersistenceStoreTests.cs b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Persistence/BasePersistenceStoreTests.cs index 104beb680..ad93313b6 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Persistence/BasePersistenceStoreTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Persistence/BasePersistenceStoreTests.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.IO; using System.Text.Json; @@ -524,7 +539,7 @@ public void GenerateHash_WhenInputIsDouble_ShouldGenerateMd5ofDouble() // Assert generatedHash.Should().Be(expectedHash); } - + [Fact] public async Task When_Key_Prefix_Set_Should_Create_With_Prefix() { @@ -551,9 +566,7 @@ private static APIGatewayProxyRequest LoadApiGatewayProxyRequest() var eventJson = File.ReadAllText("./resources/apigw_event.json"); try { -#if NET8_0_OR_GREATER IdempotencySerializer.AddTypeInfoResolver(TestJsonSerializerContext.Default); -#endif var request = IdempotencySerializer.Deserialize(eventJson); return request!; } @@ -563,35 +576,4 @@ private static APIGatewayProxyRequest LoadApiGatewayProxyRequest() throw; } } - - [Fact] - public async Task ProcessExistingRecord_WhenValidRecord_ShouldReturnRecordAndSaveToCache() - { - // Arrange - var persistenceStore = new InMemoryPersistenceStore(); - var request = LoadApiGatewayProxyRequest(); - LRUCache cache = new(2); - - persistenceStore.Configure(new IdempotencyOptionsBuilder() - .WithUseLocalCache(true) - .Build(), null, null, cache); - - var now = DateTimeOffset.UtcNow; - var existingRecord = new DataRecord( - "testFunction#5eff007a9ed2789a9f9f6bc182fc6ae6", - DataRecord.DataRecordStatus.COMPLETED, - now.AddSeconds(3600).ToUnixTimeSeconds(), - "existing response", - null); - - // Act - var result = - persistenceStore.ProcessExistingRecord(existingRecord, JsonSerializer.SerializeToDocument(request)!); - - // Assert - result.Should().Be(existingRecord); - cache.Count.Should().Be(1); - cache.TryGet("testFunction#5eff007a9ed2789a9f9f6bc182fc6ae6", out var cachedRecord).Should().BeTrue(); - cachedRecord.Should().Be(existingRecord); - } } \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Persistence/DynamoDBPersistenceStoreTests.cs b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Persistence/DynamoDBPersistenceStoreTests.cs index 09b4c781d..6dc2fb844 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Persistence/DynamoDBPersistenceStoreTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/Persistence/DynamoDBPersistenceStoreTests.cs @@ -1,12 +1,12 @@ /* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * + * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at - * + * * http://aws.amazon.com/apache2.0 - * + * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing @@ -33,7 +33,7 @@ public class DynamoDbPersistenceStoreTests : IClassFixture private readonly DynamoDBPersistenceStore _dynamoDbPersistenceStore; private readonly AmazonDynamoDBClient _client; private readonly string _tableName; - + public DynamoDbPersistenceStoreTests(DynamoDbFixture fixture) { _client = fixture.Client; @@ -42,23 +42,21 @@ public DynamoDbPersistenceStoreTests(DynamoDbFixture fixture) .WithTableName(_tableName) .WithDynamoDBClient(_client) .Build(); - _dynamoDbPersistenceStore.Configure(new IdempotencyOptionsBuilder().Build(), functionName: null, - keyPrefix: null); + _dynamoDbPersistenceStore.Configure(new IdempotencyOptionsBuilder().Build(),functionName: null, keyPrefix: null); } - + //putRecord [Fact] public async Task PutRecord_WhenRecordDoesNotExist_ShouldCreateRecordInDynamoDB() { // Arrange var now = DateTimeOffset.UtcNow; - var uniqueKey = $"key_{Guid.NewGuid()}"; var expiry = now.AddSeconds(3600).ToUnixTimeSeconds(); - var key = CreateKey(uniqueKey); - + var key = CreateKey("key"); + // Act await _dynamoDbPersistenceStore - .PutRecord(new DataRecord(uniqueKey, DataRecord.DataRecordStatus.COMPLETED, expiry, null, null), now); + .PutRecord(new DataRecord("key", DataRecord.DataRecordStatus.COMPLETED, expiry, null, null), now); // Assert var getItemResponse = @@ -75,7 +73,7 @@ await _client.GetItemAsync(new GetItemRequest } [Fact] - public async Task PutRecord_WhenRecordAlreadyExist_ShouldThrowIdempotencyItemAlreadyExistsException() + public async Task PutRecord_WhenRecordAlreadyExist_ShouldThrowIdempotencyItemAlreadyExistsException() { // Arrange var key = CreateKey("key"); @@ -84,7 +82,7 @@ public async Task PutRecord_WhenRecordAlreadyExist_ShouldThrowIdempotencyItemAlr Dictionary item = new(key); var now = DateTimeOffset.UtcNow; var expiry = now.AddSeconds(30).ToUnixTimeSeconds(); - item.Add("expiration", new AttributeValue { N = expiry.ToString() }); + item.Add("expiration", new AttributeValue {N = expiry.ToString()}); item.Add("status", new AttributeValue(DataRecord.DataRecordStatus.COMPLETED.ToString())); item.Add("data", new AttributeValue("Fake Data")); await _client.PutItemAsync(new PutItemRequest @@ -102,24 +100,24 @@ await _client.PutItemAsync(new PutItemRequest null, null ), now); - + // Assert await act.Should().ThrowAsync(); - + // item was not updated, retrieve the initial one var itemInDb = (await _client.GetItemAsync(new GetItemRequest - { - TableName = _tableName, - Key = key - })).Item; + { + TableName = _tableName, + Key = key + })).Item; itemInDb.Should().NotBeNull(); itemInDb["status"].S.Should().Be("COMPLETED"); itemInDb["expiration"].N.Should().Be(expiry.ToString()); itemInDb["data"].S.Should().Be("Fake Data"); } - + [Fact] - public async Task PutRecord_ShouldBlockUpdate_IfRecordAlreadyExistAndProgressNotExpiredAfterLambdaTimedOut() + public async Task PutRecord_ShouldBlockUpdate_IfRecordAlreadyExistAndProgressNotExpiredAfterLambdaTimedOut() { // Arrange var key = CreateKey("key"); @@ -129,18 +127,18 @@ public async Task PutRecord_ShouldBlockUpdate_IfRecordAlreadyExistAndProgressNot var now = DateTimeOffset.UtcNow; var expiry = now.AddSeconds(30).ToUnixTimeSeconds(); var progressExpiry = now.AddSeconds(30).ToUnixTimeMilliseconds(); - - item.Add("expiration", new AttributeValue { N = expiry.ToString() }); + + item.Add("expiration", new AttributeValue {N = expiry.ToString()}); item.Add("status", new AttributeValue(DataRecord.DataRecordStatus.INPROGRESS.ToString())); item.Add("data", new AttributeValue("Fake Data")); - item.Add("in_progress_expiration", new AttributeValue { N = progressExpiry.ToString() }); - + item.Add("in_progress_expiration", new AttributeValue {N = progressExpiry.ToString()}); + await _client.PutItemAsync(new PutItemRequest { TableName = _tableName, Item = item }); - + var expiry2 = now.AddSeconds(3600).ToUnixTimeSeconds(); // Act var act = () => _dynamoDbPersistenceStore.PutRecord( @@ -150,10 +148,10 @@ await _client.PutItemAsync(new PutItemRequest "Fake Data 2", null ), now); - + // Assert await act.Should().ThrowAsync(); - + // item was not updated, retrieve the initial one var itemInDb = (await _client.GetItemAsync(new GetItemRequest { @@ -165,9 +163,9 @@ await _client.PutItemAsync(new PutItemRequest itemInDb["expiration"].N.Should().Be(expiry.ToString()); itemInDb["data"].S.Should().Be("Fake Data"); } - + [Fact] - public async Task PutRecord_ShouldCreateRecordInDynamoDB_IfLambdaWasInProgressAndTimedOut() + public async Task PutRecord_ShouldCreateRecordInDynamoDB_IfLambdaWasInProgressAndTimedOut() { // Arrange var key = CreateKey("key"); @@ -177,20 +175,20 @@ public async Task PutRecord_ShouldCreateRecordInDynamoDB_IfLambdaWasInProgressAn var now = DateTimeOffset.UtcNow; var expiry = now.AddSeconds(30).ToUnixTimeSeconds(); var progressExpiry = now.AddSeconds(-30).ToUnixTimeMilliseconds(); - - item.Add("expiration", new AttributeValue { N = expiry.ToString() }); + + item.Add("expiration", new AttributeValue {N = expiry.ToString()}); item.Add("status", new AttributeValue(DataRecord.DataRecordStatus.INPROGRESS.ToString())); item.Add("data", new AttributeValue("Fake Data")); - item.Add("in_progress_expiration", new AttributeValue { N = progressExpiry.ToString() }); - + item.Add("in_progress_expiration", new AttributeValue {N = progressExpiry.ToString()}); + await _client.PutItemAsync(new PutItemRequest { TableName = _tableName, Item = item }); - + var expiry2 = now.AddSeconds(3600).ToUnixTimeSeconds(); - + // Act await _dynamoDbPersistenceStore.PutRecord( new DataRecord("key", @@ -199,7 +197,7 @@ await _dynamoDbPersistenceStore.PutRecord( null, null ), now); - + // Assert // an item is inserted var itemInDb = (await _client.GetItemAsync(new GetItemRequest @@ -207,23 +205,23 @@ await _dynamoDbPersistenceStore.PutRecord( TableName = _tableName, Key = key })).Item; - + itemInDb.Should().NotBeNull(); itemInDb["status"].S.Should().Be("INPROGRESS"); itemInDb["expiration"].N.Should().Be(expiry2.ToString()); } - + //getRecord [Fact] public async Task GetRecord_WhenRecordExistsInDynamoDb_ShouldReturnExistingRecord() { // Arrange //await InitializeAsync(); - + // Insert a fake item with same id Dictionary item = new() { - { "id", new AttributeValue("key") } //key + {"id", new AttributeValue("key")} //key }; var now = DateTimeOffset.UtcNow; var expiry = now.AddSeconds(30).ToUnixTimeSeconds(); @@ -254,10 +252,10 @@ public async Task GetRecord_WhenRecordIsAbsent_ShouldThrowException() { //Arrange await _dynamoDbPersistenceStore.DeleteRecord("key"); - + // Act Func act = () => _dynamoDbPersistenceStore.GetRecord("key"); - + // Assert await act.Should().ThrowAsync(); } @@ -282,8 +280,7 @@ await _client.PutItemAsync(new PutItemRequest Item = item }); // enable payload validation - _dynamoDbPersistenceStore.Configure( - new IdempotencyOptionsBuilder().WithPayloadValidationJmesPath("path").Build(), + _dynamoDbPersistenceStore.Configure(new IdempotencyOptionsBuilder().WithPayloadValidationJmesPath("path").Build(), null, null); // Act @@ -306,14 +303,14 @@ await _client.PutItemAsync(new PutItemRequest //deleteRecord [Fact] - public async Task DeleteRecord_WhenRecordExistsInDynamoDb_ShouldDeleteRecord() + public async Task DeleteRecord_WhenRecordExistsInDynamoDb_ShouldDeleteRecord() { // Arrange: Insert a fake item with same id var key = CreateKey("key"); Dictionary item = new(key); var now = DateTimeOffset.UtcNow; var expiry = now.AddSeconds(360).ToUnixTimeSeconds(); - item.Add("expiration", new AttributeValue { N = expiry.ToString() }); + item.Add("expiration", new AttributeValue {N=expiry.ToString()}); item.Add("status", new AttributeValue(DataRecord.DataRecordStatus.INPROGRESS.ToString())); await _client.PutItemAsync(new PutItemRequest { @@ -370,7 +367,7 @@ public async Task EndToEndWithCustomAttrNamesAndSortKey() .WithStatusAttr("state") .WithValidationAttr("valid") .Build(); - persistenceStore.Configure(new IdempotencyOptionsBuilder().Build(), functionName: null, keyPrefix: null); + persistenceStore.Configure(new IdempotencyOptionsBuilder().Build(),functionName: null, keyPrefix: null); var now = DateTimeOffset.UtcNow; var record = new DataRecord( @@ -422,6 +419,7 @@ public async Task EndToEndWithCustomAttrNamesAndSortKey() { TableName = tableNameCustom })).Count.Should().Be(0); + } finally { @@ -440,18 +438,18 @@ await _client.DeleteTableAsync(new DeleteTableRequest } [Fact] - public async Task GetRecord_WhenIdempotencyDisabled_ShouldNotCreateClients() + public async Task GetRecord_WhenIdempotencyDisabled_ShouldNotCreateClients() { try { // Arrange Environment.SetEnvironmentVariable(Constants.IdempotencyDisabledEnv, "true"); - + var store = new DynamoDBPersistenceStoreBuilder().WithTableName(_tableName).Build(); - + // Act Func act = () => store.GetRecord("fake"); - + // Assert await act.Should().ThrowAsync(); } @@ -460,136 +458,12 @@ public async Task GetRecord_WhenIdempotencyDisabled_ShouldNotCreateClients() Environment.SetEnvironmentVariable(Constants.IdempotencyDisabledEnv, "false"); } } - private static Dictionary CreateKey(string keyValue) { var key = new Dictionary { - { "id", new AttributeValue(keyValue) } + {"id", new AttributeValue(keyValue)} }; return key; } - - [Fact] - public async Task PutRecord_WhenRecordAlreadyExists_ShouldReturnExistingRecordInException() - { - // Arrange - var key = CreateKey("key"); - var now = DateTimeOffset.UtcNow; - var expiry = now.AddSeconds(30).ToUnixTimeSeconds(); - - // Insert a fake item with same id - Dictionary item = new(key); - item.Add("expiration", new AttributeValue { N = expiry.ToString() }); - item.Add("status", new AttributeValue(DataRecord.DataRecordStatus.COMPLETED.ToString())); - item.Add("data", new AttributeValue("Existing Data")); - item.Add("validation", new AttributeValue("existing-hash")); - - await _client.PutItemAsync(new PutItemRequest - { - TableName = _tableName, - Item = item - }); - - var newRecord = new DataRecord("key", - DataRecord.DataRecordStatus.INPROGRESS, - now.AddSeconds(3600).ToUnixTimeSeconds(), - null, - null); - - // Act - var exception = - await Assert.ThrowsAsync(() => - _dynamoDbPersistenceStore.PutRecord(newRecord, now)); - - // Assert - exception.Record.Should().NotBeNull(); - exception.Record.IdempotencyKey.Should().Be("key"); - exception.Record.Status.Should().Be(DataRecord.DataRecordStatus.COMPLETED); - exception.Record.ResponseData.Should().Be("Existing Data"); - exception.Record.PayloadHash.Should().Be("existing-hash"); - exception.Record.ExpiryTimestamp.Should().Be(expiry); - } - - [Fact] - public async Task PutRecord_WhenRecordWithInProgressExpiryExists_ShouldReturnExistingRecordInException() - { - // Arrange - var key = CreateKey("key"); - var now = DateTimeOffset.UtcNow; - var expiry = now.AddSeconds(30).ToUnixTimeSeconds(); - var inProgressExpiry = now.AddSeconds(30).ToUnixTimeMilliseconds(); - - // Insert a fake item with same id including in_progress_expiration - Dictionary item = new(key); - item.Add("expiration", new AttributeValue { N = expiry.ToString() }); - item.Add("status", new AttributeValue(DataRecord.DataRecordStatus.INPROGRESS.ToString())); - item.Add("data", new AttributeValue("In Progress Data")); - item.Add("in_progress_expiration", new AttributeValue { N = inProgressExpiry.ToString() }); - - await _client.PutItemAsync(new PutItemRequest - { - TableName = _tableName, - Item = item - }); - - var newRecord = new DataRecord("key", - DataRecord.DataRecordStatus.INPROGRESS, - now.AddSeconds(3600).ToUnixTimeSeconds(), - null, - null); - - // Act - var exception = - await Assert.ThrowsAsync(() => - _dynamoDbPersistenceStore.PutRecord(newRecord, now)); - - // Assert - exception.Record.Should().NotBeNull(); - exception.Record.IdempotencyKey.Should().Be("key"); - exception.Record.Status.Should().Be(DataRecord.DataRecordStatus.INPROGRESS); - exception.Record.ResponseData.Should().Be("In Progress Data"); - exception.Record.InProgressExpiryTimestamp.Should().Be(inProgressExpiry); - exception.Record.ExpiryTimestamp.Should().Be(expiry); - } - - [Fact] - public async Task PutRecord_WhenRecordExistsWithMissingOptionalFields_ShouldHandleNullValues() - { - // Arrange - var key = CreateKey("key"); - var now = DateTimeOffset.UtcNow; - var expiry = now.AddSeconds(30).ToUnixTimeSeconds(); - - // Insert a minimal record without optional fields (data, validation, in_progress_expiration) - Dictionary item = new(key); - item.Add("expiration", new AttributeValue { N = expiry.ToString() }); - item.Add("status", new AttributeValue(DataRecord.DataRecordStatus.INPROGRESS.ToString())); - - await _client.PutItemAsync(new PutItemRequest - { - TableName = _tableName, - Item = item - }); - - var newRecord = new DataRecord("key", - DataRecord.DataRecordStatus.INPROGRESS, - now.AddSeconds(3600).ToUnixTimeSeconds(), - null, - null); - - // Act - var exception = - await Assert.ThrowsAsync(() => - _dynamoDbPersistenceStore.PutRecord(newRecord, now)); - - // Assert - exception.Record.Should().NotBeNull(); - exception.Record.IdempotencyKey.Should().Be("key"); - exception.Record.Status.Should().Be(DataRecord.DataRecordStatus.INPROGRESS); - exception.Record.ResponseData.Should().BeNull(); - exception.Record.PayloadHash.Should().BeNull(); - exception.Record.InProgressExpiryTimestamp.Should().BeNull(); - exception.Record.ExpiryTimestamp.Should().Be(expiry); - } } \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/ResponseHookTest.cs b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/ResponseHookTest.cs deleted file mode 100644 index 9136c4a8c..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/ResponseHookTest.cs +++ /dev/null @@ -1,168 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading.Tasks; -using Amazon.DynamoDBv2; -using Amazon.Lambda.APIGatewayEvents; -using AWS.Lambda.Powertools.Idempotency.Internal.Serializers; -using AWS.Lambda.Powertools.Idempotency.Persistence; -using AWS.Lambda.Powertools.Idempotency.Tests.Persistence; -using FluentAssertions; -using Xunit; - -namespace AWS.Lambda.Powertools.Idempotency.Tests; - -public class ResponseHookTest : IClassFixture -{ - private readonly AmazonDynamoDBClient _client; - private readonly string _tableName; - - public ResponseHookTest(DynamoDbFixture fixture) - { - _client = fixture.Client; - _tableName = fixture.TableName; - } - - [Fact] - [Trait("Category", "Integration")] - public async Task ResponseHook_ShouldNotExecuteOnFirstCall() - { - // Arrange - var hookExecuted = false; - - Idempotency.Configure(builder => builder - .WithOptions(options => options - .WithEventKeyJmesPath("powertools_json(body).address") - .WithResponseHook((responseData, dataRecord) => { - hookExecuted = true; - if (responseData is APIGatewayProxyResponse proxyResponse) - { - var headers = new Dictionary(proxyResponse.Headers ?? new Dictionary()); - headers["x-idempotency-response"] = "true"; - headers["x-idempotency-expiration"] = dataRecord.ExpiryTimestamp.ToString(); - proxyResponse.Headers = headers; - return proxyResponse; - } - return responseData; - })) - .WithPersistenceStore(new DynamoDBPersistenceStoreBuilder() - .WithTableName(_tableName) - .WithDynamoDBClient(_client) - .Build())); - - var function = new ResponseHookTestFunction(); - var request = IdempotencySerializer.Deserialize( - await File.ReadAllTextAsync("./resources/apigw_event2.json")); - - // Act - First call - var response = await function.Handle(request); - - // Assert - Hook should not execute on first call - hookExecuted.Should().BeFalse(); - response.Headers.Should().NotContainKey("x-idempotency-response"); - function.HandlerExecuted.Should().BeTrue(); - } - - [Fact] - [Trait("Category", "Integration")] - public async Task ResponseHook_ShouldExecuteOnIdempotentCall() - { - // Arrange - var hookExecuted = false; - - Idempotency.Configure(builder => builder - .WithOptions(options => options - .WithEventKeyJmesPath("powertools_json(body).address") - .WithResponseHook((responseData, dataRecord) => { - hookExecuted = true; - if (responseData is APIGatewayProxyResponse proxyResponse) - { - var headers = new Dictionary(proxyResponse.Headers ?? new Dictionary()); - headers["x-idempotency-response"] = "true"; - headers["x-idempotency-expiration"] = dataRecord.ExpiryTimestamp.ToString(); - proxyResponse.Headers = headers; - return proxyResponse; - } - return responseData; - })) - .WithPersistenceStore(new DynamoDBPersistenceStoreBuilder() - .WithTableName(_tableName) - .WithDynamoDBClient(_client) - .Build())); - - var function = new ResponseHookTestFunction(); - var request = IdempotencySerializer.Deserialize( - await File.ReadAllTextAsync("./resources/apigw_event2.json")); - - // Act - First call to populate cache - await function.Handle(request); - function.HandlerExecuted = false; - hookExecuted = false; - - // Act - Second call (idempotent) - var response = await function.Handle(request); - - // Assert - Hook should execute on idempotent call - hookExecuted.Should().BeTrue(); - response.Headers.Should().ContainKey("x-idempotency-response"); - response.Headers["x-idempotency-response"].Should().Be("true"); - response.Headers.Should().ContainKey("x-idempotency-expiration"); - function.HandlerExecuted.Should().BeFalse(); - } - - [Fact] - [Trait("Category", "Integration")] - public async Task ResponseHook_ShouldHandleExceptionsGracefully() - { - // Arrange - Idempotency.Configure(builder => builder - .WithOptions(options => options - .WithEventKeyJmesPath("powertools_json(body).address") - .WithResponseHook((responseData, dataRecord) => { - throw new InvalidOperationException("Hook failed"); - })) - .WithPersistenceStore(new DynamoDBPersistenceStoreBuilder() - .WithTableName(_tableName) - .WithDynamoDBClient(_client) - .Build())); - - var function = new ResponseHookTestFunction(); - var request = IdempotencySerializer.Deserialize( - await File.ReadAllTextAsync("./resources/apigw_event2.json")); - - // Act - First call to populate cache - var firstResponse = await function.Handle(request); - function.HandlerExecuted = false; - - // Act - Second call (idempotent) - should not throw despite hook exception - var response = await function.Handle(request); - - // Assert - Should return original response despite hook exception - response.Should().NotBeNull(); - response.Body.Should().Be(firstResponse.Body); - function.HandlerExecuted.Should().BeFalse(); - } -} - -public class ResponseHookTestFunction -{ - public bool HandlerExecuted { get; set; } - - [Idempotent] - public async Task Handle(APIGatewayProxyRequest request) - { - HandlerExecuted = true; - - await Task.Delay(100); // Simulate some work - - return new APIGatewayProxyResponse - { - StatusCode = 200, - Body = "Hello World", - Headers = new Dictionary - { - ["Content-Type"] = "application/json" - } - }; - } -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/TestSetup.cs b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/TestSetup.cs index 41a898e37..26f0e2135 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/TestSetup.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Idempotency.Tests/TestSetup.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using Xunit; [assembly: CollectionBehavior(DisableTestParallelization = true)] \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/GlobalUsings.cs b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/GlobalUsings.cs index 8c927eb74..2cdb71da1 100644 --- a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/GlobalUsings.cs +++ b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/GlobalUsings.cs @@ -1 +1,16 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + global using Xunit; \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/JmesPathExamples.cs b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/JmesPathExamples.cs index 59542d06d..a1386ea60 100644 --- a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/JmesPathExamples.cs +++ b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/JmesPathExamples.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System.Text.Json; using Xunit.Abstractions; diff --git a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/JmesPathTests.cs b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/JmesPathTests.cs index 8e2131e79..7a82c6975 100644 --- a/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/JmesPathTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.JMESPath.Tests/JmesPathTests.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System.Text.Json; using AWS.Lambda.Powertools.JMESPath.Utilities; using Xunit.Abstractions; diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/AWS.Lambda.Powertools.Kafka.Tests.csproj b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/AWS.Lambda.Powertools.Kafka.Tests.csproj deleted file mode 100644 index e0f501b47..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/AWS.Lambda.Powertools.Kafka.Tests.csproj +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - AWS.Lambda.Powertools.Kafka.Tests - AWS.Lambda.Powertools.Kafka.Tests - net8.0 - enable - enable - - false - true - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - - - - - - - - - - PreserveNewest - - - - PreserveNewest - - - - PreserveNewest - - - - PreserveNewest - - - - PreserveNewest - - - - Client - PreserveNewest - MSBuild:Compile - - - - PreserveNewest - - - - PreserveNewest - - - - Client - PreserveNewest - MSBuild:Compile - - - - PreserveNewest - - - - - - PreserveNewest - - - - diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/AWS/Lambda/Powertools/Kafka/Tests/AvroKey.cs b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/AWS/Lambda/Powertools/Kafka/Tests/AvroKey.cs deleted file mode 100644 index 96d09316e..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/AWS/Lambda/Powertools/Kafka/Tests/AvroKey.cs +++ /dev/null @@ -1,70 +0,0 @@ -// ------------------------------------------------------------------------------ -// -// Generated by avrogen, version 1.12.0+8c27801dc8d42ccc00997f25c0b8f45f8d4a233e -// Changes to this file may cause incorrect behavior and will be lost if code -// is regenerated -// -// ------------------------------------------------------------------------------ -namespace AWS.Lambda.Powertools.Kafka.Tests -{ - using System; - using System.Collections.Generic; - using System.Text; - using global::Avro; - using global::Avro.Specific; - - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("avrogen", "1.12.0+8c27801dc8d42ccc00997f25c0b8f45f8d4a233e")] - public partial class AvroKey : global::Avro.Specific.ISpecificRecord - { - public static global::Avro.Schema _SCHEMA = global::Avro.Schema.Parse(@"{""type"":""record"",""name"":""AvroKey"",""namespace"":""AWS.Lambda.Powertools.Kafka.Tests"",""fields"":[{""name"":""id"",""type"":""int""},{""name"":""color"",""type"":{""type"":""enum"",""name"":""Color"",""namespace"":""AWS.Lambda.Powertools.Kafka.Tests"",""symbols"":[""UNKNOWN"",""GREEN"",""RED""],""default"":""UNKNOWN""}}]}"); - private int _id; - private AWS.Lambda.Powertools.Kafka.Tests.Color _color = AWS.Lambda.Powertools.Kafka.Tests.Color.UNKNOWN; - public virtual global::Avro.Schema Schema - { - get - { - return AvroKey._SCHEMA; - } - } - public int id - { - get - { - return this._id; - } - set - { - this._id = value; - } - } - public AWS.Lambda.Powertools.Kafka.Tests.Color color - { - get - { - return this._color; - } - set - { - this._color = value; - } - } - public virtual object Get(int fieldPos) - { - switch (fieldPos) - { - case 0: return this.id; - case 1: return this.color; - default: throw new global::Avro.AvroRuntimeException("Bad index " + fieldPos + " in Get()"); - }; - } - public virtual void Put(int fieldPos, object fieldValue) - { - switch (fieldPos) - { - case 0: this.id = (System.Int32)fieldValue; break; - case 1: this.color = (AWS.Lambda.Powertools.Kafka.Tests.Color)fieldValue; break; - default: throw new global::Avro.AvroRuntimeException("Bad index " + fieldPos + " in Put()"); - }; - } - } -} diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/AWS/Lambda/Powertools/Kafka/Tests/AvroProduct.cs b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/AWS/Lambda/Powertools/Kafka/Tests/AvroProduct.cs deleted file mode 100644 index f1c6aa8d4..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/AWS/Lambda/Powertools/Kafka/Tests/AvroProduct.cs +++ /dev/null @@ -1,86 +0,0 @@ -// ------------------------------------------------------------------------------ -// -// Generated by avrogen, version 1.12.0+8c27801dc8d42ccc00997f25c0b8f45f8d4a233e -// Changes to this file may cause incorrect behavior and will be lost if code -// is regenerated -// -// ------------------------------------------------------------------------------ -namespace AWS.Lambda.Powertools.Kafka.Tests -{ - using System; - using System.Collections.Generic; - using System.Text; - using global::Avro; - using global::Avro.Specific; - - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("avrogen", "1.12.0+8c27801dc8d42ccc00997f25c0b8f45f8d4a233e")] - public partial class AvroProduct : global::Avro.Specific.ISpecificRecord - { - public static global::Avro.Schema _SCHEMA = global::Avro.Schema.Parse("{\"type\":\"record\",\"name\":\"AvroProduct\",\"namespace\":\"AWS.Lambda.Powertools.Kafka.Te" + - "sts\",\"fields\":[{\"name\":\"id\",\"type\":\"int\"},{\"name\":\"name\",\"type\":\"string\"},{\"name" + - "\":\"price\",\"type\":\"double\"}]}"); - private int _id; - private string _name; - private double _price; - public virtual global::Avro.Schema Schema - { - get - { - return AvroProduct._SCHEMA; - } - } - public int id - { - get - { - return this._id; - } - set - { - this._id = value; - } - } - public string name - { - get - { - return this._name; - } - set - { - this._name = value; - } - } - public double price - { - get - { - return this._price; - } - set - { - this._price = value; - } - } - public virtual object Get(int fieldPos) - { - switch (fieldPos) - { - case 0: return this.id; - case 1: return this.name; - case 2: return this.price; - default: throw new global::Avro.AvroRuntimeException("Bad index " + fieldPos + " in Get()"); - }; - } - public virtual void Put(int fieldPos, object fieldValue) - { - switch (fieldPos) - { - case 0: this.id = (System.Int32)fieldValue; break; - case 1: this.name = (System.String)fieldValue; break; - case 2: this.price = (System.Double)fieldValue; break; - default: throw new global::Avro.AvroRuntimeException("Bad index " + fieldPos + " in Put()"); - }; - } - } -} diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/AWS/Lambda/Powertools/Kafka/Tests/Color.cs b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/AWS/Lambda/Powertools/Kafka/Tests/Color.cs deleted file mode 100644 index 963233679..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/AWS/Lambda/Powertools/Kafka/Tests/Color.cs +++ /dev/null @@ -1,23 +0,0 @@ -// ------------------------------------------------------------------------------ -// -// Generated by avrogen, version 1.12.0+8c27801dc8d42ccc00997f25c0b8f45f8d4a233e -// Changes to this file may cause incorrect behavior and will be lost if code -// is regenerated -// -// ------------------------------------------------------------------------------ -namespace AWS.Lambda.Powertools.Kafka.Tests -{ - using System; - using System.Collections.Generic; - using System.Text; - using global::Avro; - using global::Avro.Specific; - - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("avrogen", "1.12.0+8c27801dc8d42ccc00997f25c0b8f45f8d4a233e")] - public enum Color - { - UNKNOWN, - GREEN, - RED, - } -} diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/AvroKey.avsc b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/AvroKey.avsc deleted file mode 100644 index cc15c9e72..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/AvroKey.avsc +++ /dev/null @@ -1,24 +0,0 @@ -{ - "namespace": "AWS.Lambda.Powertools.Kafka.Tests", - "type": "record", - "name": "AvroKey", - "fields": [ - { - "name": "id", - "type": "int" - }, - { - "name": "color", - "type": { - "type": "enum", - "name": "Color", - "symbols": [ - "UNKNOWN", - "GREEN", - "RED" - ], - "default": "UNKNOWN" - } - } - ] -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/AvroProduct.avsc b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/AvroProduct.avsc deleted file mode 100644 index 60b8ed002..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/AvroProduct.avsc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "namespace": "AWS.Lambda.Powertools.Kafka.Tests", - "type": "record", - "name": "AvroProduct", - "fields": [ - {"name": "id", "type": "int"}, - {"name": "name", "type": "string"}, - {"name": "price", "type": "double"} - ] -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/HandlerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/HandlerTests.cs deleted file mode 100644 index aa2f83072..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/HandlerTests.cs +++ /dev/null @@ -1,414 +0,0 @@ -using System.Text; -using Amazon.Lambda.Core; -using Amazon.Lambda.TestUtilities; -using Avro.IO; -using Avro.Specific; -using AWS.Lambda.Powertools.Kafka.Avro; - -#if DEBUG -using KafkaAlias = AWS.Lambda.Powertools.Kafka; -#else -using KafkaAlias = AWS.Lambda.Powertools.Kafka.Avro; -#endif - -namespace AWS.Lambda.Powertools.Kafka.Tests.Avro; - -public class KafkaHandlerTests -{ - [Fact] - public async Task Handler_ProcessesKafkaEvent_Successfully() - { - // Arrange - var kafkaJson = GetMockKafkaEvent(); - var mockContext = new TestLambdaContext(); - var serializer = new PowertoolsKafkaAvroSerializer(); - - // Convert JSON string to stream for deserialization - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaJson)); - - // Act - Deserialize and process - var kafkaEvent = serializer.Deserialize>(stream); - var response = await Handler(kafkaEvent, mockContext); - - // Assert - Assert.Equal("Successfully processed Kafka events", response); - - // Verify event structure - Assert.Equal("aws:kafka", kafkaEvent.EventSource); - Assert.Single(kafkaEvent.Records); - - // Verify record content - var records = kafkaEvent.Records["mytopic-0"]; - Assert.Equal(3, records.Count); - - // Verify first record - var firstRecord = records[0]; - Assert.Equal("mytopic", firstRecord.Topic); - Assert.Equal(0, firstRecord.Partition); - Assert.Equal(15, firstRecord.Offset); - - // Verify deserialized value - var product = firstRecord.Value; - Assert.Equal("Laptop", product.name); - Assert.Equal(999.99, product.price); - - // Verify decoded key and headers - Assert.Equal(42, firstRecord.Key); - Assert.Equal("headerValue", firstRecord.Headers["headerKey"].DecodedValue()); - - var secondRecord = records[1]; - Assert.Equal(43, secondRecord.Key); - - var thirdRecord = records[2]; - Assert.Equal(0, thirdRecord.Key); - } - - [Fact] - public async Task Handler_ProcessesKafkaEvent_Primitive_Successfully() - { - // Arrange - var kafkaJson = GetSimpleMockKafkaEvent(); - var mockContext = new TestLambdaContext(); - var serializer = new PowertoolsKafkaAvroSerializer(); - - // Convert JSON string to stream for deserialization - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaJson)); - - // Act - Deserialize and process - var kafkaEvent = serializer.Deserialize>(stream); - var response = await HandlerSimple(kafkaEvent, mockContext); - - // Assert - Assert.Equal("Successfully processed Kafka events", response); - - // Verify event structure - Assert.Equal("aws:kafka", kafkaEvent.EventSource); - Assert.Single(kafkaEvent.Records); - - // Verify record content - var records = kafkaEvent.Records["mytopic-0"]; - Assert.Equal(3, records.Count); - - // Verify first record - var firstRecord = records[0]; - Assert.Equal("mytopic", firstRecord.Topic); - Assert.Equal(0, firstRecord.Partition); - Assert.Equal(15, firstRecord.Offset); - - // Verify deserialized value - Assert.Equal("Laptop", firstRecord.Value); - - // Verify decoded key and headers - Assert.Equal(42, firstRecord.Key); - Assert.Equal("headerValue", firstRecord.Headers["headerKey"].DecodedValue()); - - var secondRecord = records[1]; - Assert.Equal(43, secondRecord.Key); - Assert.Equal("Smartphone", secondRecord.Value); - - var thirdRecord = records[2]; - Assert.Equal(0, thirdRecord.Key); - Assert.Null(thirdRecord.Value); - } - - private string GetMockKafkaEvent() - { - // For testing, we'll create base64-encoded Avro data for our test products - var laptop = new AvroProduct { name = "Laptop", price = 999.99 }; - var smartphone = new AvroProduct { name = "Smartphone", price = 499.99 }; - var headphones = new AvroProduct { name = "Headphones", price = 99.99 }; - - // Convert to base64-encoded Avro - string laptopBase64 = ConvertToAvroBase64(laptop); - string smartphoneBase64 = ConvertToAvroBase64(smartphone); - string headphonesBase64 = ConvertToAvroBase64(headphones); - - string firstRecordKey = Convert.ToBase64String(Encoding.UTF8.GetBytes("42")); // Example key - string secondRecordKey = Convert.ToBase64String(Encoding.UTF8.GetBytes("43")); // Example key for second record - - // Create mock Kafka event JSON - return @$"{{ - ""eventSource"": ""aws:kafka"", - ""eventSourceArn"": ""arn:aws:kafka:us-east-1:0123456789019:cluster/SalesCluster/abcd1234-abcd-cafe-abab-9876543210ab-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"": {{ - ""mytopic-0"": [ - {{ - ""topic"": ""mytopic"", - ""partition"": 0, - ""offset"": 15, - ""timestamp"": 1545084650987, - ""timestampType"": ""CREATE_TIME"", - ""key"": ""{firstRecordKey}"", - ""value"": ""{laptopBase64}"", - ""headers"": [ - {{ ""headerKey"": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] }} - ] - }}, - {{ - ""topic"": ""mytopic"", - ""partition"": 0, - ""offset"": 16, - ""timestamp"": 1545084650988, - ""timestampType"": ""CREATE_TIME"", - ""key"": ""{secondRecordKey}"", - ""value"": ""{smartphoneBase64}"", - ""headers"": [ - {{ ""headerKey"": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] }} - ] - }}, - {{ - ""topic"": ""mytopic"", - ""partition"": 0, - ""offset"": 17, - ""timestamp"": 1545084650989, - ""timestampType"": ""CREATE_TIME"", - ""key"": null, - ""value"": ""{headphonesBase64}"", - ""headers"": [ - {{ ""headerKey"": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] }} - ] - }} - ] - }} - }}"; - } - - private string GetSimpleMockKafkaEvent() - { - // For testing, we'll create base64-encoded Avro data for our test products - - // Convert to base64-encoded Avro - string laptopBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes("Laptop")); - string smartphoneBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes("Smartphone")); - - string firstRecordKey = Convert.ToBase64String(Encoding.UTF8.GetBytes("42")); // Example key - string secondRecordKey = Convert.ToBase64String(Encoding.UTF8.GetBytes("43")); // Example key for second record - - // Create mock Kafka event JSON - return @$"{{ - ""eventSource"": ""aws:kafka"", - ""eventSourceArn"": ""arn:aws:kafka:us-east-1:0123456789019:cluster/SalesCluster/abcd1234-abcd-cafe-abab-9876543210ab-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"": {{ - ""mytopic-0"": [ - {{ - ""topic"": ""mytopic"", - ""partition"": 0, - ""offset"": 15, - ""timestamp"": 1545084650987, - ""timestampType"": ""CREATE_TIME"", - ""key"": ""{firstRecordKey}"", - ""value"": ""{laptopBase64}"", - ""headers"": [ - {{ ""headerKey"": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] }} - ] - }}, - {{ - ""topic"": ""mytopic"", - ""partition"": 0, - ""offset"": 16, - ""timestamp"": 1545084650988, - ""timestampType"": ""CREATE_TIME"", - ""key"": ""{secondRecordKey}"", - ""value"": ""{smartphoneBase64}"", - ""headers"": [ - {{ ""headerKey"": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] }} - ] - }}, - {{ - ""topic"": ""mytopic"", - ""partition"": 0, - ""offset"": 17, - ""timestamp"": 1545084650989, - ""timestampType"": ""CREATE_TIME"", - ""key"": null, - ""value"": null, - ""headers"": [ - {{ ""headerKey"": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] }} - ] - }} - ] - }} - }}"; - } - - private string ConvertToAvroBase64(AvroProduct product) - { - using var stream = new MemoryStream(); - var encoder = new BinaryEncoder(stream); - var writer = new SpecificDatumWriter(AvroProduct._SCHEMA); - - writer.Write(product, encoder); - encoder.Flush(); - - return Convert.ToBase64String(stream.ToArray()); - } - - // Define the test handler method - private async Task Handler(KafkaAlias.ConsumerRecords records, ILambdaContext context) - { - foreach (var record in records) - { - var product = record.Value; - context.Logger.LogInformation($"Processing {product.name} at ${product.price}"); - } - - return "Successfully processed Kafka events"; - } - - private async Task HandlerSimple(KafkaAlias.ConsumerRecords records, ILambdaContext context) - { - foreach (var record in records) - { - var product = record.Value; - context.Logger.LogInformation($"Processing {product}"); - } - - return "Successfully processed Kafka events"; - } - - [Fact] - public async Task Handler_ProcessesKafkaEvent_WithAvroKey_Successfully() - { - // Arrange - var kafkaJson = GetMockKafkaEventWithAvroKeys(); - var mockContext = new TestLambdaContext(); - var serializer = new PowertoolsKafkaAvroSerializer(); - - // Convert JSON string to stream for deserialization - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaJson)); - - // Act - Deserialize and process - var kafkaEvent = serializer.Deserialize>(stream); - var response = await HandlerWithAvroKeys(kafkaEvent, mockContext); - - // Assert - Assert.Equal("Successfully processed Kafka events", response); - - // Verify event structure - Assert.Equal("aws:kafka", kafkaEvent.EventSource); - Assert.Single(kafkaEvent.Records); - - // Verify record content - var records = kafkaEvent.Records["mytopic-0"]; - Assert.Equal(3, records.Count); - - // Verify first record - var firstRecord = records[0]; - Assert.Equal("mytopic", firstRecord.Topic); - Assert.Equal(0, firstRecord.Partition); - Assert.Equal(15, firstRecord.Offset); - - // Verify deserialized Avro key and value - Assert.Equal("Laptop", firstRecord.Value.name); - Assert.Equal(999.99, firstRecord.Value.price); - Assert.Equal(1, firstRecord.Key.id); - Assert.Equal(Color.GREEN, firstRecord.Key.color); - - // Verify headers - Assert.Equal("headerValue", firstRecord.Headers["headerKey"].DecodedValue()); - - var secondRecord = records[1]; - Assert.Equal(2, secondRecord.Key.id); - Assert.Equal(Color.UNKNOWN, secondRecord.Key.color); - - var thirdRecord = records[2]; - Assert.Equal(3, thirdRecord.Key.id); - Assert.Equal(Color.RED, thirdRecord.Key.color); - } - - private string GetMockKafkaEventWithAvroKeys() - { - // Create test products - var laptop = new AvroProduct { name = "Laptop", price = 999.99 }; - var smartphone = new AvroProduct { name = "Smartphone", price = 499.99 }; - var headphones = new AvroProduct { name = "Headphones", price = 99.99 }; - - // Create test keys - var key1 = new AvroKey { id = 1, color = Color.GREEN }; - var key2 = new AvroKey { id = 2 }; - var key3 = new AvroKey { id = 3, color = Color.RED }; - - // Convert values to base64-encoded Avro - string laptopBase64 = ConvertToAvroBase64(laptop); - string smartphoneBase64 = ConvertToAvroBase64(smartphone); - string headphonesBase64 = ConvertToAvroBase64(headphones); - - // Convert keys to base64-encoded Avro - string key1Base64 = ConvertKeyToAvroBase64(key1); - string key2Base64 = ConvertKeyToAvroBase64(key2); - string key3Base64 = ConvertKeyToAvroBase64(key3); - - // Create mock Kafka event JSON - return @$"{{ - ""eventSource"": ""aws:kafka"", - ""eventSourceArn"": ""arn:aws:kafka:us-east-1:0123456789019:cluster/SalesCluster/abcd1234-abcd-cafe-abab-9876543210ab-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"": {{ - ""mytopic-0"": [ - {{ - ""topic"": ""mytopic"", - ""partition"": 0, - ""offset"": 15, - ""timestamp"": 1545084650987, - ""timestampType"": ""CREATE_TIME"", - ""key"": ""{key1Base64}"", - ""value"": ""{laptopBase64}"", - ""headers"": [ - {{ ""headerKey"": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] }} - ] - }}, - {{ - ""topic"": ""mytopic"", - ""partition"": 0, - ""offset"": 16, - ""timestamp"": 1545084650988, - ""timestampType"": ""CREATE_TIME"", - ""key"": ""{key2Base64}"", - ""value"": ""{smartphoneBase64}"", - ""headers"": [ - {{ ""headerKey"": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] }} - ] - }}, - {{ - ""topic"": ""mytopic"", - ""partition"": 0, - ""offset"": 17, - ""timestamp"": 1545084650989, - ""timestampType"": ""CREATE_TIME"", - ""key"": ""{key3Base64}"", - ""value"": ""{headphonesBase64}"", - ""headers"": [ - {{ ""headerKey"": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] }} - ] - }} - ] - }} - }}"; - } - - private string ConvertKeyToAvroBase64(AvroKey key) - { - using var stream = new MemoryStream(); - var encoder = new BinaryEncoder(stream); - var writer = new SpecificDatumWriter(AvroKey._SCHEMA); - - writer.Write(key, encoder); - encoder.Flush(); - - return Convert.ToBase64String(stream.ToArray()); - } - - private async Task HandlerWithAvroKeys(KafkaAlias.ConsumerRecords records, - ILambdaContext context) - { - foreach (var record in records) - { - var key = record.Key.id; - var product = record.Value; - } - - return "Successfully processed Kafka events"; - } -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/PowertoolsKafkaAvroSerializerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/PowertoolsKafkaAvroSerializerTests.cs deleted file mode 100644 index 4dc2c7cc8..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/PowertoolsKafkaAvroSerializerTests.cs +++ /dev/null @@ -1,149 +0,0 @@ -using System.Runtime.Serialization; -using System.Text; -using AWS.Lambda.Powertools.Kafka.Avro; - -#if DEBUG -using KafkaAlias = AWS.Lambda.Powertools.Kafka; -#else -using KafkaAlias = AWS.Lambda.Powertools.Kafka.Avro; -#endif - -namespace AWS.Lambda.Powertools.Kafka.Tests.Avro; - -public class PowertoolsKafkaAvroSerializerTests -{ - [Fact] - public void Deserialize_KafkaEventWithAvroPayload_DeserializesToCorrectType() - { - // Arrange - var serializer = new PowertoolsKafkaAvroSerializer(); - string kafkaEventJson = File.ReadAllText("Avro/kafka-avro-event.json"); - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); - - // Act - var result = serializer.Deserialize>(stream); - - // Assert - Assert.NotNull(result); - Assert.Equal("aws:kafka", result.EventSource); - - // Verify records were deserialized - Assert.True(result.Records.ContainsKey("mytopic-0")); - var records = result.Records["mytopic-0"]; - Assert.Equal(3, records.Count); - - // Verify first record's content - var firstRecord = records[0]; - Assert.Equal("mytopic", firstRecord.Topic); - Assert.Equal(0, firstRecord.Partition); - Assert.Equal(15, firstRecord.Offset); - Assert.Equal(42, firstRecord.Key); - - // Verify deserialized Avro value - var product = firstRecord.Value; - Assert.Equal("Laptop", product.name); - Assert.Equal(1001, product.id); - Assert.Equal(999.99000000000001, product.price); - - // Verify second record - var secondRecord = records[1]; - var smartphone = secondRecord.Value; - Assert.Equal("Smartphone", smartphone.name); - } - - [Fact] - public void KafkaEvent_ImplementsIEnumerable_ForDirectIteration() - { - // Arrange - var serializer = new PowertoolsKafkaAvroSerializer(); - string kafkaEventJson = File.ReadAllText("Avro/kafka-avro-event.json"); - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); - - // Act - var result = serializer.Deserialize>(stream); - - // Assert - Test enumeration - int count = 0; - var products = new List(); - - // Directly iterate over ConsumerRecords - foreach (var record in result) - { - count++; - products.Add(record.Value.name); - } - - // Verify correct count and values - Assert.Equal(3, count); - Assert.Contains("Laptop", products); - Assert.Contains("Smartphone", products); - Assert.Equal(3, products.Count); - - // Get first record directly through Linq extension - var firstRecord = result.First(); - Assert.Equal("Laptop", firstRecord.Value.name); - Assert.Equal(1001, firstRecord.Value.id); - } - - [Fact] - public void Primitive_Deserialization() - { - // Arrange - var serializer = new PowertoolsKafkaAvroSerializer(); - string kafkaEventJson = - CreateKafkaEvent(Convert.ToBase64String("MyKey"u8.ToArray()), - Convert.ToBase64String("Myvalue"u8.ToArray())); - - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); - - // Act - var result = serializer.Deserialize>(stream); - var firstRecord = result.First(); - Assert.Equal("Myvalue", firstRecord.Value); - Assert.Equal("MyKey", firstRecord.Key); - } - - [Fact] - public void DeserializeComplexKey_WhenAllDeserializationMethodsFail_ReturnsException() - { - // Arrange - var serializer = new PowertoolsKafkaAvroSerializer(); - // Invalid JSON and not Avro binary - byte[] invalidBytes = { 0xDE, 0xAD, 0xBE, 0xEF }; - - string kafkaEventJson = CreateKafkaEvent( - keyValue: Convert.ToBase64String(invalidBytes), - valueValue: Convert.ToBase64String(Encoding.UTF8.GetBytes("test")) - ); - - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); - - Assert.Throws(() => - serializer.Deserialize>(stream)); - } - - private string CreateKafkaEvent(string keyValue, string valueValue) - { - return @$"{{ - ""eventSource"": ""aws:kafka"", - ""eventSourceArn"": ""arn:aws:kafka:us-east-1:0123456789019:cluster/TestCluster/abcd1234"", - ""bootstrapServers"": ""b-1.test-cluster.kafka.us-east-1.amazonaws.com:9092"", - ""records"": {{ - ""mytopic-0"": [ - {{ - ""topic"": ""mytopic"", - ""partition"": 0, - ""offset"": 15, - ""timestamp"": 1645084650987, - ""timestampType"": ""CREATE_TIME"", - ""key"": ""{keyValue}"", - ""value"": ""{valueValue}"", - ""headers"": [ - {{ ""headerKey"": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] }} - ] - }} - ] - }} - }}"; - } -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/kafka-avro-event.json b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/kafka-avro-event.json deleted file mode 100644 index 8d6ef2210..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Avro/kafka-avro-event.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "eventSource": "aws:kafka", - "eventSourceArn": "arn:aws:kafka:us-east-1:0123456789019:cluster/SalesCluster/abcd1234-abcd-cafe-abab-9876543210ab-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": { - "mytopic-0": [ - { - "topic": "mytopic", - "partition": 0, - "offset": 15, - "timestamp": 1545084650987, - "timestampType": "CREATE_TIME", - "key": "NDI=", - "value": "0g8MTGFwdG9wUrgehes/j0A=", - "headers": [ - { - "headerKey": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] - } - ] - }, - { - "topic": "mytopic", - "partition": 0, - "offset": 16, - "timestamp": 1545084650988, - "timestampType": "CREATE_TIME", - "key": "NDI=", - "value": "1A8UU21hcnRwaG9uZVK4HoXrv4JA", - "headers": [ - { - "headerKey": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] - } - ] - }, - { - "topic": "mytopic", - "partition": 0, - "offset": 17, - "timestamp": 1545084650989, - "timestampType": "CREATE_TIME", - "key": null, - "value": "1g8USGVhZHBob25lc0jhehSuv2JA", - "headers": [ - { - "headerKey": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] - } - ] - } - ] - } -} diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/AvroErrorHandlingTests.cs b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/AvroErrorHandlingTests.cs deleted file mode 100644 index 3ada7575d..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/AvroErrorHandlingTests.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System.Runtime.Serialization; -using System.Text; -using AWS.Lambda.Powertools.Kafka.Avro; - -#if DEBUG -using KafkaAlias = AWS.Lambda.Powertools.Kafka; -#else -using KafkaAlias = AWS.Lambda.Powertools.Kafka.Avro; -#endif - -namespace AWS.Lambda.Powertools.Kafka.Tests; - -public class AvroErrorHandlingTests -{ - [Fact] - public void AvroSerializer_WithCorruptedKeyData_ThrowSerializationException() - { - // Arrange - var serializer = new PowertoolsKafkaAvroSerializer(); - var corruptedData = new byte[] { 0xDE, 0xAD, 0xBE, 0xEF }; - - string kafkaEventJson = CreateKafkaEvent( - Convert.ToBase64String(corruptedData), - Convert.ToBase64String(Encoding.UTF8.GetBytes("valid-value")) - ); - - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); - - // Act & Assert - var ex = Assert.Throws(() => - serializer.Deserialize>(stream)); - - Assert.Contains("Failed to deserialize key data", ex.Message); - } - - [Fact] - public void AvroSerializer_WithCorruptedValueData_ThrowSerializationException() - { - // Arrange - var serializer = new PowertoolsKafkaAvroSerializer(); - var corruptedData = new byte[] { 0xDE, 0xAD, 0xBE, 0xEF }; - - string kafkaEventJson = CreateKafkaEvent( - Convert.ToBase64String(Encoding.UTF8.GetBytes("valid-key")), - Convert.ToBase64String(corruptedData) - ); - - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); - - // Act & Assert - var ex = Assert.Throws(() => - serializer.Deserialize>(stream)); - - Assert.Contains("Failed to deserialize value data", ex.Message); - } - - private string CreateKafkaEvent(string keyValue, string valueValue) - { - return @$"{{ - ""eventSource"": ""aws:kafka"", - ""records"": {{ - ""mytopic-0"": [ - {{ - ""topic"": ""mytopic"", - ""partition"": 0, - ""offset"": 15, - ""key"": ""{keyValue}"", - ""value"": ""{valueValue}"" - }} - ] - }} - }}"; - } -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/HeaderExtensionsTests.cs b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/HeaderExtensionsTests.cs deleted file mode 100644 index 7114a6988..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/HeaderExtensionsTests.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System.Text; -using AWS.Lambda.Powertools.Kafka.Avro; - -namespace AWS.Lambda.Powertools.Kafka.Tests -{ - public class HeaderExtensionsTests - { - [Fact] - public void DecodedValues_WithValidHeaders_DecodesCorrectly() - { - // Arrange - var headers = new Dictionary - { - { "header1", Encoding.UTF8.GetBytes("value1") }, - { "header2", Encoding.UTF8.GetBytes("value2") } - }; - - // Act - var decoded = headers.DecodedValues(); - - // Assert - Assert.Equal(2, decoded.Count); - Assert.Equal("value1", decoded["header1"]); - Assert.Equal("value2", decoded["header2"]); - } - - [Fact] - public void DecodedValues_WithEmptyDictionary_ReturnsEmptyDictionary() - { - // Arrange - var headers = new Dictionary(); - - // Act - var decoded = headers.DecodedValues(); - - // Assert - Assert.Empty(decoded); - } - - [Fact] - public void DecodedValues_WithNullDictionary_ReturnsEmptyDictionary() - { - // Arrange - Dictionary headers = null; - - // Act - var decoded = headers.DecodedValues(); - - // Assert - Assert.Empty(decoded); - } - - [Fact] - public void DecodedValue_WithValidBytes_DecodesCorrectly() - { - // Arrange - var bytes = Encoding.UTF8.GetBytes("test-value"); - - // Act - var decoded = bytes.DecodedValue(); - - // Assert - Assert.Equal("test-value", decoded); - } - - [Fact] - public void DecodedValue_WithEmptyBytes_ReturnsEmptyString() - { - // Arrange - var bytes = Array.Empty(); - - // Act - var decoded = bytes.DecodedValue(); - - // Assert - Assert.Equal("", decoded); - } - - [Fact] - public void DecodedValue_WithNullBytes_ReturnsEmptyString() - { - // Act - var decoded = ((byte[])null).DecodedValue(); - - // Assert - Assert.Equal("", decoded); - } - } -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Json/PowertoolsKafkaJsonSerializerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Json/PowertoolsKafkaJsonSerializerTests.cs deleted file mode 100644 index dfe21542e..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Json/PowertoolsKafkaJsonSerializerTests.cs +++ /dev/null @@ -1,416 +0,0 @@ -using System.Text; -using System.Text.Json; -using System.Text.Json.Serialization; -using AWS.Lambda.Powertools.Kafka.Json; - -#if DEBUG -using KafkaAlias = AWS.Lambda.Powertools.Kafka; -#else -using KafkaAlias = AWS.Lambda.Powertools.Kafka.Json; -#endif - -namespace AWS.Lambda.Powertools.Kafka.Tests.Json; - -public class PowertoolsKafkaJsonSerializerTests -{ - [Fact] - public void Deserialize_KafkaEventWithJsonPayload_DeserializesToCorrectType() - { - // Arrange - var serializer = new PowertoolsKafkaJsonSerializer(); - var testModel = new TestModel { Name = "Test Product", Value = 123 }; - var jsonValue = JsonSerializer.Serialize(testModel); - var base64Value = Convert.ToBase64String(Encoding.UTF8.GetBytes(jsonValue)); - - string kafkaEventJson = CreateKafkaEvent("NDI=", base64Value); // Key is 42 in base64 - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); - - // Act - var result = serializer.Deserialize>(stream); - - // Assert - Assert.NotNull(result); - var record = result.First(); - Assert.Equal(42, record.Key); - Assert.Equal("Test Product", record.Value.Name); - Assert.Equal(123, record.Value.Value); - } - - [Fact] - public void KafkaEvent_ImplementsIEnumerable_ForDirectIteration() - { - // Arrange - var serializer = new PowertoolsKafkaJsonSerializer(); - string kafkaEventJson = File.ReadAllText("Json/kafka-json-event.json"); - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); - - // Act - var result = serializer.Deserialize>(stream); - - // Assert - Test enumeration - int count = 0; - var products = new List(); - - // Directly iterate over ConsumerRecords - foreach (var record in result) - { - count++; - products.Add(record.Value.Name); - } - - // Verify correct count and values - Assert.Equal(3, count); - Assert.Contains("product5", products); - - // Get first record directly through Linq extension - var firstRecord = result.First(); - Assert.Equal("product5", firstRecord.Value.Name); - Assert.Equal(12345, firstRecord.Value.Id); - } - - [Fact] - public void Primitive_Deserialization() - { - // Arrange - var serializer = new PowertoolsKafkaJsonSerializer(); - string kafkaEventJson = - CreateKafkaEvent(Convert.ToBase64String("MyKey"u8.ToArray()), - Convert.ToBase64String("Myvalue"u8.ToArray())); - - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); - - // Act - var result = serializer.Deserialize>(stream); - var firstRecord = result.First(); - Assert.Equal("Myvalue", firstRecord.Value); - Assert.Equal("MyKey", firstRecord.Key); - } - - [Fact] - public void DeserializeComplexKey_StandardJsonDeserialization_Works() - { - // Arrange - var serializer = new PowertoolsKafkaJsonSerializer(); - var complexObject = new { Name = "Test", Id = 123 }; - var jsonBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(complexObject)); - - string kafkaEventJson = CreateKafkaEvent( - keyValue: Convert.ToBase64String(jsonBytes), - valueValue: Convert.ToBase64String(Encoding.UTF8.GetBytes("test")) - ); - - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); - - // Act - var result = serializer.Deserialize, string>>(stream); - - // Assert - var record = result.First(); - Assert.NotNull(record.Key); - Assert.Equal("Test", record.Key["Name"].ToString()); - Assert.Equal(123, int.Parse(record.Key["Id"].ToString())); - } - - [Fact] - public void DeserializeComplexKey_WithSerializerContext_UsesContext() - { - // Arrange - // Create custom context - var options = new JsonSerializerOptions(); - var context = new TestJsonSerializerContext(options); - var serializer = new PowertoolsKafkaJsonSerializer(context); - - // Create test data with the registered type - var testModel = new TestModel { Name = "TestFromContext", Value = 456 }; - var jsonBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(testModel)); - - string kafkaEventJson = CreateKafkaEvent( - keyValue: Convert.ToBase64String(jsonBytes), - valueValue: Convert.ToBase64String(Encoding.UTF8.GetBytes("test")) - ); - - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); - - // Act - var result = serializer.Deserialize>(stream); - - // Assert - var record = result.First(); - Assert.NotNull(record.Key); - Assert.Equal("TestFromContext", record.Key.Name); - Assert.Equal(456, record.Key.Value); - } - - [Fact] - public void DeserializeComplexValue_WithSerializerContext_UsesContext() - { - // Arrange - var options = new JsonSerializerOptions(); - var context = new TestJsonSerializerContext(options); - var serializer = new PowertoolsKafkaJsonSerializer(context); - - // Create test data with the registered type - var testModel = new TestModel { Name = "ValueFromContext", Value = 789 }; - var jsonBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(testModel)); - - string kafkaEventJson = CreateKafkaEvent( - keyValue: Convert.ToBase64String(Encoding.UTF8.GetBytes("testKey")), - valueValue: Convert.ToBase64String(jsonBytes) - ); - - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); - - // Act - var result = serializer.Deserialize>(stream); - - // Assert - var record = result.First(); - Assert.Equal("testKey", record.Key); - Assert.NotNull(record.Value); - Assert.Equal("ValueFromContext", record.Value.Name); - Assert.Equal(789, record.Value.Value); - } - - [Fact] - public void DeserializeComplexValue_WithCustomJsonOptions_RespectsOptions() - { - // Arrange - create custom options with different naming policy - var options = new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - PropertyNameCaseInsensitive = false // Force exact case match - }; - var serializer = new PowertoolsKafkaJsonSerializer(options); - - // Create test data with camelCase property names - var jsonBytes = Encoding.UTF8.GetBytes(@"{""id"":999,""name"":""camelCase"",""price"":29.99}"); - - string kafkaEventJson = CreateKafkaEvent( - keyValue: Convert.ToBase64String(Encoding.UTF8.GetBytes("testKey")), - valueValue: Convert.ToBase64String(jsonBytes) - ); - - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); - - // Act - var result = serializer.Deserialize>(stream); - - // Assert - var record = result.First(); - Assert.Equal(999, record.Value.Id); - Assert.Equal("camelCase", record.Value.Name); - Assert.Equal(29.99m, record.Value.Price); - } - - [Fact] - public void DeserializeComplexValue_WithEmptyData_ReturnsNullOrDefault() - { - // Arrange - var serializer = new PowertoolsKafkaJsonSerializer(); - // Empty JSON data - byte[] emptyBytes = Array.Empty(); - - string kafkaEventJson = CreateKafkaEvent( - keyValue: Convert.ToBase64String(Encoding.UTF8.GetBytes("testKey")), - valueValue: Convert.ToBase64String(emptyBytes) - ); - - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); - - // Act - var result = serializer.Deserialize>(stream); - - // Assert - var record = result.First(); - Assert.Equal("testKey", record.Key); - Assert.Null(record.Value); // Should be null for empty input - } - - [Fact] - public void DeserializeComplexValue_WithContextAndNullResult_ReturnsNull() - { - // Arrange - create a context with JsonNullHandling.Include - var options = new JsonSerializerOptions - { - DefaultIgnoreCondition = JsonIgnoreCondition.Never, - IgnoreNullValues = false - }; - var context = new TestJsonSerializerContext(options); - var serializer = new PowertoolsKafkaJsonSerializer(context); - - // JSON that explicitly sets the value to null - var jsonBytes = Encoding.UTF8.GetBytes("null"); - - string kafkaEventJson = CreateKafkaEvent( - keyValue: Convert.ToBase64String(Encoding.UTF8.GetBytes("testKey")), - valueValue: Convert.ToBase64String(jsonBytes) - ); - - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); - - // Act - var result = serializer.Deserialize>(stream); - - // Assert - var record = result.First(); - Assert.Equal("testKey", record.Key); - Assert.Null(record.Value); - } - - - /// - /// Helper method to create Kafka event JSON with specified key and value in base64 format - /// - private string CreateKafkaEvent(string keyValue, string valueValue) - { - return @$"{{ - ""eventSource"": ""aws:kafka"", - ""eventSourceArn"": ""arn:aws:kafka:us-east-1:0123456789019:cluster/TestCluster/abcd1234"", - ""bootstrapServers"": ""b-1.test-cluster.kafka.us-east-1.amazonaws.com:9092"", - ""records"": {{ - ""mytopic-0"": [ - {{ - ""topic"": ""mytopic"", - ""partition"": 0, - ""offset"": 15, - ""timestamp"": 1645084650987, - ""timestampType"": ""CREATE_TIME"", - ""key"": ""{keyValue}"", - ""value"": ""{valueValue}"", - ""headers"": [ - {{ ""headerKey"": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] }} - ] - }} - ] - }} - }}"; - } - - [Fact] - public void DirectJsonSerializerTest_InvokesFormatSpecificMethod() - { - // This test directly tests the JSON serializer methods - var serializer = new TestJsonDeserializer(); - - // Create test data with valid JSON - var testModel = new TestModel { Name = "DirectTest", Value = 555 }; - var jsonBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(testModel)); - - // Act - var result = serializer.TestDeserializeFormatSpecific(jsonBytes, typeof(TestModel), false); - - // Assert - Assert.NotNull(result); - var model = result as TestModel; - Assert.NotNull(model); - Assert.Equal("DirectTest", model!.Name); - Assert.Equal(555, model.Value); - } - - [Fact] - public void DirectJsonSerializerTest_WithContext_UsesContext() - { - // Create a context that includes TestModel - var options = new JsonSerializerOptions(); - var context = new TestJsonSerializerContext(options); - - // Create the serializer with context - var serializer = new TestJsonDeserializer(context); - - // Create test data with valid JSON - var testModel = new TestModel { Name = "ContextTest", Value = 999 }; - var jsonBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(testModel)); - - // Act - directly test the protected method - var result = serializer.TestDeserializeFormatSpecific(jsonBytes, typeof(TestModel), false); - - // Assert - Assert.NotNull(result); - var model = result as TestModel; - Assert.NotNull(model); - Assert.Equal("ContextTest", model!.Name); - Assert.Equal(999, model.Value); - } - - [Fact] - public void DirectJsonSerializerTest_WithEmptyJson_ReturnsNullOrDefault() - { - // Create the serializer - var serializer = new TestJsonDeserializer(); - - // Create empty JSON data - var emptyJsonBytes = Array.Empty(); - - // Act - test with reference type - var resultRef = serializer.TestDeserializeFormatSpecific(emptyJsonBytes, typeof(TestModel), false); - // Act - test with value type - var resultVal = serializer.TestDeserializeFormatSpecific(emptyJsonBytes, typeof(int), false); - - // Assert - Assert.Null(resultRef); // Reference type should get null - Assert.Equal(0, resultVal); // Value type should get default - } - - [Fact] - public void DirectJsonSerializerTest_WithContextResultingInNull_ReturnsNull() - { - // Create context - var options = new JsonSerializerOptions(); - var context = new TestJsonSerializerContext(options); - - // Create serializer with context - var serializer = new TestJsonDeserializer(context); - - // Create JSON that is "null" - var jsonBytes = Encoding.UTF8.GetBytes("null"); - - // Act - even with context, null JSON should return null - var result = serializer.TestDeserializeFormatSpecific(jsonBytes, typeof(TestModel), false); - - // Assert - Assert.Null(result); - } - - /// - /// Test helper to directly access protected methods - /// - private class TestJsonDeserializer : PowertoolsKafkaJsonSerializer - { - public TestJsonDeserializer() : base() { } - - public TestJsonDeserializer(JsonSerializerOptions options) : base(options) { } - - public TestJsonDeserializer(JsonSerializerContext context) : base(context) { } - - public object? TestDeserializeFormatSpecific(byte[] data, Type targetType, bool isKey) - { - // Call the protected method directly - return base.DeserializeComplexTypeFormat(data, targetType, isKey); - } - } -} - -[JsonSerializable(typeof(TestModel))] -public partial class TestJsonSerializerContext : JsonSerializerContext -{ -} - -public class TestModel -{ - public string Name { get; set; } = string.Empty; - public int Value { get; set; } -} - -public record JsonProduct -{ - public int Id { get; set; } - public string Name { get; set; } = string.Empty; - public decimal Price { get; set; } -} - -public struct ValueTypeProduct -{ - public int Id { get; set; } - public string Name { get; set; } - public decimal Price { get; set; } -} diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Json/kafka-json-event.json b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Json/kafka-json-event.json deleted file mode 100644 index d85c40654..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Json/kafka-json-event.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "eventSource": "aws:kafka", - "eventSourceArn": "arn:aws:kafka:us-east-1:0123456789019:cluster/SalesCluster/abcd1234-abcd-cafe-abab-9876543210ab-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": { - "mytopic-0": [ - { - "topic": "mytopic", - "partition": 0, - "offset": 15, - "timestamp": 1545084650987, - "timestampType": "CREATE_TIME", - "key": "cmVjb3JkS2V5", - "value": "ewogICJpZCI6IDEyMzQ1LAogICJuYW1lIjogInByb2R1Y3Q1IiwKICAicHJpY2UiOiA0NQp9", - "headers": [ - { - "headerKey": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] - } - ] - }, - { - "topic": "mytopic", - "partition": 0, - "offset": 15, - "timestamp": 1545084650987, - "timestampType": "CREATE_TIME", - "value": "ewogICJpZCI6IDEyMzQ1LAogICJuYW1lIjogInByb2R1Y3Q1IiwKICAicHJpY2UiOiA0NQp9", - "headers": [ - { - "headerKey": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] - } - ] - }, - { - "topic": "mytopic", - "partition": 0, - "offset": 15, - "timestamp": 1545084650987, - "timestampType": "CREATE_TIME", - "key": null, - "value": "ewogICJpZCI6IDEyMzQ1LAogICJuYW1lIjogInByb2R1Y3Q1IiwKICAicHJpY2UiOiA0NQp9", - "headers": [ - { - "headerKey": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] - } - ] - } - ] - } -} diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/JsonErrorHandlingTests.cs b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/JsonErrorHandlingTests.cs deleted file mode 100644 index 5ce8987bd..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/JsonErrorHandlingTests.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System.Runtime.Serialization; -using System.Text; -using AWS.Lambda.Powertools.Kafka.Json; - -#if DEBUG -using KafkaAlias = AWS.Lambda.Powertools.Kafka; -#else -using KafkaAlias = AWS.Lambda.Powertools.Kafka.Json; -#endif - -namespace AWS.Lambda.Powertools.Kafka.Tests; - -public class JsonErrorHandlingTests -{ - [Fact] - public void JsonSerializer_WithCorruptedKeyData_ThrowSerializationException() - { - // Arrange - var serializer = new PowertoolsKafkaJsonSerializer(); - var corruptedData = new byte[] { 0xDE, 0xAD, 0xBE, 0xEF }; - - string kafkaEventJson = CreateKafkaEvent( - Convert.ToBase64String(corruptedData), - Convert.ToBase64String(Encoding.UTF8.GetBytes("valid-value")) - ); - - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); - - // Act & Assert - var ex = Assert.Throws(() => - serializer.Deserialize>(stream)); - - Assert.Contains("Failed to deserialize key data", ex.Message); - } - - [Fact] - public void JsonSerializer_WithCorruptedValueData_ThrowSerializationException() - { - // Arrange - var serializer = new PowertoolsKafkaJsonSerializer(); - var corruptedData = new byte[] { 0xDE, 0xAD, 0xBE, 0xEF }; - - string kafkaEventJson = CreateKafkaEvent( - Convert.ToBase64String(Encoding.UTF8.GetBytes("valid-key")), - Convert.ToBase64String(corruptedData) - ); - - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); - - // Act & Assert - var ex = Assert.Throws(() => - serializer.Deserialize>(stream)); - - Assert.Contains("Failed to deserialize value data", ex.Message); - } - - private string CreateKafkaEvent(string keyValue, string valueValue) - { - return @$"{{ - ""eventSource"": ""aws:kafka"", - ""records"": {{ - ""mytopic-0"": [ - {{ - ""topic"": ""mytopic"", - ""partition"": 0, - ""offset"": 15, - ""key"": ""{keyValue}"", - ""value"": ""{valueValue}"" - }} - ] - }} - }}"; - } -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/JsonTests.cs b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/JsonTests.cs deleted file mode 100644 index 4c719100c..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/JsonTests.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System.Text; -using Amazon.Lambda.Core; -using Amazon.Lambda.TestUtilities; -using AWS.Lambda.Powertools.Kafka.Json; - -#if DEBUG -using KafkaAlias = AWS.Lambda.Powertools.Kafka; -#else -using KafkaAlias = AWS.Lambda.Powertools.Kafka.Json; -#endif - -namespace AWS.Lambda.Powertools.Kafka.Tests; - -public class JsonTests -{ - [Fact] - public void Given_JsonStreamInput_When_DeserializedWithJsonSerializer_Then_CorrectlyDeserializes() - { - // Given - var serializer = new PowertoolsKafkaJsonSerializer(); - string json = @"{ - ""eventSource"": ""aws:kafka"", - ""records"": { - ""mytopic-0"": [ - { - ""topic"": ""mytopic"", - ""partition"": 0, - ""offset"": 15, - ""timestamp"": 1645084650987, - ""key"": """ + Convert.ToBase64String(Encoding.UTF8.GetBytes("key1")) + @""", - ""value"": """ + Convert.ToBase64String(Encoding.UTF8.GetBytes("{\"Name\":\"JSON Test\",\"Price\":199.99,\"Id\":456}")) + @""" - } - ] - } - }"; - - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); - - // When - var result = serializer.Deserialize>(stream); - - // Then - Assert.Equal("aws:kafka", result.EventSource); - Assert.Single(result.Records); - var record = result.First(); - Assert.Equal("key1", record.Key); - Assert.Equal("JSON Test", record.Value.Name); - Assert.Equal(199.99m, record.Value.Price); - Assert.Equal(456, record.Value.Id); - } - - [Fact] - public void Given_RawUtf8Data_When_ProcessedWithDefaultHandler_Then_DeserializesToStrings() - { - // Given - string Handler(KafkaAlias.ConsumerRecords records, ILambdaContext context) - { - foreach (var record in records) - { - context.Logger.LogInformation($"Key: {record.Key}, Value: {record.Value}"); - } - return "Processed raw data"; - } - - var mockLogger = new TestLambdaLogger(); - var mockContext = new TestLambdaContext { Logger = mockLogger }; - - // Create Kafka event with raw base64-encoded strings - string kafkaEventJson = @$"{{ - ""eventSource"": ""aws:kafka"", - ""records"": {{ - ""mytopic-0"": [ - {{ - ""topic"": ""mytopic"", - ""partition"": 0, - ""offset"": 15, - ""key"": ""{Convert.ToBase64String(Encoding.UTF8.GetBytes("simple-key"))}"", - ""value"": ""{Convert.ToBase64String(Encoding.UTF8.GetBytes("Simple UTF-8 text value"))}"", - ""headers"": [ - {{ ""content-type"": [{(int)'t'}, {(int)'e'}, {(int)'x'}, {(int)'t'}] }} - ] - }} - ] - }} - }}"; - - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); - - // Use the default serializer which handles base64 → UTF-8 conversion - var serializer = new PowertoolsKafkaJsonSerializer(); - var records = serializer.Deserialize>(stream); - - // When - var result = Handler(records, mockContext); - - // Then - Assert.Equal("Processed raw data", result); - Assert.Contains("Key: simple-key, Value: Simple UTF-8 text value", mockLogger.Buffer.ToString()); - } -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/KafkaHandlerFunctionalTests.cs b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/KafkaHandlerFunctionalTests.cs deleted file mode 100644 index 9d3602848..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/KafkaHandlerFunctionalTests.cs +++ /dev/null @@ -1,418 +0,0 @@ -using System.Runtime.Serialization; -using System.Text; -using Amazon.Lambda.Core; -using Amazon.Lambda.TestUtilities; -using AWS.Lambda.Powertools.Kafka.Json; -using TestKafka; - -#if DEBUG -using KafkaAvro = AWS.Lambda.Powertools.Kafka; -using KafkaProto = AWS.Lambda.Powertools.Kafka; -using KafkaJson = AWS.Lambda.Powertools.Kafka; -#else -using KafkaAvro = AWS.Lambda.Powertools.Kafka.Avro; -using KafkaProto = AWS.Lambda.Powertools.Kafka.Protobuf; -using KafkaJson = AWS.Lambda.Powertools.Kafka.Json; -#endif - -namespace AWS.Lambda.Powertools.Kafka.Tests; - -public class KafkaHandlerFunctionalTests -{ - #region JSON Serializer Tests - - [Fact] - public void Given_SingleJsonRecord_When_ProcessedWithHandler_Then_SuccessfullyDeserializedAndProcessed() - { - // Given - string Handler(KafkaJson.ConsumerRecords records, ILambdaContext context) - { - foreach (var record in records) - { - context.Logger.LogInformation($"Processing {record.Value.Name} at ${record.Value.Price}"); - } - return "Successfully processed JSON Kafka events"; - } - - var mockLogger = new TestLambdaLogger(); - var mockContext = new TestLambdaContext { Logger = mockLogger }; - - // Create a single record - var records = new KafkaJson.ConsumerRecords - { - Records = new Dictionary>> - { - { "mytopic-0", new List> - { - new() - { - Topic = "mytopic", - Partition = 0, - Offset = 15, - Timestamp = 1645084650987, - TimestampType = "CREATE_TIME", - Key = "product-123", - Value = new JsonProduct { Name = "Laptop", Price = 999.99m, Id = 123 }, - Headers = new Dictionary - { - { "source", Encoding.UTF8.GetBytes("online-store") } - } - } - } - } - } - }; - - // When - var result = Handler(records, mockContext); - - // Then - Assert.Equal("Successfully processed JSON Kafka events", result); - Assert.Contains("Processing Laptop at $999.99", mockLogger.Buffer.ToString()); - } - - [Fact] - public void Given_MultipleJsonRecords_When_ProcessedWithHandler_Then_AllRecordsProcessed() - { - // Given - int processedCount = 0; - string Handler(KafkaJson.ConsumerRecords records, ILambdaContext context) - { - foreach (var record in records) - { - context.Logger.LogInformation($"Processing {record.Value.Name}"); - processedCount++; - } - return $"Processed {processedCount} records"; - } - - var mockLogger = new TestLambdaLogger(); - var mockContext = new TestLambdaContext { Logger = mockLogger }; - - // Create multiple records - var records = new KafkaJson.ConsumerRecords - { - Records = new Dictionary>> - { - { "mytopic-0", new List> - { - new() { Topic = "mytopic", Value = new JsonProduct { Name = "Laptop" } }, - new() { Topic = "mytopic", Value = new JsonProduct { Name = "Phone" } }, - new() { Topic = "mytopic", Value = new JsonProduct { Name = "Tablet" } } - } - } - } - }; - - // When - var result = Handler(records, mockContext); - - // Then - Assert.Equal("Processed 3 records", result); - Assert.Contains("Processing Laptop", mockLogger.Buffer.ToString()); - Assert.Contains("Processing Phone", mockLogger.Buffer.ToString()); - Assert.Contains("Processing Tablet", mockLogger.Buffer.ToString()); - } - - [Fact] - public void Given_JsonRecordWithMetadata_When_ProcessedWithHandler_Then_MetadataIsAccessible() - { - // Given - string Handler(KafkaJson.ConsumerRecords records, ILambdaContext context) - { - var record = records.First(); - context.Logger.LogInformation($"Topic: {record.Topic}, Partition: {record.Partition}, Offset: {record.Offset}, Time: {record.Timestamp}"); - return "Metadata accessed"; - } - - var mockLogger = new TestLambdaLogger(); - var mockContext = new TestLambdaContext { Logger = mockLogger }; - - var records = new KafkaJson.ConsumerRecords - { - Records = new Dictionary>> - { - { "mytopic-0", new List> - { - new() - { - Topic = "sales-data", - Partition = 3, - Offset = 42, - Timestamp = 1645084650987, - TimestampType = "CREATE_TIME", - Value = new JsonProduct { Name = "Metadata Test" } - } - } - } - } - }; - - // When - var result = Handler(records, mockContext); - - // Then - Assert.Equal("Metadata accessed", result); - Assert.Contains("Topic: sales-data, Partition: 3, Offset: 42", mockLogger.Buffer.ToString()); - } - - [Fact] - public void Given_JsonRecordWithHeaders_When_ProcessedWithHandler_Then_HeadersAreAccessible() - { - // Given - string Handler(KafkaJson.ConsumerRecords records, ILambdaContext context) - { - var record = records.First(); - var source = record.Headers["source"].DecodedValue(); - var contentType = record.Headers["content-type"].DecodedValue(); - context.Logger.LogInformation($"Headers: source={source}, content-type={contentType}"); - return "Headers processed"; - } - - var mockLogger = new TestLambdaLogger(); - var mockContext = new TestLambdaContext { Logger = mockLogger }; - - var records = new KafkaJson.ConsumerRecords - { - Records = new Dictionary>> - { - { "mytopic-0", new List> - { - new() - { - Value = new JsonProduct { Name = "Header Test" }, - Headers = new Dictionary - { - { "source", Encoding.UTF8.GetBytes("web-app") }, - { "content-type", Encoding.UTF8.GetBytes("application/json") } - } - } - } - } - } - }; - - // When - var result = Handler(records, mockContext); - - // Then - Assert.Equal("Headers processed", result); - Assert.Contains("Headers: source=web-app, content-type=application/json", mockLogger.Buffer.ToString()); - } - - #endregion - - #region Avro Serializer Tests - - [Fact] - public void Given_SingleAvroRecord_When_ProcessedWithHandler_Then_SuccessfullyDeserializedAndProcessed() - { - // Given - string Handler(KafkaAvro.ConsumerRecords records, ILambdaContext context) - { - foreach (var record in records) - { - context.Logger.LogInformation($"Processing {record.Value.name} at ${record.Value.price}"); - } - return "Successfully processed Avro Kafka events"; - } - - var mockLogger = new TestLambdaLogger(); - var mockContext = new TestLambdaContext { Logger = mockLogger }; - - // Create a single record - var records = new KafkaAvro.ConsumerRecords - { - Records = new Dictionary>> - { - { "mytopic-0", new List> - { - new() - { - Topic = "mytopic", - Partition = 0, - Offset = 15, - Key = "avro-key", - Value = new AvroProduct { name = "Camera", price = 349.95 } - } - } - } - } - }; - - // When - var result = Handler(records, mockContext); - - // Then - Assert.Equal("Successfully processed Avro Kafka events", result); - Assert.Contains("Processing Camera at $349.95", mockLogger.Buffer.ToString()); - } - - [Fact] - public void Given_ComplexAvroKey_When_ProcessedWithHandler_Then_KeyIsCorrectlyDeserialized() - { - // Given - string Handler(KafkaAvro.ConsumerRecords records, ILambdaContext context) - { - var record = records.First(); - context.Logger.LogInformation($"Processing product with key ID: {record.Key.id}, color: {record.Key.color}"); - return "Successfully processed complex keys"; - } - - var mockLogger = new TestLambdaLogger(); - var mockContext = new TestLambdaContext { Logger = mockLogger }; - - var records = new KafkaAvro.ConsumerRecords - { - Records = new Dictionary>> - { - { "mytopic-0", new List> - { - new() - { - Key = new AvroKey { id = 42, color = Color.GREEN }, - Value = new AvroProduct { name = "Green Item", price = 49.99 } - } - } - } - } - }; - - // When - var result = Handler(records, mockContext); - - // Then - Assert.Equal("Successfully processed complex keys", result); - Assert.Contains("Processing product with key ID: 42, color: GREEN", mockLogger.Buffer.ToString()); - } - - [Fact] - public void Given_MissingAvroSchema_When_DeserializedWithAvroSerializer_Then_ReturnsException() - { - // Arrange - var serializer = new AWS.Lambda.Powertools.Kafka.Avro.PowertoolsKafkaAvroSerializer(); - - // Create data that looks like Avro but without schema - byte[] invalidAvroData = { 0x01, 0x02, 0x03, 0x04 }; // Just some random bytes - string base64Data = Convert.ToBase64String(invalidAvroData); - - string kafkaEventJson = @$"{{ - ""eventSource"": ""aws:kafka"", - ""records"": {{ - ""mytopic-0"": [ - {{ - ""topic"": ""mytopic"", - ""partition"": 0, - ""offset"": 15, - ""key"": ""{Convert.ToBase64String(Encoding.UTF8.GetBytes("test-key"))}"", - ""value"": ""{base64Data}"" - }} - ] - }} - }}"; - - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); - Assert.Throws(() => - serializer.Deserialize>(stream)); - } - - #endregion - - #region Protobuf Serializer Tests - - [Fact] - public void Given_SingleProtobufRecord_When_ProcessedWithHandler_Then_SuccessfullyDeserializedAndProcessed() - { - // Given - string Handler(KafkaProto.ConsumerRecords records, ILambdaContext context) - { - foreach (var record in records) - { - context.Logger.LogInformation($"Processing {record.Value.Name} at ${record.Value.Price}"); - } - return "Successfully processed Protobuf Kafka events"; - } - - var mockLogger = new TestLambdaLogger(); - var mockContext = new TestLambdaContext { Logger = mockLogger }; - - // Create a single record - var records = new KafkaProto.ConsumerRecords - { - Records = new Dictionary>> - { - { "mytopic-0", new List> - { - new() - { - Topic = "mytopic", - Partition = 0, - Offset = 15, - Key = 42, - Value = new ProtobufProduct { Name = "Smart Watch", Id = 789, Price = 249.99 } - } - } - } - } - }; - - // When - var result = Handler(records, mockContext); - - // Then - Assert.Equal("Successfully processed Protobuf Kafka events", result); - Assert.Contains("Processing Smart Watch at $249.99", mockLogger.Buffer.ToString()); - } - - [Fact] - public void Given_NullKeyOrValue_When_ProcessedWithHandler_Then_HandlesNullsCorrectly() - { - // Given - string Handler(KafkaProto.ConsumerRecords records, ILambdaContext context) - { - foreach (var record in records) - { - string keyInfo = record.Key.HasValue ? record.Key.Value.ToString() : "null"; - string valueInfo = record.Value != null ? record.Value.Name : "null"; - context.Logger.LogInformation($"Key: {keyInfo}, Value: {valueInfo}"); - } - return "Processed records with nulls"; - } - - var mockLogger = new TestLambdaLogger(); - var mockContext = new TestLambdaContext { Logger = mockLogger }; - - var records = new KafkaProto.ConsumerRecords - { - Records = new Dictionary>> - { - { "mytopic-0", new List> - { - new() { Key = 1, Value = new ProtobufProduct { Name = "Valid Product" } }, - new() { Key = null, Value = new ProtobufProduct { Name = "No Key" } }, - new() { Key = 3, Value = null } - } - } - } - }; - - // When - var result = Handler(records, mockContext); - - // Then - Assert.Equal("Processed records with nulls", result); - Assert.Contains("Key: 1, Value: Valid Product", mockLogger.Buffer.ToString()); - Assert.Contains("Key: null, Value: No Key", mockLogger.Buffer.ToString()); - Assert.Contains("Key: 3, Value: null", mockLogger.Buffer.ToString()); - } - - #endregion -} - -// Model classes for testing -public class JsonProduct -{ - public string Name { get; set; } - public int Id { get; set; } - public decimal Price { get; set; } -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/PowertoolsKafkaSerializerBaseTests.cs b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/PowertoolsKafkaSerializerBaseTests.cs deleted file mode 100644 index b8832b0d0..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/PowertoolsKafkaSerializerBaseTests.cs +++ /dev/null @@ -1,751 +0,0 @@ -using System.Runtime.Serialization; -using System.Text; -using System.Text.Json; -using System.Text.Json.Serialization; -using AWS.Lambda.Powertools.Kafka.Avro; - -namespace AWS.Lambda.Powertools.Kafka.Tests -{ - /// - /// Additional tests for PowertoolsKafkaSerializerBase - /// - public class PowertoolsKafkaSerializerBaseTests - { - /// - /// Simple serializer implementation for testing base class - /// - private class TestKafkaSerializer : PowertoolsKafkaSerializerBase - { - public TestKafkaSerializer() : base() - { - } - - public TestKafkaSerializer(JsonSerializerOptions options) : base(options) - { - } - - public TestKafkaSerializer(JsonSerializerContext context) : base(context) - { - } - - public TestKafkaSerializer(JsonSerializerOptions options, JsonSerializerContext context) - : base(options, context) - { - } - - // Implementation of the abstract method for test purposes - protected override object? DeserializeComplexTypeFormat(byte[] data, - Type targetType, bool isKey, SchemaMetadata? schemaMetadata = null) - { - // Test implementation using JSON for all complex types - var jsonStr = Encoding.UTF8.GetString(data); - - if (SerializerContext != null) - { - var typeInfo = SerializerContext.GetTypeInfo(targetType); - if (typeInfo != null) - { - return JsonSerializer.Deserialize(jsonStr, typeInfo); - } - } - - return JsonSerializer.Deserialize(jsonStr, targetType, JsonOptions); - } - - // Expose protected methods for direct testing - public object? TestDeserializeFormatSpecific(byte[] data, Type targetType, bool isKey, - SchemaMetadata? schemaMetadata = null) - { - return DeserializeFormatSpecific(data, targetType, isKey, schemaMetadata); - } - - public object? TestDeserializeComplexTypeFormat(byte[] data, Type targetType, bool isKey, - SchemaMetadata? schemaMetadata = null) - { - return DeserializeComplexTypeFormat(data, targetType, isKey, schemaMetadata); - } - - public object? TestDeserializePrimitiveValue(byte[] data, Type targetType) - { - return DeserializePrimitiveValue(data, targetType); - } - - public bool TestIsPrimitiveOrSimpleType(Type type) - { - return IsPrimitiveOrSimpleType(type); - } - - public object TestDeserializeValue(string base64Value, Type valueType, - SchemaMetadata? schemaMetadata = null) - { - return DeserializeValue(base64Value, valueType, schemaMetadata); - } - } - - [Fact] - public void Deserialize_BooleanValues_HandlesCorrectly() - { - // Arrange - var serializer = new TestKafkaSerializer(); - string kafkaEventJson = CreateKafkaEvent( - keyValue: "dHJ1ZQ==", // "true" in base64 - valueValue: "AQ==" // byte[1] = {1} in base64 - ); - - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); - - // Act - var result = serializer.Deserialize>(stream); - - // Assert - Assert.NotNull(result); - var firstRecord = result.First(); - Assert.Equal("true", firstRecord.Key); - Assert.True(firstRecord.Value); - } - - [Fact] - public void Deserialize_NumericValues_HandlesCorrectly() - { - // Arrange - var serializer = new TestKafkaSerializer(); - string kafkaEventJson = CreateKafkaEvent( - keyValue: "NDI=", // "42" in base64 - valueValue: "MTIzNA==" // "1234" in base64 - ); - - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); - - // Act - var result = serializer.Deserialize>(stream); - - // Assert - Assert.NotNull(result); - var firstRecord = result.First(); - Assert.Equal(42, firstRecord.Key); - Assert.Equal(1234, firstRecord.Value); - } - - [Fact] - public void Deserialize_GuidValues_HandlesCorrectly() - { - // Arrange - var guid = Guid.NewGuid(); - var serializer = new TestKafkaSerializer(); - string kafkaEventJson = CreateKafkaEvent( - keyValue: Convert.ToBase64String(guid.ToByteArray()), - valueValue: Convert.ToBase64String(Encoding.UTF8.GetBytes(guid.ToString())) - ); - - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); - - // Act - var result = serializer.Deserialize>(stream); - - // Assert - Assert.NotNull(result); - var firstRecord = result.First(); - Assert.Equal(guid, firstRecord.Key); - Assert.Equal(guid.ToString(), firstRecord.Value); - } - - [Fact] - public void Deserialize_InvalidJson_ThrowsException() - { - // Arrange - var serializer = new TestKafkaSerializer(); - string invalidJson = "{ this is not valid json }"; - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(invalidJson)); - - // Act & Assert - Assert.ThrowsAny(() => - serializer.Deserialize>(stream)); - } - - [Fact] - public void Deserialize_MalformedBase64_ThrowsException() - { - // Arrange - var serializer = new TestKafkaSerializer(); - string kafkaEventJson = CreateKafkaEvent( - keyValue: "not-base64!", - valueValue: "valid-base64==" - ); - - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); - - // Act & Assert - var ex = Assert.Throws(() => - serializer.Deserialize>(stream)); - - Assert.Contains("Failed to deserialize key data", ex.Message); - } - - [Fact] - public void Serialize_ValidObject_WritesToStream() - { - // Arrange - var serializer = new TestKafkaSerializer(); - var testObject = new { Name = "Test", Value = 42 }; - using var responseStream = new MemoryStream(); - - // Act - serializer.Serialize(testObject, responseStream); - responseStream.Position = 0; - string result = Encoding.UTF8.GetString(responseStream.ToArray()); - - // Assert - Assert.Contains("\"Name\":\"Test\"", result); - Assert.Contains("\"Value\":42", result); - } - - [Fact] - public void Serialize_NullObject_WritesNullToStream() - { - // Arrange - var serializer = new TestKafkaSerializer(); - using var responseStream = new MemoryStream(); - - // Act - serializer.Serialize(null, responseStream); - responseStream.Position = 0; - string result = Encoding.UTF8.GetString(responseStream.ToArray()); - - // Assert - Assert.Equal("null", result); - } - - [Fact] - public void DeserializePrimitiveValue_EmptyBytes_ReturnsNull() - { - // Arrange - var serializer = new TestKafkaSerializer(); - - // Act - var result = serializer.TestDeserializePrimitiveValue(Array.Empty(), typeof(string)); - - // Assert - Assert.Null(result); - } - - [Fact] - public void DeserializePrimitiveValue_LongValue_DeserializesCorrectly() - { - // Arrange - var serializer = new TestKafkaSerializer(); - var longBytes = BitConverter.GetBytes(long.MaxValue); - - // Act - var result = serializer.TestDeserializePrimitiveValue(longBytes, typeof(long)); - - // Assert - Assert.Equal(long.MaxValue, result); - } - - [Fact] - public void DeserializePrimitiveValue_DoubleValue_DeserializesCorrectly() - { - // Arrange - var serializer = new TestKafkaSerializer(); - var doubleBytes = BitConverter.GetBytes(3.14159); - - // Act - var result = serializer.TestDeserializePrimitiveValue(doubleBytes, typeof(double)); - - // Assert - Assert.Equal(3.14159, result); - } - - [Fact] - public void ProcessHeaders_MultipleHeaders_DeserializesCorrectly() - { - // Arrange - var serializer = new TestKafkaSerializer(); - string kafkaEventJson = @$"{{ - ""eventSource"": ""aws:kafka"", - ""records"": {{ - ""mytopic-0"": [ - {{ - ""topic"": ""mytopic"", - ""partition"": 0, - ""offset"": 15, - ""key"": ""{Convert.ToBase64String(Encoding.UTF8.GetBytes("key"))}"", - ""value"": ""{Convert.ToBase64String(Encoding.UTF8.GetBytes("value"))}"", - ""headers"": [ - {{ ""header1"": [104, 101, 108, 108, 111] }}, - {{ ""header2"": [119, 111, 114, 108, 100] }} - ] - }} - ] - }} - }}"; - - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); - - // Act - var result = serializer.Deserialize>(stream); - - // Assert - var record = result.First(); - Assert.Equal(2, record.Headers.Count); - Assert.Equal("hello", Encoding.ASCII.GetString(record.Headers["header1"])); - Assert.Equal("world", Encoding.ASCII.GetString(record.Headers["header2"])); - } - - [Fact] - public void Deserialize_WithSerializerContext_UsesContextForRegisteredTypes() - { - // Arrange - var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; - var context = new TestSerializerContext(options); - // Use only options for constructor, but we'll make the context available for the model deserialization - var serializer = new TestKafkaSerializer(options); - - var testModel = new TestModel { Name = "Test", Value = 123 }; - var modelJson = JsonSerializer.Serialize(testModel, context.TestModel); - var base64Value = Convert.ToBase64String(Encoding.UTF8.GetBytes(modelJson)); - - string kafkaEventJson = CreateKafkaEvent( - keyValue: Convert.ToBase64String(Encoding.UTF8.GetBytes("testKey")), - valueValue: base64Value - ); - - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); - - // Act - var result = serializer.Deserialize>(stream); - - // Assert - Assert.NotNull(result); - var record = result.First(); - Assert.Equal("testKey", record.Key); - Assert.Equal("Test", record.Value.Name); - Assert.Equal(123, record.Value.Value); - } - - [Fact] - public void Serialize_WithSerializerContext_UsesContextForRegisteredTypes() - { - // Arrange - var options = new JsonSerializerOptions(); - var context = new TestSerializerContext(options); - var serializer = new TestKafkaSerializer(options, context); - - var testModel = new TestModel { Name = "Test", Value = 123 }; - using var responseStream = new MemoryStream(); - - // Act - serializer.Serialize(testModel, responseStream); - responseStream.Position = 0; - string result = Encoding.UTF8.GetString(responseStream.ToArray()); - - // Assert - Assert.Contains("\"Name\":\"Test\"", result); - Assert.Contains("\"Value\":123", result); - } - - [Fact] - public void Deserialize_WithSerializerContext_FallsBackWhenTypeNotRegistered() - { - // Arrange - var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; - var context = new TestSerializerContext(options); - var serializer = new TestKafkaSerializer(options, context); - - // Using a non-registered type (Dictionary instead of TestModel) - var dictionary = new Dictionary { ["Key"] = 42 }; - var dictJson = JsonSerializer.Serialize(dictionary); - var base64Value = Convert.ToBase64String(Encoding.UTF8.GetBytes(dictJson)); - - string kafkaEventJson = CreateKafkaEvent( - keyValue: Convert.ToBase64String(Encoding.UTF8.GetBytes("testKey")), - valueValue: base64Value - ); - - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); - - // Act - var result = serializer.Deserialize>>(stream); - - // Assert - Assert.NotNull(result); - var record = result.First(); - Assert.Equal("testKey", record.Key); - Assert.Single(record.Value); - Assert.Equal(42, record.Value["Key"]); - } - - [Fact] - public void Serialize_NonRegisteredType_FallsBackToRegularSerialization() - { - // Arrange - var options = new JsonSerializerOptions(); - // Use serializer WITHOUT context to test the fallback path - var serializer = new TestKafkaSerializer(options); - - // Using a non-registered type - var nonRegisteredType = new { Id = Guid.NewGuid(), Message = "Not in context" }; - using var responseStream = new MemoryStream(); - - // Act - serializer.Serialize(nonRegisteredType, responseStream); - responseStream.Position = 0; - string result = Encoding.UTF8.GetString(responseStream.ToArray()); - - // Assert - Assert.Contains("\"Id\":", result); - Assert.Contains("\"Message\":\"Not in context\"", result); - } - - [Fact] - public void Deserialize_NonConsumerRecordWithSerializerContext_UsesTypeInfo() - { - // Arrange - var options = new JsonSerializerOptions(); - var context = new TestSerializerContext(options); - var serializer = new TestKafkaSerializer(options, context); - - var testModel = new TestModel { Name = "DirectDeserialization", Value = 42 }; - var json = JsonSerializer.Serialize(testModel); - - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); - - // Act - var result = serializer.Deserialize(stream); - - // Assert - Assert.NotNull(result); - Assert.Equal("DirectDeserialization", result.Name); - Assert.Equal(42, result.Value); - } - - [Fact] - public void Deserialize_NonConsumerRecordWithoutTypeInfo_UsesRegularDeserialize() - { - // Arrange - var options = new JsonSerializerOptions(); - var context = new TestSerializerContext(options); - var serializer = new TestKafkaSerializer(options, context); - - // Dictionary is not registered in TestSerializerContext - var dict = new Dictionary { ["test"] = 123 }; - var json = JsonSerializer.Serialize(dict); - - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); - - // Act - var result = serializer.Deserialize>(stream); - - // Assert - Assert.NotNull(result); - Assert.Equal(123, result["test"]); - } - - [Fact] - public void Deserialize_NonConsumerRecordFailed_ThrowsException() - { - // Arrange - var serializer = new TestKafkaSerializer(); - var invalidJson = "{ invalid json"; - - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(invalidJson)); - - // Act & Assert - // With invalid JSON input, JsonSerializer throws JsonException directly - var ex = Assert.Throws(() => - serializer.Deserialize(stream)); - - // Check that we're getting a JSON parsing error - Assert.Contains("invalid", ex.Message.ToLower()); - } - - [Theory] - [InlineData(new byte[] { 42 }, 42)] // Single byte - [InlineData(new byte[] { 0x2A, 0x00, 0x00, 0x00 }, 42)] // Four bytes - public void DeserializePrimitiveValue_IntWithDifferentByteFormats_DeserializesCorrectly(byte[] bytes, - int expected) - { - // Arrange - var serializer = new TestKafkaSerializer(); - - // Act - var result = serializer.TestDeserializePrimitiveValue(bytes, typeof(int)); - - // Assert - Assert.Equal(expected, result); - } - - [Theory] - [InlineData(new byte[] { 0x2A, 0x00, 0x00, 0x00 }, 42L)] // Four bytes as int - [InlineData(new byte[] { 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 42L)] // Eight bytes as long - public void DeserializePrimitiveValue_LongWithDifferentByteFormats_DeserializesCorrectly(byte[] bytes, - long expected) - { - // Arrange - var serializer = new TestKafkaSerializer(); - - // Act - var result = serializer.TestDeserializePrimitiveValue(bytes, typeof(long)); - - // Assert - Assert.Equal(expected, result); - } - - [Fact] - public void DeserializePrimitiveValue_DoubleWithShortBytes_ReturnsZero() - { - // Arrange - var serializer = new TestKafkaSerializer(); - var shortBytes = new byte[] { 0x00, 0x00, 0x00, 0x00 }; // Less than 8 bytes - - // Act - var result = serializer.TestDeserializePrimitiveValue(shortBytes, typeof(double)); - - // Assert - Assert.Equal(0.0, result); - } - - [Fact] - public void Serialize_WithTypeInfoFromContext_WritesToStream() - { - // Arrange - var options = new JsonSerializerOptions(); - var context = new TestSerializerContext(options); - var serializer = new TestKafkaSerializer(options, context); - - var testModel = new TestModel { Name = "ContextSerialization", Value = 555 }; - using var responseStream = new MemoryStream(); - - // Act - serializer.Serialize(testModel, responseStream); - responseStream.Position = 0; - string result = Encoding.UTF8.GetString(responseStream.ToArray()); - - // Assert - Assert.Contains("\"Name\":\"ContextSerialization\"", result); - Assert.Contains("\"Value\":555", result); - } - - [Fact] - public void Deserialize_WithSchemaMetadata_PopulatesSchemaMetadataProperties() - { - // Arrange - var serializer = new TestKafkaSerializer(); - - string kafkaEventJson = @$"{{ - ""eventSource"": ""aws:kafka"", - ""eventSourceArn"": ""arn:aws:kafka:us-east-1:0123456789019:cluster/TestCluster/abcd1234"", - ""bootstrapServers"": ""b-1.test-cluster.kafka.us-east-1.amazonaws.com:9092"", - ""records"": {{ - ""mytopic-0"": [ - {{ - ""topic"": ""mytopic"", - ""partition"": 0, - ""offset"": 15, - ""timestamp"": 1645084650987, - ""timestampType"": ""CREATE_TIME"", - ""key"": ""{Convert.ToBase64String(Encoding.UTF8.GetBytes("testKey"))}"", - ""value"": ""{Convert.ToBase64String(Encoding.UTF8.GetBytes("testValue"))}"", - ""headers"": [ - {{ ""headerKey"": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] }} - ], - ""keySchemaMetadata"": {{ - ""dataFormat"": ""JSON"", - ""schemaId"": ""key-schema-001"" - }}, - ""valueSchemaMetadata"": {{ - ""dataFormat"": ""AVRO"", - ""schemaId"": ""value-schema-002"" - }} - }} - ] - }} - }}"; - - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); - - // Act - var result = serializer.Deserialize>(stream); - - // Assert - Assert.NotNull(result); - var record = result.First(); - - // Assert key schema metadata - Assert.NotNull(record.KeySchemaMetadata); - Assert.Equal("JSON", record.KeySchemaMetadata.DataFormat); - Assert.Equal("key-schema-001", record.KeySchemaMetadata.SchemaId); - - // Assert value schema metadata - Assert.NotNull(record.ValueSchemaMetadata); - Assert.Equal("AVRO", record.ValueSchemaMetadata.DataFormat); - Assert.Equal("value-schema-002", record.ValueSchemaMetadata.SchemaId); - } - - // NEW TESTS FOR LATEST CHANGES - - [Fact] - public void DeserializeFormatSpecific_PrimitiveType_UsesDeserializePrimitiveValue() - { - // Arrange - var serializer = new TestKafkaSerializer(); - var stringBytes = Encoding.UTF8.GetBytes("primitive-test"); - - // Act - var result = - serializer.TestDeserializeFormatSpecific(stringBytes, typeof(string), isKey: false, - schemaMetadata: null); - - // Assert - Assert.Equal("primitive-test", result); - } - - [Fact] - public void DeserializeFormatSpecific_ComplexType_UsesDeserializeComplexTypeFormat() - { - // Arrange - var serializer = new TestKafkaSerializer(); - var complexObject = new TestModel { Name = "complex-test", Value = 42 }; - var jsonBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(complexObject)); - - // Act - var result = - serializer.TestDeserializeFormatSpecific(jsonBytes, typeof(TestModel), isKey: false, - schemaMetadata: null); - - // Assert - Assert.NotNull(result); - var testModel = (TestModel)result!; - Assert.Equal("complex-test", testModel.Name); - Assert.Equal(42, testModel.Value); - } - - [Fact] - public void DeserializeComplexTypeFormat_ValidJson_DeserializesCorrectly() - { - // Arrange - var serializer = new TestKafkaSerializer(); - var complexObject = new TestModel { Name = "direct-test", Value = 123 }; - var jsonBytes = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(complexObject)); - - // Act - var result = - serializer.TestDeserializeComplexTypeFormat(jsonBytes, typeof(TestModel), isKey: true, - schemaMetadata: null); - - // Assert - Assert.NotNull(result); - var testModel = (TestModel)result!; - Assert.Equal("direct-test", testModel.Name); - Assert.Equal(123, testModel.Value); - } - - [Fact] - public void DeserializeComplexTypeFormat_InvalidJson_ThrowsException() - { - // Arrange - var serializer = new TestKafkaSerializer(); - var invalidBytes = new byte[] { 0xDE, 0xAD, 0xBE, 0xEF }; // Invalid JSON data - - // Act & Assert - // The TestKafkaSerializer throws JsonException directly for invalid JSON - var ex = Assert.Throws(() => - serializer.TestDeserializeComplexTypeFormat(invalidBytes, typeof(TestModel), isKey: true, - schemaMetadata: null)); - - Assert.Contains("invalid", ex.Message.ToLower()); - } - - [Fact] - public void DeserializeValue_Base64String_DeserializesCorrectly() - { - // Arrange - var serializer = new TestKafkaSerializer(); - var testValue = "test-value-123"; - var base64Value = Convert.ToBase64String(Encoding.UTF8.GetBytes(testValue)); - - // Act - var result = serializer.TestDeserializeValue(base64Value, typeof(string), schemaMetadata: null); - - // Assert - Assert.Equal(testValue, result); - } - - [Fact] - public void DeserializeValue_WithSchemaMetadata_PassesMetadataToFormatSpecific() - { - // Arrange - var serializer = new TestKafkaSerializer(); - var testValue = "test-value-with-metadata"; - var base64Value = Convert.ToBase64String(Encoding.UTF8.GetBytes(testValue)); - var schemaMetadata = new SchemaMetadata { DataFormat = "JSON", SchemaId = "test-schema-001" }; - - // Act - var result = serializer.TestDeserializeValue(base64Value, typeof(string), schemaMetadata); - - // Assert - Assert.Equal(testValue, result); - } - - [Fact] - public void IsPrimitiveOrSimpleType_ChecksVariousTypes() - { - // Arrange - var serializer = new TestKafkaSerializer(); - - // Act & Assert - // Primitive types - Assert.True(serializer.TestIsPrimitiveOrSimpleType(typeof(int))); - Assert.True(serializer.TestIsPrimitiveOrSimpleType(typeof(long))); - Assert.True(serializer.TestIsPrimitiveOrSimpleType(typeof(bool))); - - // Simple types - Assert.True(serializer.TestIsPrimitiveOrSimpleType(typeof(string))); - Assert.True(serializer.TestIsPrimitiveOrSimpleType(typeof(Guid))); - Assert.True(serializer.TestIsPrimitiveOrSimpleType(typeof(DateTime))); - - // Complex types - Assert.False(serializer.TestIsPrimitiveOrSimpleType(typeof(TestModel))); - Assert.False(serializer.TestIsPrimitiveOrSimpleType(typeof(Dictionary))); - } - - // Helper method to create Kafka event JSON with specified key and value - private string CreateKafkaEvent(string keyValue, string valueValue) - { - return @$"{{ - ""eventSource"": ""aws:kafka"", - ""eventSourceArn"": ""arn:aws:kafka:us-east-1:0123456789019:cluster/TestCluster/abcd1234"", - ""bootstrapServers"": ""b-1.test-cluster.kafka.us-east-1.amazonaws.com:9092"", - ""records"": {{ - ""mytopic-0"": [ - {{ - ""topic"": ""mytopic"", - ""partition"": 0, - ""offset"": 15, - ""timestamp"": 1645084650987, - ""timestampType"": ""CREATE_TIME"", - ""key"": ""{keyValue}"", - ""value"": ""{valueValue}"", - ""headers"": [ - {{ ""headerKey"": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] }} - ] - }} - ] - }} - }}"; - } - } - - [JsonSerializable(typeof(TestModel))] - [JsonSerializable(typeof(ConsumerRecords))] - [JsonSerializable(typeof(Dictionary))] - public partial class TestSerializerContext : JsonSerializerContext - { - } - - public class TestModel - { - public string Name { get; set; } - public int Value { get; set; } - } -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/HandlerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/HandlerTests.cs deleted file mode 100644 index ac2de2cad..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/HandlerTests.cs +++ /dev/null @@ -1,368 +0,0 @@ -using System.Text; -using Amazon.Lambda.Core; -using Amazon.Lambda.TestUtilities; -using AWS.Lambda.Powertools.Kafka.Protobuf; -using Google.Protobuf; -using TestKafka; - -#if DEBUG -using KafkaAlias = AWS.Lambda.Powertools.Kafka; -#else -using KafkaAlias = AWS.Lambda.Powertools.Kafka.Protobuf; -#endif - -namespace AWS.Lambda.Powertools.Kafka.Tests.Protobuf; - -public class ProtobufHandlerTests -{ - [Fact] - public async Task Handler_ProcessesKafkaEvent_Successfully() - { - // Arrange - var kafkaJson = GetMockKafkaEvent(); - var mockContext = new TestLambdaContext(); - var serializer = new PowertoolsKafkaProtobufSerializer(); - - // Convert JSON string to stream for deserialization - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaJson)); - - // Act - Deserialize and process - var kafkaEvent = serializer.Deserialize>(stream); - var response = await Handler(kafkaEvent, mockContext); - - // Assert - Assert.Equal("Successfully processed Protobuf Kafka events", response); - - // Verify event structure - Assert.Equal("aws:kafka", kafkaEvent.EventSource); - Assert.Single(kafkaEvent.Records); - - // Verify record content - var records = kafkaEvent.Records["mytopic-0"]; - Assert.Equal(3, records.Count); - - // Verify first record - var firstRecord = records[0]; - Assert.Equal("mytopic", firstRecord.Topic); - Assert.Equal(0, firstRecord.Partition); - Assert.Equal(15, firstRecord.Offset); - - // Verify deserialized value - var product = firstRecord.Value; - Assert.Equal("Laptop", product.Name); - Assert.Equal(999.99, product.Price); - - // Verify decoded key and headers - Assert.Equal(42, firstRecord.Key); - Assert.Equal("headerValue", firstRecord.Headers["headerKey"].DecodedValue()); - - var secondRecord = records[1]; - Assert.Equal(43, secondRecord.Key); - - var thirdRecord = records[2]; - Assert.Equal(0, thirdRecord.Key); - } - - [Fact] - public async Task Handler_ProcessesKafkaEvent_WithProtobufKey_Successfully() - { - // Arrange - var kafkaJson = GetMockKafkaEventWithProtobufKeys(); - var mockContext = new TestLambdaContext(); - var serializer = new PowertoolsKafkaProtobufSerializer(); - - // Convert JSON string to stream for deserialization - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaJson)); - - // Act - Deserialize and process - var kafkaEvent = serializer.Deserialize>(stream); - var response = await HandlerWithProtobufKeys(kafkaEvent, mockContext); - - // Assert - Assert.Equal("Successfully processed Protobuf Kafka events with complex keys", response); - - // Verify event structure - Assert.Equal("aws:kafka", kafkaEvent.EventSource); - Assert.Single(kafkaEvent.Records); - - // Verify record content - var records = kafkaEvent.Records["mytopic-0"]; - Assert.Equal(3, records.Count); - - // Verify first record - var firstRecord = records[0]; - Assert.Equal("mytopic", firstRecord.Topic); - Assert.Equal(0, firstRecord.Partition); - Assert.Equal(15, firstRecord.Offset); - - // Verify deserialized Protobuf key and value - Assert.Equal("Laptop", firstRecord.Value.Name); - Assert.Equal(999.99, firstRecord.Value.Price); - Assert.Equal(1, firstRecord.Key.Id); - Assert.Equal(TestKafka.Color.Green, firstRecord.Key.Color); - - // Verify headers - Assert.Equal("headerValue", firstRecord.Headers["headerKey"].DecodedValue()); - - var secondRecord = records[1]; - Assert.Equal(2, secondRecord.Key.Id); - Assert.Equal(TestKafka.Color.Unknown, secondRecord.Key.Color); - - var thirdRecord = records[2]; - Assert.Equal(3, thirdRecord.Key.Id); - Assert.Equal(TestKafka.Color.Red, thirdRecord.Key.Color); - } - - private string GetMockKafkaEvent() - { - // For testing, we'll create base64-encoded Protobuf data for our test products - var laptop = new ProtobufProduct - { - Name = "Laptop", - Id = 1001, - Price = 999.99 - }; - - var smartphone = new ProtobufProduct - { - Name = "Smartphone", - Id = 1002, - Price = 499.99 - }; - - var headphones = new ProtobufProduct - { - Name = "Headphones", - Id = 1003, - Price = 99.99 - }; - - // Convert to base64-encoded Protobuf - string laptopBase64 = Convert.ToBase64String(laptop.ToByteArray()); - string smartphoneBase64 = Convert.ToBase64String(smartphone.ToByteArray()); - string headphonesBase64 = Convert.ToBase64String(headphones.ToByteArray()); - - string firstRecordKey = Convert.ToBase64String(Encoding.UTF8.GetBytes("42")); // Example key - string secondRecordKey = Convert.ToBase64String(Encoding.UTF8.GetBytes("43")); // Example key for second record - - // Create mock Kafka event JSON - return @$"{{ - ""eventSource"": ""aws:kafka"", - ""eventSourceArn"": ""arn:aws:kafka:us-east-1:0123456789019:cluster/SalesCluster/abcd1234-abcd-cafe-abab-9876543210ab-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"": {{ - ""mytopic-0"": [ - {{ - ""topic"": ""mytopic"", - ""partition"": 0, - ""offset"": 15, - ""timestamp"": 1545084650987, - ""timestampType"": ""CREATE_TIME"", - ""key"": ""{firstRecordKey}"", - ""value"": ""{laptopBase64}"", - ""headers"": [ - {{ ""headerKey"": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] }} - ] - }}, - {{ - ""topic"": ""mytopic"", - ""partition"": 0, - ""offset"": 16, - ""timestamp"": 1545084650988, - ""timestampType"": ""CREATE_TIME"", - ""key"": ""{secondRecordKey}"", - ""value"": ""{smartphoneBase64}"", - ""headers"": [ - {{ ""headerKey"": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] }} - ] - }}, - {{ - ""topic"": ""mytopic"", - ""partition"": 0, - ""offset"": 17, - ""timestamp"": 1545084650989, - ""timestampType"": ""CREATE_TIME"", - ""key"": null, - ""value"": ""{headphonesBase64}"", - ""headers"": [ - {{ ""headerKey"": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] }} - ] - }} - ] - }} - }}"; - } - - private string GetMockKafkaEventWithProtobufKeys() - { - // Create test products - var laptop = new ProtobufProduct - { - Name = "Laptop", - Id = 1001, - Price = 999.99 - }; - - var smartphone = new ProtobufProduct - { - Name = "Smartphone", - Id = 1002, - Price = 499.99 - }; - - var headphones = new ProtobufProduct - { - Name = "Headphones", - Id = 1003, - Price = 99.99 - }; - - // Create test keys - var key1 = new ProtobufKey { Id = 1, Color = TestKafka.Color.Green }; - var key2 = new ProtobufKey { Id = 2 }; - var key3 = new ProtobufKey { Id = 3, Color = TestKafka.Color.Red }; - - // Convert values to base64-encoded Protobuf - string laptopBase64 = Convert.ToBase64String(laptop.ToByteArray()); - string smartphoneBase64 = Convert.ToBase64String(smartphone.ToByteArray()); - string headphonesBase64 = Convert.ToBase64String(headphones.ToByteArray()); - - // Convert keys to base64-encoded Protobuf - string key1Base64 = Convert.ToBase64String(key1.ToByteArray()); - string key2Base64 = Convert.ToBase64String(key2.ToByteArray()); - string key3Base64 = Convert.ToBase64String(key3.ToByteArray()); - - // Create mock Kafka event JSON - return @$"{{ - ""eventSource"": ""aws:kafka"", - ""eventSourceArn"": ""arn:aws:kafka:us-east-1:0123456789019:cluster/SalesCluster/abcd1234-abcd-cafe-abab-9876543210ab-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"": {{ - ""mytopic-0"": [ - {{ - ""topic"": ""mytopic"", - ""partition"": 0, - ""offset"": 15, - ""timestamp"": 1545084650987, - ""timestampType"": ""CREATE_TIME"", - ""key"": ""{key1Base64}"", - ""value"": ""{laptopBase64}"", - ""headers"": [ - {{ ""headerKey"": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] }} - ] - }}, - {{ - ""topic"": ""mytopic"", - ""partition"": 0, - ""offset"": 16, - ""timestamp"": 1545084650988, - ""timestampType"": ""CREATE_TIME"", - ""key"": ""{key2Base64}"", - ""value"": ""{smartphoneBase64}"", - ""headers"": [ - {{ ""headerKey"": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] }} - ] - }}, - {{ - ""topic"": ""mytopic"", - ""partition"": 0, - ""offset"": 17, - ""timestamp"": 1545084650989, - ""timestampType"": ""CREATE_TIME"", - ""key"": ""{key3Base64}"", - ""value"": ""{headphonesBase64}"", - ""headers"": [ - {{ ""headerKey"": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] }} - ] - }} - ] - }} - }}"; - } - - // Define the test handler method - private async Task Handler(KafkaAlias.ConsumerRecords records, ILambdaContext context) - { - foreach (var record in records) - { - var product = record.Value; - context.Logger.LogInformation($"Processing {product.Name} at ${product.Price}"); - } - - return "Successfully processed Protobuf Kafka events"; - } - - private async Task HandlerWithProtobufKeys(KafkaAlias.ConsumerRecords records, - ILambdaContext context) - { - foreach (var record in records) - { - var key = record.Key; - var product = record.Value; - context.Logger.LogInformation($"Processing key {key.Id} - {product.Name} at ${product.Price}"); - } - - return "Successfully processed Protobuf Kafka events with complex keys"; - } - - [Fact] - public void SimpleHandlerTest() - { - string Handler(KafkaAlias.ConsumerRecords records, ILambdaContext context) - { - foreach (var record in records) - { - var product = record.Value; - context.Logger.LogInformation($"Processing {product.Name} at ${product.Price}"); - } - - return "Successfully processed Protobuf Kafka events"; - } - // Simulate the handler execution - var mockLogger = new TestLambdaLogger(); - var mockContext = new TestLambdaContext - { - Logger = mockLogger - }; - - var records = new KafkaAlias.ConsumerRecords - { - Records = new Dictionary>> - { - { "mytopic-0", new List> - { - new() - { - Topic = "mytopic", - Partition = 0, - Offset = 15, - Key = 42, - Value = new ProtobufProduct { Name = "Test Product", Id = 1, Price = 99.99 } - } - } - } - } - }; - - // Call the handler - var result = Handler(records, mockContext); - - // Assert the result - Assert.Equal("Successfully processed Protobuf Kafka events", result); - - // Verify the context logger output - Assert.Contains("Processing Test Product at $99.99", mockLogger.Buffer.ToString()); - - // Verify the records were processed - Assert.Single(records.Records); - Assert.Contains("mytopic-0", records.Records.Keys); - Assert.Single(records.Records["mytopic-0"]); - Assert.Equal("mytopic", records.Records["mytopic-0"][0].Topic); - Assert.Equal(0, records.Records["mytopic-0"][0].Partition); - Assert.Equal(15, records.Records["mytopic-0"][0].Offset); - Assert.Equal(42, records.Records["mytopic-0"][0].Key); - Assert.Equal("Test Product", records.Records["mytopic-0"][0].Value.Name); - Assert.Equal(1, records.Records["mytopic-0"][0].Value.Id); - Assert.Equal(99.99, records.Records["mytopic-0"][0].Value.Price); - } -} diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/Key.proto b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/Key.proto deleted file mode 100644 index deedcf5dc..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/Key.proto +++ /dev/null @@ -1,14 +0,0 @@ -syntax = "proto3"; - -option csharp_namespace = "TestKafka"; - -message ProtobufKey { - int32 id = 1; - Color color = 2; -} - -enum Color { - UNKNOWN = 0; - GREEN = 1; - RED = 2; -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/PowertoolsKafkaProtobufSerializerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/PowertoolsKafkaProtobufSerializerTests.cs deleted file mode 100644 index fc9074db7..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/PowertoolsKafkaProtobufSerializerTests.cs +++ /dev/null @@ -1,305 +0,0 @@ -using System.Runtime.Serialization; -using System.Text; -using AWS.Lambda.Powertools.Kafka.Protobuf; -using Com.Example.Protobuf; -using TestKafka; - -#if DEBUG -using KafkaAlias = AWS.Lambda.Powertools.Kafka; -#else -using KafkaAlias = AWS.Lambda.Powertools.Kafka.Protobuf; -#endif - -namespace AWS.Lambda.Powertools.Kafka.Tests.Protobuf; - -public class PowertoolsKafkaProtobufSerializerTests -{ - [Fact] - public void Deserialize_KafkaEventWithProtobufPayload_DeserializesToCorrectType() - { - // Arrange - var serializer = new PowertoolsKafkaProtobufSerializer(); - string kafkaEventJson = File.ReadAllText("Protobuf/kafka-protobuf-event.json"); - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); - - // Act - var result = serializer.Deserialize>(stream); - - // Assert - Assert.NotNull(result); - Assert.Equal("aws:kafka", result.EventSource); - - // Verify records were deserialized - Assert.True(result.Records.ContainsKey("mytopic-0")); - var records = result.Records["mytopic-0"]; - Assert.Equal(3, records.Count); // Fixed to expect 3 records instead of 1 - - // Verify first record's content - var firstRecord = records[0]; - Assert.Equal("mytopic", firstRecord.Topic); - Assert.Equal(0, firstRecord.Partition); - Assert.Equal(15, firstRecord.Offset); - Assert.Equal(42, firstRecord.Key); - - // Verify deserialized Protobuf value - var product = firstRecord.Value; - Assert.Equal("Laptop", product.Name); - Assert.Equal(1001, product.Id); - Assert.Equal(999.99, product.Price); - - // Verify second record - var secondRecord = records[1]; - var smartphone = secondRecord.Value; - Assert.Equal("Smartphone", smartphone.Name); - Assert.Equal(1002, smartphone.Id); - Assert.Equal(599.99, smartphone.Price); - - // Verify third record - var thirdRecord = records[2]; - var headphones = thirdRecord.Value; - Assert.Equal("Headphones", headphones.Name); - Assert.Equal(1003, headphones.Id); - Assert.Equal(149.99, headphones.Price); - } - - [Fact] - public void KafkaEvent_ImplementsIEnumerable_ForDirectIteration() - { - // Arrange - var serializer = new PowertoolsKafkaProtobufSerializer(); - string kafkaEventJson = File.ReadAllText("Protobuf/kafka-protobuf-event.json"); - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); - - // Act - var result = serializer.Deserialize>(stream); - - // Assert - Test enumeration - int count = 0; - var products = new List(); - - // Directly iterate over ConsumerRecords - foreach (var record in result) - { - count++; - products.Add(record.Value.Name); - } - - // Verify correct count and values - Assert.Equal(3, count); - Assert.Contains("Laptop", products); - Assert.Contains("Smartphone", products); - Assert.Contains("Headphones", products); - - // Get first record directly through Linq extension - var firstRecord = result.First(); - Assert.Equal("Laptop", firstRecord.Value.Name); - Assert.Equal(1001, firstRecord.Value.Id); - } - - [Fact] - public void Primitive_Deserialization() - { - // Arrange - var serializer = new PowertoolsKafkaProtobufSerializer(); - string kafkaEventJson = - CreateKafkaEvent(Convert.ToBase64String("MyKey"u8.ToArray()), - Convert.ToBase64String("Myvalue"u8.ToArray())); - - - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); - - // Act - var result = serializer.Deserialize>(stream); - var firstRecord = result.First(); - Assert.Equal("Myvalue", firstRecord.Value); - Assert.Equal("MyKey", firstRecord.Key); - } - - [Fact] - public void DeserializeComplexKey_WhenAllDeserializationMethodsFail_ReturnsException() - { - // Arrange - var serializer = new PowertoolsKafkaProtobufSerializer(); - // Invalid JSON and not Protobuf binary - byte[] invalidBytes = { 0xDE, 0xAD, 0xBE, 0xEF }; - - string kafkaEventJson = CreateKafkaEvent( - keyValue: Convert.ToBase64String(invalidBytes), - valueValue: Convert.ToBase64String(Encoding.UTF8.GetBytes("test")) - ); - - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); - - // Act - var message = - Assert.Throws(() => - serializer.Deserialize>(stream)); - Assert.Contains("Failed to deserialize key data: Unsupported", message.Message); - } - - [Fact] - public void Deserialize_Confluent_DeserializeCorrectly() - { - // Arrange - var serializer = new PowertoolsKafkaProtobufSerializer(); - string kafkaEventJson = File.ReadAllText("Protobuf/kafka-protobuf-confluent-event.json"); - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); - - // Act - var result = serializer.Deserialize>(stream); - - // Assert - Assert.NotNull(result); - Assert.Equal("aws:kafka", result.EventSource); - - // Verify records - Assert.True(result.Records.ContainsKey("confluent_proto-0")); - var records = result.Records["confluent_proto-0"]; - Assert.Equal(4, records.Count); - - // Verify all records have been deserialized correctly (all should have the same content) - Assert.Equal("a8e40971-1552-420d-a7c9-b8982325702d", records[0].Value.UserId); - Assert.Equal("Bob", records[0].Value.Name); - Assert.Equal("bob@example.com", records[0].Value.Email); - Assert.Equal("Seattle", records[0].Value.Address.City); - Assert.Equal(28, records[0].Value.Age); - - Assert.Equal("4dcfc61b-3993-49c3-a04f-8a6c7aaf7881", records[1].Value.UserId); - Assert.Equal("Bob", records[1].Value.Name); - Assert.Equal("bob@example.com", records[1].Value.Email); - Assert.Equal("Seattle", records[1].Value.Address.City); - Assert.Equal(28, records[1].Value.Age); - - Assert.Equal("2a861628-0800-4b76-bd3f-6ecba7cd286c", records[2].Value.UserId); - Assert.Equal("Bob", records[2].Value.Name); - Assert.Equal("Seattle", records[2].Value.Address.City); - Assert.Equal(28, records[2].Value.Age); - } - - [Fact] - public void Deserialize_Glue_DeserializeCorrectly() - { - // Arrange - var serializer = new PowertoolsKafkaProtobufSerializer(); - string kafkaEventJson = File.ReadAllText("Protobuf/kafka-protobuf-glue-event.json"); - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); - - // Act - var result = serializer.Deserialize>(stream); - - // Assert - Assert.NotNull(result); - Assert.Equal("aws:kafka", result.EventSource); - - // Verify records - Assert.True(result.Records.ContainsKey("gsr_proto-0")); - var records = result.Records["gsr_proto-0"]; - Assert.Equal(4, records.Count); - - // Verify all records have been deserialized correctly (all should have the same content) - Assert.Equal("u859", records[0].Value.UserId); - Assert.Equal("Alice", records[0].Value.Name); - Assert.Equal("alice@example.com", records[0].Value.Email); - Assert.Equal("dark", records[0].Value.Address.City); - Assert.Equal(54, records[0].Value.Age); - - Assert.Equal("u809", records[1].Value.UserId); - Assert.Equal("Alice", records[1].Value.Name); - Assert.Equal("alice@example.com", records[1].Value.Email); - Assert.Equal("dark", records[1].Value.Address.City); - Assert.Equal(40, records[1].Value.Age); - - Assert.Equal("u453", records[2].Value.UserId); - Assert.Equal("Alice", records[2].Value.Name); - Assert.Equal("dark", records[2].Value.Address.City); - Assert.Equal(74, records[2].Value.Age); - } - - [Fact] - public void Deserialize_MessageIndexWithCorruptData_HandlesError() - { - // Arrange - Create invalid message index data (starts with 5 but doesn't have 5 entries) - byte[] invalidData = [5, 1, 2]; // Claims to have 5 entries but only has 2 - string kafkaEventJson = CreateKafkaEvent("NDI=", Convert.ToBase64String(invalidData)); - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); - var serializer = new PowertoolsKafkaProtobufSerializer(); - - // Act & Assert - var ex = Assert.Throws(() => - serializer.Deserialize>(stream)); - - // Verify the exception message contains useful information - Assert.Contains("Failed to deserialize value data:", ex.Message); - } - - /* - 1/ If this field is None = We go in the easy way that is decode pure protobuf - 2/ If the schemaId in this field is a uuid (16+ chars), its Glue and then you need to strip only the first byte and the deserialize - 3/ If the len(schemaId) is 4, it means it is Confluent and then you need to strip the message fields numbers - */ - [Theory] - [InlineData( - "CgMxMjMSBFRlc3QaDHRlc3RAZ214LmNvbSAKMgoyMDI1LTA2LTIwOgR0YWcxOgR0YWcySg4KBXRoZW1lEgVsaWdodFIaCgpNeXRoZW5xdWFpEgZadXJpY2gaBDgwMDI=", - null)] - [InlineData( - "AAoDMTIzEgRUZXN0Ggx0ZXN0QGdteC5jb20gCjIKMjAyNS0wNi0yMDoEdGFnMToEdGFnMkoOCgV0aGVtZRIFbGlnaHRSGgoKTXl0aGVucXVhaRIGWnVyaWNoGgQ4MDAy", - "123")] - [InlineData( - "BAIACgMxMjMSBFRlc3QaDHRlc3RAZ214LmNvbSAKMgoyMDI1LTA2LTIwOgR0YWcxOgR0YWcyQQAAAAAAAChASg4KBXRoZW1lEgVsaWdodFIaCgpNeXRoZW5xdWFpEgZadXJpY2gaBDgwMDI=", - "456")] - [InlineData( - "AQoDMTIzEgRUZXN0Ggx0ZXN0QGdteC5jb20gCjIKMjAyNS0wNi0yMDoEdGFnMToEdGFnMkoOCgV0aGVtZRIFbGlnaHRSGgoKTXl0aGVucXVhaRIGWnVyaWNoGgQ4MDAy", - "12345678-1234-1234-1234-123456789012")] - public void Deserialize_MultipleFormats_EachFormatDeserializesCorrectly(string base64Value, - string? schemaId) - { - // Arrange - var serializer = new PowertoolsKafkaProtobufSerializer(); - string kafkaEventJson = CreateKafkaEvent("NDI=", base64Value, schemaId); // Key is 42 in base64 - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); - - // Act - var result = serializer.Deserialize>(stream); - - // Assert - var record = result.First(); - Assert.NotNull(record); - Assert.Equal(42, record.Key); // Key should be 42 - - // Value should be the same regardless of message index format - Assert.Equal("Test", record.Value.Name); - Assert.Equal("Zurich", record.Value.Address.City); - Assert.Equal(10, record.Value.Age); - Assert.Single(record.Value.Preferences); - Assert.Equal("light",record.Value.Preferences.First().Value); - } - - private string CreateKafkaEvent(string keyValue, string valueValue, string? schemaId = null) - { - return @$"{{ - ""eventSource"": ""aws:kafka"", - ""eventSourceArn"": ""arn:aws:kafka:us-east-1:0123456789019:cluster/TestCluster/abcd1234"", - ""bootstrapServers"": ""b-1.test-cluster.kafka.us-east-1.amazonaws.com:9092"", - ""records"": {{ - ""mytopic-0"": [ - {{ - ""topic"": ""mytopic"", - ""partition"": 0, - ""offset"": 15, - ""timestamp"": 1645084650987, - ""timestampType"": ""CREATE_TIME"", - ""key"": ""{keyValue}"", - ""value"": ""{valueValue}"", - ""headers"": [ - {{ ""headerKey"": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] }} - ], - ""valueSchemaMetadata"": {{ - ""dataFormat"": ""PROTOBUF"", - ""schemaId"": ""{schemaId}"" - }} - }} - ] - }} - }}"; - } -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/Product.proto b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/Product.proto deleted file mode 100644 index 1d4c64e90..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/Product.proto +++ /dev/null @@ -1,9 +0,0 @@ -syntax = "proto3"; - -option csharp_namespace = "TestKafka"; - -message ProtobufProduct { - int32 id = 1; - string name = 2; - double price = 3; -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/UserProfile.proto b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/UserProfile.proto deleted file mode 100644 index 9ebc26ed3..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/UserProfile.proto +++ /dev/null @@ -1,21 +0,0 @@ -syntax = "proto3"; -package com.example.protobuf; - -message Address { - string street = 1; - string city = 2; - string zip = 3; -} - -message UserProfile { - string userId = 1; - string name = 2; - string email = 3; - int32 age = 4; - bool isActive = 5; - string signupDate = 6; - repeated string tags = 7; - double score = 8; - map preferences = 9; - Address address = 10; -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/kafka-protobuf-confluent-event.json b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/kafka-protobuf-confluent-event.json deleted file mode 100644 index 6e7acf978..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/kafka-protobuf-confluent-event.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "bootstrapServers": "boot-u18.warpnest.kdhspn.c25.kafka.us-east-1.amazonaws.com:9098,boot-3xz.warpnest.kdhspn.c25.kafka.us-east-1.amazonaws.com:9098,boot-vvi.warpnest.kdhspn.c25.kafka.us-east-1.amazonaws.com:9098", - "eventSource": "aws:kafka", - "eventSourceArn": "arn:aws:kafka:us-east-1:408865831329:cluster/WarpNest/717bd2d1-e34b-4a86-9ae8-f7a16158c0f6-25", - "records": { - "confluent_proto-0": [ - { - "headers": [], - "key": "YThlNDA5NzEtMTU1Mi00MjBkLWE3YzktYjg5ODIzMjU3MDJk", - "offset": 4209910, - "partition": 0, - "timestamp": 1750358101849, - "timestampType": "CREATE_TIME", - "topic": "confluent_proto", - "value": "AgIKJGE4ZTQwOTcxLTE1NTItNDIwZC1hN2M5LWI4OTgyMzI1NzAyZBIDQm9iGg9ib2JAZXhhbXBsZS5jb20gHCgBMgoyMDI0LTAyLTAyOgR0YWcxOgR0YWcyQQAAAAAAAFZASg4KBXRoZW1lEgVsaWdodFIZCgcxMjMgQXZlEgdTZWF0dGxlGgU5ODEwMQ==", - "valueSchemaMetadata": { - "dataFormat": "PROTOBUF", - "schemaId": "1" - } - }, - { - "headers": [], - "key": "NGRjZmM2MWItMzk5My00OWMzLWEwNGYtOGE2YzdhYWY3ODgx", - "offset": 4209911, - "partition": 0, - "timestamp": 1750358102849, - "timestampType": "CREATE_TIME", - "topic": "confluent_proto", - "value": "AgIKJDRkY2ZjNjFiLTM5OTMtNDljMy1hMDRmLThhNmM3YWFmNzg4MRIDQm9iGg9ib2JAZXhhbXBsZS5jb20gHCgBMgoyMDI0LTAyLTAyOgR0YWcxOgR0YWcyQQAAAAAAAFZASg4KBXRoZW1lEgVsaWdodFIZCgcxMjMgQXZlEgdTZWF0dGxlGgU5ODEwMQ==", - "valueSchemaMetadata": { - "dataFormat": "PROTOBUF", - "schemaId": "1" - } - }, - { - "headers": [], - "key": "MmE4NjE2MjgtMDgwMC00Yjc2LWJkM2YtNmVjYmE3Y2QyODZj", - "offset": 4209912, - "partition": 0, - "timestamp": 1750358103849, - "timestampType": "CREATE_TIME", - "topic": "confluent_proto", - "value": "AgIKJDJhODYxNjI4LTA4MDAtNGI3Ni1iZDNmLTZlY2JhN2NkMjg2YxIDQm9iGg9ib2JAZXhhbXBsZS5jb20gHCgBMgoyMDI0LTAyLTAyOgR0YWcxOgR0YWcyQQAAAAAAAFZASg4KBXRoZW1lEgVsaWdodFIZCgcxMjMgQXZlEgdTZWF0dGxlGgU5ODEwMQ==", - "valueSchemaMetadata": { - "dataFormat": "PROTOBUF", - "schemaId": "1" - } - }, - { - "headers": [], - "key": "NzEzMjBjNzMtZWM1Ny00NDZlLWJkNWItOTI1MmQ2OTQzMTgy", - "offset": 4209913, - "partition": 0, - "timestamp": 1750358104849, - "timestampType": "CREATE_TIME", - "topic": "confluent_proto", - "value": "AgIKJDcxMzIwYzczLWVjNTctNDQ2ZS1iZDViLTkyNTJkNjk0MzE4MhIDQm9iGg9ib2JAZXhhbXBsZS5jb20gHCgBMgoyMDI0LTAyLTAyOgR0YWcxOgR0YWcyQQAAAAAAAFZASg4KBXRoZW1lEgVsaWdodFIZCgcxMjMgQXZlEgdTZWF0dGxlGgU5ODEwMQ==", - "valueSchemaMetadata": { - "dataFormat": "PROTOBUF", - "schemaId": "1" - } - } - ] - } -} - diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/kafka-protobuf-event.json b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/kafka-protobuf-event.json deleted file mode 100644 index b3e0139e3..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/kafka-protobuf-event.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "eventSource": "aws:kafka", - "eventSourceArn": "arn:aws:kafka:us-east-1:0123456789019:cluster/SalesCluster/abcd1234-abcd-cafe-abab-9876543210ab-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": { - "mytopic-0": [ - { - "topic": "mytopic", - "partition": 0, - "offset": 15, - "timestamp": 1545084650987, - "timestampType": "CREATE_TIME", - "key": "NDI=", - "value": "COkHEgZMYXB0b3AZUrgehes/j0A=", - "headers": [ - { - "headerKey": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] - } - ] - }, - { - "topic": "mytopic", - "partition": 0, - "offset": 16, - "timestamp": 1545084650988, - "timestampType": "CREATE_TIME", - "key": "NDI=", - "value": "COoHEgpTbWFydHBob25lGVK4HoXrv4JA", - "headers": [ - { - "headerKey": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] - } - ] - }, - { - "topic": "mytopic", - "partition": 0, - "offset": 17, - "timestamp": 1545084650989, - "timestampType": "CREATE_TIME", - "key": null, - "value": "COsHEgpIZWFkcGhvbmVzGUjhehSuv2JA", - "headers": [ - { - "headerKey": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] - } - ] - } - ] - } -} diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/kafka-protobuf-glue-event.json b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/kafka-protobuf-glue-event.json deleted file mode 100644 index 292413444..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/Protobuf/kafka-protobuf-glue-event.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "bootstrapServers": "boot-u18.warpnest.kdhspn.c25.kafka.us-east-1.amazonaws.com:9098,boot-3xz.warpnest.kdhspn.c25.kafka.us-east-1.amazonaws.com:9098,boot-vvi.warpnest.kdhspn.c25.kafka.us-east-1.amazonaws.com:9098", - "eventSource": "aws:kafka", - "eventSourceArn": "arn:aws:kafka:us-east-1:408865831329:cluster/WarpNest/717bd2d1-e34b-4a86-9ae8-f7a16158c0f6-25", - "records": { - "gsr_proto-0": [ - { - "headers": [], - "key": "dTg1OQ==", - "offset": 4130352, - "partition": 0, - "timestamp": 1750284651283, - "timestampType": "CREATE_TIME", - "topic": "gsr_proto", - "value": "AQoEdTg1ORIFQWxpY2UaEWFsaWNlQGV4YW1wbGUuY29tIDYyCjIwMjQtMDEtMDE6GgoIMTIzIE1haW4SB1NlYXR0bGUaBTk4MTAxQgR0YWcxQgR0YWcySZZFopoJWkdAUg0KBXRoZW1lEgRkYXJr", - "valueSchemaMetadata": { - "dataFormat": "PROTOBUF", - "schemaId": "7d55d475-2244-4485-8341-f74468c1e058" - } - }, - { - "headers": [], - "key": "dTgwOQ==", - "offset": 4130353, - "partition": 0, - "timestamp": 1750284652283, - "timestampType": "CREATE_TIME", - "topic": "gsr_proto", - "value": "AQoEdTgwORIFQWxpY2UaEWFsaWNlQGV4YW1wbGUuY29tICgyCjIwMjQtMDEtMDE6GgoIMTIzIE1haW4SB1NlYXR0bGUaBTk4MTAxQgR0YWcxQgR0YWcySTnSqQSHn0FAUg0KBXRoZW1lEgRkYXJr", - "valueSchemaMetadata": { - "dataFormat": "PROTOBUF", - "schemaId": "7d55d475-2244-4485-8341-f74468c1e058" - } - }, - { - "headers": [], - "key": "dTQ1Mw==", - "offset": 4130354, - "partition": 0, - "timestamp": 1750284653283, - "timestampType": "CREATE_TIME", - "topic": "gsr_proto", - "value": "AQoEdTQ1MxIFQWxpY2UaEWFsaWNlQGV4YW1wbGUuY29tIEooATIKMjAyNC0wMS0wMToaCggxMjMgTWFpbhIHU2VhdHRsZRoFOTgxMDFCBHRhZzFCBHRhZzJJRJi47bmvV0BSDQoFdGhlbWUSBGRhcms=", - "valueSchemaMetadata": { - "dataFormat": "PROTOBUF", - "schemaId": "7d55d475-2244-4485-8341-f74468c1e058" - } - }, - { - "headers": [], - "key": "dTcwNQ==", - "offset": 4130355, - "partition": 0, - "timestamp": 1750284654283, - "timestampType": "CREATE_TIME", - "topic": "gsr_proto", - "value": "AQoEdTcwNRIFQWxpY2UaEWFsaWNlQGV4YW1wbGUuY29tIBMyCjIwMjQtMDEtMDE6GgoIMTIzIE1haW4SB1NlYXR0bGUaBTk4MTAxQgR0YWcxQgR0YWcySUSydyF28ldAUg0KBXRoZW1lEgRkYXJr", - "valueSchemaMetadata": { - "dataFormat": "PROTOBUF", - "schemaId": "7d55d475-2244-4485-8341-f74468c1e058" - } - } - ] - } -} - diff --git a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/ProtobufErrorHandlingTests.cs b/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/ProtobufErrorHandlingTests.cs deleted file mode 100644 index 0813d426f..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Kafka.Tests/ProtobufErrorHandlingTests.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System.Runtime.Serialization; -using System.Text; -using AWS.Lambda.Powertools.Kafka.Protobuf; - -#if DEBUG -using KafkaAlias = AWS.Lambda.Powertools.Kafka; -#else -using KafkaAlias = AWS.Lambda.Powertools.Kafka.Protobuf; -#endif - -namespace AWS.Lambda.Powertools.Kafka.Tests; - -public class ProtobufErrorHandlingTests -{ - [Fact] - public void ProtobufSerializer_WithCorruptedKeyData_ThrowSerializationException() - { - // Arrange - var serializer = new PowertoolsKafkaProtobufSerializer(); - var corruptedData = new byte[] { 0xDE, 0xAD, 0xBE, 0xEF }; - - string kafkaEventJson = CreateKafkaEvent( - Convert.ToBase64String(corruptedData), - Convert.ToBase64String(Encoding.UTF8.GetBytes("valid-value")) - ); - - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); - - // Act & Assert - var ex = Assert.Throws(() => - serializer.Deserialize>(stream)); - - Assert.Contains("Failed to deserialize key data", ex.Message); - } - - [Fact] - public void ProtobufSerializer_WithCorruptedValueData_ThrowSerializationException() - { - // Arrange - var serializer = new PowertoolsKafkaProtobufSerializer(); - var corruptedData = new byte[] { 0xDE, 0xAD, 0xBE, 0xEF }; - - string kafkaEventJson = CreateKafkaEvent( - Convert.ToBase64String(Encoding.UTF8.GetBytes("valid-key")), - Convert.ToBase64String(corruptedData) - ); - - using var stream = new MemoryStream(Encoding.UTF8.GetBytes(kafkaEventJson)); - - // Act & Assert - var ex = Assert.Throws(() => - serializer.Deserialize>(stream)); - - Assert.Contains("Failed to deserialize value data", ex.Message); - } - - private string CreateKafkaEvent(string keyValue, string valueValue) - { - return @$"{{ - ""eventSource"": ""aws:kafka"", - ""records"": {{ - ""mytopic-0"": [ - {{ - ""topic"": ""mytopic"", - ""partition"": 0, - ""offset"": 15, - ""key"": ""{keyValue}"", - ""value"": ""{valueValue}"" - }} - ] - }} - }}"; - } -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Attributes/LoggerAspectTests.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Attributes/LoggerAspectTests.cs index 946af0112..f3a8335e6 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Attributes/LoggerAspectTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Attributes/LoggerAspectTests.cs @@ -1,7 +1,22 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; -using System.IO; using AWS.Lambda.Powertools.Common; using AWS.Lambda.Powertools.Logging.Internal; +using AWS.Lambda.Powertools.Logging.Serializers; using AWS.Lambda.Powertools.Logging.Tests.Handlers; using AWS.Lambda.Powertools.Logging.Tests.Serializers; using Microsoft.Extensions.Logging; @@ -13,31 +28,21 @@ namespace AWS.Lambda.Powertools.Logging.Tests.Attributes; [Collection("Sequential")] public class LoggerAspectTests : IDisposable { - static LoggerAspectTests() - { - ResetAllState(); - } - + private ISystemWrapper _mockSystemWrapper; + private readonly IPowertoolsConfigurations _mockPowertoolsConfigurations; + public LoggerAspectTests() { - // Start each test with clean state - ResetAllState(); + _mockSystemWrapper = Substitute.For(); + _mockPowertoolsConfigurations = Substitute.For(); } - + [Fact] public void OnEntry_ShouldInitializeLogger_WhenCalledWithValidArguments() { // Arrange - var consoleOut = Substitute.For(); - - var config = new PowertoolsLoggerConfiguration - { - Service = "TestService", - MinimumLogLevel = LogLevel.Information, - LogOutput = consoleOut - }; - - var logger = PowertoolsLoggerFactory.Create(config).CreatePowertoolsLogger(); + // Add seriolization context for AOT + PowertoolsLoggingSerializer.AddSerializerContext(TestJsonContext.Default); var instance = new object(); var name = "TestMethod"; @@ -59,29 +64,17 @@ public void OnEntry_ShouldInitializeLogger_WhenCalledWithValidArguments() } }; - var aspectArgs = new AspectEventArgs - { - Instance = instance, - Name = name, - Args = args, - Type = hostType, - Method = method, - ReturnType = returnType, - Triggers = triggers - }; + _mockSystemWrapper.GetRandom().Returns(0.7); // Act - var loggingAspect = new LoggingAspect(logger); - loggingAspect.OnEntry(aspectArgs); + var loggingAspect = new LoggingAspect(_mockPowertoolsConfigurations, _mockSystemWrapper); + loggingAspect.OnEntry(instance, name, args, hostType, method, returnType, triggers); // Assert - consoleOut.Received(1).WriteLine(Arg.Is(s => - s.Contains("\"Level\":\"Information\"") && - s.Contains("\"Service\":\"TestService\"") && - s.Contains("\"Name\":\"AWS.Lambda.Powertools.Logging.Logger\"") && - s.Contains("\"Message\":{\"FullName\":\"Powertools\",\"Age\":20,\"Headers\":null}") && - s.Contains("\"CorrelationId\":\"20\"") && - s.Contains("\"SamplingRate\":0.5") + _mockSystemWrapper.Received().LogLine(Arg.Is(s => + s.Contains( + "\"Level\":\"Information\",\"Service\":\"TestService\",\"Name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"Message\":{\"FullName\":\"Powertools\",\"Age\":20,\"Headers\":null},\"SamplingRate\":0.5}") + && s.Contains("\"CorrelationId\":\"20\"") )); } @@ -89,86 +82,9 @@ public void OnEntry_ShouldInitializeLogger_WhenCalledWithValidArguments() public void OnEntry_ShouldLog_Event_When_EnvironmentVariable_Set() { // Arrange - Environment.SetEnvironmentVariable(Constants.LoggerLogEventNameEnv, "true"); - var consoleOut = Substitute.For(); - - var config = new PowertoolsLoggerConfiguration - { - Service = "TestService", - MinimumLogLevel = LogLevel.Information, - LogEvent = true, - LogOutput = consoleOut - }; - - var logger = PowertoolsLoggerFactory.Create(config).CreatePowertoolsLogger(); - - var instance = new object(); - var name = "TestMethod"; - var args = new object[] { new TestObject { FullName = "Powertools", Age = 20 } }; - var hostType = typeof(string); - var method = typeof(TestHandlers).GetMethod("TestMethod"); - var returnType = typeof(string); - var triggers = new Attribute[] - { - new LoggingAttribute - { - Service = "TestService", - LoggerOutputCase = LoggerOutputCase.PascalCase, - LogLevel = LogLevel.Information, - CorrelationIdPath = "/Age", - ClearState = true - } - }; - - var aspectArgs = new AspectEventArgs - { - Instance = instance, - Name = name, - Args = args, - Type = hostType, - Method = method, - ReturnType = returnType, - Triggers = triggers - }; + // Add seriolization context for AOT + PowertoolsLoggingSerializer.AddSerializerContext(TestJsonContext.Default); - // Act - var loggingAspect = new LoggingAspect(logger); - loggingAspect.OnEntry(aspectArgs); - - var updatedConfig = PowertoolsLoggingBuilderExtensions.GetCurrentConfiguration(); - - // Assert - Assert.Equal("TestService", updatedConfig.Service); - Assert.Equal(LoggerOutputCase.PascalCase, updatedConfig.LoggerOutputCase); - Assert.Equal(0, updatedConfig.SamplingRate); - Assert.True(updatedConfig.LogEvent); - - consoleOut.Received(1).WriteLine(Arg.Is(s => - s.Contains("\"Level\":\"Information\"") && - s.Contains("\"Service\":\"TestService\"") && - s.Contains("\"Name\":\"AWS.Lambda.Powertools.Logging.Logger\"") && - s.Contains("\"Message\":{\"FullName\":\"Powertools\",\"Age\":20,\"Headers\":null}") && - s.Contains("\"CorrelationId\":\"20\"") - )); - } - - [Fact] - public void OnEntry_Should_NOT_Log_Event_When_EnvironmentVariable_Set_But_Attribute_False() - { - // Arrange - Environment.SetEnvironmentVariable(Constants.LoggerLogEventNameEnv, "true"); - var consoleOut = Substitute.For(); - - var config = new PowertoolsLoggerConfiguration - { - Service = "TestService", - MinimumLogLevel = LogLevel.Information, - LogEvent = true, - LogOutput = consoleOut - }; - - var logger = PowertoolsLoggerFactory.Create(config).CreatePowertoolsLogger(); - var instance = new object(); var name = "TestMethod"; var args = new object[] { new TestObject { FullName = "Powertools", Age = 20 } }; @@ -187,50 +103,35 @@ public void OnEntry_Should_NOT_Log_Event_When_EnvironmentVariable_Set_But_Attrib ClearState = true } }; - - var aspectArgs = new AspectEventArgs - { - Instance = instance, - Name = name, - Args = args, - Type = hostType, - Method = method, - ReturnType = returnType, - Triggers = triggers - }; + // Env returns true + _mockPowertoolsConfigurations.LoggerLogEvent.Returns(true); + + // Act + var loggingAspect = new LoggingAspect(_mockPowertoolsConfigurations, _mockSystemWrapper); + loggingAspect.OnEntry(instance, name, args, hostType, method, returnType, triggers); - // Act - var loggingAspect = new LoggingAspect(logger); - loggingAspect.OnEntry(aspectArgs); - - var updatedConfig = PowertoolsLoggingBuilderExtensions.GetCurrentConfiguration(); - // Assert - Assert.Equal("TestService", updatedConfig.Service); - Assert.Equal(LoggerOutputCase.PascalCase, updatedConfig.LoggerOutputCase); - Assert.Equal(0, updatedConfig.SamplingRate); - Assert.True(updatedConfig.LogEvent); + var config = _mockPowertoolsConfigurations.CurrentConfig(); + Assert.NotNull(Logger.LoggerProvider); + Assert.Equal("TestService", config.Service); + Assert.Equal(LoggerOutputCase.PascalCase, config.LoggerOutputCase); + Assert.Equal(0, config.SamplingRate); - consoleOut.DidNotReceive().WriteLine(Arg.Any()); + _mockSystemWrapper.Received().LogLine(Arg.Is(s => + s.Contains( + "\"Level\":\"Information\",\"Service\":\"TestService\",\"Name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"Message\":{\"FullName\":\"Powertools\",\"Age\":20,\"Headers\":null}}") + && s.Contains("\"CorrelationId\":\"20\"") + )); } - + [Fact] public void OnEntry_ShouldLog_SamplingRate_When_EnvironmentVariable_Set() { // Arrange - var consoleOut = Substitute.For(); - - var config = new PowertoolsLoggerConfiguration - { - Service = "TestService", - MinimumLogLevel = LogLevel.Information, - SamplingRate = 0.5, - LogOutput = consoleOut - }; - - var logger = PowertoolsLoggerFactory.Create(config).CreatePowertoolsLogger(); - + // Add seriolization context for AOT + PowertoolsLoggingSerializer.AddSerializerContext(TestJsonContext.Default); + var instance = new object(); var name = "TestMethod"; var args = new object[] { new TestObject { FullName = "Powertools", Age = 20 } }; @@ -250,54 +151,31 @@ public void OnEntry_ShouldLog_SamplingRate_When_EnvironmentVariable_Set() } }; + // Env returns true + _mockPowertoolsConfigurations.LoggerSampleRate.Returns(0.5); - var aspectArgs = new AspectEventArgs - { - Instance = instance, - Name = name, - Args = args, - Type = hostType, - Method = method, - ReturnType = returnType, - Triggers = triggers - }; + // Act + var loggingAspect = new LoggingAspect(_mockPowertoolsConfigurations, _mockSystemWrapper); + loggingAspect.OnEntry(instance, name, args, hostType, method, returnType, triggers); - // Act - var loggingAspect = new LoggingAspect(logger); - loggingAspect.OnEntry(aspectArgs); - // Assert - var updatedConfig = PowertoolsLoggingBuilderExtensions.GetCurrentConfiguration(); - - Assert.Equal("TestService", updatedConfig.Service); - Assert.Equal(LoggerOutputCase.PascalCase, updatedConfig.LoggerOutputCase); - Assert.Equal(0.5, updatedConfig.SamplingRate); - - consoleOut.Received(1).WriteLine(Arg.Is(s => - s.Contains("\"Level\":\"Information\"") && - s.Contains("\"Service\":\"TestService\"") && - s.Contains("\"Name\":\"AWS.Lambda.Powertools.Logging.Logger\"") && - s.Contains("\"Message\":{\"FullName\":\"Powertools\",\"Age\":20,\"Headers\":null}") && - s.Contains("\"CorrelationId\":\"20\"") && - s.Contains("\"SamplingRate\":0.5") + var config = _mockPowertoolsConfigurations.CurrentConfig(); + Assert.NotNull(Logger.LoggerProvider); + Assert.Equal("TestService", config.Service); + Assert.Equal(LoggerOutputCase.PascalCase, config.LoggerOutputCase); + Assert.Equal(0.5, config.SamplingRate); + + _mockSystemWrapper.Received().LogLine(Arg.Is(s => + s.Contains( + "\"Level\":\"Information\",\"Service\":\"TestService\",\"Name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"Message\":{\"FullName\":\"Powertools\",\"Age\":20,\"Headers\":null},\"SamplingRate\":0.5}") + && s.Contains("\"CorrelationId\":\"20\"") )); } - + [Fact] public void OnEntry_ShouldLogEvent_WhenLogEventIsTrue() { // Arrange - var consoleOut = Substitute.For(); - - var config = new PowertoolsLoggerConfiguration - { - Service = "TestService", - MinimumLogLevel = LogLevel.Information, - LogOutput = consoleOut, - }; - - var logger = PowertoolsLoggerFactory.Create(config).CreatePowertoolsLogger(); - var eventObject = new { testData = "test-data" }; var triggers = new Attribute[] { @@ -306,43 +184,26 @@ public void OnEntry_ShouldLogEvent_WhenLogEventIsTrue() LogEvent = true } }; - + // Act - - var aspectArgs = new AspectEventArgs - { - Args = new object[] { eventObject }, - Triggers = triggers - }; - // Act - var loggingAspect = new LoggingAspect(logger); - loggingAspect.OnEntry(aspectArgs); - + var loggingAspect = new LoggingAspect(_mockPowertoolsConfigurations, _mockSystemWrapper); + loggingAspect.OnEntry(null, null, new object[] { eventObject }, null, null, null, triggers); + // Assert - consoleOut.Received(1).WriteLine(Arg.Is(s => - s.Contains("\"level\":\"Information\"") && - s.Contains("\"service\":\"TestService\"") && - s.Contains("\"name\":\"AWS.Lambda.Powertools.Logging.Logger\"") && - s.Contains("\"message\":{\"test_data\":\"test-data\"}") + _mockSystemWrapper.Received().LogLine(Arg.Is(s => + s.Contains( + "\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"message\":{\"test_data\":\"test-data\"}}") )); } - + [Fact] public void OnEntry_ShouldNot_Log_Info_When_LogLevel_Higher_EnvironmentVariable() { // Arrange - var consoleOut = Substitute.For(); - - var config = new PowertoolsLoggerConfiguration - { - Service = "TestService", - MinimumLogLevel = LogLevel.Error, - LogOutput = consoleOut - }; - - var logger = PowertoolsLoggerFactory.Create(config).CreatePowertoolsLogger(); - + // Add seriolization context for AOT + PowertoolsLoggingSerializer.AddSerializerContext(TestJsonContext.Default); + var instance = new object(); var name = "TestMethod"; var args = new object[] { new TestObject { FullName = "Powertools", Age = 20 } }; @@ -355,49 +216,35 @@ public void OnEntry_ShouldNot_Log_Info_When_LogLevel_Higher_EnvironmentVariable( { Service = "TestService", LoggerOutputCase = LoggerOutputCase.PascalCase, - + LogEvent = true, CorrelationIdPath = "/age" } }; - - var aspectArgs = new AspectEventArgs - { - Instance = instance, - Name = name, - Args = args, - Type = hostType, - Method = method, - ReturnType = returnType, - Triggers = triggers - }; + // Env returns true + _mockPowertoolsConfigurations.LogLevel.Returns(LogLevel.Error.ToString()); + + // Act + var loggingAspect = new LoggingAspect(_mockPowertoolsConfigurations, _mockSystemWrapper); + loggingAspect.OnEntry(instance, name, args, hostType, method, returnType, triggers); - // Act - var loggingAspect = new LoggingAspect(logger); - loggingAspect.OnEntry(aspectArgs); - - var updatedConfig = PowertoolsLoggingBuilderExtensions.GetCurrentConfiguration(); - // Assert - Assert.Equal("TestService", updatedConfig.Service); - Assert.Equal(LoggerOutputCase.PascalCase, updatedConfig.LoggerOutputCase); - - consoleOut.DidNotReceive().WriteLine(Arg.Any()); + var config = _mockPowertoolsConfigurations.CurrentConfig(); + Assert.NotNull(Logger.LoggerProvider); + Assert.Equal("TestService", config.Service); + Assert.Equal(LoggerOutputCase.PascalCase, config.LoggerOutputCase); + + _mockSystemWrapper.DidNotReceive().LogLine(Arg.Any()); } - + [Fact] public void OnEntry_Should_LogDebug_WhenSet_EnvironmentVariable() { // Arrange - Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", "Debug"); - - var consoleOut = Substitute.For(); - var config = new PowertoolsLoggerConfiguration - { - LogOutput = consoleOut - }; - + // Add seriolization context for AOT + PowertoolsLoggingSerializer.AddSerializerContext(TestJsonContext.Default); + var instance = new object(); var name = "TestMethod"; var args = new object[] @@ -417,38 +264,25 @@ public void OnEntry_Should_LogDebug_WhenSet_EnvironmentVariable() CorrelationIdPath = "/Headers/MyRequestIdHeader" } }; - - var logger = PowertoolsLoggerFactory.Create(config).CreatePowertoolsLogger(); + // Env returns true + _mockPowertoolsConfigurations.LogLevel.Returns(LogLevel.Debug.ToString()); - var aspectArgs = new AspectEventArgs - { - Instance = instance, - Name = name, - Args = args, - Type = hostType, - Method = method, - ReturnType = returnType, - Triggers = triggers - }; + // Act + var loggingAspect = new LoggingAspect(_mockPowertoolsConfigurations, _mockSystemWrapper); + loggingAspect.OnEntry(instance, name, args, hostType, method, returnType, triggers); - // Act - var stringWriter = new StringWriter(); - Console.SetOut(stringWriter); - var loggingAspect = new LoggingAspect(logger); - loggingAspect.OnEntry(aspectArgs); - // Assert - var updatedConfig = PowertoolsLoggingBuilderExtensions.GetCurrentConfiguration(); - - Assert.Equal("TestService", updatedConfig.Service); - Assert.Equal(LoggerOutputCase.PascalCase, updatedConfig.LoggerOutputCase); - Assert.Equal(LogLevel.Debug, updatedConfig.MinimumLogLevel); - - string consoleOutput = stringWriter.ToString(); - Assert.Contains("Skipping Lambda Context injection because ILambdaContext context parameter not found.", consoleOutput); - - consoleOut.Received(1).WriteLine(Arg.Is(s => + var config = _mockPowertoolsConfigurations.CurrentConfig(); + Assert.NotNull(Logger.LoggerProvider); + Assert.Equal("TestService", config.Service); + Assert.Equal(LoggerOutputCase.PascalCase, config.LoggerOutputCase); + Assert.Equal(LogLevel.Debug, config.MinimumLevel); + + _mockSystemWrapper.Received(1).LogLine(Arg.Is(s => + s == "Skipping Lambda Context injection because ILambdaContext context parameter not found.")); + + _mockSystemWrapper.Received(1).LogLine(Arg.Is(s => s.Contains("\"CorrelationId\":\"test\"") && s.Contains( "\"Message\":{\"FullName\":\"Powertools\",\"Age\":20,\"Headers\":{\"MyRequestIdHeader\":\"test\"}") @@ -457,28 +291,7 @@ public void OnEntry_Should_LogDebug_WhenSet_EnvironmentVariable() public void Dispose() { - ResetAllState(); - } - - private static void ResetAllState() - { - // Clear environment variables - Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_CASE", null); - Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", null); - Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", null); - - // Reset all logging components LoggingAspect.ResetForTest(); - Logger.Reset(); - PowertoolsLoggingBuilderExtensions.ResetAllProviders(); - LoggerFactoryHolder.Reset(); - - // Force default configuration - var config = new PowertoolsLoggerConfiguration - { - MinimumLogLevel = LogLevel.Information, - LoggerOutputCase = LoggerOutputCase.SnakeCase - }; - PowertoolsLoggingBuilderExtensions.UpdateConfiguration(config); + PowertoolsLoggingSerializer.ClearOptions(); } } \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Attributes/LoggingAttributeTest.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Attributes/LoggingAttributeTest.cs index 5fc3e7179..9843c71ef 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Attributes/LoggingAttributeTest.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Attributes/LoggingAttributeTest.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.Collections.Generic; using System.IO; @@ -7,12 +22,10 @@ using Amazon.Lambda.CloudWatchEvents.S3Events; using Amazon.Lambda.TestUtilities; using AWS.Lambda.Powertools.Common; -using AWS.Lambda.Powertools.Common.Core; -using AWS.Lambda.Powertools.Common.Tests; using AWS.Lambda.Powertools.Logging.Internal; +using AWS.Lambda.Powertools.Logging.Serializers; using AWS.Lambda.Powertools.Logging.Tests.Handlers; using AWS.Lambda.Powertools.Logging.Tests.Serializers; -using Microsoft.Extensions.Logging; using NSubstitute; using Xunit; @@ -22,65 +35,77 @@ namespace AWS.Lambda.Powertools.Logging.Tests.Attributes public class LoggingAttributeTests : IDisposable { private TestHandlers _testHandlers; - + public LoggingAttributeTests() { _testHandlers = new TestHandlers(); } - + [Fact] - public void OnEntry_WhenLambdaContextDoesNotExist_IgnoresLambdaContext_No_Debug() + public void OnEntry_WhenLambdaContextDoesNotExist_IgnoresLambdaContext() { // Arrange - var stringWriter = new StringWriter(); - Console.SetOut(stringWriter); + var consoleOut = Substitute.For(); + SystemWrapper.Instance.SetOut(consoleOut); + // Act _testHandlers.TestMethod(); - + // Assert var allKeys = Logger.GetAllKeys() .ToDictionary(keyValuePair => keyValuePair.Key, keyValuePair => keyValuePair.Value); - - Assert.Empty(allKeys); - - var st = stringWriter.ToString(); - Assert.Empty(st); + + Assert.NotNull(Logger.LoggerProvider); + Assert.True(allKeys.ContainsKey(LoggingConstants.KeyColdStart)); + //Assert.True((bool)allKeys[LoggingConstants.KeyColdStart]); + Assert.False(allKeys.ContainsKey(LoggingConstants.KeyFunctionName)); + Assert.False(allKeys.ContainsKey(LoggingConstants.KeyFunctionVersion)); + Assert.False(allKeys.ContainsKey(LoggingConstants.KeyFunctionMemorySize)); + Assert.False(allKeys.ContainsKey(LoggingConstants.KeyFunctionArn)); + Assert.False(allKeys.ContainsKey(LoggingConstants.KeyFunctionRequestId)); + + consoleOut.DidNotReceive().WriteLine(Arg.Any()); } - + [Fact] public void OnEntry_WhenLambdaContextDoesNotExist_IgnoresLambdaContextAndLogDebug() { // Arrange - var consoleOut = GetConsoleOutput(); - var stringWriter = new StringWriter(); - Console.SetOut(stringWriter); - Logger.Configure(options => - { - options.LogOutput = consoleOut; - }); - + var consoleOut = Substitute.For(); + SystemWrapper.Instance.SetOut(consoleOut); + // Act _testHandlers.TestMethodDebug(); - + // Assert var allKeys = Logger.GetAllKeys() .ToDictionary(keyValuePair => keyValuePair.Key, keyValuePair => keyValuePair.Value); - - Assert.Empty(allKeys); - - var st = stringWriter.ToString(); - Assert.Contains("Skipping Lambda Context injection because ILambdaContext context parameter not found", st); + + Assert.NotNull(Logger.LoggerProvider); + Assert.True(allKeys.ContainsKey(LoggingConstants.KeyColdStart)); + //Assert.True((bool)allKeys[LoggingConstants.KeyColdStart]); + Assert.False(allKeys.ContainsKey(LoggingConstants.KeyFunctionName)); + Assert.False(allKeys.ContainsKey(LoggingConstants.KeyFunctionVersion)); + Assert.False(allKeys.ContainsKey(LoggingConstants.KeyFunctionMemorySize)); + Assert.False(allKeys.ContainsKey(LoggingConstants.KeyFunctionArn)); + Assert.False(allKeys.ContainsKey(LoggingConstants.KeyFunctionRequestId)); + + consoleOut.Received(1).WriteLine( + Arg.Is(i => + i == $"Skipping Lambda Context injection because ILambdaContext context parameter not found.") + ); } - + [Fact] public void OnEntry_WhenEventArgDoesNotExist_DoesNotLogEventArg() { // Arrange - var consoleOut = GetConsoleOutput(); - + var consoleOut = Substitute.For(); + SystemWrapper.Instance.SetOut(consoleOut); + // Act _testHandlers.LogEventNoArgs(); - + consoleOut.DidNotReceive().WriteLine( Arg.Any() ); @@ -90,18 +115,17 @@ public void OnEntry_WhenEventArgDoesNotExist_DoesNotLogEventArg() public void OnEntry_WhenEventArgExist_LogEvent() { // Arrange - var consoleOut = GetConsoleOutput(); + var consoleOut = Substitute.For(); + SystemWrapper.Instance.SetOut(consoleOut); var correlationId = Guid.NewGuid().ToString(); - Logger.Configure(options => - { - options.LogOutput = consoleOut; - }); - + + // Add seriolization context for AOT + PowertoolsLoggingSerializer.AddSerializerContext(TestJsonContext.Default); var context = new TestLambdaContext() { FunctionName = "PowertoolsLoggingSample-HelloWorldFunction-Gg8rhPwO7Wa1" }; - + var testObj = new TestObject { Headers = new Header @@ -112,7 +136,7 @@ public void OnEntry_WhenEventArgExist_LogEvent() // Act _testHandlers.LogEvent(testObj, context); - + consoleOut.Received(1).WriteLine( Arg.Is(i => i.Contains("FunctionName\":\"PowertoolsLoggingSample-HelloWorldFunction-Gg8rhPwO7Wa1")) ); @@ -122,8 +146,11 @@ public void OnEntry_WhenEventArgExist_LogEvent() public void OnEntry_WhenEventArgExist_LogEvent_False_Should_Not_Log() { // Arrange - var consoleOut = GetConsoleOutput(); - + var consoleOut = Substitute.For(); + SystemWrapper.Instance.SetOut(consoleOut); + + // Add seriolization context for AOT + PowertoolsLoggingSerializer.AddSerializerContext(TestJsonContext.Default); var context = new TestLambdaContext() { FunctionName = "PowertoolsLoggingSample-HelloWorldFunction-Gg8rhPwO7Wa1" @@ -131,42 +158,41 @@ public void OnEntry_WhenEventArgExist_LogEvent_False_Should_Not_Log() // Act _testHandlers.LogEventFalse(context); - + consoleOut.DidNotReceive().WriteLine( Arg.Any() ); } - + [Fact] public void OnEntry_WhenEventArgDoesNotExist_DoesNotLogEventArgAndLogDebug() { // Arrange - var consoleOut = GetConsoleOutput(); - var stringWriter = new StringWriter(); - Console.SetOut(stringWriter); - Logger.Configure(options => - { - options.LogOutput = consoleOut; - }); - + var consoleOut = Substitute.For(); + SystemWrapper.Instance.SetOut(consoleOut); + // Act _testHandlers.LogEventDebug(); - - // Assert - var st = stringWriter.ToString(); - Assert.Contains("Skipping Event Log because event parameter not found.", st); - Assert.Contains("Skipping Lambda Context injection because ILambdaContext context parameter not found", st); + + consoleOut.Received(1).WriteLine( + Arg.Is(i => i == "Skipping Event Log because event parameter not found.") + ); } - + [Fact] public void OnExit_WhenHandler_ClearState_Enabled_ClearKeys() { + // Arrange + var consoleOut = Substitute.For(); + SystemWrapper.Instance.SetOut(consoleOut); + // Act _testHandlers.ClearState(); - + + Assert.NotNull(Logger.LoggerProvider); Assert.False(Logger.GetAllKeys().Any()); } - + [Theory] [InlineData(CorrelationIdPaths.ApiGatewayRest)] [InlineData(CorrelationIdPaths.ApplicationLoadBalancer)] @@ -176,7 +202,10 @@ public void OnEntry_WhenEventArgExists_CapturesCorrelationId(string correlationI { // Arrange var correlationId = Guid.NewGuid().ToString(); - + + // Add seriolization context for AOT + PowertoolsLoggingSerializer.AddSerializerContext(TestJsonContext.Default); + // Act switch (correlationIdPath) { @@ -214,15 +243,15 @@ public void OnEntry_WhenEventArgExists_CapturesCorrelationId(string correlationI }); break; } - + // Assert var allKeys = Logger.GetAllKeys() .ToDictionary(keyValuePair => keyValuePair.Key, keyValuePair => keyValuePair.Value); - + Assert.True(allKeys.ContainsKey(LoggingConstants.KeyCorrelationId)); Assert.Equal((string)allKeys[LoggingConstants.KeyCorrelationId], correlationId); } - + [Theory] [InlineData(LoggerOutputCase.SnakeCase)] [InlineData(LoggerOutputCase.PascalCase)] @@ -231,7 +260,10 @@ public void When_Capturing_CorrelationId_Converts_To_Case(LoggerOutputCase outpu { // Arrange var correlationId = Guid.NewGuid().ToString(); - + + // Add seriolization context for AOT + PowertoolsLoggingSerializer.AddSerializerContext(TestJsonContext.Default); + // Act switch (outputCase) { @@ -263,11 +295,11 @@ public void When_Capturing_CorrelationId_Converts_To_Case(LoggerOutputCase outpu }); break; } - + // Assert var allKeys = Logger.GetAllKeys() .ToDictionary(keyValuePair => keyValuePair.Key, keyValuePair => keyValuePair.Value); - + Assert.True(allKeys.ContainsKey(LoggingConstants.KeyCorrelationId)); Assert.Equal((string)allKeys[LoggingConstants.KeyCorrelationId], correlationId); } @@ -280,7 +312,10 @@ public void When_Capturing_CorrelationId_Converts_To_Case_From_Environment_Var(L { // Arrange var correlationId = Guid.NewGuid().ToString(); - + + // Add seriolization context for AOT + PowertoolsLoggingSerializer.AddSerializerContext(TestJsonContext.Default); + // Act switch (outputCase) { @@ -314,11 +349,11 @@ public void When_Capturing_CorrelationId_Converts_To_Case_From_Environment_Var(L }); break; } - + // Assert var allKeys = Logger.GetAllKeys() .ToDictionary(keyValuePair => keyValuePair.Key, keyValuePair => keyValuePair.Value); - + Assert.True(allKeys.ContainsKey(LoggingConstants.KeyCorrelationId)); Assert.Equal((string)allKeys[LoggingConstants.KeyCorrelationId], correlationId); } @@ -327,58 +362,42 @@ public void When_Capturing_CorrelationId_Converts_To_Case_From_Environment_Var(L public void When_Setting_SamplingRate_Should_Add_Key() { // Arrange - var consoleOut = GetConsoleOutput(); - Logger.Configure(options => - { - options.LogOutput = consoleOut; - }); - + var consoleOut = Substitute.For(); + SystemWrapper.Instance.SetOut(consoleOut); + // Act _testHandlers.HandlerSamplingRate(); // Assert - consoleOut.Received(1).WriteLine(Arg.Is(s => - s.Contains("\"level\":\"Information\"") && - s.Contains("\"service\":\"service_undefined\"") && - s.Contains("\"name\":\"AWS.Lambda.Powertools.Logging.Logger\"") && - s.Contains("\"message\":\"test\"") && - s.Contains("\"samplingRate\":0.5") - )); + + consoleOut.Received().WriteLine( + Arg.Is(i => i.Contains("\"message\":\"test\",\"samplingRate\":0.5")) + ); } [Fact] public void When_Setting_Service_Should_Update_Key() { // Arrange - var consoleOut = new TestLoggerOutput(); - Logger.Configure(options => - { - options.LogOutput = consoleOut; - }); - + var consoleOut = new StringWriter(); + SystemWrapper.Instance.SetOut(consoleOut); + // Act _testHandlers.HandlerService(); // Assert var st = consoleOut.ToString(); - - Assert.Contains("\"level\":\"Information\"", st); - Assert.Contains("\"service\":\"test\"", st); - Assert.Contains("\"name\":\"AWS.Lambda.Powertools.Logging.Logger\"", st); - Assert.Contains("\"message\":\"test\"", st); + Assert.Contains("\"level\":\"Information\",\"service\":\"test\",\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"message\":\"test\"", st); } [Fact] public void When_Setting_LogLevel_Should_Update_LogLevel() { // Arrange - var consoleOut = new TestLoggerOutput();; - Logger.Configure(options => - { - options.LogOutput = consoleOut; - }); - + var consoleOut = new StringWriter(); + SystemWrapper.Instance.SetOut(consoleOut); + // Act _testHandlers.TestLogLevelCritical(); @@ -392,12 +411,8 @@ public void When_Setting_LogLevel_Should_Update_LogLevel() public void When_Setting_LogLevel_HigherThanInformation_Should_Not_LogEvent() { // Arrange - var consoleOut = GetConsoleOutput(); - Logger.Configure(options => - { - options.LogOutput = consoleOut; - }); - + var consoleOut = Substitute.For(); + SystemWrapper.Instance.SetOut(consoleOut); var context = new TestLambdaContext() { FunctionName = "PowertoolsLoggingSample-HelloWorldFunction-Gg8rhPwO7Wa1" @@ -414,175 +429,101 @@ public void When_Setting_LogLevel_HigherThanInformation_Should_Not_LogEvent() public void When_LogLevel_Debug_Should_Log_Message_When_No_Context_And_LogEvent_True() { // Arrange - var consoleOut = GetConsoleOutput(); - var stringWriter = new StringWriter(); - Console.SetOut(stringWriter); - - Logger.Configure(options => - { - options.LogOutput = consoleOut; - }); + var consoleOut = Substitute.For(); + SystemWrapper.Instance.SetOut(consoleOut); // Act _testHandlers.TestLogEventWithoutContext(); - + // Assert - var st = stringWriter.ToString(); - Assert.Contains("Skipping Event Log because event parameter not found.", st); - Assert.Contains("Skipping Lambda Context injection because ILambdaContext context parameter not found", st); - + consoleOut.Received(1).WriteLine(Arg.Is(s => s == "Skipping Event Log because event parameter not found.")); } [Fact] public void Should_Log_When_Not_Using_Decorator() { // Arrange - var consoleOut = GetConsoleOutput(); - Logger.Configure(options => - { - options.LogOutput = consoleOut; - }); + var consoleOut = Substitute.For(); + SystemWrapper.Instance.SetOut(consoleOut); var test = new TestHandlers(); // Act test.TestLogNoDecorator(); - + // Assert - consoleOut.Received(1).WriteLine(Arg.Is(s => - s.Contains("\"level\":\"Information\"") && - s.Contains("\"service\":\"service_undefined\"") && - s.Contains("\"name\":\"AWS.Lambda.Powertools.Logging.Logger\"") && - s.Contains("\"message\":\"test\"") - )); + consoleOut.Received().WriteLine( + Arg.Is(i => i.Contains("\"level\":\"Information\",\"service\":\"service_undefined\",\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"message\":\"test\"}")) + ); } - [Fact] - public void LoggingAspect_ShouldRespectDynamicLogLevelChanges() + public void Dispose() { - // Arrange - var consoleOut = GetConsoleOutput(); - var stringWriter = new StringWriter(); - Console.SetOut(stringWriter); - - Logger.Configure(options => - { - options.LogOutput = consoleOut; - options.MinimumLogLevel = LogLevel.Warning; - }); - - // Act - _testHandlers.TestMethodDebug(); // Uses LogLevel.Debug attribute - - // Assert - var st = stringWriter.ToString(); - Assert.Contains("Skipping Lambda Context injection because ILambdaContext context parameter not found", st); + Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_CASE", ""); + Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", ""); + LoggingAspect.ResetForTest(); + PowertoolsLoggingSerializer.ClearOptions(); } + } + + [Collection("A Sequential")] + public class ServiceTests : IDisposable + { + private readonly TestServiceHandler _testHandler; - [Fact] - public void LoggingAspect_ShouldCorrectlyResetLogLevelAfterExecution() + public ServiceTests() { - // Arrange - var consoleOut = GetConsoleOutput(); - Logger.Configure(options => - { - options.LogOutput = consoleOut; - options.MinimumLogLevel = LogLevel.Warning; - }); - - // Act - First call with Debug level attribute - _testHandlers.TestMethodDebug(); - consoleOut.ClearReceivedCalls(); - - // Act - Then log directly at Debug level (should still work) - Logger.LogDebug("This should be logged"); - - // Assert - consoleOut.Received(1).WriteLine(Arg.Is(s => - s.Contains("\"level\":\"Debug\"") && - s.Contains("\"message\":\"This should be logged\""))); + _testHandler = new TestServiceHandler(); } [Fact] - public void LoggingAspect_ShouldRespectAttributePrecedenceOverEnvironment() + public void When_Setting_Service_Should_Override_Env() { // Arrange - Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", "Error"); - var consoleOut = GetConsoleOutput(); - var stringWriter = new StringWriter(); - Console.SetOut(stringWriter); - - Logger.Configure(options => - { - options.LogOutput = consoleOut; - }); - + var consoleOut = Substitute.For(); + SystemWrapper.Instance.SetOut(consoleOut); + // Act - _testHandlers.TestMethodDebug(); // Uses LogLevel.Debug attribute - + _testHandler.LogWithEnv(); + _testHandler.Handler(); + // Assert - var st = stringWriter.ToString(); - Assert.Contains("Skipping Lambda Context injection because ILambdaContext context parameter not found", st); + + consoleOut.Received(1).WriteLine( + Arg.Is(i => i.Contains("\"level\":\"Information\",\"service\":\"Environment Service\",\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"message\":\"Service: Environment Service\"")) + ); + consoleOut.Received(1).WriteLine( + Arg.Is(i => i.Contains("\"level\":\"Information\",\"service\":\"Attribute Service\",\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"message\":\"Service: Attribute Service\"")) + ); } [Fact] - public void LoggingAspect_ShouldImmediatelyApplyFilterLevelChanges() + public void When_Setting_Service_Should_Override_Env_And_Empty() { // Arrange - var consoleOut = GetConsoleOutput(); - - Logger.Configure(options => - { - options.LogOutput = consoleOut; - options.MinimumLogLevel = LogLevel.Error; - }); - + var consoleOut = Substitute.For(); + SystemWrapper.Instance.SetOut(consoleOut); + // Act - Logger.LogInformation("This should NOT be logged"); - _testHandlers.TestMethodDebug(); // Should change level to Debug - Logger.LogInformation("This should be logged"); - + _testHandler.LogWithAndWithoutEnv(); + _testHandler.Handler(); + // Assert - consoleOut.Received(1).WriteLine(Arg.Is(s => - s.Contains("\"message\":\"This should be logged\""))); - consoleOut.DidNotReceive().WriteLine(Arg.Is(s => - s.Contains("\"message\":\"This should NOT be logged\""))); + consoleOut.Received(2).WriteLine( + Arg.Is(i => i.Contains("\"level\":\"Information\",\"service\":\"service_undefined\",\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"message\":\"Service: service_undefined\"")) + ); + consoleOut.Received(1).WriteLine( + Arg.Is(i => i.Contains("\"level\":\"Information\",\"service\":\"Attribute Service\",\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"message\":\"Service: Attribute Service\"")) + ); } - + public void Dispose() { - ResetAllState(); - } - - private IConsoleWrapper GetConsoleOutput() - { - // Create a new mock each time - var output = Substitute.For(); - return output; - } - - private void ResetAllState() - { - // Clear environment variables - Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_CASE", null); - Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", null); - Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", null); - - // Reset all logging components + Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_CASE", ""); + Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", ""); LoggingAspect.ResetForTest(); - Logger.Reset(); - PowertoolsLoggingBuilderExtensions.ResetAllProviders(); - LoggerFactoryHolder.Reset(); - - // Force default configuration - var config = new PowertoolsLoggerConfiguration - { - MinimumLogLevel = LogLevel.Information, - LoggerOutputCase = LoggerOutputCase.SnakeCase - }; - PowertoolsLoggingBuilderExtensions.UpdateConfiguration(config); - LambdaLifecycleTracker.Reset(); + PowertoolsLoggingSerializer.ClearOptions(); } } } \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Attributes/ServiceTests.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Attributes/ServiceTests.cs deleted file mode 100644 index 657730e00..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Attributes/ServiceTests.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System; -using AWS.Lambda.Powertools.Common; -using AWS.Lambda.Powertools.Logging.Internal; -using AWS.Lambda.Powertools.Logging.Tests.Handlers; -using NSubstitute; -using Xunit; - -namespace AWS.Lambda.Powertools.Logging.Tests.Attributes; - -[Collection("A Sequential")] -public class ServiceTests : IDisposable -{ - private readonly TestServiceHandler _testHandler; - - public ServiceTests() - { - _testHandler = new TestServiceHandler(); - } - - [Fact] - public void When_Setting_Service_Should_Override_Env() - { - Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", "Environment Service"); - - var consoleOut = Substitute.For(); - Logger.Configure(options => - options.LogOutput = consoleOut); - - // Act - _testHandler.LogWithEnv(); - _testHandler.Handler(); - - // Assert - - consoleOut.Received(1).WriteLine(Arg.Is(i => - i.Contains("\"level\":\"Information\"") && - i.Contains("\"service\":\"Environment Service\"") && - i.Contains("\"name\":\"AWS.Lambda.Powertools.Logging.Logger\"") && - i.Contains("\"message\":\"Service: Environment Service\"") - )); - consoleOut.Received(1).WriteLine(Arg.Is(i => - i.Contains("\"level\":\"Information\"") && - i.Contains("\"service\":\"Attribute Service\"") && - i.Contains("\"name\":\"AWS.Lambda.Powertools.Logging.Logger\"") && - i.Contains("\"message\":\"Service: Attribute Service\"") - )); - } - - public void Dispose() - { - Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_CASE", ""); - Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", ""); - LoggingAspect.ResetForTest(); - Logger.Reset(); - PowertoolsLoggingBuilderExtensions.ResetAllProviders(); - } -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Buffering/LambdaContextBufferingTests.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Buffering/LambdaContextBufferingTests.cs deleted file mode 100644 index b6172371c..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Buffering/LambdaContextBufferingTests.cs +++ /dev/null @@ -1,543 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using System.Threading.Tasks; -using Amazon.Lambda.Core; -using Amazon.Lambda.TestUtilities; -using AWS.Lambda.Powertools.Common.Tests; -using AWS.Lambda.Powertools.Logging.Internal; -using Microsoft.Extensions.Logging; -using Xunit; -using Xunit.Abstractions; -using LogLevel = Microsoft.Extensions.Logging.LogLevel; - -namespace AWS.Lambda.Powertools.Logging.Tests.Buffering -{ - [Collection("Sequential")] - public class LambdaContextBufferingTests : IDisposable - { - private readonly ITestOutputHelper _output; - private readonly TestLoggerOutput _consoleOut; - - public LambdaContextBufferingTests(ITestOutputHelper output) - { - _output = output; - _consoleOut = new TestLoggerOutput(); - LogBufferManager.ResetForTesting(); - } - - [Fact] - public void FlushOnErrorEnabled_AutomaticallyFlushesBuffer() - { - // Arrange - var logger = CreateLoggerWithFlushOnError(true); - var handler = new ErrorOnlyHandler(logger); - var context = CreateTestContext("test-request-3"); - - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-invocation"); - // Act - handler.TestMethod("Event", context); - - // Assert - var output = _consoleOut.ToString(); - Assert.Contains("Debug message", output); - Assert.Contains("Error triggering flush", output); - } - - [Fact] - public void Decorator_Clears_Buffer_On_Exit() - { - // Arrange - var logger = CreateLoggerWithFlushOnError(false); - var handler = new NoFlushHandler(logger); - var context = CreateTestContext("test-request-3"); - - // Act - handler.TestMethod("Event", context); - - // Assert - var output = _consoleOut.ToString(); - Assert.DoesNotContain("Debug message", output); - - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-request-3"); - Logger.FlushBuffer(); - - var debugNotFlushed = _consoleOut.ToString(); - Assert.DoesNotContain("Debug message", debugNotFlushed); - - // second event - handler.TestMethod("Event", context); - - // Assert - var output2 = _consoleOut.ToString(); - Assert.DoesNotContain("Debug message", output2); - - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-request-4"); - Logger.FlushBuffer(); - - var debugNotFlushed2 = _consoleOut.ToString(); - Assert.DoesNotContain("Debug message", debugNotFlushed2); - } - - [Fact] - public async Task AsyncOperations_MaintainBufferContext() - { - // Arrange - var logger = CreateLogger(LogLevel.Information, LogLevel.Debug); - var handler = new AsyncLambdaHandler(logger); - var context = CreateTestContext("async-test"); - - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-invocation"); - - // Act - await handler.TestMethodAsync("Event", context); - - // Assert - var output = _consoleOut.ToString(); - Assert.Contains("Async info message", output); - Assert.Contains("Debug from task 1", output); - Assert.Contains("Debug from task 2", output); - } - - [Fact] - public async Task Should_Log_All_Levels_Bellow() - { - // Arrange - var logger = CreateLogger(LogLevel.Information, LogLevel.Information); - var handler = new AsyncLambdaHandler(logger); - var context = CreateTestContext("async-test"); - - // Act - await handler.TestMethodAsync("Event", context); - - // Assert - var output = _consoleOut.ToString(); - Assert.Contains("Async info message", output); - Assert.Contains("Async debug message", output); - Assert.Contains("Async trace message", output); - Assert.Contains("Async warning message", output); - Assert.Contains("Debug from task 1", output); - Assert.Contains("Debug from task 2", output); - } - - private TestLambdaContext CreateTestContext(string requestId) - { - return new TestLambdaContext - { - FunctionName = "test-function", - FunctionVersion = "1", - AwsRequestId = requestId, - InvokedFunctionArn = "arn:aws:lambda:us-east-1:123456789012:function:test-function" - }; - } - - private ILogger CreateLogger(LogLevel minimumLevel, LogLevel bufferAtLevel) - { - return LoggerFactory.Create(builder => - { - builder.AddPowertoolsLogger(config => - { - config.Service = "test-service"; - config.MinimumLogLevel = minimumLevel; - config.LogOutput = _consoleOut; - config.LogBuffering = new LogBufferingOptions - { - BufferAtLogLevel = bufferAtLevel - }; - }); - }).CreatePowertoolsLogger(); - } - - private ILogger CreateLoggerWithFlushOnError(bool flushOnError) - { - return LoggerFactory.Create(builder => - { - builder.AddPowertoolsLogger(config => - { - config.Service = "test-service"; - config.MinimumLogLevel = LogLevel.Information; - config.LogOutput = _consoleOut; - config.LogBuffering = new LogBufferingOptions - { - BufferAtLogLevel = LogLevel.Debug, - FlushOnErrorLog = flushOnError - }; - }); - }).CreatePowertoolsLogger(); - } - - public void Dispose() - { - Logger.ClearBuffer(); - LogBufferManager.ResetForTesting(); - Logger.Reset(); - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", null); - } - } - - - [Collection("Sequential")] - [SuppressMessage("Usage", "xUnit1031:Do not use blocking task operations in test method")] - public class StaticLoggerBufferingTests : IDisposable - { - private readonly TestLoggerOutput _consoleOut; - private readonly ITestOutputHelper _output; - - public StaticLoggerBufferingTests(ITestOutputHelper output) - { - _output = output; - _consoleOut = new TestLoggerOutput(); - - // Configure static Logger with our test output - Logger.Configure(options => - options.LogOutput = _consoleOut); - } - - [Fact] - public void StaticLogger_BasicBufferingBehavior() - { - // Arrange - explicitly configure Logger for this test - // First reset any existing configuration - Logger.Reset(); - - // Configure the logger with the test output - Logger.Configure(options => - { - options.LogOutput = _consoleOut; - options.MinimumLogLevel = LogLevel.Information; - options.LogBuffering = new LogBufferingOptions - { - - BufferAtLogLevel = LogLevel.Debug, - FlushOnErrorLog = false // Disable auto-flush to test manual flush - }; - }); - - // Set invocation ID manually - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-static-request-1"); - - // Act - log messages - Logger.AppendKey("custom-key", "custom-value"); - Logger.LogInformation("Information message"); - Logger.LogDebug("Debug message"); // Should be buffered - - // Check the internal state before flush - var outputBeforeFlush = _consoleOut.ToString(); - _output.WriteLine($"Before flush: {outputBeforeFlush}"); - Assert.DoesNotContain("Debug message", outputBeforeFlush); - - // Flush the buffer - Logger.FlushBuffer(); - - // Assert after flush - var outputAfterFlush = _consoleOut.ToString(); - _output.WriteLine($"After flush: {outputAfterFlush}"); - Assert.Contains("Debug message", outputAfterFlush); - } - - [Fact] - public void StaticLogger_WithLoggingDecoratedHandler() - { - // Arrange - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "invocation-1"); - Logger.Configure(options => - { - options.LogOutput = _consoleOut; - options.LogBuffering = new LogBufferingOptions - { - - BufferAtLogLevel = LogLevel.Debug, - FlushOnErrorLog = true - }; - }); - - var handler = new StaticLambdaHandler(); - var context = new TestLambdaContext - { - AwsRequestId = "test-static-request-2", - FunctionName = "test-function" - }; - - // Act - handler.TestMethod("test-event", context); - - // Assert - var output = _consoleOut.ToString(); - Assert.Contains("Information message", output); - Assert.Contains("Debug message", output); - Assert.Contains("Error message", output); - Assert.Contains("custom-key", output); - Assert.Contains("custom-value", output); - } - - [Fact] - public void StaticLogger_ClearBufferRemovesLogs() - { - // Arrange - Logger.Configure(options => - { - options.LogOutput = _consoleOut; - options.MinimumLogLevel = LogLevel.Information; - options.LogBuffering = new LogBufferingOptions - { - - BufferAtLogLevel = LogLevel.Debug - }; - }); - - // Set invocation ID - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-static-request-3"); - - // Act - log message and clear buffer - Logger.LogDebug("Debug message before clear"); - Logger.ClearBuffer(); - Logger.LogDebug("Debug message after clear"); - Logger.FlushBuffer(); - - // Assert - var output = _consoleOut.ToString(); - Assert.DoesNotContain("Debug message before clear", output); - Assert.Contains("Debug message after clear", output); - } - - [Fact] - public void StaticLogger_FlushOnErrorLogEnabled() - { - // Arrange - Logger.Configure(options => - { - options.LogOutput = _consoleOut; - options.MinimumLogLevel = LogLevel.Information; - options.LogBuffering = new LogBufferingOptions - { - - BufferAtLogLevel = LogLevel.Debug, - FlushOnErrorLog = true - }; - }); - - // Set invocation ID - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-static-request-4"); - - // Act - log debug then error - Logger.LogDebug("Debug message"); - Logger.LogError("Error message"); - - // Assert - error should trigger flush - var output = _consoleOut.ToString(); - Assert.Contains("Debug message", output); - Assert.Contains("Error message", output); - } - - [Fact] - public void StaticLogger_MultipleInvocationsIsolated_And_Clear() - { - // Arrange - Logger.Configure(options => - { - options.LogOutput = _consoleOut; - options.MinimumLogLevel = LogLevel.Information; - options.LogBuffering = new LogBufferingOptions - { - - BufferAtLogLevel = LogLevel.Debug - }; - }); - - // Act - first invocation - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-static-request-5A"); - Logger.LogDebug("Debug from invocation A"); - - // Switch to second invocation - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-static-request-5B"); - Logger.LogDebug("Debug from invocation B"); - - // Switch back to first invocation and flush - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-static-request-5C"); - Logger.LogDebug("Debug from invocation C"); - - Logger.FlushBuffer(); - - // Assert - after second flush - var outputAfterSecondFlush = _consoleOut.ToString(); - Assert.DoesNotContain("Debug from invocation A", outputAfterSecondFlush); - Assert.DoesNotContain("Debug from invocation B", outputAfterSecondFlush); - Assert.Contains("Debug from invocation C", outputAfterSecondFlush); - } - - [Fact] - public void StaticLogger_FlushOnErrorDisabled() - { - // Arrange - Logger.Reset(); - Logger.Configure(options => - { - options.LogOutput = _consoleOut; - options.MinimumLogLevel = LogLevel.Information; - options.LogBuffering = new LogBufferingOptions - { - BufferAtLogLevel = LogLevel.Debug, - FlushOnErrorLog = false - }; - }); - - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-static-request-6"); - - // Act - log debug then error - Logger.LogDebug("Debug message with auto-flush disabled"); - Logger.LogError("Error message that should not trigger flush"); - - // Assert - debug message should remain buffered - var output = _consoleOut.ToString(); - Assert.DoesNotContain("Debug message with auto-flush disabled", output); - Assert.Contains("Error message that should not trigger flush", output); - - // Now manually flush and verify debug message appears - Logger.FlushBuffer(); - output = _consoleOut.ToString(); - Assert.Contains("Debug message with auto-flush disabled", output); - } - - [Fact] - public void StaticLogger_AsyncOperationsMaintainContext() - { - // Arrange - // Logger.Reset(); - Logger.Configure(options => - { - options.LogOutput = _consoleOut; - options.MinimumLogLevel = LogLevel.Information; - options.LogBuffering = new LogBufferingOptions - { - BufferAtLogLevel = LogLevel.Debug, - FlushOnErrorLog = false - }; - }); - - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-static-request-8"); - - // Act - simulate async operations - Task.Run(() => { Logger.LogDebug("Debug from task 1"); }).Wait(); - - Task.Run(() => { Logger.LogDebug("Debug from task 2"); }).Wait(); - - Logger.LogInformation("Main thread info message"); - - // Flush buffers - Logger.FlushBuffer(); - - // Assert - var output = _consoleOut.ToString(); - Assert.Contains("Debug from task 1", output); - Assert.Contains("Debug from task 2", output); - Assert.Contains("Main thread info message", output); - } - - public void Dispose() - { - // Clean up all state between tests - Logger.ClearBuffer(); - LogBufferManager.ResetForTesting(); - LoggerFactoryHolder.Reset(); - _consoleOut.Clear(); - Logger.Reset(); - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", null); - } - } - - public class StaticLambdaHandler - { - [Logging(LogEvent = true)] - public void TestMethod(string message, ILambdaContext lambdaContext) - { - Logger.AppendKey("custom-key", "custom-value"); - Logger.LogInformation("Information message"); - Logger.LogDebug("Debug message"); - Logger.LogError("Error message"); - Logger.FlushBuffer(); - } - } - - // Lambda handlers for testing - public class LambdaHandler - { - private readonly ILogger _logger; - - public LambdaHandler(ILogger logger) - { - _logger = logger; - } - - [Logging(LogEvent = true)] - public void TestMethod(string message, ILambdaContext lambdaContext) - { - _logger.AppendKey("custom-key", "custom-value"); - _logger.LogInformation("Information message"); - _logger.LogDebug("Debug message"); - _logger.LogError("Error message"); - _logger.FlushBuffer(); - } - } - - public class ErrorOnlyHandler - { - private readonly ILogger _logger; - - public ErrorOnlyHandler(ILogger logger) - { - _logger = logger; - } - - [Logging(LogEvent = true)] - public void TestMethod(string message, ILambdaContext lambdaContext) - { - _logger.LogDebug("Debug message"); - _logger.LogError("Error triggering flush"); - } - } - - public class NoFlushHandler - { - private readonly ILogger _logger; - - public NoFlushHandler(ILogger logger) - { - _logger = logger; - } - - [Logging(LogEvent = true)] - public void TestMethod(string message, ILambdaContext lambdaContext) - { - _logger.LogDebug("Debug message"); - _logger.LogError("Error triggering flush"); - // No flush here - Decorator clears buffer on exit - } - } - - public class AsyncLambdaHandler - { - private readonly ILogger _logger; - - public AsyncLambdaHandler(ILogger logger) - { - _logger = logger; - } - - [Logging(LogEvent = true)] - public async Task TestMethodAsync(string message, ILambdaContext lambdaContext) - { - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-invocation"); - - _logger.LogInformation("Async info message"); - _logger.LogDebug("Async debug message"); - _logger.LogTrace("Async trace message"); - _logger.LogWarning("Async warning message"); - - var task1 = Task.Run(() => { _logger.LogDebug("Debug from task 1"); }); - - var task2 = Task.Run(() => { _logger.LogDebug("Debug from task 2"); }); - - await Task.WhenAll(task1, task2); - _logger.FlushBuffer(); - } - } -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Buffering/LogBufferCircularCacheTests.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Buffering/LogBufferCircularCacheTests.cs deleted file mode 100644 index c0640c928..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Buffering/LogBufferCircularCacheTests.cs +++ /dev/null @@ -1,270 +0,0 @@ -using System; -using System.IO; -using AWS.Lambda.Powertools.Common; -using AWS.Lambda.Powertools.Common.Tests; -using AWS.Lambda.Powertools.Logging.Internal; -using AWS.Lambda.Powertools.Logging.Internal.Helpers; -using Microsoft.Extensions.Logging; -using Xunit; - -namespace AWS.Lambda.Powertools.Logging.Tests.Buffering; - -public class LogBufferCircularCacheTests : IDisposable -{ - private readonly TestLoggerOutput _consoleOut; - - public LogBufferCircularCacheTests() - { - _consoleOut = new TestLoggerOutput(); - LogBufferManager.ResetForTesting(); - } - - [Trait("Category", "CircularBuffer")] - [Fact] - public void Buffer_WhenMaxSizeExceeded_DiscardOldestEntries() - { - // Arrange - var config = new PowertoolsLoggerConfiguration - { - MinimumLogLevel = LogLevel.Information, - LogBuffering = new LogBufferingOptions - { - BufferAtLogLevel = LogLevel.Debug, - MaxBytes = 1200 // Small buffer size to trigger overflow - Needs to be adjusted based on the log message size - }, - LogOutput = _consoleOut - }; - - var logger = LoggerFactoryHelper.CreateAndConfigureFactory(config).CreatePowertoolsLogger(); - - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "circular-buffer-test"); - - // Act - add many debug logs to fill buffer - for (int i = 0; i < 5; i++) - { - logger.LogDebug($"Old debug message {i} that should be removed"); - } - - // Add more logs that should push out the older ones - for (int i = 0; i < 5; i++) - { - logger.LogDebug($"New debug message {i} that should remain"); - } - - // Flush buffer - logger.FlushBuffer(); - - // Assert - var output = _consoleOut.ToString(); - - // First entries should be discarded - Assert.DoesNotContain("Old debug message 0", output); - Assert.DoesNotContain("Old debug message 1", output); - - // Later entries should be present - Assert.Contains("New debug message 3", output); - Assert.Contains("New debug message 4", output); - } - - [Trait("Category", "CircularBuffer")] - [Fact] - public void Buffer_WhenMaxSizeExceeded_DiscardOldestEntries_Warn() - { - // Arrange - var config = new PowertoolsLoggerConfiguration - { - MinimumLogLevel = LogLevel.Information, - LogBuffering = new LogBufferingOptions - { - BufferAtLogLevel = LogLevel.Debug, - MaxBytes = 1024 // Small buffer size to trigger overflow - }, - LogOutput = _consoleOut - }; - - var logger = LoggerFactoryHelper.CreateAndConfigureFactory(config).CreatePowertoolsLogger(); - - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "circular-buffer-test"); - - var stringWriter = new StringWriter(); - Console.SetOut(stringWriter); - - // Act - add many debug logs to fill buffer - for (int i = 0; i < 5; i++) - { - logger.LogDebug($"Old debug message {i} that should be removed"); - } - - // Add more logs that should push out the older ones - for (int i = 0; i < 5; i++) - { - logger.LogDebug($"New debug message {i} that should remain"); - } - - // Flush buffer - logger.FlushBuffer(); - - // Assert - var st = stringWriter.ToString(); - Assert.Contains("Some logs are not displayed because they were evicted from the buffer. Increase buffer size to store more logs in the buffer", st); - } - - [Trait("Category", "CircularBuffer")] - [Fact] - public void Buffer_WhenMaxSizeExceeded_DiscardOldestEntries_Warn_With_Warning_Level() - { - // Arrange - var config = new PowertoolsLoggerConfiguration - { - MinimumLogLevel = LogLevel.Information, - LogBuffering = new LogBufferingOptions - { - BufferAtLogLevel = LogLevel.Warning, - MaxBytes = 1024 // Small buffer size to trigger overflow - }, - LogOutput = _consoleOut - }; - - var logger = LoggerFactoryHelper.CreateAndConfigureFactory(config).CreatePowertoolsLogger(); - - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "circular-buffer-test"); - - var stringWriter = new StringWriter(); - Console.SetOut(stringWriter); - - // Act - add many debug logs to fill buffer - for (int i = 0; i < 5; i++) - { - logger.LogDebug($"Old debug message {i} that should be removed"); - } - - // Add more logs that should push out the older ones - for (int i = 0; i < 5; i++) - { - logger.LogDebug($"New debug message {i} that should remain"); - } - - // Flush buffer - logger.FlushBuffer(); - - // Assert - var st = stringWriter.ToString(); - Assert.Contains("Some logs are not displayed because they were evicted from the buffer. Increase buffer size to store more logs in the buffer", st); - } - - [Trait("Category", "CircularBuffer")] - [Fact] - public void Buffer_WithLargeLogEntry_DiscardsManySmallEntries() - { - // Arrange - var config = new PowertoolsLoggerConfiguration - { - MinimumLogLevel = LogLevel.Information, - LogBuffering = new LogBufferingOptions - { - BufferAtLogLevel = LogLevel.Debug, - MaxBytes = 2048 // Small buffer size to trigger overflow - }, - LogOutput = _consoleOut - }; - - var logger = LoggerFactoryHelper.CreateAndConfigureFactory(config).CreatePowertoolsLogger(); - - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "large-entry-test"); - - // Act - add many small entries first - for (int i = 0; i < 10; i++) - { - logger.LogDebug($"Small message {i}"); - } - - // Add one very large entry that should displace many small ones - var largeMessage = new string('X', 80); // Large enough to push out multiple small entries - logger.LogDebug($"Large message: {largeMessage}"); - - // Flush buffer - logger.FlushBuffer(); - - // Assert - var output = _consoleOut.ToString(); - - // Several early small messages should be discarded - for (int i = 0; i < 5; i++) - { - Assert.DoesNotContain($"Small message {i}", output); - } - - // Large message should be present - Assert.Contains("Large message: XXXX", output); - - // Some later small messages should remain - Assert.Contains("Small message 9", output); - } - - [Trait("Category", "CircularBuffer")] - [Fact] - public void Buffer_WithExtremelyLargeEntry_Logs_Directly_And_Warning() - { - // Arrange - var config = new PowertoolsLoggerConfiguration - { - MinimumLogLevel = LogLevel.Information, - LogBuffering = new LogBufferingOptions - { - BufferAtLogLevel = LogLevel.Debug, - MaxBytes = 5096 // Even with a larger buffer - }, - LogOutput = _consoleOut - }; - - var logger = LoggerFactoryHelper.CreateAndConfigureFactory(config).CreatePowertoolsLogger(); - - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "extreme-entry-test"); - - var stringWriter = new StringWriter(); - Console.SetOut(stringWriter); - - // Act - add some small entries first - for (int i = 0; i < 4; i++) - { - logger.LogDebug($"Initial message {i}"); - } - - // Add entry larger than the entire buffer - should displace everything - var hugeMessage = new string('X', 3000); - logger.LogDebug($"Huge message: {hugeMessage}"); - - var st = stringWriter.ToString(); - Assert.Contains("Cannot add item to the buffer", st); - - // Add more entries after - for (int i = 0; i < 4; i++) - { - logger.LogDebug($"Final message {i}"); - } - - // Flush buffer - logger.FlushBuffer(); - - // Assert - var output = _consoleOut.ToString(); - - // Initial messages should be discarded - for (int i = 0; i < 4; i++) - { - Assert.Contains($"Initial message {i}", output); - } - - // Some of the final messages should be present - Assert.Contains("Final message 3", output); - } - - public void Dispose() - { - // Clean up all state between tests - Logger.ClearBuffer(); - LogBufferManager.ResetForTesting(); - Logger.Reset(); - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", null); - } -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Buffering/LogBufferingHandlerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Buffering/LogBufferingHandlerTests.cs deleted file mode 100644 index cbe954111..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Buffering/LogBufferingHandlerTests.cs +++ /dev/null @@ -1,342 +0,0 @@ -using System; -using System.Threading.Tasks; -using AWS.Lambda.Powertools.Common; -using AWS.Lambda.Powertools.Common.Tests; -using AWS.Lambda.Powertools.Logging.Internal; -using Microsoft.Extensions.Logging; -using Xunit; -using Xunit.Abstractions; - -namespace AWS.Lambda.Powertools.Logging.Tests.Buffering -{ - [Collection("Sequential")] - public class LogBufferingHandlerTests : IDisposable - { - private readonly ITestOutputHelper _output; - private readonly TestLoggerOutput _consoleOut; - - public LogBufferingHandlerTests(ITestOutputHelper output) - { - _output = output; - _consoleOut = new TestLoggerOutput(); - LogBufferManager.ResetForTesting(); - } - - [Fact] - public void BasicBufferingBehavior_BuffersDebugLogsOnly() - { - // Arrange - var logger = CreateLogger(LogLevel.Information, LogLevel.Debug); - var handler = new HandlerWithoutFlush(logger); // Use a handler that doesn't flush - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-invocation"); - - // Act - log messages without flushing - handler.TestMethod(); - - // Assert - before flush - var outputBeforeFlush = _consoleOut.ToString(); - Assert.Contains("Information message", outputBeforeFlush); - Assert.Contains("Error message", outputBeforeFlush); - Assert.Contains("custom-key", outputBeforeFlush); - Assert.Contains("custom-value", outputBeforeFlush); - Assert.DoesNotContain("Debug message", outputBeforeFlush); // Debug should be buffered - - // Now flush the buffer - Logger.FlushBuffer(); - - // Assert - after flush - var outputAfterFlush = _consoleOut.ToString(); - Assert.Contains("Debug message", outputAfterFlush); // Debug should now be present - } - - [Fact] - public void FlushOnErrorEnabled_AutomaticallyFlushesBuffer() - { - // Arrange - var logger = CreateLoggerWithFlushOnError(true); - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-invocation"); - - // Act - with custom handler that doesn't manually flush - var handler = new CustomHandlerWithoutFlush(logger); - handler.TestMethod(); - - // Assert - var output = _consoleOut.ToString(); - Assert.Contains("Debug message", output); // Should be flushed by error log - Assert.Contains("Error triggering flush", output); - } - - [Fact] - public void FlushOnErrorDisabled_DoesNotAutomaticallyFlushBuffer() - { - // Arrange - var logger = CreateLoggerWithFlushOnError(false); - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-invocation"); - - // Act - var handler = new CustomHandlerWithoutFlush(logger); - handler.TestMethod(); - - // Assert - var output = _consoleOut.ToString(); - Assert.DoesNotContain("Debug message", output); // Should remain buffered - Assert.Contains("Error triggering flush", output); - } - - [Fact] - public void ClearingBuffer_RemovesBufferedLogs() - { - // Arrange - var logger = CreateLogger(LogLevel.Information, LogLevel.Debug); - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-invocation"); - - // Act - var handler = new ClearBufferHandler(logger); - handler.TestMethod(); - - // Assert - var output = _consoleOut.ToString(); - Assert.DoesNotContain("Debug message before clear", output); - Assert.Contains("Debug message after clear", output); - } - - [Fact] - public void MultipleInvocations_IsolateLogBuffers() - { - // Arrange - var logger = CreateLogger(LogLevel.Information, LogLevel.Debug); - var handler = new Handlers(logger); - - // Act - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "invocation-1"); - handler.TestMethod(); - - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "invocation-2"); - // Create a custom handler that logs different messages - var customHandler = new MultipleInvocationHandler(logger); - customHandler.TestMethod(); - - // Assert - var output = _consoleOut.ToString(); - Assert.Contains("Information message", output); // From first invocation - Assert.Contains("Second invocation info", output); // From second invocation - } - - [Fact] - public void MultipleProviders_AllProvidersReceiveLogs() - { - // Arrange - var config = new PowertoolsLoggerConfiguration - { - MinimumLogLevel = LogLevel.Information, - LogBuffering = new LogBufferingOptions { BufferAtLogLevel = LogLevel.Debug }, - LogOutput = _consoleOut - }; - - var powertoolsConfig = new PowertoolsConfigurations(new PowertoolsEnvironment()); - - // Create two separate providers - var provider1 = new BufferingLoggerProvider(config, powertoolsConfig); - var provider2 = new BufferingLoggerProvider(config, powertoolsConfig); - - var logger1 = provider1.CreateLogger("Provider1"); - var logger2 = provider2.CreateLogger("Provider2"); - - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "multi-provider-test"); - - // Act - logger1.LogDebug("Debug from provider 1"); - logger2.LogDebug("Debug from provider 2"); - - // Flush logs from all providers - Logger.FlushBuffer(); - - // Assert - var output = _consoleOut.ToString(); - Assert.Contains("Debug from provider 1", output); - Assert.Contains("Debug from provider 2", output); - } - - [Fact] - public async Task AsyncOperations_MaintainBufferContext() - { - // Arrange - var logger = CreateLogger(LogLevel.Information, LogLevel.Debug); - var handler = new AsyncHandler(logger); - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "async-test"); - - // Act - await handler.TestMethodAsync(); - - // Assert - var output = _consoleOut.ToString(); - Assert.Contains("Async info message", output); - Assert.Contains("Debug from task 1", output); - Assert.Contains("Debug from task 2", output); - } - - private ILogger CreateLogger(LogLevel minimumLevel, LogLevel bufferAtLevel) - { - return LoggerFactory.Create(builder => - { - builder.AddPowertoolsLogger(config => - { - config.Service = "test-service"; - config.MinimumLogLevel = minimumLevel; - config.LogOutput = _consoleOut; - config.LogBuffering = new LogBufferingOptions - { - BufferAtLogLevel = bufferAtLevel, - FlushOnErrorLog = false - }; - }); - }).CreatePowertoolsLogger(); - } - - private ILogger CreateLoggerWithFlushOnError(bool flushOnError) - { - return LoggerFactory.Create(builder => - { - builder.AddPowertoolsLogger(config => - { - config.Service = "test-service"; - config.MinimumLogLevel = LogLevel.Information; - config.LogOutput = _consoleOut; - config.LogBuffering = new LogBufferingOptions - { - BufferAtLogLevel = LogLevel.Debug, - FlushOnErrorLog = flushOnError - }; - }); - }).CreatePowertoolsLogger(); - } - - public void Dispose() - { - // Clean up all state between tests - Logger.ClearBuffer(); - LogBufferManager.ResetForTesting(); - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", null); - } - } - - // Additional test handlers with specific behavior - public class CustomHandlerWithoutFlush - { - private readonly ILogger _logger; - - public CustomHandlerWithoutFlush(ILogger logger) - { - _logger = logger; - } - - public void TestMethod() - { - _logger.LogDebug("Debug message"); - _logger.LogError("Error triggering flush"); - // No manual flush - } - } - - public class ClearBufferHandler - { - private readonly ILogger _logger; - - public ClearBufferHandler(ILogger logger) - { - _logger = logger; - } - - public void TestMethod() - { - _logger.LogDebug("Debug message before clear"); - Logger.ClearBuffer(); // Clear the buffer - _logger.LogDebug("Debug message after clear"); - Logger.FlushBuffer(); // Flush only second message - } - } - - public class MultipleInvocationHandler - { - private readonly ILogger _logger; - - public MultipleInvocationHandler(ILogger logger) - { - _logger = logger; - } - - public void TestMethod() - { - _logger.LogInformation("Second invocation info"); - _logger.LogDebug("Second invocation debug"); - _logger.FlushBuffer(); - } - } - - public class Handlers - { - private readonly ILogger _logger; - - public Handlers(ILogger logger) - { - _logger = logger; - } - - public void TestMethod() - { - _logger.AppendKey("custom-key", "custom-value"); - _logger.LogInformation("Information message"); - _logger.LogDebug("Debug message"); - - _logger.LogError("Error message"); - - _logger.FlushBuffer(); - } - } - - public class HandlerWithoutFlush - { - private readonly ILogger _logger; - - public HandlerWithoutFlush(ILogger logger) - { - _logger = logger; - } - - public void TestMethod() - { - _logger.AppendKey("custom-key", "custom-value"); - _logger.LogInformation("Information message"); - _logger.LogDebug("Debug message"); - _logger.LogError("Error message"); - // No flush here - } - } - - public class AsyncHandler - { - private readonly ILogger _logger; - - public AsyncHandler(ILogger logger) - { - _logger = logger; - } - - public async Task TestMethodAsync() - { - _logger.LogInformation("Async info message"); - _logger.LogDebug("Async debug message"); - - var task1 = Task.Run(() => { - _logger.LogDebug("Debug from task 1"); - }); - - var task2 = Task.Run(() => { - _logger.LogDebug("Debug from task 2"); - }); - - await Task.WhenAll(task1, task2); - _logger.FlushBuffer(); - } - } -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Buffering/LogBufferingTests.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Buffering/LogBufferingTests.cs deleted file mode 100644 index 00d1e759a..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Buffering/LogBufferingTests.cs +++ /dev/null @@ -1,499 +0,0 @@ -using System; -using AWS.Lambda.Powertools.Common; -using AWS.Lambda.Powertools.Common.Tests; -using AWS.Lambda.Powertools.Logging.Internal; -using AWS.Lambda.Powertools.Logging.Internal.Helpers; -using Microsoft.Extensions.Logging; -using Xunit; - -namespace AWS.Lambda.Powertools.Logging.Tests.Buffering -{ - [Collection("Sequential")] - public class LogBufferingTests : IDisposable - { - private readonly TestLoggerOutput _consoleOut; - - public LogBufferingTests() - { - _consoleOut = new TestLoggerOutput(); - } - - [Trait("Category", "BufferManager")] - [Fact] - public void SetInvocationId_IsolatesLogsBetweenInvocations_And_Clear() - { - // Arrange - var config = new PowertoolsLoggerConfiguration - { - LogBuffering = new LogBufferingOptions(), - LogOutput = _consoleOut - }; - - var logger = LoggerFactoryHelper.CreateAndConfigureFactory(config).CreatePowertoolsLogger(); - - // Act - - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "invocation-1"); - logger.LogDebug("Debug message from invocation 1"); - - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "invocation-2"); - logger.LogDebug("Debug message from invocation 2"); - - logger.FlushBuffer(); - - // Assert - var output = _consoleOut.ToString(); - Assert.DoesNotContain("Debug message from invocation 1", output); - Assert.Contains("Debug message from invocation 2", output); - } - - [Trait("Category", "BufferedLogger")] - [Fact] - public void BufferedLogger_OnlyBuffersConfiguredLogLevels() - { - // Arrange - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "invocation-1"); - - var config = new PowertoolsLoggerConfiguration - { - MinimumLogLevel = LogLevel.Information, - LogBuffering = new LogBufferingOptions - { - BufferAtLogLevel = LogLevel.Trace - }, - LogOutput = _consoleOut - }; - var logger = LoggerFactoryHelper.CreateAndConfigureFactory(config).CreatePowertoolsLogger(); - - // Act - logger.LogTrace("Trace message"); // should buffer - logger.LogDebug("Debug message"); // Should be buffered - logger.LogInformation("Info message"); // Above minimum, should be logged directly - - // Assert - var output = _consoleOut.ToString(); - Assert.DoesNotContain("Trace message", output); - Assert.DoesNotContain("Debug message", output); // Not flushed yet - Assert.Contains("Info message", output); - - // Flush the buffer - Logger.FlushBuffer(); - - output = _consoleOut.ToString(); - Assert.Contains("Trace message", output); // Now should be visible - } - - [Trait("Category", "BufferedLogger")] - [Fact] - public void BufferedLogger_Buffer_Takes_Precedence_Same_Level() - { - // Arrange - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "invocation-1"); - - var config = new PowertoolsLoggerConfiguration - { - MinimumLogLevel = LogLevel.Information, - LogBuffering = new LogBufferingOptions - { - BufferAtLogLevel = LogLevel.Information - }, - LogOutput = _consoleOut - }; - var logger = LoggerFactoryHelper.CreateAndConfigureFactory(config).CreatePowertoolsLogger(); - - // Act - logger.LogTrace("Trace message"); // Below buffer threshold, should be ignored - logger.LogDebug("Debug message"); // Should be buffered - logger.LogInformation("Info message"); // Above minimum, should be logged directly - - // Assert - var output = _consoleOut.ToString(); - Assert.Empty(output); - - // Flush the buffer - Logger.FlushBuffer(); - - output = _consoleOut.ToString(); - Assert.Contains("Info message", output); // Now should be visible - Assert.Contains("Debug message", output); // Now should be visible - Assert.Contains("Trace message", output); // Now should be visible - } - - [Trait("Category", "BufferedLogger")] - [Fact] - public void BufferedLogger_Buffer_Takes_Precedence_Higher_Level() - { - // Arrange - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "invocation-1"); - - var config = new PowertoolsLoggerConfiguration - { - MinimumLogLevel = LogLevel.Information, - LogBuffering = new LogBufferingOptions - { - BufferAtLogLevel = LogLevel.Warning - }, - LogOutput = _consoleOut - }; - var logger = LoggerFactoryHelper.CreateAndConfigureFactory(config).CreatePowertoolsLogger(); - - // Act - logger.LogWarning("Warning message"); // Should be buffered - logger.LogInformation("Info message"); // Should be buffered - logger.LogDebug("Debug message"); - - // Assert - var output = _consoleOut.ToString(); - Assert.Empty(output); - - // Flush the buffer - Logger.FlushBuffer(); - - output = _consoleOut.ToString(); - Assert.Contains("Info message", output); // Now should be visible - Assert.Contains("Warning message", output); - Assert.Contains("Debug message", output); - } - - [Trait("Category", "BufferedLogger")] - [Fact] - public void BufferedLogger_Buffer_Log_Level_Error_Does_Not_Buffer() - { - // Arrange - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "invocation-1"); - - var config = new PowertoolsLoggerConfiguration - { - MinimumLogLevel = LogLevel.Information, - LogBuffering = new LogBufferingOptions - { - BufferAtLogLevel = LogLevel.Error - }, - LogOutput = _consoleOut - }; - var logger = LoggerFactoryHelper.CreateAndConfigureFactory(config).CreatePowertoolsLogger(); - - // Act - logger.LogError("Error message"); // Should be buffered - logger.LogInformation("Info message"); // Should be buffered - - // Assert - var output = _consoleOut.ToString(); - Assert.Contains("Error message", output); - Assert.Contains("Info message", output); - } - - [Trait("Category", "BufferedLogger")] - [Fact] - public void FlushOnErrorLog_FlushesBufferWhenEnabled() - { - // Arrange - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "invocation-1"); - var config = new PowertoolsLoggerConfiguration - { - MinimumLogLevel = LogLevel.Information, - LogBuffering = new LogBufferingOptions - { - BufferAtLogLevel = LogLevel.Debug, - FlushOnErrorLog = true - }, - LogOutput = _consoleOut - }; - - var logger = LoggerFactoryHelper.CreateAndConfigureFactory(config).CreatePowertoolsLogger(); - - // Act - logger.LogDebug("Debug message 1"); // Should be buffered - logger.LogDebug("Debug message 2"); // Should be buffered - logger.LogError("Error message"); // Should trigger flush of buffer - - // Assert - var output = _consoleOut.ToString(); - Assert.Contains("Debug message 1", output); - Assert.Contains("Debug message 2", output); - Assert.Contains("Error message", output); - } - - [Trait("Category", "BufferedLogger")] - [Fact] - public void ClearBuffer_RemovesAllBufferedLogs() - { - // Arrange - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "invocation-1"); - var config = new PowertoolsLoggerConfiguration - { - MinimumLogLevel = LogLevel.Information, - LogBuffering = new LogBufferingOptions - { - BufferAtLogLevel = LogLevel.Debug - }, - LogOutput = _consoleOut - }; - - var logger = LoggerFactoryHelper.CreateAndConfigureFactory(config).CreatePowertoolsLogger(); - - - // Act - logger.LogDebug("Debug message 1"); // Should be buffered - logger.LogDebug("Debug message 2"); // Should be buffered - - Logger.ClearBuffer(); // Should clear all buffered logs - Logger.FlushBuffer(); // No logs should be output - - logger.LogDebug("Debug message 3"); // Should be buffered - Logger.FlushBuffer(); // Should output debug message 3 - - // Assert - var output = _consoleOut.ToString(); - Assert.DoesNotContain("Debug message 1", output); - Assert.DoesNotContain("Debug message 2", output); - Assert.Contains("Debug message 3", output); - } - - [Trait("Category", "BufferedLogger")] - [Fact] - public void BufferSizeLimit_DiscardOldestEntriesWhenExceeded() - { - // Arrange - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "invocation-1"); - var config = new PowertoolsLoggerConfiguration - { - MinimumLogLevel = LogLevel.Information, - LogBuffering = new LogBufferingOptions - { - BufferAtLogLevel = LogLevel.Debug, - MaxBytes = 1000 // Small buffer size to force overflow - }, - LogOutput = _consoleOut - }; - - var logger = LoggerFactoryHelper.CreateAndConfigureFactory(config).CreatePowertoolsLogger(); - - // Act - // Add enough logs to exceed buffer size - for (int i = 0; i < 20; i++) - { - logger.LogDebug($"Debug message {i} with enough characters to consume space in the buffer"); - } - - Logger.FlushBuffer(); - - // Assert - var output = _consoleOut.ToString(); - Assert.DoesNotContain("Debug message 0", output); // Older messages should be discarded - Assert.Contains("Debug message 19", output); // Newest messages should be kept - } - - [Trait("Category", "LoggerLifecycle")] - [Fact] - public void DisposingProvider_FlushesBufferedLogs() - { - // Arrange - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "invocation-1"); - var config = new PowertoolsLoggerConfiguration - { - MinimumLogLevel = LogLevel.Information, - LogBuffering = new LogBufferingOptions - { - BufferAtLogLevel = LogLevel.Debug - }, - LogOutput = _consoleOut - }; - - var provider = LoggerFactoryHelper.CreateAndConfigureFactory(config); - var logger = provider.CreatePowertoolsLogger(); - - // Act - logger.LogDebug("Debug message before disposal"); // Should be buffered - provider.Dispose(); // Should flush buffer - - // Assert - var output = _consoleOut.ToString(); - Assert.Contains("Debug message before disposal", output); - } - - [Trait("Category", "LoggerConfiguration")] - [Fact] - public void LoggerInitialization_RegistersWithBufferManager() - { - // Arrange - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-id"); - var config = new PowertoolsLoggerConfiguration - { - LogBuffering = new LogBufferingOptions(), - LogOutput = _consoleOut - }; - - var logger = LoggerFactoryHelper.CreateAndConfigureFactory(config).CreatePowertoolsLogger(); - - logger.LogDebug("Test message"); - Logger.FlushBuffer(); - - // Assert - var output = _consoleOut.ToString(); - Assert.Contains("Test message", output); - } - - [Trait("Category", "LoggerOutput")] - [Fact] - public void CustomLogOutput_ReceivesLogs() - { - // Arrange - var customOutput = new TestLoggerOutput(); - var config = new PowertoolsLoggerConfiguration - { - MinimumLogLevel = LogLevel.Debug, // Set to Debug to ensure we log directly - LogOutput = customOutput - }; - - var logger = LoggerFactoryHelper.CreateAndConfigureFactory(config).CreatePowertoolsLogger(); - - // Act - logger.LogDebug("Direct debug message"); - - // Assert - var output = customOutput.ToString(); - Assert.Contains("Direct debug message", output); - } - - [Trait("Category", "LoggerIntegration")] - [Fact] - public void RegisteringMultipleProviders_AllWorkCorrectly() - { - // Arrange - create a clean configuration for this test - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "shared-invocation"); - var config = new PowertoolsLoggerConfiguration - { - MinimumLogLevel = LogLevel.Information, - LogBuffering = new LogBufferingOptions - { - BufferAtLogLevel = LogLevel.Debug - }, - LogOutput = _consoleOut - }; - - PowertoolsLoggingBuilderExtensions.UpdateConfiguration(config); - - // Create providers using the shared configuration - var env = new PowertoolsEnvironment(); - var powertoolsConfig = new PowertoolsConfigurations(env); - - var provider1 = new BufferingLoggerProvider(config, powertoolsConfig); - var provider2 = new BufferingLoggerProvider(config, powertoolsConfig); - - var logger1 = provider1.CreateLogger("Logger1"); - var logger2 = provider2.CreateLogger("Logger2"); - - // Act - logger1.LogDebug("Debug from logger1"); - logger2.LogDebug("Debug from logger2"); - Logger.FlushBuffer(); - - // Assert - var output = _consoleOut.ToString(); - Assert.Contains("Debug from logger1", output); - Assert.Contains("Debug from logger2", output); - } - - [Trait("Category", "LoggerLifecycle")] - [Fact] - public void RegisteringLogBufferManager_HandlesMultipleProviders() - { - // Ensure we start with clean state - LogBufferManager.ResetForTesting(); - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-invocation"); - // Arrange - var config = new PowertoolsLoggerConfiguration - { - LogBuffering = new LogBufferingOptions(), - LogOutput = _consoleOut - }; - - var powertoolsConfig = new PowertoolsConfigurations(new PowertoolsEnvironment()); - - // Create and register first provider - var provider1 = new BufferingLoggerProvider(config, powertoolsConfig); - var logger1 = provider1.CreateLogger("Logger1"); - // Explicitly dispose and unregister first provider - provider1.Dispose(); - - // Now create and register a second provider - var provider2 = new BufferingLoggerProvider(config, powertoolsConfig); - var logger2 = provider2.CreateLogger("Logger2"); - - // Act - logger1.LogDebug("Debug from first provider"); - logger2.LogDebug("Debug from second provider"); - - // Only the second provider should be registered with the LogBufferManager - Logger.FlushBuffer(); - - // Assert - var output = _consoleOut.ToString(); - // Only the second provider's logs should be flushed - Assert.DoesNotContain("Debug from first provider", output); - Assert.Contains("Debug from second provider", output); - } - - [Trait("Category", "BufferEmpty")] - [Fact] - public void FlushingEmptyBuffer_DoesNotCauseErrors() - { - // Arrange - LogBufferManager.ResetForTesting(); - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "empty-test"); - var config = new PowertoolsLoggerConfiguration - { - LogBuffering = new LogBufferingOptions(), - LogOutput = _consoleOut - }; - var powertoolsConfig = new PowertoolsConfigurations(new PowertoolsEnvironment()); - var provider = new BufferingLoggerProvider(config, powertoolsConfig); - - // Act - flush without any logs - Logger.FlushBuffer(); - - // Assert - should not throw exceptions - Assert.Empty(_consoleOut.ToString()); - } - - [Trait("Category", "LogLevelThreshold")] - [Fact] - public void LogsAtExactBufferThreshold_AreBuffered() - { - // Arrange - LogBufferManager.ResetForTesting(); - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "threshold-test"); - var config = new PowertoolsLoggerConfiguration - { - MinimumLogLevel = LogLevel.Information, - LogBuffering = new LogBufferingOptions - { - BufferAtLogLevel = LogLevel.Debug - }, - LogOutput = _consoleOut - }; - var powertoolsConfig = new PowertoolsConfigurations(new PowertoolsEnvironment()); - var provider = new BufferingLoggerProvider(config, powertoolsConfig); - var logger = provider.CreateLogger("TestLogger"); - - // Act - logger.LogDebug("Debug message exactly at threshold"); // Should be buffered - - // Assert before flush - Assert.DoesNotContain("Debug message exactly at threshold", _consoleOut.ToString()); - - // After flush - Logger.FlushBuffer(); - Assert.Contains("Debug message exactly at threshold", _consoleOut.ToString()); - } - - public void Dispose() - { - // Clean up all state between tests - Logger.ClearBuffer(); - LogBufferManager.ResetForTesting(); - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", null); - } - } -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Context/LambdaContextTest.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Context/LambdaContextTest.cs index 31e980ba8..feb9283e9 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Context/LambdaContextTest.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Context/LambdaContextTest.cs @@ -56,33 +56,6 @@ public void Extract_WhenHasLambdaContextArgument_InitializesLambdaContextInfo() Assert.Null(LoggingLambdaContext.Instance); } - [Fact] - public void Extract_When_LambdaContext_Is_Null_But_Not_First_Parameter_Returns_False() - { - // Arrange - ILambdaContext lambdaContext = null; - var args = Substitute.For(); - var method = Substitute.For(); - var parameter1 = Substitute.For(); - var parameter2 = Substitute.For(); - - // Setup parameters - parameter1.ParameterType.Returns(typeof(string)); - parameter2.ParameterType.Returns(typeof(ILambdaContext)); - - // Setup method - method.GetParameters().Returns(new[] { parameter1, parameter2 }); - - // Setup args - args.Method = method; - args.Args = new object[] { "requestContext", lambdaContext }; - - // Act && Assert - LoggingLambdaContext.Clear(); - Assert.Null(LoggingLambdaContext.Instance); - Assert.False(LoggingLambdaContext.Extract(args)); - } - [Fact] public void Extract_When_Args_Null_Returns_False() { diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/FactoryTests.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/FactoryTests.cs deleted file mode 100644 index c113ff070..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/FactoryTests.cs +++ /dev/null @@ -1,148 +0,0 @@ -using AWS.Lambda.Powertools.Logging.Internal; -using Microsoft.Extensions.Logging; -using NSubstitute; -using Xunit; - -namespace AWS.Lambda.Powertools.Logging.Tests; - -public class LoggingAspectFactoryTests -{ - [Fact] - public void GetInstance_ShouldReturnLoggingAspectInstance() - { - // Act - var result = LoggingAspectFactory.GetInstance(typeof(LoggingAspectFactoryTests)); - - // Assert - Assert.NotNull(result); - Assert.IsType(result); - } -} - -public class PowertoolsLoggerFactoryTests - { - [Fact] - public void Constructor_WithLoggerFactory_CreatesPowertoolsLoggerFactory() - { - // Arrange - var mockFactory = Substitute.For(); - - // Act - var factory = new PowertoolsLoggerFactory(mockFactory); - - // Assert - Assert.NotNull(factory); - } - - [Fact] - public void DefaultConstructor_CreatesPowertoolsLoggerFactory() - { - // Act - var factory = new PowertoolsLoggerFactory(); - - // Assert - Assert.NotNull(factory); - } - - [Fact] - public void Create_WithConfigAction_ReturnsPowertoolsLoggerFactory() - { - // Act - var factory = PowertoolsLoggerFactory.Create(options => - { - options.Service = "TestService"; - }); - - // Assert - Assert.NotNull(factory); - } - - [Fact] - public void Create_WithConfiguration_ReturnsLoggerFactory() - { - // Arrange - var configuration = new PowertoolsLoggerConfiguration - { - Service = "TestService" - }; - - // Act - var factory = PowertoolsLoggerFactory.Create(configuration); - - // Assert - Assert.NotNull(factory); - } - - [Fact] - public void CreateBuilder_ReturnsLoggerBuilder() - { - // Act - var builder = PowertoolsLoggerFactory.CreateBuilder(); - - // Assert - Assert.NotNull(builder); - Assert.IsType(builder); - } - - [Fact] - public void CreateLogger_Generic_ReturnsLogger() - { - // Arrange - var mockFactory = Substitute.For(); - mockFactory.CreateLogger(Arg.Any()).Returns(Substitute.For()); - var factory = new PowertoolsLoggerFactory(mockFactory); - - // Act - var logger = factory.CreateLogger(); - - // Assert - Assert.NotNull(logger); - mockFactory.Received(1).CreateLogger(typeof(PowertoolsLoggerFactoryTests).FullName); - } - - [Fact] - public void CreateLogger_WithCategory_ReturnsLogger() - { - // Arrange - var mockFactory = Substitute.For(); - mockFactory.CreateLogger("TestCategory").Returns(Substitute.For()); - var factory = new PowertoolsLoggerFactory(mockFactory); - - // Act - var logger = factory.CreateLogger("TestCategory"); - - // Assert - Assert.NotNull(logger); - mockFactory.Received(1).CreateLogger("TestCategory"); - } - - [Fact] - public void CreatePowertoolsLogger_ReturnsPowertoolsLogger() - { - // Arrange - var mockFactory = Substitute.For(); - mockFactory.CreatePowertoolsLogger().Returns(Substitute.For()); - var factory = new PowertoolsLoggerFactory(mockFactory); - - // Act - var logger = factory.CreatePowertoolsLogger(); - - // Assert - Assert.NotNull(logger); - mockFactory.Received(1).CreatePowertoolsLogger(); - } - - [Fact] - public void Dispose_DisposesInnerFactory() - { - // Arrange - var mockFactory = Substitute.For(); - var factory = new PowertoolsLoggerFactory(mockFactory); - - // Act - factory.Dispose(); - - // Assert - mockFactory.Received(1).Dispose(); - } - } \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Formatter/LogFormatterTest.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Formatter/LogFormatterTest.cs index 85250bed1..a1f055f9b 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Formatter/LogFormatterTest.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Formatter/LogFormatterTest.cs @@ -1,5 +1,21 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Reflection; using System.Text.Json; @@ -10,8 +26,6 @@ using AWS.Lambda.Powertools.Logging.Internal; using AWS.Lambda.Powertools.Logging.Serializers; using AWS.Lambda.Powertools.Logging.Tests.Handlers; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; using NSubstitute; using NSubstitute.ExceptionExtensions; using NSubstitute.ReturnsExtensions; @@ -33,12 +47,8 @@ public LogFormatterTest() [Fact] public void Serialize_ShouldHandleEnumValues() { - var consoleOut = Substitute.For(); - Logger.Configure(options => - { - options.LogOutput = consoleOut; - }); - + var consoleOut = Substitute.For(); + SystemWrapper.Instance.SetOut(consoleOut); var lambdaContext = new TestLambdaContext { FunctionName = "funtionName", @@ -58,7 +68,7 @@ public void Serialize_ShouldHandleEnumValues() i.Contains("\"message\":\"Dog\"") )); - var json = JsonSerializer.Serialize(Pet.Dog, new PowertoolsLoggingSerializer().GetSerializerOptions()); + var json = JsonSerializer.Serialize(Pet.Dog, PowertoolsLoggingSerializer.GetSerializerOptions()); Assert.Contains("Dog", json); } @@ -97,6 +107,13 @@ public void Log_WhenCustomFormatter_LogsCustomFormat() var configurations = Substitute.For(); configurations.Service.Returns(service); + var loggerConfiguration = new LoggerConfiguration + { + Service = service, + MinimumLevel = minimumLevel, + LoggerOutputCase = LoggerOutputCase.PascalCase + }; + var globalExtraKeys = new Dictionary { { Guid.NewGuid().ToString(), Guid.NewGuid().ToString() }, @@ -156,20 +173,12 @@ public void Log_WhenCustomFormatter_LogsCustomFormat() } }; - var systemWrapper = Substitute.For(); logFormatter.FormatLogEntry(new LogEntry()).ReturnsForAnyArgs(formattedLogEntry); - - var config = new PowertoolsLoggerConfiguration - { - Service = service, - MinimumLogLevel = minimumLevel, - LoggerOutputCase = LoggerOutputCase.PascalCase, - LogFormatter = logFormatter, - LogOutput = systemWrapper - }; + Logger.UseFormatter(logFormatter); + var systemWrapper = Substitute.For(); - var provider = new PowertoolsLoggerProvider(config, configurations); + var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); var logger = provider.CreateLogger(loggerName); var scopeExtraKeys = new Dictionary @@ -179,7 +188,7 @@ public void Log_WhenCustomFormatter_LogsCustomFormat() }; // Act - logger.LogInformation(message, scopeExtraKeys); + logger.LogInformation(scopeExtraKeys, message); // Assert logFormatter.Received(1).FormatLogEntry(Arg.Is @@ -212,20 +221,14 @@ public void Log_WhenCustomFormatter_LogsCustomFormat() x.LambdaContext.AwsRequestId == lambdaContext.AwsRequestId )); - systemWrapper.Received(1).WriteLine(JsonSerializer.Serialize(formattedLogEntry)); + systemWrapper.Received(1).LogLine(JsonSerializer.Serialize(formattedLogEntry)); } [Fact] public void Should_Log_CustomFormatter_When_Decorated() { - ResetAllState(); - var consoleOut = Substitute.For(); - Logger.Configure(options => - { - options.LogOutput = consoleOut; - options.LogFormatter = new CustomLogFormatter(); - }); - + var consoleOut = Substitute.For(); + SystemWrapper.Instance.SetOut(consoleOut); var lambdaContext = new TestLambdaContext { FunctionName = "funtionName", @@ -235,38 +238,24 @@ public void Should_Log_CustomFormatter_When_Decorated() MemoryLimitInMB = 128 }; - // Logger.UseFormatter(new CustomLogFormatter()); + Logger.UseFormatter(new CustomLogFormatter()); _testHandler.TestCustomFormatterWithDecorator("test", lambdaContext); // serializer works differently in .net 8 and AOT. In .net 6 it writes properties that have null // in .net 8 it removes null properties -#if NET8_0_OR_GREATER consoleOut.Received(1).WriteLine( Arg.Is(i => i.Contains( "\"correlation_ids\":{\"aws_request_id\":\"requestId\"},\"lambda_function\":{\"name\":\"funtionName\",\"arn\":\"function::arn\",\"memory_limit_in_mb\":128,\"version\":\"version\",\"cold_start\":true},\"level\":\"Information\"")) ); -#else - consoleOut.Received(1).WriteLine( - Arg.Is(i => - i.Contains( - "{\"message\":\"test\",\"service\":\"my_service\",\"correlation_ids\":{\"aws_request_id\":\"requestId\",\"x_ray_trace_id\":null,\"correlation_id\":null},\"lambda_function\":{\"name\":\"funtionName\",\"arn\":\"function::arn\",\"memory_limit_in_m_b\":128,\"version\":\"version\",\"cold_start\":true},\"level\":\"Information\",\"timestamp\":\"2024-01-01T00:00:00.0000000\",\"logger\":{\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"sample_rate\"")) - ); -#endif } [Fact] public void Should_Log_CustomFormatter_When_No_Decorated_Just_Log() { - ResetAllState(); - var consoleOut = Substitute.For(); - Logger.Configure(options => - { - options.LogOutput = consoleOut; - options.LogFormatter = new CustomLogFormatter(); - }); - + var consoleOut = Substitute.For(); + SystemWrapper.Instance.SetOut(consoleOut); var lambdaContext = new TestLambdaContext { FunctionName = "funtionName", @@ -276,82 +265,44 @@ public void Should_Log_CustomFormatter_When_No_Decorated_Just_Log() MemoryLimitInMB = 128 }; - // Logger.UseFormatter(new CustomLogFormatter()); + Logger.UseFormatter(new CustomLogFormatter()); _testHandler.TestCustomFormatterNoDecorator("test", lambdaContext); // serializer works differently in .net 8 and AOT. In .net 6 it writes properties that have null // in .net 8 it removes null properties -#if NET8_0_OR_GREATER consoleOut.Received(1).WriteLine( Arg.Is(i => i == "{\"message\":\"test\",\"service\":\"service_undefined\",\"correlation_ids\":{},\"lambda_function\":{\"cold_start\":true},\"level\":\"Information\",\"timestamp\":\"2024-01-01T00:00:00.0000000\",\"logger\":{\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"sample_rate\":0}}") ); -#else - consoleOut.Received(1).WriteLine( - Arg.Is(i => - i == - "{\"message\":\"test\",\"service\":\"service_undefined\",\"correlation_ids\":{\"aws_request_id\":null,\"x_ray_trace_id\":null,\"correlation_id\":null},\"lambda_function\":{\"name\":null,\"arn\":null,\"memory_limit_in_m_b\":null,\"version\":null,\"cold_start\":true},\"level\":\"Information\",\"timestamp\":\"2024-01-01T00:00:00.0000000\",\"logger\":{\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"sample_rate\":0}}") - ); -#endif } [Fact] public void Should_Log_CustomFormatter_When_Decorated_No_Context() { - var consoleOut = Substitute.For(); - Logger.Configure(options => - { - options.LogOutput = consoleOut; - options.LogFormatter = new CustomLogFormatter(); - }); - - // Logger.UseFormatter(new CustomLogFormatter()); + var consoleOut = Substitute.For(); + SystemWrapper.Instance.SetOut(consoleOut); + + Logger.UseFormatter(new CustomLogFormatter()); _testHandler.TestCustomFormatterWithDecoratorNoContext("test"); -#if NET8_0_OR_GREATER consoleOut.Received(1).WriteLine( Arg.Is(i => i == "{\"message\":\"test\",\"service\":\"my_service\",\"correlation_ids\":{},\"lambda_function\":{\"cold_start\":true},\"level\":\"Information\",\"timestamp\":\"2024-01-01T00:00:00.0000000\",\"logger\":{\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"sample_rate\":0.2}}") ); -#else - consoleOut.Received(1).WriteLine( - Arg.Is(i => - i == - "{\"message\":\"test\",\"service\":\"my_service\",\"correlation_ids\":{\"aws_request_id\":null,\"x_ray_trace_id\":null,\"correlation_id\":null},\"lambda_function\":{\"name\":null,\"arn\":null,\"memory_limit_in_m_b\":null,\"version\":null,\"cold_start\":true},\"level\":\"Information\",\"timestamp\":\"2024-01-01T00:00:00.0000000\",\"logger\":{\"name\":\"AWS.Lambda.Powertools.Logging.Logger\",\"sample_rate\":0.2}}") - ); -#endif } public void Dispose() { - ResetAllState(); - } - - private static void ResetAllState() - { - // Clear environment variables - Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_CASE", null); - Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", null); - Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", null); - - // Reset all logging components + Logger.UseDefaultFormatter(); + Logger.RemoveAllKeys(); + LoggingLambdaContext.Clear(); LoggingAspect.ResetForTest(); - Logger.Reset(); - PowertoolsLoggingBuilderExtensions.ResetAllProviders(); - LoggerFactoryHolder.Reset(); - - // Force default configuration - var config = new PowertoolsLoggerConfiguration - { - MinimumLogLevel = LogLevel.Information, - LoggerOutputCase = LoggerOutputCase.SnakeCase - }; - PowertoolsLoggingBuilderExtensions.UpdateConfiguration(config); + PowertoolsLoggingSerializer.ClearOptions(); } } @@ -375,16 +326,15 @@ public void Log_WhenCustomFormatterReturnNull_ThrowsLogFormatException() logFormatter.FormatLogEntry(new LogEntry()).ReturnsNullForAnyArgs(); Logger.UseFormatter(logFormatter); - var systemWrapper = Substitute.For(); - var config = new PowertoolsLoggerConfiguration + var systemWrapper = Substitute.For(); + var loggerConfiguration = new LoggerConfiguration { Service = service, - MinimumLogLevel = LogLevel.Information, - LoggerOutputCase = LoggerOutputCase.PascalCase, - LogFormatter = logFormatter + MinimumLevel = LogLevel.Information, + LoggerOutputCase = LoggerOutputCase.PascalCase }; - var provider = new PowertoolsLoggerProvider(config, configurations); + var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); var logger = provider.CreateLogger(loggerName); // Act @@ -393,7 +343,7 @@ public void Log_WhenCustomFormatterReturnNull_ThrowsLogFormatException() // Assert Assert.Throws(Act); logFormatter.Received(1).FormatLogEntry(Arg.Any()); - systemWrapper.DidNotReceiveWithAnyArgs().WriteLine(Arg.Any()); + systemWrapper.DidNotReceiveWithAnyArgs().LogLine(Arg.Any()); //Clean up Logger.UseDefaultFormatter(); @@ -419,17 +369,17 @@ public void Log_WhenCustomFormatterRaisesException_ThrowsLogFormatException() var logFormatter = Substitute.For(); logFormatter.FormatLogEntry(new LogEntry()).ThrowsForAnyArgs(new Exception(errorMessage)); + Logger.UseFormatter(logFormatter); - var systemWrapper = Substitute.For(); - var config = new PowertoolsLoggerConfiguration + var systemWrapper = Substitute.For(); + var loggerConfiguration = new LoggerConfiguration { Service = service, - MinimumLogLevel = LogLevel.Information, - LoggerOutputCase = LoggerOutputCase.PascalCase, - LogFormatter = logFormatter + MinimumLevel = LogLevel.Information, + LoggerOutputCase = LoggerOutputCase.PascalCase }; - var provider = new PowertoolsLoggerProvider(config, configurations); + var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); var logger = provider.CreateLogger(loggerName); // Act @@ -438,7 +388,7 @@ public void Log_WhenCustomFormatterRaisesException_ThrowsLogFormatException() // Assert Assert.Throws(Act); logFormatter.Received(1).FormatLogEntry(Arg.Any()); - systemWrapper.DidNotReceiveWithAnyArgs().WriteLine(Arg.Any()); + systemWrapper.DidNotReceiveWithAnyArgs().LogLine(Arg.Any()); //Clean up Logger.UseDefaultFormatter(); diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Formatter/LogFormattingTests.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Formatter/LogFormattingTests.cs deleted file mode 100644 index 46a5a459e..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Formatter/LogFormattingTests.cs +++ /dev/null @@ -1,735 +0,0 @@ -using System; -using System.Collections.Generic; -using AWS.Lambda.Powertools.Common.Core; -using AWS.Lambda.Powertools.Common.Tests; -using AWS.Lambda.Powertools.Logging.Tests.Handlers; -using Microsoft.Extensions.Logging; -using Xunit; -using Xunit.Abstractions; - -namespace AWS.Lambda.Powertools.Logging.Tests.Formatter -{ - [Collection("Sequential")] - public class LogFormattingTests - { - private readonly ITestOutputHelper _output; - - public LogFormattingTests(ITestOutputHelper output) - { - _output = output; - } - - [Fact] - public void TestNumericFormatting() - { - // Set culture for thread and format provider - var originalCulture = System.Threading.Thread.CurrentThread.CurrentCulture; - System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US"); - - - var output = new TestLoggerOutput(); - var logger = LoggerFactory.Create(builder => - { - builder.AddPowertoolsLogger(config => - { - config.Service = "format-test-service"; - config.MinimumLogLevel = LogLevel.Debug; - config.LoggerOutputCase = LoggerOutputCase.SnakeCase; - config.TimestampFormat = "yyyy-MM-dd HH:mm:ss.fff"; - config.LogOutput = output; - }); - }).CreatePowertoolsLogger(); - - // Test numeric format specifiers - logger.LogInformation("Price: {price:0.00}", 123.4567); - logger.LogInformation("Percentage: {percent:0.0%}", 0.1234); - // Use explicit dollar sign instead of culture-dependent 'C' - // The logger explicitly uses InvariantCulture when formatting values, which uses "¤" as the currency symbol. - // This is by design to ensure consistent logging output regardless of server culture settings. - // By using $ directly in the format string as shown above, you bypass the culture-specific currency symbol and get the expected output in your tests. - logger.LogInformation("Currency: {amount:$#,##0.00}", 42.5); - - logger.LogInformation("Hex: {hex:X}", 255); - - var logOutput = output.ToString(); - _output.WriteLine(logOutput); - - // These should all be properly formatted in the log - Assert.Contains("\"price\":123.46", logOutput); - Assert.Contains("\"percent\":\"12.3%\"", logOutput); - Assert.Contains("\"amount\":\"$42.50\"", logOutput); - Assert.Contains("\"hex\":\"FF\"", logOutput); - } - - [Fact] - public void TestCustomObjectFormatting() - { - var output = new TestLoggerOutput(); - var logger = LoggerFactory.Create(builder => - { - builder.AddPowertoolsLogger(config => - { - config.Service = "object-format-service"; - config.MinimumLogLevel = LogLevel.Debug; - config.LoggerOutputCase = LoggerOutputCase.CamelCase; - config.TimestampFormat = "yyyy-MM-dd HH:mm:ss.fff"; - config.LogOutput = output; - }); - }).CreatePowertoolsLogger(); - - var user = new User - { - FirstName = "John", - LastName = "Doe", - Age = 42 - }; - - // Regular object formatting (uses ToString()) - logger.LogInformation("User data: {user}", user); - - // Object serialization with @ prefix - logger.LogInformation("User object: {@user}", user); - - var logOutput = output.ToString(); - _output.WriteLine(logOutput); - - // First log should use ToString() - Assert.Contains("\"message\":\"User data: Doe, John (42)\"", logOutput); - Assert.Contains("\"user\":\"Doe, John (42)\"", logOutput); - - // Second log should serialize the object - Assert.Contains("\"user\":{", logOutput); - Assert.Contains("\"firstName\":\"John\"", logOutput); - Assert.Contains("\"lastName\":\"Doe\"", logOutput); - Assert.Contains("\"age\":42", logOutput); - } - - [Fact] - public void TestComplexObjectWithIgnoredProperties() - { - var output = new TestLoggerOutput(); - var logger = LoggerFactory.Create(builder => - { - builder.AddPowertoolsLogger(config => - { - config.Service = "complex-object-service"; - config.MinimumLogLevel = LogLevel.Debug; - config.LoggerOutputCase = LoggerOutputCase.SnakeCase; - config.TimestampFormat = "yyyy-MM-dd HH:mm:ss.fff"; - config.LogOutput = output; - }); - }).CreatePowertoolsLogger(); - - var example = new ExampleClass - { - Name = "test", - Price = 1.999, - ThisIsBig = "big", - ThisIsHidden = "hidden" - }; - - // Test with @ prefix for serialization - logger.LogInformation("Example serialized: {@example}", example); - - var logOutput = output.ToString(); - _output.WriteLine(logOutput); - - // Should serialize the object properties - Assert.Contains("\"example\":{", logOutput); - Assert.Contains("\"name\":\"test\"", logOutput); - Assert.Contains("\"price\":1.999", logOutput); - Assert.Contains("\"this_is_big\":\"big\"", logOutput); - - // The JsonIgnore property should be excluded - Assert.DoesNotContain("this_is_hidden", logOutput); - } - - [Fact] - public void TestMixedFormatting() - { - var output = new TestLoggerOutput(); - var logger = LoggerFactory.Create(builder => - { - builder.AddPowertoolsLogger(config => - { - config.Service = "mixed-format-service"; - config.MinimumLogLevel = LogLevel.Debug; - config.LoggerOutputCase = LoggerOutputCase.PascalCase; - config.TimestampFormat = "yyyy-MM-dd HH:mm:ss.fff"; - config.LogOutput = output; - }); - }).CreatePowertoolsLogger(); - - var user = new User - { - FirstName = "Jane", - LastName = "Smith", - Age = 35 - }; - - // Mix regular values with formatted values and objects - logger.LogInformation( - "Details: User={@user}, Price={price:$#,##0.00}, Date={date:yyyy-MM-dd}", - user, - 123.45, - new DateTime(2023, 4, 5) - ); - - var logOutput = output.ToString(); - _output.WriteLine(logOutput); - - // Verify all formatted parts - Assert.Contains("\"User\":{", logOutput); - Assert.Contains("\"FirstName\":\"Jane\"", logOutput); - Assert.Contains("\"Price\":\"$123.45\"", logOutput); - Assert.Contains("\"Date\":\"2023-04-05\"", logOutput); - } - - [Fact] - public void TestNestedObjectSerialization() - { - var output = new TestLoggerOutput(); - var logger = LoggerFactory.Create(builder => - { - builder.AddPowertoolsLogger(config => - { - config.Service = "nested-object-service"; - config.MinimumLogLevel = LogLevel.Debug; - config.LoggerOutputCase = LoggerOutputCase.SnakeCase; - config.TimestampFormat = "yyyy-MM-dd HH:mm:ss.fff"; - config.LogOutput = output; - }); - }).CreatePowertoolsLogger(); - - var parent = new ParentClass - { - Name = "Parent", - Child = new ChildClass { Name = "Child" } - }; - - // Regular object formatting (uses ToString()) - logger.LogInformation("Parent: {parent}", parent); - - // Object serialization with @ prefix - logger.LogInformation("Parent with child: {@parent}", parent); - - var logOutput = output.ToString(); - _output.WriteLine(logOutput); - - // Regular formatting should use ToString() - Assert.Contains("\"parent\":\"Parent with Child\"", logOutput); - - // Serialized object should include nested structure - Assert.Contains("\"parent\":{", logOutput); - Assert.Contains("\"name\":\"Parent\"", logOutput); - Assert.Contains("\"child\":{", logOutput); - Assert.Contains("\"name\":\"Child\"", logOutput); - } - - [Fact] - public void TestCollectionFormatting() - { - var output = new TestLoggerOutput(); - var logger = LoggerFactory.Create(builder => - { - builder.AddPowertoolsLogger(config => - { - config.Service = "collection-format-service"; - config.MinimumLogLevel = LogLevel.Debug; - config.LoggerOutputCase = LoggerOutputCase.CamelCase; - config.TimestampFormat = "yyyy-MM-dd HH:mm:ss.fff"; - config.LogOutput = output; - }); - }).CreatePowertoolsLogger(); - - var items = new[] { 1, 2, 3 }; - var dict = new Dictionary { ["key1"] = "value1", ["key2"] = 42 }; - - // Regular array formatting - logger.LogInformation("Array: {items}", items); - - // Serialized array with @ prefix - logger.LogInformation("Array serialized: {@items}", items); - - // Dictionary formatting - logger.LogInformation("Dictionary: {dict}", dict); - - // Serialized dictionary - logger.LogInformation("Dictionary serialized: {@dict}", dict); - - var logOutput = output.ToString(); - _output.WriteLine(logOutput); - - // Regular array formatting uses ToString() - Assert.Contains("\"items\":\"System.Int32[]\"", logOutput); - - // Serialized array should include all items - Assert.Contains("\"items\":[1,2,3]", logOutput); - - // Dictionary formatting depends on ToString() implementation - Assert.Contains("\"dict\":\"System.Collections.Generic.Dictionary", logOutput); - - // Serialized dictionary should include all key-value pairs - Assert.Contains("\"dict\":{", logOutput); - Assert.Contains("\"key1\":\"value1\"", logOutput); - Assert.Contains("\"key2\":42", logOutput); - } - - [Fact] - public void TestNullAndEdgeCases() - { - var output = new TestLoggerOutput(); - var logger = LoggerFactory.Create(builder => - { - builder.AddPowertoolsLogger(config => - { - config.Service = "null-edge-case-service"; - config.MinimumLogLevel = LogLevel.Debug; - config.LoggerOutputCase = LoggerOutputCase.SnakeCase; - config.TimestampFormat = "yyyy-MM-dd HH:mm:ss.fff"; - config.LogOutput = output; - }); - }).CreatePowertoolsLogger(); - - User user = null; - - // Test null formatting - logger.LogInformation("Null object: {user}", user); - logger.LogInformation("Null serialized: {@user}", user); - - // Extreme values - logger.LogInformation("Max value: {max}", int.MaxValue); - logger.LogInformation("Min value: {min}", int.MinValue); - logger.LogInformation("Max double: {maxDouble}", double.MaxValue); - - var logOutput = output.ToString(); - _output.WriteLine(logOutput); - - // Null objects should be null in output - Assert.Contains("\"user\":null", logOutput); - - // Extreme values should be preserved - Assert.Contains("\"max\":2147483647", logOutput); - Assert.Contains("\"min\":-2147483648", logOutput); - Assert.Contains("\"max_double\":1.7976931348623157E+308", logOutput); - } - - [Fact] - public void TestDateTimeFormats() - { - var output = new TestLoggerOutput(); - var logger = LoggerFactory.Create(builder => - { - builder.AddPowertoolsLogger(config => - { - config.Service = "datetime-format-service"; - config.MinimumLogLevel = LogLevel.Debug; - config.LoggerOutputCase = LoggerOutputCase.CamelCase; - config.TimestampFormat = "yyyy-MM-dd HH:mm:ss.fff"; - config.LogOutput = output; - }); - }).CreatePowertoolsLogger(); - - var date = new DateTime(2023, 12, 31, 23, 59, 59); - - // Test different date formats - logger.LogInformation("ISO: {date:o}", date); - logger.LogInformation("Short date: {date:d}", date); - logger.LogInformation("Custom: {date:yyyy-MM-dd'T'HH:mm:ss.fff}", date); - logger.LogInformation("Time only: {date:HH:mm:ss}", date); - - var logOutput = output.ToString(); - _output.WriteLine(logOutput); - - // Verify different formats - Assert.Contains("\"date\":\"2023-12-31T23:59:59", logOutput); // ISO format - Assert.Contains("\"date\":\"12/31/2023\"", logOutput); // Short date - Assert.Contains("\"date\":\"2023-12-31T23:59:59.000\"", logOutput); // Custom - Assert.Contains("\"date\":\"23:59:59\"", logOutput); // Time only - } - - [Fact] - public void TestExceptionLogging() - { - var output = new TestLoggerOutput(); - var logger = LoggerFactory.Create(builder => - { - builder.AddPowertoolsLogger(config => - { - config.Service = "exception-test-service"; - config.MinimumLogLevel = LogLevel.Debug; - config.LoggerOutputCase = LoggerOutputCase.SnakeCase; - config.TimestampFormat = "yyyy-MM-dd HH:mm:ss.fff"; - config.LogOutput = output; - }); - }).CreatePowertoolsLogger(); - - try - { - throw new InvalidOperationException("Test exception"); - } - catch (Exception ex) - { - logger.LogError(ex, "An error occurred with {data}", "test value"); - - // Test with nested exceptions - var outerEx = new Exception("Outer exception", ex); - logger.LogError(outerEx, "Nested exception test"); - } - - var logOutput = output.ToString(); - _output.WriteLine(logOutput); - - // Verify exception details are included - Assert.Contains("\"message\":\"An error occurred with test value\"", logOutput); - Assert.Contains("\"exception\":{", logOutput); - Assert.Contains("\"type\":\"System.InvalidOperationException\"", logOutput); - Assert.Contains("\"message\":\"Test exception\"", logOutput); - Assert.Contains("\"stack_trace\":", logOutput); - - // Verify nested exception details - Assert.Contains("\"message\":\"Nested exception test\"", logOutput); - Assert.Contains("\"inner_exception\":{", logOutput); - } - - [Fact] - public void TestScopedLogging() - { - var output = new TestLoggerOutput(); - var logger = LoggerFactory.Create(builder => - { - builder.AddPowertoolsLogger(config => - { - config.Service = "scope-test-service"; - config.MinimumLogLevel = LogLevel.Debug; - config.LoggerOutputCase = LoggerOutputCase.SnakeCase; - config.TimestampFormat = "yyyy-MM-dd HH:mm:ss.fff"; - config.LogOutput = output; - }); - }).CreatePowertoolsLogger(); - - // Log without any scope - logger.LogInformation("Outside any scope"); - - // Create a scope and log within it - using (logger.BeginScope(new { RequestId = "req-123", UserId = "user-456" })) - { - logger.LogInformation("Inside first scope"); - - // Nested scope - using (logger.BeginScope(new { OperationId = "op-789" })) - { - logger.LogInformation("Inside nested scope"); - } - - logger.LogInformation("Back to first scope"); - } - - // Back outside all scopes - logger.LogInformation("Outside all scopes again"); - - var logOutput = output.ToString(); - _output.WriteLine(logOutput); - - // Verify scope information is included correctly - Assert.Contains("\"message\":\"Inside first scope\"", logOutput); - Assert.Contains("\"request_id\":\"req-123\"", logOutput); - Assert.Contains("\"user_id\":\"user-456\"", logOutput); - - // Nested scope should include both scopes' data - Assert.Contains("\"message\":\"Inside nested scope\"", logOutput); - Assert.Contains("\"operation_id\":\"op-789\"", logOutput); - } - - [Fact] - public void TestDifferentLogLevels() - { - var output = new TestLoggerOutput(); - var logger = LoggerFactory.Create(builder => - { - builder.AddPowertoolsLogger(config => - { - config.Service = "log-level-test-service"; - config.MinimumLogLevel = LogLevel.Debug; - config.LoggerOutputCase = LoggerOutputCase.SnakeCase; - config.TimestampFormat = "yyyy-MM-dd HH:mm:ss.fff"; - config.LogOutput = output; - }); - }).CreatePowertoolsLogger(); - - logger.LogTrace("This is a trace message"); - logger.LogDebug("This is a debug message"); - logger.LogInformation("This is an info message"); - logger.LogWarning("This is a warning message"); - logger.LogError("This is an error message"); - logger.LogCritical("This is a critical message"); - - var logOutput = output.ToString(); - _output.WriteLine(logOutput); - - // Trace shouldn't be logged (below default) - Assert.DoesNotContain("\"level\":\"Trace\"", logOutput); - - // Debug and above should be logged - Assert.Contains("\"level\":\"Debug\"", logOutput); - Assert.Contains("\"level\":\"Information\"", logOutput); - Assert.Contains("\"level\":\"Warning\"", logOutput); - Assert.Contains("\"level\":\"Error\"", logOutput); - Assert.Contains("\"level\":\"Critical\"", logOutput); - } - - [Fact] - public void Should_Log_Multiple_Formats_No_Duplicates() - { - var output = new TestLoggerOutput(); - LambdaLifecycleTracker.Reset(); - LoggerFactory.Create(builder => - { - builder.AddPowertoolsLogger(config => - { - config.Service = "log-level-test-service"; - config.MinimumLogLevel = LogLevel.Debug; - config.LoggerOutputCase = LoggerOutputCase.SnakeCase; - config.LogOutput = output; - }); - }).CreatePowertoolsLogger(); - - var user = new User - { - FirstName = "John", - LastName = "Doe", - Age = 42, - TimeStamp = "FakeTime" - }; - - Logger.LogInformation("{Name} is {Age} years old", user.Name, user.Age); - - Assert.Contains("\"message\":\"John Doe is 42 years old\"", output.ToString()); - Assert.Contains("\"name\":\"AWS.Lambda.Powertools.Logging.Logger\"", output.ToString()); // does not override name - - output.Clear(); - - Logger.LogInformation("{level}", user); - Assert.Contains("\"level\":\"Information\"", output.ToString()); // does not override level - Assert.Contains("\"message\":\"Doe, John (42)\"", output.ToString()); // does not override message - Assert.DoesNotContain("\"timestamp\":\"FakeTime\"", output.ToString()); - - output.Clear(); - - Logger.LogInformation("{coldstart}", user); // still not sure if convert to PascalCase to compare or not - Assert.Contains("\"cold_start\":true", output.ToString()); - - output.Clear(); - - Logger.AppendKey("level", "Override"); - Logger.AppendKey("message", "Override"); - Logger.AppendKey("timestamp", "Override"); - Logger.AppendKey("name", "Override"); - Logger.AppendKey("service", "Override"); - Logger.AppendKey("cold_start", "Override"); - Logger.AppendKey("message2", "Its ok!"); - - Logger.LogInformation("no override"); - Assert.DoesNotContain("\"level\":\"Override\"", output.ToString()); - Assert.DoesNotContain("\"message\":\"Override\"", output.ToString()); - Assert.DoesNotContain("\"timestamp\":\"Override\"", output.ToString()); - Assert.DoesNotContain("\"name\":\"Override\"", output.ToString()); - Assert.DoesNotContain("\"service\":\"Override\"", output.ToString()); - Assert.DoesNotContain("\"cold_start\":\"Override\"", output.ToString()); - Assert.Contains("\"message2\":\"Its ok!\"", output.ToString()); - Assert.Contains("\"level\":\"Information\"", output.ToString()); - } - - [Fact] - public void Should_Log_Multiple_Formats() - { - LambdaLifecycleTracker.Reset(); - var output = new TestLoggerOutput(); - var logger = LoggerFactory.Create(builder => - { - builder.AddPowertoolsLogger(config => - { - config.Service = "log-level-test-service"; - config.MinimumLogLevel = LogLevel.Debug; - config.LoggerOutputCase = LoggerOutputCase.SnakeCase; - config.LogOutput = output; - }); - }).CreatePowertoolsLogger(); - - var user = new User - { - FirstName = "John", - LastName = "Doe", - Age = 42 - }; - - Logger.LogInformation("{Name} is {Age} years old", user.FirstName, user.Age); - - var logOutput = output.ToString(); - Assert.Contains("\"level\":\"Information\"", logOutput); - Assert.Contains("\"message\":\"John is 42 years old\"", logOutput); - Assert.Contains("\"service\":\"log-level-test-service\"", logOutput); - Assert.Contains("\"name\":\"AWS.Lambda.Powertools.Logging.Logger\"", logOutput); - - output.Clear(); - - // Message template string - Logger.LogInformation("{user}", user); - - logOutput = output.ToString(); - Assert.Contains("\"level\":\"Information\"", logOutput); - Assert.Contains("\"message\":\"Doe, John (42)\"", logOutput); - Assert.Contains("\"service\":\"log-level-test-service\"", logOutput); - Assert.Contains("\"name\":\"AWS.Lambda.Powertools.Logging.Logger\"", logOutput); - Assert.Contains("\"user\":\"Doe, John (42)\"", logOutput); - // Verify user properties are NOT included in output (since @ prefix wasn't used) - Assert.DoesNotContain("\"first_name\":", logOutput); - Assert.DoesNotContain("\"last_name\":", logOutput); - Assert.DoesNotContain("\"age\":", logOutput); - - output.Clear(); - - // Object serialization with @ prefix - Logger.LogInformation("{@user}", user); - - logOutput = output.ToString(); - Assert.Contains("\"level\":\"Information\"", logOutput); - Assert.Contains("\"message\":\"Doe, John (42)\"", logOutput); - Assert.Contains("\"service\":\"log-level-test-service\"", logOutput); - Assert.Contains("\"cold_start\":true", logOutput); - Assert.Contains("\"name\":\"AWS.Lambda.Powertools.Logging.Logger\"", logOutput); - // Verify serialized user object with all properties - Assert.Contains("\"user\":{", logOutput); - Assert.Contains("\"first_name\":\"John\"", logOutput); - Assert.Contains("\"last_name\":\"Doe\"", logOutput); - Assert.Contains("\"age\":42", logOutput); - Assert.Contains("\"name\":\"John Doe\"", logOutput); - Assert.Contains("\"time_stamp\":null", logOutput); - Assert.Contains("}", logOutput); - - output.Clear(); - - Logger.LogInformation("{cold_start}", false); - - logOutput = output.ToString(); - // Assert that the reserved field wasn't replaced - Assert.Contains("\"cold_start\":true", logOutput); - Assert.DoesNotContain("\"cold_start\":false", logOutput); - - output.Clear(); - - Logger.AppendKey("level", "fakeLevel"); - Logger.LogInformation("no override"); - - logOutput = output.ToString(); - - Assert.Contains("\"level\":\"Information\"", logOutput); - Assert.DoesNotContain("\"level\":\"fakeLevel\"", logOutput); - - output.Clear(); - - Logger.LogInformation("{Name} is {Age} years old and {@user}", user.FirstName, user.Age, user); - - logOutput = output.ToString(); - - Assert.Contains("\"message\":\"John is 42 years old and Doe, John (42)\"", logOutput); - // Verify serialized user object with all properties - Assert.Contains("\"user\":{", logOutput); - Assert.Contains("\"first_name\":\"John\"", logOutput); - Assert.Contains("\"last_name\":\"Doe\"", logOutput); - Assert.Contains("\"age\":42", logOutput); - Assert.Contains("\"name\":\"John Doe\"", logOutput); - Assert.Contains("\"time_stamp\":null", logOutput); - Assert.Contains("}", logOutput); - - _output.WriteLine(logOutput); - } - - [Fact] - public void TestMessageTemplateFormatting() - { - var output = new TestLoggerOutput(); - var logger = LoggerFactory.Create(builder => - { - builder.AddPowertoolsLogger(config => - { - config.Service = "template-format-service"; - config.MinimumLogLevel = LogLevel.Debug; - config.LoggerOutputCase = LoggerOutputCase.SnakeCase; - config.LogOutput = output; - }); - }).CreatePowertoolsLogger(); - - // Simple template with one parameter - logger.LogInformation("This is a test with {param}", "Hello"); - - var logOutput = output.ToString(); - _output.WriteLine(logOutput); - - // Verify full formatted message appears correctly - Assert.Contains("\"message\":\"This is a test with Hello\"", logOutput); - // Verify parameter is also included separately - Assert.Contains("\"param\":\"Hello\"", logOutput); - - output.Clear(); - - // Multiple parameters - logger.LogInformation("Test with {first} and {second}", "One", "Two"); - - logOutput = output.ToString(); - _output.WriteLine(logOutput); - - // Verify message with multiple parameters - Assert.Contains("\"message\":\"Test with One and Two\"", logOutput); - Assert.Contains("\"first\":\"One\"", logOutput); - Assert.Contains("\"second\":\"Two\"", logOutput); - } - - public class ParentClass - { - public string Name { get; set; } - public ChildClass Child { get; set; } - - public override string ToString() - { - return $"Parent with Child"; - } - } - - public class ChildClass - { - public string Name { get; set; } - - public override string ToString() - { - return $"Child: {Name}"; - } - } - - public class Node - { - public string Name { get; set; } - public Node Parent { get; set; } - public List Children { get; set; } = new List(); - - public override string ToString() - { - return $"Node: {Name}"; - } - } - - public class User - { - public string FirstName { get; set; } - public string LastName { get; set; } - public int Age { get; set; } - public string Name => $"{FirstName} {LastName}"; - public string TimeStamp { get; set; } - - public override string ToString() - { - return $"{LastName}, {FirstName} ({Age})"; - } - } - } -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/ExceptionFunctionHandler.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/ExceptionFunctionHandler.cs index 56dfcc872..170f2a922 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/ExceptionFunctionHandler.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/ExceptionFunctionHandler.cs @@ -34,14 +34,14 @@ public string HandlerLoggerForExceptions(string input, ILambdaContext context) Logger.LogDebug("Hello {input}", input); Logger.LogTrace("Hello {input}", input); - Logger.LogInformation("Testing with parameter Log Information Method {company}", "AWS" ); + Logger.LogInformation("Testing with parameter Log Information Method {company}", new[] { "AWS" }); var customKeys = new Dictionary { {"test1", "value1"}, {"test2", "value2"} }; - Logger.LogInformation("Retrieved data for city {cityName} with count {company}", "AWS", customKeys); + Logger.LogInformation(customKeys, "Retrieved data for city {cityName} with count {company}", "AWS"); Logger.AppendKey("aws",1); Logger.AppendKey("aws",3); @@ -52,4 +52,10 @@ public string HandlerLoggerForExceptions(string input, ILambdaContext context) return "OK"; } + + [Logging(LogEvent = true)] + public string HandleOk(string input) + { + return input.ToUpper(CultureInfo.InvariantCulture); + } } \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/ExceptionFunctionHandlerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/ExceptionFunctionHandlerTests.cs index 7622ac232..f9ffd5ebf 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/ExceptionFunctionHandlerTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/ExceptionFunctionHandlerTests.cs @@ -7,7 +7,6 @@ namespace AWS.Lambda.Powertools.Logging.Tests.Handlers; -[Collection("Sequential")] public sealed class ExceptionFunctionHandlerTests : IDisposable { [Fact] @@ -43,6 +42,6 @@ public void Utility_Should_Not_Throw_Exceptions_To_Client() public void Dispose() { LoggingAspect.ResetForTest(); - Logger.Reset(); + PowertoolsLoggingSerializer.ClearOptions(); } } \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/HandlerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/HandlerTests.cs deleted file mode 100644 index 0046ce5c8..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/HandlerTests.cs +++ /dev/null @@ -1,572 +0,0 @@ -using System.Text.Json.Serialization; -#if NET8_0_OR_GREATER -using System; -using System.Collections.Generic; -using System.IO; -using System.Text.Json; -using System.Text.Json.Serialization; -using System.Threading.Tasks; -using Amazon.Lambda.Core; -using Amazon.Lambda.TestUtilities; -using AWS.Lambda.Powertools.Common; -using AWS.Lambda.Powertools.Common.Tests; -using AWS.Lambda.Powertools.Logging.Internal.Helpers; -using AWS.Lambda.Powertools.Logging.Tests.Formatter; -using AWS.Lambda.Powertools.Logging.Tests.Utilities; -using Microsoft.Extensions.Logging; -using Xunit; -using Xunit.Abstractions; -using LogLevel = Microsoft.Extensions.Logging.LogLevel; - -namespace AWS.Lambda.Powertools.Logging.Tests.Handlers; - -public class Handlers -{ - private readonly ILogger _logger; - - public Handlers(ILogger logger) - { - _logger = logger; - PowertoolsLoggingBuilderExtensions.ResetAllProviders(); - } - - [Logging(LogEvent = true)] - public void TestMethod(string message, ILambdaContext lambdaContext) - { - _logger.AppendKey("custom-key", "custom-value"); - _logger.LogInformation("Information message"); - _logger.LogDebug("debug message"); - - var example = new ExampleClass - { - Name = "test", - Price = 1.999, - ThisIsBig = "big", - ThisIsHidden = "hidden" - }; - - _logger.LogInformation("Example object: {example}", example); - _logger.LogInformation("Another JSON log {d:0.000}", 1.2333); - - _logger.LogDebug(example); - _logger.LogInformation(example); - } - - [Logging(LogEvent = true, CorrelationIdPath = "price")] - public void TestMethodCorrelation(ExampleClass message, ILambdaContext lambdaContext) - { - } -} - -public class StaticHandler -{ - [Logging(LogEvent = true, LoggerOutputCase = LoggerOutputCase.PascalCase, Service = "my-service122")] - public void TestMethod(string message, ILambdaContext lambdaContext) - { - Logger.LogInformation("Static method"); - } -} - -public class HandlerTests -{ - private readonly ITestOutputHelper _output; - - public HandlerTests(ITestOutputHelper output) - { - _output = output; - } - - [Fact] - public void TestMethod() - { - var output = new TestLoggerOutput(); - - var logger = LoggerFactory.Create(builder => - { - builder.AddPowertoolsLogger(config => - { - config.Service = "my-service122"; - config.SamplingRate = 0.002; - config.MinimumLogLevel = LogLevel.Debug; - config.LoggerOutputCase = LoggerOutputCase.PascalCase; - config.TimestampFormat = "yyyy-MM-dd HH:mm:ss.fff"; - config.JsonOptions = new JsonSerializerOptions - { - WriteIndented = true - // PropertyNamingPolicy = null, - // DictionaryKeyPolicy = PascalCaseNamingPolicy.Instance, - }; - config.LogOutput = output; - }); - }).CreateLogger(); - - - var handler = new Handlers(logger); - - handler.TestMethod("Event", new TestLambdaContext - { - FunctionName = "test-function", - FunctionVersion = "1", - AwsRequestId = "123", - InvokedFunctionArn = "arn:aws:lambda:us-east-1:123456789012:function:test-function" - }); - - handler.TestMethodCorrelation(new ExampleClass - { - Name = "test-function", - Price = 1.999, - ThisIsBig = "big", - }, null); - - var logOutput = output.ToString(); - _output.WriteLine(logOutput); - - // Check if the output contains newlines and spacing (indentation) - Assert.Contains("\n", logOutput); - Assert.Contains(" ", logOutput); - - // Verify write indented JSON - Assert.Contains("\"Level\": \"Information\"", logOutput); - Assert.Contains("\"Service\": \"my-service122\"", logOutput); - Assert.Contains("\"Message\": \"Information message\"", logOutput); - Assert.Contains("\"Custom-key\": \"custom-value\"", logOutput); - Assert.Contains("\"FunctionName\": \"test-function\"", logOutput); - Assert.Contains("\"SamplingRate\": 0.002", logOutput); - } - - [Fact] - public void TestMethodCustom() - { - var output = new TestLoggerOutput(); - var logger = LoggerFactory.Create(builder => - { - builder.AddPowertoolsLogger(config => - { - config.Service = "my-service122"; - config.SamplingRate = 0.002; - config.MinimumLogLevel = LogLevel.Debug; - config.LoggerOutputCase = LoggerOutputCase.CamelCase; - config.JsonOptions = new JsonSerializerOptions - { - // PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, - // DictionaryKeyPolicy = JsonNamingPolicy.KebabCaseLower - }; - - config.LogFormatter = new CustomLogFormatter(); - config.LogOutput = output; - }); - }).CreatePowertoolsLogger(); - - var handler = new Handlers(logger); - - handler.TestMethod("Event", new TestLambdaContext - { - FunctionName = "test-function", - FunctionVersion = "1", - AwsRequestId = "123", - InvokedFunctionArn = "arn:aws:lambda:us-east-1:123456789012:function:test-function" - }); - - var logOutput = output.ToString(); - _output.WriteLine(logOutput); - - // Verify CamelCase formatting (custom formatter) - Assert.Contains("\"service\":\"my-service122\"", logOutput); - Assert.Contains("\"level\":\"Information\"", logOutput); - Assert.Contains("\"message\":\"Information message\"", logOutput); - Assert.Contains("\"correlationIds\":{\"awsRequestId\":\"123\"}", logOutput); - } - - [Fact] - public void TestBuffer() - { - var output = new TestLoggerOutput(); - var logger = LoggerFactory.Create(builder => - { - // builder.AddFilter("AWS.Lambda.Powertools.Logging.Tests.Handlers.Handlers", LogLevel.Debug); - builder.AddPowertoolsLogger(config => - { - config.Service = "my-service122"; - config.SamplingRate = 0.002; - config.MinimumLogLevel = LogLevel.Information; - config.JsonOptions = new JsonSerializerOptions - { - WriteIndented = true, - // PropertyNamingPolicy = JsonNamingPolicy.KebabCaseUpper, - DictionaryKeyPolicy = JsonNamingPolicy.KebabCaseUpper - }; - config.LogOutput = output; - config.LogBuffering = new LogBufferingOptions - { - BufferAtLogLevel = LogLevel.Debug - }; - }); - }).CreatePowertoolsLogger(); - - var handler = new Handlers(logger); - - handler.TestMethod("Event", new TestLambdaContext - { - FunctionName = "test-function", - FunctionVersion = "1", - AwsRequestId = "123", - InvokedFunctionArn = "arn:aws:lambda:us-east-1:123456789012:function:test-function" - }); - - var logOutput = output.ToString(); - _output.WriteLine(logOutput); - - // Verify buffering behavior - only Information logs or higher should be in output - Assert.Contains("Information message", logOutput); - Assert.DoesNotContain("debug message", logOutput); // Debug should be buffered - - // Verify JSON options with indentation - Assert.Contains("\n", logOutput); - Assert.Contains(" ", logOutput); // Check for indentation - - // Check that kebab-case dictionary keys are working - Assert.Contains("\"CUSTOM-KEY\"", logOutput); - } - - [Fact] - public void TestMethodStatic() - { - var output = new TestLoggerOutput(); - var handler = new StaticHandler(); - - Logger.Configure(options => - { - options.LogOutput = output; - options.LoggerOutputCase = LoggerOutputCase.CamelCase; - }); - - handler.TestMethod("Event", new TestLambdaContext - { - FunctionName = "test-function", - FunctionVersion = "1", - AwsRequestId = "123", - InvokedFunctionArn = "arn:aws:lambda:us-east-1:123456789012:function:test-function" - }); - - var logOutput = output.ToString(); - _output.WriteLine(logOutput); - - // Verify static logger configuration - // Verify override of LoggerOutputCase from attribute - Assert.Contains("\"Service\":\"my-service122\"", logOutput); - Assert.Contains("\"Level\":\"Information\"", logOutput); - Assert.Contains("\"Message\":\"Static method\"", logOutput); - } - - [Fact] - public async Task Should_Log_Properties_Setup_Constructor() - { - var output = new TestLoggerOutput(); - _ = new SimpleFunctionWithStaticConfigure(output); - - await SimpleFunctionWithStaticConfigure.FunctionHandler(); - - var logOutput = output.ToString(); - _output.WriteLine(logOutput); - - - Assert.Contains("\"service\":\"MyServiceName\"", logOutput); - Assert.Contains("\"level\":\"Information\"", logOutput); - Assert.Contains("\"message\":\"Starting up!\"", logOutput); - Assert.Contains("\"xray_trace_id\"", logOutput); - } - - [Fact] - public async Task Should_Flush_On_Exception_Async() - { - var output = new TestLoggerOutput(); - var handler = new SimpleFunctionWithStaticConfigure(output); - - try - { - await handler.AsyncException(); - } - catch - { - } - - var logOutput = output.ToString(); - _output.WriteLine(logOutput); - - Assert.Contains("\"level\":\"Debug\"", logOutput); - Assert.Contains("\"message\":\"Debug!!\"", logOutput); - Assert.Contains("\"xray_trace_id\"", logOutput); - } - - [Fact] - public void Should_Flush_On_Exception() - { - var output = new TestLoggerOutput(); - var handler = new SimpleFunctionWithStaticConfigure(output); - - try - { - handler.SyncException(); - } - catch - { - } - - var logOutput = output.ToString(); - _output.WriteLine(logOutput); - - Assert.Contains("\"level\":\"Debug\"", logOutput); - Assert.Contains("\"message\":\"Debug!!\"", logOutput); - Assert.Contains("\"xray_trace_id\"", logOutput); - } - - [Fact] - public void TestJsonOptionsPropertyNaming() - { - var output = new TestLoggerOutput(); - var logger = LoggerFactory.Create(builder => - { - builder.AddPowertoolsLogger(config => - { - config.Service = "json-options-service"; - config.MinimumLogLevel = LogLevel.Debug; - config.JsonOptions = new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, - WriteIndented = false - }; - config.LogOutput = output; - }); - }).CreatePowertoolsLogger(); - - var handler = new Handlers(logger); - var example = new ExampleClass - { - Name = "TestValue", - Price = 29.99, - ThisIsBig = "LargeValue" - }; - - logger.LogInformation("Testing JSON options with example: {@example}", example); - - var logOutput = output.ToString(); - _output.WriteLine(logOutput); - - // Verify snake_case naming policy is applied - Assert.Contains("\"this_is_big\":\"LargeValue\"", logOutput); - Assert.Contains("\"name\":\"TestValue\"", logOutput); - } - - [Fact] - public void TestJsonOptionsDictionaryKeyPolicy() - { - var output = new TestLoggerOutput(); - var logger = LoggerFactory.Create(builder => - { - builder.AddPowertoolsLogger(config => - { - config.Service = "json-dictionary-service"; - config.JsonOptions = new JsonSerializerOptions - { - DictionaryKeyPolicy = JsonNamingPolicy.CamelCase, - WriteIndented = false - }; - config.LogOutput = output; - }); - }).CreatePowertoolsLogger(); - - var dictionary = new Dictionary - { - { "UserID", 12345 }, - { "OrderDetails", new { ItemCount = 3, Total = 150.75 } }, - { "ShippingAddress", "123 Main St" } - }; - - logger.LogInformation("Dictionary with custom key policy: {@dictionary}", dictionary); - - var logOutput = output.ToString(); - _output.WriteLine(logOutput); - - // Fix assertion to match actual camelCase behavior with acronyms - Assert.Contains("\"userID\":12345", logOutput); // ID remains uppercase - Assert.Contains("\"orderDetails\":", logOutput); - Assert.Contains("\"shippingAddress\":", logOutput); - } - - [Fact] - public void TestJsonOptionsWriteIndented() - { - var output = new TestLoggerOutput(); - var logger = LoggerFactory.Create(builder => - { - builder.AddPowertoolsLogger(config => - { - config.Service = "json-indented-service"; - config.JsonOptions = new JsonSerializerOptions - { - WriteIndented = true - }; - config.LogOutput = output; - }); - }).CreatePowertoolsLogger(); - - var example = new ExampleClass - { - Name = "IndentedTest", - Price = 59.99, - ThisIsBig = "IndentedValue" - }; - - logger.LogInformation("Testing indented JSON: {@example}", example); - - var logOutput = output.ToString(); - _output.WriteLine(logOutput); - - // Check if the output contains newlines and spacing (indentation) - Assert.Contains("\n", logOutput); - Assert.Contains(" ", logOutput); - } - - /// - /// Test sampling behavior with environment variables using the [Logging] attribute - /// POWERTOOLS_LOG_LEVEL=Error and POWERTOOLS_LOGGER_SAMPLE_RATE=0.9 - /// - [Fact] - public void EnvironmentVariableSampling_HandlerWithSampling_ShouldElevateInfoLogs() - { - // Arrange - Set environment variables for sampling test - var originalLogLevel = Environment.GetEnvironmentVariable("POWERTOOLS_LOG_LEVEL"); - var originalSampleRate = Environment.GetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE"); - - try - { - Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", "Error"); - Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE", "0.9"); - - var output = new TestLoggerOutput(); - Logger.Configure(options => { options.LogOutput = output; }); - - var handler = new EnvironmentVariableSamplingHandler(); - - // Act - Try multiple times to trigger sampling (90% chance each time) - bool samplingTriggered = false; - string logOutput = ""; - - // Try up to 20 times to trigger sampling - for (int i = 0; i < 20 && !samplingTriggered; i++) - { - output.Clear(); - Logger.Reset(); - Logger.Configure(options => { options.LogOutput = output; }); - - handler.HandleWithSampling(new string[] { }); - - logOutput = output.ToString(); - samplingTriggered = logOutput.Contains("Changed log level to DEBUG based on Sampling configuration"); - } - - // Assert - Assert.True(samplingTriggered, "Sampling should have been triggered within 20 attempts with 90% rate"); - Assert.Contains("This is an info message — should not appear", logOutput); - } - finally - { - // Cleanup - Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", originalLogLevel); - Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE", originalSampleRate); - Logger.Reset(); - } - } - - /// - /// Test with 100% sampling rate to guarantee sampling works consistently - /// - [Fact] - public void EnvironmentVariableSampling_HandlerWithFullSampling_ShouldAlwaysElevateInfoLogs() - { - // Arrange - var originalLogLevel = Environment.GetEnvironmentVariable("POWERTOOLS_LOG_LEVEL"); - var originalSampleRate = Environment.GetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE"); - - try - { - Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", "Error"); - Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE", "1.0"); - - var output = new TestLoggerOutput(); - Logger.Configure(options => { options.LogOutput = output; }); - - var handler = new EnvironmentVariableSamplingHandler(); - - // Act - handler.HandleWithFullSampling(new string[] { }); - - // Assert - var logOutput = output.ToString(); - _output.WriteLine(logOutput); - - Assert.Contains("Changed log level to DEBUG based on Sampling configuration", logOutput); - Assert.Contains("This is an info message — should appear with 100% sampling", logOutput); - Assert.Contains("\"service\":\"HelloWorldService\"", logOutput); - } - finally - { - // Cleanup - Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", originalLogLevel); - Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE", originalSampleRate); - Logger.Reset(); - } - } - - /// - /// Test with 0% sampling rate to ensure info logs are not elevated - /// - [Fact] - public void EnvironmentVariableSampling_HandlerWithNoSampling_ShouldNotElevateInfoLogs() - { - // Arrange - var originalLogLevel = Environment.GetEnvironmentVariable("POWERTOOLS_LOG_LEVEL"); - var originalSampleRate = Environment.GetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE"); - - try - { - Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", "Error"); - Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE", "0"); - - var output = new TestLoggerOutput(); - Logger.Configure(options => { options.LogOutput = output; }); - - var handler = new EnvironmentVariableSamplingHandler(); - - // Act - handler.HandleWithNoSampling(new string[] { }); - - // Assert - var logOutput = output.ToString(); - _output.WriteLine(logOutput); - - Assert.DoesNotContain("Changed log level to DEBUG based on Sampling configuration", logOutput); - Assert.DoesNotContain("This is an info message — should NOT appear with 0% sampling", logOutput); - } - finally - { - // Cleanup - Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", originalLogLevel); - Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE", originalSampleRate); - Logger.Reset(); - } - } -} - -#endif - -public class ExampleClass -{ - public string Name { get; set; } - - public double Price { get; set; } - - public string ThisIsBig { get; set; } - - [JsonIgnore] public string ThisIsHidden { get; set; } -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/TestHandlers.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/TestHandlers.cs index dd332ea4c..08fe54d47 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/TestHandlers.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Handlers/TestHandlers.cs @@ -1,12 +1,25 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.Text.Json.Serialization; -using System.Threading.Tasks; using Amazon.Lambda.APIGatewayEvents; using Amazon.Lambda.ApplicationLoadBalancerEvents; using Amazon.Lambda.CloudWatchEvents; using Amazon.Lambda.CloudWatchEvents.S3Events; using Amazon.Lambda.Core; -using AWS.Lambda.Powertools.Common; using AWS.Lambda.Powertools.Logging.Tests.Serializers; using LogLevel = Microsoft.Extensions.Logging.LogLevel; @@ -176,103 +189,23 @@ public class TestServiceHandler { public void LogWithEnv() { - Logger.LogInformation("Service: Environment Service"); - } - - [Logging(Service = "Attribute Service")] - public void Handler() - { - Logger.LogInformation("Service: Attribute Service"); - } -} - -public class SimpleFunctionWithStaticConfigure -{ - public SimpleFunctionWithStaticConfigure(IConsoleWrapper output) - { - // Constructor logic can go here if needed - Logger.Configure(logger => - { - logger.LogOutput = output; - logger.Service = "MyServiceName"; - logger.LogBuffering = new LogBufferingOptions - { - BufferAtLogLevel = LogLevel.Debug, - }; - }); - } - - [Logging] - public static async Task FunctionHandler() - { - // only set on handler - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-invocation"); - - Logger.LogInformation("Starting up!"); + Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", "Environment Service"); - return new APIGatewayHttpApiV2ProxyResponse - { - Body = "Hello", - StatusCode = 200 - }; - } - - [Logging(FlushBufferOnUncaughtError = true)] - public APIGatewayHttpApiV2ProxyResponse SyncException() - { - // only set on handler - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-invocation"); - - Logger.LogDebug("Debug!!"); - Logger.LogInformation("Starting up!"); - - throw new Exception(); + Logger.LogInformation("Service: Environment Service"); } - [Logging(FlushBufferOnUncaughtError = true)] - public async Task AsyncException() + public void LogWithAndWithoutEnv() { - // only set on handler - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "test-invocation"); - - Logger.LogDebug("Debug!!"); - Logger.LogInformation("Starting up!"); - - throw new Exception(); - } -} - -public class EnvironmentVariableSamplingHandler -{ - /// - /// Handler that tests sampling behavior with environment variables: - /// Using environment variables POWERTOOLS_LOG_LEVEL=Error and POWERTOOLS_LOGGER_SAMPLE_RATE=0.9 - /// - [Logging(Service = "HelloWorldService", LoggerOutputCase = LoggerOutputCase.CamelCase, LogEvent = true)] - public void HandleWithSampling(string[] args) - { - var logLevel = Environment.GetEnvironmentVariable("POWERTOOLS_LOG_LEVEL"); - var sampleRate = Environment.GetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE"); + Logger.LogInformation("Service: service_undefined"); - // This should NOT be logged (Info < Error) unless sampling elevates the log level - Logger.LogInformation("This is an info message — should not appear"); - } - - /// - /// Handler for testing with guaranteed sampling (100%) - /// - [Logging(Service = "HelloWorldService", LoggerOutputCase = LoggerOutputCase.CamelCase, LogEvent = true)] - public void HandleWithFullSampling(string[] args) - { - Logger.LogInformation("This is an info message — should appear with 100% sampling"); + Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", "Environment Service"); + + Logger.LogInformation("Service: service_undefined"); } - - /// - /// Handler for testing with no sampling (0%) - /// - [Logging(Service = "HelloWorldService", LoggerOutputCase = LoggerOutputCase.CamelCase, LogEvent = true)] - public void HandleWithNoSampling(string[] args) + + [Logging(Service = "Attribute Service")] + public void Handler() { - Logger.LogInformation("This is an info message — should NOT appear with 0% sampling"); + Logger.LogInformation("Service: Attribute Service"); } } \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerBuilderTests.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerBuilderTests.cs deleted file mode 100644 index bbb1c43b5..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerBuilderTests.cs +++ /dev/null @@ -1,208 +0,0 @@ -using System; -using System.Text.Json; -using AWS.Lambda.Powertools.Common.Tests; -using AWS.Lambda.Powertools.Logging.Internal; -using AWS.Lambda.Powertools.Logging.Tests.Formatter; -using AWS.Lambda.Powertools.Logging.Tests.Handlers; -using Microsoft.Extensions.Logging; -using Xunit; -using Xunit.Abstractions; - -namespace AWS.Lambda.Powertools.Logging.Tests; - -public class PowertoolsLoggerBuilderTests -{ - private readonly ITestOutputHelper _output; - - public PowertoolsLoggerBuilderTests(ITestOutputHelper output) - { - _output = output; - } - - [Fact] - public void WithService_SetsServiceName() - { - var output = new TestLoggerOutput(); - var logger = new PowertoolsLoggerBuilder() - .WithLogOutput(output) - .WithService("test-builder-service") - .Build(); - - logger.LogInformation("Testing service name"); - - var logOutput = output.ToString(); - _output.WriteLine(logOutput); - - Assert.Contains("\"service\":\"test-builder-service\"", logOutput, StringComparison.OrdinalIgnoreCase); - } - - [Fact] - public void WithSamplingRate_SetsSamplingRate() - { - var output = new TestLoggerOutput(); - var logger = new PowertoolsLoggerBuilder() - .WithLogOutput(output) - .WithService("sampling-test") - .WithSamplingRate(0.5) - .Build(); - - // We can't directly test sampling rate in a deterministic way, - // but we can verify the logger is created successfully - logger.LogInformation("Testing sampling rate"); - - var logOutput = output.ToString(); - _output.WriteLine(logOutput); - - Assert.Contains("\"message\":\"Testing sampling rate\"", logOutput, StringComparison.OrdinalIgnoreCase); - } - - [Fact] - public void WithMinimumLogLevel_FiltersLowerLevels() - { - var output = new TestLoggerOutput(); - var logger = new PowertoolsLoggerBuilder() - .WithLogOutput(output) - .WithService("log-level-test") - .WithMinimumLogLevel(LogLevel.Warning) - .Build(); - - logger.LogDebug("Debug message"); - logger.LogInformation("Info message"); - logger.LogWarning("Warning message"); - - var logOutput = output.ToString(); - _output.WriteLine(logOutput); - - Assert.DoesNotContain("Debug message", logOutput); - Assert.DoesNotContain("Info message", logOutput); - Assert.Contains("Warning message", logOutput); - } - -#if NET8_0_OR_GREATER - [Fact] - public void WithJsonOptions_AppliesFormatting() - { - var output = new TestLoggerOutput(); - var logger = new PowertoolsLoggerBuilder() - .WithService("json-options-test") - .WithLogOutput(output) - .WithJsonOptions(new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, - }) - .Build(); - - var testObject = new ExampleClass - { - Name = "TestName", - ThisIsBig = "BigValue" - }; - - logger.LogInformation("Test object: {@testObject}", testObject); - - var logOutput = output.ToString(); - _output.WriteLine(logOutput); - - Assert.Contains("\"this_is_big\":\"BigValue\"", logOutput); - Assert.Contains("\"name\":\"TestName\"", logOutput); - Assert.Contains("\n", logOutput); // Indentation includes newlines - } -#endif - - [Fact] - public void WithTimestampFormat_FormatsTimestamp() - { - var output = new TestLoggerOutput(); - var logger = new PowertoolsLoggerBuilder() - .WithLogOutput(output) - .WithService("timestamp-test") - .WithTimestampFormat("yyyy-MM-dd") - .Build(); - - logger.LogInformation("Testing timestamp format"); - - var logOutput = output.ToString(); - _output.WriteLine(logOutput); - - // Should match yyyy-MM-dd format (e.g., "2023-04-25") - Assert.Matches("\"timestamp\":\"\\d{4}-\\d{2}-\\d{2}\"", logOutput); - } - - [Fact] - public void WithOutputCase_ChangesPropertyCasing() - { - var output = new TestLoggerOutput(); - var logger = new PowertoolsLoggerBuilder() - .WithLogOutput(output) - .WithService("case-test") - .WithOutputCase(LoggerOutputCase.PascalCase) - .Build(); - - logger.LogInformation("Testing output case"); - - var logOutput = output.ToString(); - _output.WriteLine(logOutput); - - Assert.Contains("\"Service\":\"case-test\"", logOutput); - Assert.Contains("\"Level\":\"Information\"", logOutput); - Assert.Contains("\"Message\":\"Testing output case\"", logOutput); - } - - [Fact] - public void WithLogBuffering_BuffersLowLevelLogs() - { - var output = new TestLoggerOutput(); - var logger = new PowertoolsLoggerBuilder() - .WithLogOutput(output) - .WithService("buffer-test") - .WithLogBuffering(options => - { - options.BufferAtLogLevel = LogLevel.Debug; - }) - .Build(); - - Environment.SetEnvironmentVariable("_X_AMZN_TRACE_ID", "config-test"); - logger.LogDebug("Debug buffered message"); - logger.LogInformation("Info message"); - - // Without FlushBuffer(), the debug message should be buffered - var initialOutput = output.ToString(); - _output.WriteLine("Before flush: " + initialOutput); - - Assert.DoesNotContain("Debug buffered message", initialOutput); - Assert.Contains("Info message", initialOutput); - - // After flushing, the debug message should appear - logger.FlushBuffer(); - var afterFlushOutput = output.ToString(); - _output.WriteLine("After flush: " + afterFlushOutput); - - Assert.Contains("Debug buffered message", afterFlushOutput); - } - - [Fact] - public void BuilderChaining_ConfiguresAllProperties() - { - var output = new TestLoggerOutput(); - var customFormatter = new CustomLogFormatter(); - - var logger = new PowertoolsLoggerBuilder() - .WithService("chained-config-service") - .WithSamplingRate(0.1) - .WithMinimumLogLevel(LogLevel.Information) - .WithOutputCase(LoggerOutputCase.SnakeCase) - .WithFormatter(customFormatter) - .WithLogOutput(output) - .Build(); - - logger.LogInformation("Testing fully configured logger"); - - var logOutput = output.ToString(); - _output.WriteLine(logOutput); - - // Verify multiple configured properties are applied - Assert.Contains("\"service\":\"chained-config-service\"", logOutput); - Assert.Contains("\"message\":\"Testing fully configured logger\"", logOutput); - Assert.Contains("\"sample_rate\":0.1", logOutput); - } -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerExtensionsTests.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerExtensionsTests.cs deleted file mode 100644 index 8e1ea3c63..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerExtensionsTests.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Linq; -using AWS.Lambda.Powertools.Logging.Internal; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Xunit; - -namespace AWS.Lambda.Powertools.Logging.Tests; - -public class PowertoolsLoggerExtensionsTests -{ - [Fact] - public void AddPowertoolsLogger_WithClearExistingProviders_False_KeepsExistingProviders() - { - // Arrange - var serviceCollection = new ServiceCollection(); - serviceCollection.AddLogging(builder => - { - // Add a mock existing provider first - builder.Services.AddSingleton(); - - // Act - builder.AddPowertoolsLogger(clearExistingProviders: false); - }); - - var serviceProvider = serviceCollection.BuildServiceProvider(); - var loggerProviders = serviceProvider.GetServices(); - - // Assert - var collection = loggerProviders as ILoggerProvider[] ?? loggerProviders.ToArray(); - Assert.Contains(collection, p => p is MockLoggerProvider); - Assert.Contains(collection, p => p is PowertoolsLoggerProvider); - Assert.True(collection.Count() >= 2); // Should have both providers - } - - [Fact] - public void AddPowertoolsLogger_WithClearExistingProviders_True_RemovesExistingProviders() - { - // Arrange - var serviceCollection = new ServiceCollection(); - serviceCollection.AddLogging(builder => - { - // Add a mock existing provider first - builder.Services.AddSingleton(); - - // Act - builder.AddPowertoolsLogger(clearExistingProviders: true); - }); - - var serviceProvider = serviceCollection.BuildServiceProvider(); - var loggerProviders = serviceProvider.GetServices(); - - // Assert - var collection = loggerProviders as ILoggerProvider[] ?? loggerProviders.ToArray(); - Assert.DoesNotContain(collection, p => p is MockLoggerProvider); - Assert.Contains(collection, p => p is PowertoolsLoggerProvider); - Assert.Single(collection); // Should only have Powertools provider - } - - private class MockLoggerProvider : ILoggerProvider - { - public ILogger CreateLogger(string categoryName) => new MockLogger(); - public void Dispose() { } - } - - private class MockLogger : ILogger - { - public IDisposable BeginScope(TState state) => null; - public bool IsEnabled(LogLevel logLevel) => true; - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { } - } -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerTest.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerTest.cs index 05141510f..e034ce33b 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerTest.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/PowertoolsLoggerTest.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.Collections.Generic; using System.Globalization; @@ -5,8 +20,6 @@ using System.Linq; using System.Text; using AWS.Lambda.Powertools.Common; -using AWS.Lambda.Powertools.Common.Core; -using AWS.Lambda.Powertools.Common.Tests; using AWS.Lambda.Powertools.Logging.Internal; using AWS.Lambda.Powertools.Logging.Serializers; using AWS.Lambda.Powertools.Logging.Tests.Utilities; @@ -21,32 +34,30 @@ public class PowertoolsLoggerTest : IDisposable { public PowertoolsLoggerTest() { - // Logger.UseDefaultFormatter(); + Logger.UseDefaultFormatter(); } - private static void Log_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel logLevel, LogLevel MinimumLogLevel) + private static void Log_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel logLevel, LogLevel minimumLevel) { // Arrange var loggerName = Guid.NewGuid().ToString(); var service = Guid.NewGuid().ToString(); var configurations = Substitute.For(); - var systemWrapper = Substitute.For(); + var systemWrapper = Substitute.For(); // Configure the substitute for IPowertoolsConfigurations configurations.Service.Returns(service); configurations.LoggerOutputCase.Returns(LoggerOutputCase.PascalCase.ToString()); - configurations.LogLevel.Returns(MinimumLogLevel.ToString()); + configurations.LogLevel.Returns(minimumLevel.ToString()); - var loggerConfiguration = new PowertoolsLoggerConfiguration + var loggerConfiguration = new LoggerConfiguration { - Service = service, - LoggerOutputCase = LoggerOutputCase.PascalCase, - MinimumLogLevel = MinimumLogLevel, - LogOutput = systemWrapper // Set the output directly on configuration + Service = null, + MinimumLevel = LogLevel.None }; - - var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); + + var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); var logger = provider.CreateLogger(loggerName); switch (logLevel) @@ -77,34 +88,32 @@ private static void Log_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel logLeve } // Assert - systemWrapper.Received(1).WriteLine( + systemWrapper.Received(1).LogLine( Arg.Is(s => s.Contains(service)) ); } - private static void Log_WhenMinimumLogLevelIsAboveLogLevel_DoesNotLog(LogLevel logLevel, - LogLevel MinimumLogLevel) + private static void Log_WhenMinimumLevelIsAboveLogLevel_DoesNotLog(LogLevel logLevel, LogLevel minimumLevel) { // Arrange var loggerName = Guid.NewGuid().ToString(); var service = Guid.NewGuid().ToString(); var configurations = Substitute.For(); - var systemWrapper = Substitute.For(); + var systemWrapper = Substitute.For(); // Configure the substitute for IPowertoolsConfigurations configurations.Service.Returns(service); configurations.LoggerOutputCase.Returns(LoggerOutputCase.PascalCase.ToString()); - configurations.LogLevel.Returns(MinimumLogLevel.ToString()); + configurations.LogLevel.Returns(minimumLevel.ToString()); - var loggerConfiguration = new PowertoolsLoggerConfiguration + var loggerConfiguration = new LoggerConfiguration { Service = service, - MinimumLogLevel = MinimumLogLevel, - LogOutput = systemWrapper // Set the output directly on configuration + MinimumLevel = minimumLevel }; - - var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); + + var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); var logger = provider.CreateLogger(loggerName); switch (logLevel) @@ -135,33 +144,33 @@ private static void Log_WhenMinimumLogLevelIsAboveLogLevel_DoesNotLog(LogLevel l } // Assert - systemWrapper.DidNotReceive().WriteLine( + systemWrapper.DidNotReceive().LogLine( Arg.Any() ); } [Theory] [InlineData(LogLevel.Trace)] - public void LogTrace_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel MinimumLogLevel) + public void LogTrace_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel minimumLevel) { - Log_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel.Trace, MinimumLogLevel); + Log_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel.Trace, minimumLevel); } [Theory] [InlineData(LogLevel.Trace)] [InlineData(LogLevel.Debug)] - public void LogDebug_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel MinimumLogLevel) + public void LogDebug_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel minimumLevel) { - Log_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel.Debug, MinimumLogLevel); + Log_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel.Debug, minimumLevel); } [Theory] [InlineData(LogLevel.Trace)] [InlineData(LogLevel.Debug)] [InlineData(LogLevel.Information)] - public void LogInformation_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel MinimumLogLevel) + public void LogInformation_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel minimumLevel) { - Log_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel.Information, MinimumLogLevel); + Log_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel.Information, minimumLevel); } [Theory] @@ -169,9 +178,9 @@ public void LogInformation_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel Mini [InlineData(LogLevel.Debug)] [InlineData(LogLevel.Information)] [InlineData(LogLevel.Warning)] - public void LogWarning_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel MinimumLogLevel) + public void LogWarning_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel minimumLevel) { - Log_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel.Warning, MinimumLogLevel); + Log_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel.Warning, minimumLevel); } [Theory] @@ -180,9 +189,9 @@ public void LogWarning_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel MinimumL [InlineData(LogLevel.Information)] [InlineData(LogLevel.Warning)] [InlineData(LogLevel.Error)] - public void LogError_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel MinimumLogLevel) + public void LogError_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel minimumLevel) { - Log_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel.Error, MinimumLogLevel); + Log_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel.Error, minimumLevel); } [Theory] @@ -192,9 +201,9 @@ public void LogError_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel MinimumLog [InlineData(LogLevel.Warning)] [InlineData(LogLevel.Error)] [InlineData(LogLevel.Critical)] - public void LogCritical_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel MinimumLogLevel) + public void LogCritical_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel minimumLevel) { - Log_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel.Critical, MinimumLogLevel); + Log_WhenMinimumLevelIsBelowLogLevel_Logs(LogLevel.Critical, minimumLevel); } [Theory] @@ -203,9 +212,9 @@ public void LogCritical_WhenMinimumLogLevelIsBelowLogLevel_Logs(LogLevel Minimum [InlineData(LogLevel.Warning)] [InlineData(LogLevel.Error)] [InlineData(LogLevel.Critical)] - public void LogTrace_WhenMinimumLogLevelIsAboveLogLevel_DoesNotLog(LogLevel MinimumLogLevel) + public void LogTrace_WhenMinimumLevelIsAboveLogLevel_DoesNotLog(LogLevel minimumLevel) { - Log_WhenMinimumLogLevelIsAboveLogLevel_DoesNotLog(LogLevel.Trace, MinimumLogLevel); + Log_WhenMinimumLevelIsAboveLogLevel_DoesNotLog(LogLevel.Trace, minimumLevel); } [Theory] @@ -213,33 +222,33 @@ public void LogTrace_WhenMinimumLogLevelIsAboveLogLevel_DoesNotLog(LogLevel Mini [InlineData(LogLevel.Warning)] [InlineData(LogLevel.Error)] [InlineData(LogLevel.Critical)] - public void LogDebug_WhenMinimumLogLevelIsAboveLogLevel_DoesNotLog(LogLevel MinimumLogLevel) + public void LogDebug_WhenMinimumLevelIsAboveLogLevel_DoesNotLog(LogLevel minimumLevel) { - Log_WhenMinimumLogLevelIsAboveLogLevel_DoesNotLog(LogLevel.Debug, MinimumLogLevel); + Log_WhenMinimumLevelIsAboveLogLevel_DoesNotLog(LogLevel.Debug, minimumLevel); } [Theory] [InlineData(LogLevel.Warning)] [InlineData(LogLevel.Error)] [InlineData(LogLevel.Critical)] - public void LogInformation_WhenMinimumLogLevelIsAboveLogLevel_DoesNotLog(LogLevel MinimumLogLevel) + public void LogInformation_WhenMinimumLevelIsAboveLogLevel_DoesNotLog(LogLevel minimumLevel) { - Log_WhenMinimumLogLevelIsAboveLogLevel_DoesNotLog(LogLevel.Information, MinimumLogLevel); + Log_WhenMinimumLevelIsAboveLogLevel_DoesNotLog(LogLevel.Information, minimumLevel); } [Theory] [InlineData(LogLevel.Error)] [InlineData(LogLevel.Critical)] - public void LogWarning_WhenMinimumLogLevelIsAboveLogLevel_DoesNotLog(LogLevel MinimumLogLevel) + public void LogWarning_WhenMinimumLevelIsAboveLogLevel_DoesNotLog(LogLevel minimumLevel) { - Log_WhenMinimumLogLevelIsAboveLogLevel_DoesNotLog(LogLevel.Warning, MinimumLogLevel); + Log_WhenMinimumLevelIsAboveLogLevel_DoesNotLog(LogLevel.Warning, minimumLevel); } [Theory] [InlineData(LogLevel.Critical)] - public void LogError_WhenMinimumLogLevelIsAboveLogLevel_DoesNotLog(LogLevel MinimumLogLevel) + public void LogError_WhenMinimumLevelIsAboveLogLevel_DoesNotLog(LogLevel minimumLevel) { - Log_WhenMinimumLogLevelIsAboveLogLevel_DoesNotLog(LogLevel.Error, MinimumLogLevel); + Log_WhenMinimumLevelIsAboveLogLevel_DoesNotLog(LogLevel.Error, minimumLevel); } [Theory] @@ -249,9 +258,9 @@ public void LogError_WhenMinimumLogLevelIsAboveLogLevel_DoesNotLog(LogLevel Mini [InlineData(LogLevel.Warning)] [InlineData(LogLevel.Error)] [InlineData(LogLevel.Critical)] - public void LogNone_WithAnyMinimumLogLevel_DoesNotLog(LogLevel MinimumLogLevel) + public void LogNone_WithAnyMinimumLevel_DoesNotLog(LogLevel minimumLevel) { - Log_WhenMinimumLogLevelIsAboveLogLevel_DoesNotLog(LogLevel.None, MinimumLogLevel); + Log_WhenMinimumLevelIsAboveLogLevel_DoesNotLog(LogLevel.None, minimumLevel); } [Fact] @@ -261,29 +270,32 @@ public void Log_ConfigurationIsNotProvided_ReadsFromEnvironmentVariables() var service = Guid.NewGuid().ToString(); var logLevel = LogLevel.Trace; var loggerSampleRate = 0.7; + var randomSampleRate = 0.5; var configurations = Substitute.For(); configurations.Service.Returns(service); configurations.LogLevel.Returns(logLevel.ToString()); configurations.LoggerSampleRate.Returns(loggerSampleRate); - var systemWrapper = Substitute.For(); + var systemWrapper = Substitute.For(); + systemWrapper.GetRandom().Returns(randomSampleRate); - var loggerConfiguration = new PowertoolsLoggerConfiguration + var loggerConfiguration = new LoggerConfiguration { - Service = service, - MinimumLogLevel = logLevel, - LogOutput = systemWrapper, - SamplingRate = loggerSampleRate + Service = null, + MinimumLevel = LogLevel.None }; - var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); + // Act + + var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); + var logger = provider.CreateLogger("test"); logger.LogInformation("Test"); // Assert - systemWrapper.Received(1).WriteLine( + systemWrapper.Received(1).LogLine( Arg.Is(s => s.Contains(service) && s.Contains(loggerSampleRate.ToString(CultureInfo.InvariantCulture)) @@ -297,38 +309,36 @@ public void Log_SamplingRateGreaterThanRandom_ChangedLogLevelToDebug() // Arrange var service = Guid.NewGuid().ToString(); var logLevel = LogLevel.Trace; - var loggerSampleRate = 1.0; // Use 100% to guarantee activation + var loggerSampleRate = 0.7; + var randomSampleRate = 0.5; var configurations = Substitute.For(); configurations.Service.Returns(service); configurations.LogLevel.Returns(logLevel.ToString()); configurations.LoggerSampleRate.Returns(loggerSampleRate); - var systemWrapper = Substitute.For(); - - var loggerConfiguration = new PowertoolsLoggerConfiguration + var systemWrapper = Substitute.For(); + systemWrapper.GetRandom().Returns(randomSampleRate); + + var loggerConfiguration = new LoggerConfiguration { - Service = service, - MinimumLogLevel = logLevel, - LogOutput = systemWrapper, - SamplingRate = loggerSampleRate + Service = null, + MinimumLevel = LogLevel.None }; - + // Act - var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); - var logger = provider.CreateLogger("test"); + + var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); - // First call - skipped due to cold start protection - logger.LogInformation("Test1"); + var logger = provider.CreateLogger("test"); - // Second call - should trigger sampling with 100% rate - logger.LogInformation("Test2"); + logger.LogInformation("Test"); - // Assert - Check that the debug message was printed (with any sampler value since it's random) - systemWrapper.Received(1).WriteLine( + // Assert + systemWrapper.Received(1).LogLine( Arg.Is(s => - s.Contains("Changed log level to DEBUG based on Sampling configuration") && - s.Contains($"Sampling Rate: {loggerSampleRate}") + s == + $"Changed log level to DEBUG based on Sampling configuration. Sampling Rate: {loggerSampleRate}, Sampler Value: {randomSampleRate}." ) ); } @@ -347,23 +357,22 @@ public void Log_SamplingRateGreaterThanOne_SkipsSamplingRateConfiguration() configurations.LogLevel.Returns(logLevel.ToString()); configurations.LoggerSampleRate.Returns(loggerSampleRate); - var systemWrapper = Substitute.For(); + var systemWrapper = Substitute.For(); - var loggerConfiguration = new PowertoolsLoggerConfiguration + var loggerConfiguration = new LoggerConfiguration { - Service = service, - MinimumLogLevel = logLevel, - LogOutput = systemWrapper, - SamplingRate = loggerSampleRate - }; - - var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); + Service = null, + MinimumLevel = LogLevel.None + }; + + // Act + var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); var logger = provider.CreateLogger(loggerName); logger.LogInformation("Test"); // Assert - systemWrapper.Received(1).WriteLine( + systemWrapper.Received(1).LogLine( Arg.Is(s => s == $"Skipping sampling rate configuration because of invalid value. Sampling rate: {loggerSampleRate}" @@ -378,23 +387,24 @@ public void Log_EnvVarSetsCaseToCamelCase_OutputsCamelCaseLog() var loggerName = Guid.NewGuid().ToString(); var service = Guid.NewGuid().ToString(); var logLevel = LogLevel.Information; + var randomSampleRate = 0.5; var configurations = Substitute.For(); configurations.Service.Returns(service); configurations.LogLevel.Returns(logLevel.ToString()); configurations.LoggerOutputCase.Returns(LoggerOutputCase.CamelCase.ToString()); - var systemWrapper = Substitute.For(); + var systemWrapper = Substitute.For(); + systemWrapper.GetRandom().Returns(randomSampleRate); - var loggerConfiguration = new PowertoolsLoggerConfiguration + var loggerConfiguration = new LoggerConfiguration { Service = null, - MinimumLogLevel = LogLevel.None, - LogOutput = systemWrapper + MinimumLevel = LogLevel.None }; // Act - var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); + var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); var logger = provider.CreateLogger(loggerName); var message = new @@ -406,7 +416,7 @@ public void Log_EnvVarSetsCaseToCamelCase_OutputsCamelCaseLog() logger.LogInformation(message); // Assert - systemWrapper.Received(1).WriteLine( + systemWrapper.Received(1).LogLine( Arg.Is(s => s.Contains("\"message\":{\"propOne\":\"Value 1\",\"propTwo\":\"Value 2\"}") ) @@ -420,23 +430,24 @@ public void Log_AttributeSetsCaseToCamelCase_OutputsCamelCaseLog() var loggerName = Guid.NewGuid().ToString(); var service = Guid.NewGuid().ToString(); var logLevel = LogLevel.Information; + var randomSampleRate = 0.5; var configurations = Substitute.For(); configurations.Service.Returns(service); configurations.LogLevel.Returns(logLevel.ToString()); - var systemWrapper = Substitute.For(); + var systemWrapper = Substitute.For(); + systemWrapper.GetRandom().Returns(randomSampleRate); - var loggerConfiguration = new PowertoolsLoggerConfiguration + var loggerConfiguration = new LoggerConfiguration { Service = null, - MinimumLogLevel = LogLevel.None, - LoggerOutputCase = LoggerOutputCase.CamelCase, - LogOutput = systemWrapper + MinimumLevel = LogLevel.None, + LoggerOutputCase = LoggerOutputCase.CamelCase }; - + // Act - var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); + var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); var logger = provider.CreateLogger(loggerName); var message = new @@ -448,7 +459,7 @@ public void Log_AttributeSetsCaseToCamelCase_OutputsCamelCaseLog() logger.LogInformation(message); // Assert - systemWrapper.Received(1).WriteLine( + systemWrapper.Received(1).LogLine( Arg.Is(s => s.Contains("\"message\":{\"propOne\":\"Value 1\",\"propTwo\":\"Value 2\"}") ) @@ -462,22 +473,23 @@ public void Log_EnvVarSetsCaseToPascalCase_OutputsPascalCaseLog() var loggerName = Guid.NewGuid().ToString(); var service = Guid.NewGuid().ToString(); var logLevel = LogLevel.Information; + var randomSampleRate = 0.5; var configurations = Substitute.For(); configurations.Service.Returns(service); configurations.LogLevel.Returns(logLevel.ToString()); configurations.LoggerOutputCase.Returns(LoggerOutputCase.PascalCase.ToString()); - var systemWrapper = Substitute.For(); + var systemWrapper = Substitute.For(); + systemWrapper.GetRandom().Returns(randomSampleRate); - var loggerConfiguration = new PowertoolsLoggerConfiguration + var loggerConfiguration = new LoggerConfiguration { Service = null, - MinimumLogLevel = LogLevel.None, - LogOutput = systemWrapper + MinimumLevel = LogLevel.None }; - - var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); + + var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); var logger = provider.CreateLogger(loggerName); var message = new @@ -489,7 +501,7 @@ public void Log_EnvVarSetsCaseToPascalCase_OutputsPascalCaseLog() logger.LogInformation(message); // Assert - systemWrapper.Received(1).WriteLine( + systemWrapper.Received(1).LogLine( Arg.Is(s => s.Contains("\"Message\":{\"PropOne\":\"Value 1\",\"PropTwo\":\"Value 2\"}") ) @@ -503,22 +515,23 @@ public void Log_AttributeSetsCaseToPascalCase_OutputsPascalCaseLog() var loggerName = Guid.NewGuid().ToString(); var service = Guid.NewGuid().ToString(); var logLevel = LogLevel.Information; + var randomSampleRate = 0.5; var configurations = Substitute.For(); configurations.Service.Returns(service); configurations.LogLevel.Returns(logLevel.ToString()); - var systemWrapper = Substitute.For(); + var systemWrapper = Substitute.For(); + systemWrapper.GetRandom().Returns(randomSampleRate); - var loggerConfiguration = new PowertoolsLoggerConfiguration + var loggerConfiguration = new LoggerConfiguration { Service = null, - MinimumLogLevel = LogLevel.None, - LoggerOutputCase = LoggerOutputCase.PascalCase, - LogOutput = systemWrapper + MinimumLevel = LogLevel.None, + LoggerOutputCase = LoggerOutputCase.PascalCase }; - - var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); + + var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); var logger = provider.CreateLogger(loggerName); var message = new @@ -530,7 +543,7 @@ public void Log_AttributeSetsCaseToPascalCase_OutputsPascalCaseLog() logger.LogInformation(message); // Assert - systemWrapper.Received(1).WriteLine(Arg.Is(s => + systemWrapper.Received(1).LogLine(Arg.Is(s => s.Contains("\"Message\":{\"PropOne\":\"Value 1\",\"PropTwo\":\"Value 2\"}") )); } @@ -542,22 +555,23 @@ public void Log_EnvVarSetsCaseToSnakeCase_OutputsSnakeCaseLog() var loggerName = Guid.NewGuid().ToString(); var service = Guid.NewGuid().ToString(); var logLevel = LogLevel.Information; + var randomSampleRate = 0.5; var configurations = Substitute.For(); configurations.Service.Returns(service); configurations.LogLevel.Returns(logLevel.ToString()); configurations.LoggerOutputCase.Returns(LoggerOutputCase.SnakeCase.ToString()); - var systemWrapper = Substitute.For(); + var systemWrapper = Substitute.For(); + systemWrapper.GetRandom().Returns(randomSampleRate); - var loggerConfiguration = new PowertoolsLoggerConfiguration + var loggerConfiguration = new LoggerConfiguration { Service = null, - MinimumLogLevel = LogLevel.None, - LogOutput = systemWrapper + MinimumLevel = LogLevel.None }; - - var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); + + var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); var logger = provider.CreateLogger(loggerName); var message = new @@ -569,7 +583,7 @@ public void Log_EnvVarSetsCaseToSnakeCase_OutputsSnakeCaseLog() logger.LogInformation(message); // Assert - systemWrapper.Received(1).WriteLine(Arg.Is(s => + systemWrapper.Received(1).LogLine(Arg.Is(s => s.Contains("\"message\":{\"prop_one\":\"Value 1\",\"prop_two\":\"Value 2\"}") )); } @@ -581,22 +595,23 @@ public void Log_AttributeSetsCaseToSnakeCase_OutputsSnakeCaseLog() var loggerName = Guid.NewGuid().ToString(); var service = Guid.NewGuid().ToString(); var logLevel = LogLevel.Information; + var randomSampleRate = 0.5; var configurations = Substitute.For(); configurations.Service.Returns(service); configurations.LogLevel.Returns(logLevel.ToString()); - var systemWrapper = Substitute.For(); + var systemWrapper = Substitute.For(); + systemWrapper.GetRandom().Returns(randomSampleRate); - var loggerConfiguration = new PowertoolsLoggerConfiguration + var loggerConfiguration = new LoggerConfiguration { Service = null, - MinimumLogLevel = LogLevel.None, - LoggerOutputCase = LoggerOutputCase.SnakeCase, - LogOutput = systemWrapper + MinimumLevel = LogLevel.None, + LoggerOutputCase = LoggerOutputCase.SnakeCase }; - - var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); + + var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); var logger = provider.CreateLogger(loggerName); var message = new @@ -608,7 +623,7 @@ public void Log_AttributeSetsCaseToSnakeCase_OutputsSnakeCaseLog() logger.LogInformation(message); // Assert - systemWrapper.Received(1).WriteLine(Arg.Is(s => + systemWrapper.Received(1).LogLine(Arg.Is(s => s.Contains("\"message\":{\"prop_one\":\"Value 1\",\"prop_two\":\"Value 2\"}") )); } @@ -620,21 +635,22 @@ public void Log_NoOutputCaseSet_OutputDefaultsToSnakeCaseLog() var loggerName = Guid.NewGuid().ToString(); var service = Guid.NewGuid().ToString(); var logLevel = LogLevel.Information; + var randomSampleRate = 0.5; var configurations = Substitute.For(); configurations.Service.Returns(service); configurations.LogLevel.Returns(logLevel.ToString()); - var systemWrapper = Substitute.For(); + var systemWrapper = Substitute.For(); + systemWrapper.GetRandom().Returns(randomSampleRate); - var loggerConfiguration = new PowertoolsLoggerConfiguration + var loggerConfiguration = new LoggerConfiguration { Service = null, - MinimumLogLevel = LogLevel.None, - LogOutput = systemWrapper - }; - - var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); + MinimumLevel = LogLevel.None + }; + + var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); var logger = provider.CreateLogger(loggerName); var message = new @@ -646,7 +662,7 @@ public void Log_NoOutputCaseSet_OutputDefaultsToSnakeCaseLog() logger.LogInformation(message); // Assert - systemWrapper.Received(1).WriteLine(Arg.Is(s => + systemWrapper.Received(1).LogLine(Arg.Is(s => s.Contains("\"message\":{\"prop_one\":\"Value 1\",\"prop_two\":\"Value 2\"}"))); } @@ -661,15 +677,15 @@ public void BeginScope_WhenScopeIsObject_ExtractScopeKeys() var configurations = Substitute.For(); configurations.Service.Returns(service); configurations.LogLevel.Returns(logLevel.ToString()); - var systemWrapper = Substitute.For(); + var systemWrapper = Substitute.For(); - var loggerConfiguration = new PowertoolsLoggerConfiguration + var loggerConfiguration = new LoggerConfiguration { Service = service, - MinimumLogLevel = logLevel + MinimumLevel = logLevel }; - - var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); + + var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); var logger = (PowertoolsLogger)provider.CreateLogger(loggerName); var scopeKeys = new @@ -704,15 +720,15 @@ public void BeginScope_WhenScopeIsObjectDictionary_ExtractScopeKeys() var configurations = Substitute.For(); configurations.Service.Returns(service); configurations.LogLevel.Returns(logLevel.ToString()); - var systemWrapper = Substitute.For(); + var systemWrapper = Substitute.For(); - var loggerConfiguration = new PowertoolsLoggerConfiguration + var loggerConfiguration = new LoggerConfiguration { Service = service, - MinimumLogLevel = logLevel + MinimumLevel = logLevel }; - - var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); + + var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); var logger = (PowertoolsLogger)provider.CreateLogger(loggerName); var scopeKeys = new Dictionary @@ -747,15 +763,15 @@ public void BeginScope_WhenScopeIsStringDictionary_ExtractScopeKeys() var configurations = Substitute.For(); configurations.Service.Returns(service); configurations.LogLevel.Returns(logLevel.ToString()); - var systemWrapper = Substitute.For(); + var systemWrapper = Substitute.For(); - var loggerConfiguration = new PowertoolsLoggerConfiguration + var loggerConfiguration = new LoggerConfiguration { Service = service, - MinimumLogLevel = logLevel + MinimumLevel = logLevel }; - - var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); + + var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); var logger = (PowertoolsLogger)provider.CreateLogger(loggerName); var scopeKeys = new Dictionary @@ -797,21 +813,20 @@ public void Log_WhenExtraKeysIsObjectDictionary_AppendExtraKeys(LogLevel logLeve // Arrange var loggerName = Guid.NewGuid().ToString(); var service = Guid.NewGuid().ToString(); - var message = "{@keys}"; + var message = Guid.NewGuid().ToString(); var configurations = Substitute.For(); configurations.Service.Returns(service); configurations.LogLevel.Returns(logLevel.ToString()); configurations.LoggerOutputCase.Returns(LoggerOutputCase.PascalCase.ToString()); - var systemWrapper = Substitute.For(); + var systemWrapper = Substitute.For(); - var loggerConfiguration = new PowertoolsLoggerConfiguration + var loggerConfiguration = new LoggerConfiguration { Service = service, - MinimumLogLevel = LogLevel.Trace, - LogOutput = systemWrapper + MinimumLevel = LogLevel.Trace, }; - var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); + var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); var logger = (PowertoolsLogger)provider.CreateLogger(loggerName); var scopeKeys = new Dictionary @@ -822,29 +837,29 @@ public void Log_WhenExtraKeysIsObjectDictionary_AppendExtraKeys(LogLevel logLeve if (logMethod) { - logger.Log(logLevel, message,scopeKeys); + logger.Log(logLevel, scopeKeys, message); } else { switch (logLevel) { case LogLevel.Trace: - logger.LogTrace(message,scopeKeys); + logger.LogTrace(scopeKeys, message); break; case LogLevel.Debug: - logger.LogDebug(message,scopeKeys); + logger.LogDebug(scopeKeys, message); break; case LogLevel.Information: - logger.LogInformation(message,scopeKeys); + logger.LogInformation(scopeKeys, message); break; case LogLevel.Warning: - logger.LogWarning(message,scopeKeys); + logger.LogWarning(scopeKeys, message); break; case LogLevel.Error: - logger.LogError(message,scopeKeys); + logger.LogError(scopeKeys, message); break; case LogLevel.Critical: - logger.LogCritical(message,scopeKeys); + logger.LogCritical(scopeKeys, message); break; case LogLevel.None: break; @@ -853,7 +868,7 @@ public void Log_WhenExtraKeysIsObjectDictionary_AppendExtraKeys(LogLevel logLeve } } - systemWrapper.Received(1).WriteLine(Arg.Is(s => + systemWrapper.Received(1).LogLine(Arg.Is(s => s.Contains(scopeKeys.Keys.First()) && s.Contains(scopeKeys.Keys.Last()) && s.Contains(scopeKeys.Values.First().ToString()) && @@ -881,22 +896,21 @@ public void Log_WhenExtraKeysIsStringDictionary_AppendExtraKeys(LogLevel logLeve // Arrange var loggerName = Guid.NewGuid().ToString(); var service = Guid.NewGuid().ToString(); - var message = "{@keys}"; + var message = Guid.NewGuid().ToString(); var configurations = Substitute.For(); configurations.Service.Returns(service); configurations.LogLevel.Returns(logLevel.ToString()); configurations.LoggerOutputCase.Returns(LoggerOutputCase.PascalCase.ToString()); - var systemWrapper = Substitute.For(); + var systemWrapper = Substitute.For(); - var loggerConfiguration = new PowertoolsLoggerConfiguration + var loggerConfiguration = new LoggerConfiguration { Service = service, - MinimumLogLevel = LogLevel.Trace, - LogOutput = systemWrapper + MinimumLevel = LogLevel.Trace, }; - - var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); + + var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); var logger = (PowertoolsLogger)provider.CreateLogger(loggerName); var scopeKeys = new Dictionary @@ -907,29 +921,29 @@ public void Log_WhenExtraKeysIsStringDictionary_AppendExtraKeys(LogLevel logLeve if (logMethod) { - logger.Log(logLevel, message,scopeKeys); + logger.Log(logLevel, scopeKeys, message); } else { switch (logLevel) { case LogLevel.Trace: - logger.LogTrace(message,scopeKeys); + logger.LogTrace(scopeKeys, message); break; case LogLevel.Debug: - logger.LogDebug(message,scopeKeys); + logger.LogDebug(scopeKeys, message); break; case LogLevel.Information: - logger.LogInformation(message,scopeKeys); + logger.LogInformation(scopeKeys, message); break; case LogLevel.Warning: - logger.LogWarning(message,scopeKeys); + logger.LogWarning(scopeKeys, message); break; case LogLevel.Error: - logger.LogError(message,scopeKeys); + logger.LogError(scopeKeys, message); break; case LogLevel.Critical: - logger.LogCritical(message,scopeKeys); + logger.LogCritical(scopeKeys, message); break; case LogLevel.None: break; @@ -938,7 +952,7 @@ public void Log_WhenExtraKeysIsStringDictionary_AppendExtraKeys(LogLevel logLeve } } - systemWrapper.Received(1).WriteLine(Arg.Is(s => + systemWrapper.Received(1).LogLine(Arg.Is(s => s.Contains(scopeKeys.Keys.First()) && s.Contains(scopeKeys.Keys.Last()) && s.Contains(scopeKeys.Values.First()) && @@ -966,22 +980,21 @@ public void Log_WhenExtraKeysAsObject_AppendExtraKeys(LogLevel logLevel, bool lo // Arrange var loggerName = Guid.NewGuid().ToString(); var service = Guid.NewGuid().ToString(); - var message = "{@keys}"; + var message = Guid.NewGuid().ToString(); var configurations = Substitute.For(); configurations.Service.Returns(service); configurations.LogLevel.Returns(logLevel.ToString()); configurations.LoggerOutputCase.Returns(LoggerOutputCase.PascalCase.ToString()); - var systemWrapper = Substitute.For(); + var systemWrapper = Substitute.For(); - var loggerConfiguration = new PowertoolsLoggerConfiguration + var loggerConfiguration = new LoggerConfiguration { Service = service, - MinimumLogLevel = LogLevel.Trace, - LogOutput = systemWrapper + MinimumLevel = LogLevel.Trace, }; - - var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); + + var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); var logger = (PowertoolsLogger)provider.CreateLogger(loggerName); var scopeKeys = new @@ -992,29 +1005,29 @@ public void Log_WhenExtraKeysAsObject_AppendExtraKeys(LogLevel logLevel, bool lo if (logMethod) { - logger.Log(logLevel, message, scopeKeys); + logger.Log(logLevel, scopeKeys, message); } else { switch (logLevel) { case LogLevel.Trace: - logger.LogTrace(message,scopeKeys); + logger.LogTrace(scopeKeys, message); break; case LogLevel.Debug: - logger.LogDebug(message,scopeKeys); + logger.LogDebug(scopeKeys, message); break; case LogLevel.Information: - logger.LogInformation(message,scopeKeys); + logger.LogInformation(scopeKeys, message); break; case LogLevel.Warning: - logger.LogWarning(message,scopeKeys); + logger.LogWarning(scopeKeys, message); break; case LogLevel.Error: - logger.LogError(message,scopeKeys); + logger.LogError(scopeKeys, message); break; case LogLevel.Critical: - logger.LogCritical(message,scopeKeys); + logger.LogCritical(scopeKeys, message); break; case LogLevel.None: break; @@ -1023,7 +1036,7 @@ public void Log_WhenExtraKeysAsObject_AppendExtraKeys(LogLevel logLevel, bool lo } } - systemWrapper.Received(1).WriteLine(Arg.Is(s => + systemWrapper.Received(1).LogLine(Arg.Is(s => s.Contains("PropOne") && s.Contains("PropTwo") && s.Contains(scopeKeys.PropOne) && @@ -1041,21 +1054,22 @@ public void Log_WhenException_LogsExceptionDetails() var service = Guid.NewGuid().ToString(); var error = new InvalidOperationException("TestError"); var logLevel = LogLevel.Information; + var randomSampleRate = 0.5; var configurations = Substitute.For(); configurations.Service.Returns(service); configurations.LogLevel.Returns(logLevel.ToString()); - var systemWrapper = Substitute.For(); + var systemWrapper = Substitute.For(); + systemWrapper.GetRandom().Returns(randomSampleRate); - var loggerConfiguration = new PowertoolsLoggerConfiguration + var loggerConfiguration = new LoggerConfiguration { Service = null, - MinimumLogLevel = LogLevel.None, - LogOutput = systemWrapper + MinimumLevel = LogLevel.None }; - - var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); + + var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); var logger = provider.CreateLogger(loggerName); try @@ -1068,16 +1082,15 @@ public void Log_WhenException_LogsExceptionDetails() } // Assert - systemWrapper.Received(1).WriteLine(Arg.Is(s => + systemWrapper.Received(1).LogLine(Arg.Is(s => s.Contains("\"exception\":{\"type\":\"" + error.GetType().FullName + "\",\"message\":\"" + error.Message + "\"") )); - systemWrapper.Received(1).WriteLine(Arg.Is(s => - s.Contains( - "\"exception\":{\"type\":\"System.InvalidOperationException\",\"message\":\"TestError\",\"source\":\"AWS.Lambda.Powertools.Logging.Tests\",\"stack_trace\":\" at AWS.Lambda.Powertools.Logging.Tests.PowertoolsLoggerTest.Log_WhenException_LogsExceptionDetails()") + systemWrapper.Received(1).LogLine(Arg.Is(s => + s.Contains("\"exception\":{\"type\":\"System.InvalidOperationException\",\"message\":\"TestError\",\"source\":\"AWS.Lambda.Powertools.Logging.Tests\",\"stack_trace\":\" at AWS.Lambda.Powertools.Logging.Tests.PowertoolsLoggerTest.Log_WhenException_LogsExceptionDetails()") )); } - + [Fact] public void Log_Inner_Exception() { @@ -1087,48 +1100,40 @@ public void Log_Inner_Exception() var error = new InvalidOperationException("Parent exception message", new ArgumentNullException(nameof(service), "Very important inner exception message")); var logLevel = LogLevel.Information; + var randomSampleRate = 0.5; var configurations = Substitute.For(); configurations.Service.Returns(service); configurations.LogLevel.Returns(logLevel.ToString()); - var systemWrapper = Substitute.For(); + var systemWrapper = Substitute.For(); + systemWrapper.GetRandom().Returns(randomSampleRate); - var loggerConfiguration = new PowertoolsLoggerConfiguration + var loggerConfiguration = new LoggerConfiguration { Service = null, - MinimumLogLevel = LogLevel.None, - LogOutput = systemWrapper + MinimumLevel = LogLevel.None }; - - var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); + + var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); var logger = provider.CreateLogger(loggerName); logger.LogError( - error, + error, "Something went wrong and we logged an exception itself with an inner exception. This is a param {arg}", 12345); // Assert - systemWrapper.Received(1).WriteLine(Arg.Is(s => + systemWrapper.Received(1).LogLine(Arg.Is(s => s.Contains("\"exception\":{\"type\":\"" + error.GetType().FullName + "\",\"message\":\"" + error.Message + "\"") )); - - systemWrapper.Received(1).WriteLine(Arg.Is(s => - s.Contains("\"level\":\"Error\"") && - s.Contains("\"service\":\"" + service + "\"") && - s.Contains("\"name\":\"" + loggerName + "\"") && - s.Contains("\"message\":\"Something went wrong and we logged an exception itself with an inner exception. This is a param 12345\"") && - s.Contains("\"exception\":{") && - s.Contains("\"type\":\"System.InvalidOperationException\"") && - s.Contains("\"message\":\"Parent exception message\"") && - s.Contains("\"inner_exception\":{") && - s.Contains("\"type\":\"System.ArgumentNullException\"") && - s.Contains("\"message\":\"Very important inner exception message (Parameter 'service')\"") + + systemWrapper.Received(1).LogLine(Arg.Is(s => + s.Contains("\"level\":\"Error\",\"service\":\"" + service+ "\",\"name\":\"" + loggerName + "\",\"message\":\"Something went wrong and we logged an exception itself with an inner exception. This is a param 12345\",\"exception\":{\"type\":\"System.InvalidOperationException\",\"message\":\"Parent exception message\",\"inner_exception\":{\"type\":\"System.ArgumentNullException\",\"message\":\"Very important inner exception message (Parameter 'service')\"}}}") )); } - + [Fact] public void Log_Nested_Inner_Exception() { @@ -1138,43 +1143,35 @@ public void Log_Nested_Inner_Exception() var error = new InvalidOperationException("Parent exception message", new ArgumentNullException(nameof(service), new Exception("Very important nested inner exception message"))); - + var logLevel = LogLevel.Information; + var randomSampleRate = 0.5; var configurations = Substitute.For(); configurations.Service.Returns(service); configurations.LogLevel.Returns(logLevel.ToString()); - var systemWrapper = Substitute.For(); + var systemWrapper = Substitute.For(); + systemWrapper.GetRandom().Returns(randomSampleRate); - var loggerConfiguration = new PowertoolsLoggerConfiguration + var loggerConfiguration = new LoggerConfiguration { Service = null, - MinimumLogLevel = LogLevel.None, - LogOutput = systemWrapper + MinimumLevel = LogLevel.None }; - - var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); + + var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); var logger = provider.CreateLogger(loggerName); - + logger.LogError( - error, + error, "Something went wrong and we logged an exception itself with an inner exception. This is a param {arg}", 12345); // Assert - systemWrapper.Received(1).WriteLine(Arg.Is(s => - s.Contains("\"message\":\"Something went wrong and we logged an exception itself with an inner exception. This is a param 12345\"") && - s.Contains("\"exception\":{") && - s.Contains("\"type\":\"System.InvalidOperationException\"") && - s.Contains("\"message\":\"Parent exception message\"") && - s.Contains("\"inner_exception\":{") && - s.Contains("\"type\":\"System.ArgumentNullException\"") && - s.Contains("\"message\":\"service\"") && - s.Contains("\"inner_exception\":{") && - s.Contains("\"type\":\"System.Exception\"") && - s.Contains("\"message\":\"Very important nested inner exception message\"") + systemWrapper.Received(1).LogLine(Arg.Is(s => + s.Contains("\"message\":\"Something went wrong and we logged an exception itself with an inner exception. This is a param 12345\",\"exception\":{\"type\":\"System.InvalidOperationException\",\"message\":\"Parent exception message\",\"inner_exception\":{\"type\":\"System.ArgumentNullException\",\"message\":\"service\",\"inner_exception\":{\"type\":\"System.Exception\",\"message\":\"Very important nested inner exception message\"}}}}") )); } @@ -1186,21 +1183,22 @@ public void Log_WhenNestedException_LogsExceptionDetails() var service = Guid.NewGuid().ToString(); var error = new InvalidOperationException("TestError"); var logLevel = LogLevel.Information; + var randomSampleRate = 0.5; var configurations = Substitute.For(); configurations.Service.Returns(service); configurations.LogLevel.Returns(logLevel.ToString()); - var systemWrapper = Substitute.For(); + var systemWrapper = Substitute.For(); + systemWrapper.GetRandom().Returns(randomSampleRate); - var loggerConfiguration = new PowertoolsLoggerConfiguration + var loggerConfiguration = new LoggerConfiguration { Service = null, - MinimumLogLevel = LogLevel.None, - LogOutput = systemWrapper + MinimumLevel = LogLevel.None }; - - var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); + + var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); var logger = provider.CreateLogger(loggerName); try @@ -1213,14 +1211,14 @@ public void Log_WhenNestedException_LogsExceptionDetails() } // Assert - systemWrapper.Received(1).WriteLine(Arg.Is(s => + systemWrapper.Received(1).LogLine(Arg.Is(s => s.Contains("\"error\":{\"type\":\"" + error.GetType().FullName + "\",\"message\":\"" + error.Message + "\"") )); } [Fact] - public void Log_WhenByteArray_LogsBase64EncodedString() + public void Log_WhenByteArray_LogsByteArrayNumbers() { // Arrange var loggerName = Guid.NewGuid().ToString(); @@ -1228,29 +1226,29 @@ public void Log_WhenByteArray_LogsBase64EncodedString() var bytes = new byte[10]; new Random().NextBytes(bytes); var logLevel = LogLevel.Information; + var randomSampleRate = 0.5; var configurations = Substitute.For(); configurations.Service.Returns(service); configurations.LogLevel.Returns(logLevel.ToString()); - var systemWrapper = Substitute.For(); + var systemWrapper = Substitute.For(); + systemWrapper.GetRandom().Returns(randomSampleRate); - var loggerConfiguration = new PowertoolsLoggerConfiguration + var loggerConfiguration = new LoggerConfiguration { Service = null, - MinimumLogLevel = LogLevel.None, - LogOutput = systemWrapper + MinimumLevel = LogLevel.None }; - var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); + var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); var logger = provider.CreateLogger(loggerName); // Act logger.LogInformation(new { Name = "Test Object", Bytes = bytes }); // Assert - var base64String = Convert.ToBase64String(bytes); - systemWrapper.Received(1).WriteLine(Arg.Is(s => - s.Contains($"\"bytes\":\"{base64String}\"") + systemWrapper.Received(1).LogLine(Arg.Is(s => + s.Contains("\"bytes\":[" + string.Join(",", bytes) + "]") )); } @@ -1267,28 +1265,29 @@ public void Log_WhenMemoryStream_LogsBase64String() Position = 0 }; var logLevel = LogLevel.Information; + var randomSampleRate = 0.5; var configurations = Substitute.For(); configurations.Service.Returns(service); configurations.LogLevel.Returns(logLevel.ToString()); - var systemWrapper = Substitute.For(); + var systemWrapper = Substitute.For(); + systemWrapper.GetRandom().Returns(randomSampleRate); - var loggerConfiguration = new PowertoolsLoggerConfiguration + var loggerConfiguration = new LoggerConfiguration { Service = null, - MinimumLogLevel = LogLevel.None, - LogOutput = systemWrapper + MinimumLevel = LogLevel.None }; - - var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); + + var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); var logger = provider.CreateLogger(loggerName); // Act logger.LogInformation(new { Name = "Test Object", Stream = memoryStream }); // Assert - systemWrapper.Received(1).WriteLine(Arg.Is(s => + systemWrapper.Received(1).LogLine(Arg.Is(s => s.Contains("\"stream\":\"" + Convert.ToBase64String(bytes) + "\"") )); } @@ -1308,28 +1307,29 @@ public void Log_WhenMemoryStream_LogsBase64String_UnsafeRelaxedJsonEscaping() Position = 0 }; var logLevel = LogLevel.Information; + var randomSampleRate = 0.5; var configurations = Substitute.For(); configurations.Service.Returns(service); configurations.LogLevel.Returns(logLevel.ToString()); - var systemWrapper = Substitute.For(); + var systemWrapper = Substitute.For(); + systemWrapper.GetRandom().Returns(randomSampleRate); - var loggerConfiguration = new PowertoolsLoggerConfiguration + var loggerConfiguration = new LoggerConfiguration { Service = null, - MinimumLogLevel = LogLevel.None, - LogOutput = systemWrapper + MinimumLevel = LogLevel.None }; - - var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); + + var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); var logger = provider.CreateLogger(loggerName); // Act logger.LogInformation(new { Name = "Test Object", Stream = memoryStream }); // Assert - systemWrapper.Received(1).WriteLine(Arg.Is(s => + systemWrapper.Received(1).LogLine(Arg.Is(s => s.Contains("\"stream\":\"" + Convert.ToBase64String(bytes) + "\"") )); } @@ -1337,55 +1337,35 @@ public void Log_WhenMemoryStream_LogsBase64String_UnsafeRelaxedJsonEscaping() [Fact] public void Log_Set_Execution_Environment_Context() { + var _originalValue = Environment.GetEnvironmentVariable("POWERTOOLS_SERVICE_NAME"); + // Arrange var loggerName = Guid.NewGuid().ToString(); + var assemblyName = "AWS.Lambda.Powertools.Logger"; + var assemblyVersion = "1.0.0"; - var env = new PowertoolsEnvironment(); - // Act - var configurations = new PowertoolsConfigurations(env); - - var loggerConfiguration = new PowertoolsLoggerConfiguration - { - Service = null, - MinimumLogLevel = LogLevel.None - }; - - var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); - var logger = provider.CreateLogger(loggerName); - logger.LogInformation("Test"); - - // Assert - Assert.Contains($"{Constants.FeatureContextIdentifier}/Logging/", - env.GetEnvironmentVariable("AWS_EXECUTION_ENV")); - } - - [Fact] - public void Log_Skip_If_Exists_Execution_Environment_Context() - { - // Arrange - var loggerName = Guid.NewGuid().ToString(); - - var env = new PowertoolsEnvironment(); - env.SetEnvironmentVariable("AWS_EXECUTION_ENV", - $"{Constants.FeatureContextIdentifier}/Logging/AlreadyThere"); + var env = Substitute.For(); + env.GetAssemblyName(Arg.Any()).Returns(assemblyName); + env.GetAssemblyVersion(Arg.Any()).Returns(assemblyVersion); // Act - var configurations = new PowertoolsConfigurations(env); + var systemWrapper = new SystemWrapper(env); + var configurations = new PowertoolsConfigurations(systemWrapper); - var loggerConfiguration = new PowertoolsLoggerConfiguration + var loggerConfiguration = new LoggerConfiguration { Service = null, - MinimumLogLevel = LogLevel.None + MinimumLevel = LogLevel.None }; - - var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); + + var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); var logger = provider.CreateLogger(loggerName); logger.LogInformation("Test"); // Assert - Assert.Equal($"{Constants.FeatureContextIdentifier}/Logging/AlreadyThere", - env.GetEnvironmentVariable("AWS_EXECUTION_ENV")); - env.SetEnvironmentVariable("AWS_EXECUTION_ENV", null); + env.Received(1).SetEnvironmentVariable("AWS_EXECUTION_ENV", + $"{Constants.FeatureContextIdentifier}/Logger/{assemblyVersion}"); + env.Received(1).GetEnvironmentVariable("AWS_EXECUTION_ENV"); } [Fact] @@ -1395,22 +1375,23 @@ public void Log_Should_Serialize_DateOnly() var loggerName = Guid.NewGuid().ToString(); var service = Guid.NewGuid().ToString(); var logLevel = LogLevel.Information; + var randomSampleRate = 0.5; var configurations = Substitute.For(); configurations.Service.Returns(service); configurations.LogLevel.Returns(logLevel.ToString()); - var systemWrapper = Substitute.For(); + var systemWrapper = Substitute.For(); + systemWrapper.GetRandom().Returns(randomSampleRate); - var loggerConfiguration = new PowertoolsLoggerConfiguration + var loggerConfiguration = new LoggerConfiguration { Service = null, - MinimumLogLevel = LogLevel.None, - LoggerOutputCase = LoggerOutputCase.CamelCase, - LogOutput = systemWrapper + MinimumLevel = LogLevel.None, + LoggerOutputCase = LoggerOutputCase.CamelCase }; - - var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); + + var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); var logger = provider.CreateLogger(loggerName); var message = new @@ -1427,10 +1408,9 @@ public void Log_Should_Serialize_DateOnly() logger.LogInformation(message); // Assert - systemWrapper.Received(1).WriteLine( + systemWrapper.Received(1).LogLine( Arg.Is(s => - s.Contains( - "\"message\":{\"propOne\":\"Value 1\",\"propTwo\":\"Value 2\",\"propThree\":{\"propFour\":1},\"date\":\"2022-01-01\"}") + s.Contains("\"message\":{\"propOne\":\"Value 1\",\"propTwo\":\"Value 2\",\"propThree\":{\"propFour\":1},\"date\":\"2022-01-01\"}}") ) ); } @@ -1448,17 +1428,17 @@ public void Log_Should_Serialize_TimeOnly() configurations.Service.Returns(service); configurations.LogLevel.Returns(logLevel.ToString()); - var systemWrapper = Substitute.For(); + var systemWrapper = Substitute.For(); + systemWrapper.GetRandom().Returns(randomSampleRate); - var loggerConfiguration = new PowertoolsLoggerConfiguration + var loggerConfiguration = new LoggerConfiguration { Service = null, - MinimumLogLevel = LogLevel.None, - LoggerOutputCase = LoggerOutputCase.CamelCase, - LogOutput = systemWrapper + MinimumLevel = LogLevel.None, + LoggerOutputCase = LoggerOutputCase.CamelCase }; - - var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); + + var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); var logger = provider.CreateLogger(loggerName); var message = new @@ -1471,21 +1451,20 @@ public void Log_Should_Serialize_TimeOnly() logger.LogInformation(message); // Assert - systemWrapper.Received(1).WriteLine( + systemWrapper.Received(1).LogLine( Arg.Is(s => s.Contains("\"message\":{\"propOne\":\"Value 1\",\"propTwo\":\"Value 2\",\"time\":\"12:00:00\"}") ) ); } - + [Theory] - [InlineData("WARN", LogLevel.Warning)] - [InlineData("Fatal", LogLevel.Critical)] - [InlineData("NotValid", LogLevel.Critical)] - [InlineData("NotValid", LogLevel.Warning)] - public void Log_Should_Use_Powertools_Log_Level_When_Lambda_Log_Level_Enabled(string awsLogLevel, - LogLevel logLevel) + [InlineData(true, "WARN", LogLevel.Warning)] + [InlineData(false, "Fatal", LogLevel.Critical)] + [InlineData(false, "NotValid", LogLevel.Critical)] + [InlineData(true, "NotValid", LogLevel.Warning)] + public void Log_Should_Use_Powertools_Log_Level_When_Lambda_Log_Level_Enabled(bool willLog, string awsLogLevel, LogLevel logLevel) { // Arrange var loggerName = Guid.NewGuid().ToString(); @@ -1495,14 +1474,15 @@ public void Log_Should_Use_Powertools_Log_Level_When_Lambda_Log_Level_Enabled(st environment.GetEnvironmentVariable("POWERTOOLS_LOG_LEVEL").Returns(logLevel.ToString()); environment.GetEnvironmentVariable("AWS_LAMBDA_LOG_LEVEL").Returns(awsLogLevel); - var configurations = new PowertoolsConfigurations(environment); + var systemWrapper = new SystemWrapperMock(environment); + var configurations = new PowertoolsConfigurations(systemWrapper); - var loggerConfiguration = new PowertoolsLoggerConfiguration + var loggerConfiguration = new LoggerConfiguration { LoggerOutputCase = LoggerOutputCase.CamelCase }; - - var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); + + var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); var logger = provider.CreateLogger(loggerName); var message = new @@ -1514,17 +1494,17 @@ public void Log_Should_Use_Powertools_Log_Level_When_Lambda_Log_Level_Enabled(st // Act logger.LogWarning(message); - + // Assert Assert.True(logger.IsEnabled(logLevel)); Assert.Equal(logLevel, configurations.GetLogLevel()); + Assert.Equal(willLog, systemWrapper.LogMethodCalled); } - + [Theory] - [InlineData("WARN", LogLevel.Warning)] - [InlineData("Fatal", LogLevel.Critical)] - public void Log_Should_Use_AWS_Lambda_Log_Level_When_Enabled(string awsLogLevel, - LogLevel logLevel) + [InlineData(true, "WARN", LogLevel.Warning)] + [InlineData(true, "Fatal", LogLevel.Critical)] + public void Log_Should_Use_AWS_Lambda_Log_Level_When_Enabled(bool willLog, string awsLogLevel, LogLevel logLevel) { // Arrange var loggerName = Guid.NewGuid().ToString(); @@ -1534,14 +1514,15 @@ public void Log_Should_Use_AWS_Lambda_Log_Level_When_Enabled(string awsLogLevel, environment.GetEnvironmentVariable("POWERTOOLS_LOG_LEVEL").Returns(string.Empty); environment.GetEnvironmentVariable("AWS_LAMBDA_LOG_LEVEL").Returns(awsLogLevel); - var configurations = new PowertoolsConfigurations(environment); + var systemWrapper = new SystemWrapperMock(environment); + var configurations = new PowertoolsConfigurations(systemWrapper); - var loggerConfiguration = new PowertoolsLoggerConfiguration + var loggerConfiguration = new LoggerConfiguration { LoggerOutputCase = LoggerOutputCase.CamelCase, }; - - var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); + + var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); var logger = provider.CreateLogger(loggerName); var message = new @@ -1553,13 +1534,14 @@ public void Log_Should_Use_AWS_Lambda_Log_Level_When_Enabled(string awsLogLevel, // Act logger.LogWarning(message); - + // Assert Assert.True(logger.IsEnabled(logLevel)); Assert.Equal(LogLevel.Information, configurations.GetLogLevel()); //default Assert.Equal(logLevel, configurations.GetLambdaLogLevel()); + Assert.Equal(willLog, systemWrapper.LogMethodCalled); } - + [Fact] public void Log_Should_Show_Warning_When_AWS_Lambda_Log_Level_Enabled() { @@ -1570,53 +1552,49 @@ public void Log_Should_Show_Warning_When_AWS_Lambda_Log_Level_Enabled() environment.GetEnvironmentVariable("POWERTOOLS_LOG_LEVEL").Returns("Debug"); environment.GetEnvironmentVariable("AWS_LAMBDA_LOG_LEVEL").Returns("Warn"); - var systemWrapper = new TestLoggerOutput(); - var configurations = new PowertoolsConfigurations(environment); + var systemWrapper = new SystemWrapperMock(environment); + var configurations = new PowertoolsConfigurations(systemWrapper); - var loggerConfiguration = new PowertoolsLoggerConfiguration + var loggerConfiguration = new LoggerConfiguration { - LoggerOutputCase = LoggerOutputCase.CamelCase, - LogOutput = systemWrapper + LoggerOutputCase = LoggerOutputCase.CamelCase }; - - var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); + + var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); var logger = provider.CreateLogger(loggerName); var logLevel = configurations.GetLogLevel(); var lambdaLogLevel = configurations.GetLambdaLogLevel(); - + // Assert Assert.True(logger.IsEnabled(LogLevel.Warning)); Assert.Equal(LogLevel.Debug, logLevel); Assert.Equal(LogLevel.Warning, lambdaLogLevel); - Assert.Contains( - $"Current log level ({logLevel}) does not match AWS Lambda Advanced Logging Controls minimum log level ({lambdaLogLevel}). This can lead to data loss, consider adjusting them.", - systemWrapper.ToString()); + Assert.Contains($"Current log level ({logLevel}) does not match AWS Lambda Advanced Logging Controls minimum log level ({lambdaLogLevel}). This can lead to data loss, consider adjusting them.", + systemWrapper.LogMethodCalledWithArgument); } - + [Theory] - [InlineData(true, "LogLevel")] - [InlineData(false, "Level")] - public void Log_PascalCase_Outputs_Correct_Level_Property_When_AWS_Lambda_Log_Level_Enabled_Or_Disabled( - bool alcEnabled, string levelProp) + [InlineData(true,"LogLevel")] + [InlineData(false,"Level")] + public void Log_PascalCase_Outputs_Correct_Level_Property_When_AWS_Lambda_Log_Level_Enabled_Or_Disabled(bool alcEnabled, string levelProp) { // Arrange var loggerName = Guid.NewGuid().ToString(); - + var environment = Substitute.For(); environment.GetEnvironmentVariable("POWERTOOLS_LOG_LEVEL").Returns("Information"); - if (alcEnabled) + if(alcEnabled) environment.GetEnvironmentVariable("AWS_LAMBDA_LOG_LEVEL").Returns("Info"); - var systemWrapper = new TestLoggerOutput(); - var configurations = new PowertoolsConfigurations(environment); - var loggerConfiguration = new PowertoolsLoggerConfiguration + var systemWrapper = new SystemWrapperMock(environment); + var configurations = new PowertoolsConfigurations(systemWrapper); + var loggerConfiguration = new LoggerConfiguration { - LoggerOutputCase = LoggerOutputCase.PascalCase, - LogOutput = systemWrapper + LoggerOutputCase = LoggerOutputCase.PascalCase }; - var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); + var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); var logger = provider.CreateLogger(loggerName); var message = new @@ -1627,9 +1605,10 @@ public void Log_PascalCase_Outputs_Correct_Level_Property_When_AWS_Lambda_Log_Le logger.LogInformation(message); // Assert - Assert.Contains($"\"{levelProp}\":\"Information\"", systemWrapper.ToString()); + Assert.True(systemWrapper.LogMethodCalled); + Assert.Contains($"\"{levelProp}\":\"Information\"",systemWrapper.LogMethodCalledWithArgument); } - + [Theory] [InlineData(LoggerOutputCase.CamelCase)] [InlineData(LoggerOutputCase.SnakeCase)] @@ -1637,22 +1616,21 @@ public void Log_CamelCase_Outputs_Level_When_AWS_Lambda_Log_Level_Enabled(Logger { // Arrange var loggerName = Guid.NewGuid().ToString(); - + var environment = Substitute.For(); environment.GetEnvironmentVariable("POWERTOOLS_LOG_LEVEL").Returns(string.Empty); environment.GetEnvironmentVariable("AWS_LAMBDA_LOG_LEVEL").Returns("Info"); - var systemWrapper = new TestLoggerOutput(); - var configurations = new PowertoolsConfigurations(environment); + var systemWrapper = new SystemWrapperMock(environment); + var configurations = new PowertoolsConfigurations(systemWrapper); configurations.LoggerOutputCase.Returns(casing.ToString()); - - var loggerConfiguration = new PowertoolsLoggerConfiguration + + var loggerConfiguration = new LoggerConfiguration { - LoggerOutputCase = casing, - LogOutput = systemWrapper + LoggerOutputCase = casing }; - - var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); + + var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); var logger = provider.CreateLogger(loggerName); var message = new @@ -1663,9 +1641,10 @@ public void Log_CamelCase_Outputs_Level_When_AWS_Lambda_Log_Level_Enabled(Logger logger.LogInformation(message); // Assert - Assert.Contains("\"level\":\"Information\"", systemWrapper.ToString()); + Assert.True(systemWrapper.LogMethodCalled); + Assert.Contains("\"level\":\"Information\"",systemWrapper.LogMethodCalledWithArgument); } - + [Theory] [InlineData("TRACE", LogLevel.Trace)] [InlineData("debug", LogLevel.Debug)] @@ -1680,11 +1659,12 @@ public void Should_Map_AWS_Log_Level_And_Default_To_Information(string awsLogLev var environment = Substitute.For(); environment.GetEnvironmentVariable("AWS_LAMBDA_LOG_LEVEL").Returns(awsLogLevel); - var configuration = new PowertoolsConfigurations(environment); + var systemWrapper = new SystemWrapperMock(environment); + var configuration = new PowertoolsConfigurations(systemWrapper); // Act var logLvl = configuration.GetLambdaLogLevel(); - + // Assert Assert.Equal(logLevel, logLvl); } @@ -1699,14 +1679,15 @@ public void Log_Should_Use_Powertools_Log_Level_When_Set(bool willLog, LogLevel var environment = Substitute.For(); environment.GetEnvironmentVariable("POWERTOOLS_LOG_LEVEL").Returns(logLevel.ToString()); - var configurations = new PowertoolsConfigurations(environment); + var systemWrapper = new SystemWrapperMock(environment); + var configurations = new PowertoolsConfigurations(systemWrapper); - var loggerConfiguration = new PowertoolsLoggerConfiguration + var loggerConfiguration = new LoggerConfiguration { LoggerOutputCase = LoggerOutputCase.CamelCase }; - - var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); + + var provider = new LoggerProvider(loggerConfiguration, configurations, systemWrapper); var logger = provider.CreateLogger(loggerName); var message = new @@ -1722,185 +1703,13 @@ public void Log_Should_Use_Powertools_Log_Level_When_Set(bool willLog, LogLevel // Assert Assert.True(logger.IsEnabled(logLevel)); Assert.Equal(logLevel.ToString(), configurations.LogLevel); - } - - [Theory] - [InlineData(true, "on-demand")] - [InlineData(false, "provisioned-concurrency")] - public void Log_Cold_Start(bool willLog, string awsInitType) - { - // Arrange - var logOutput = new TestLoggerOutput(); - Environment.SetEnvironmentVariable("AWS_LAMBDA_INITIALIZATION_TYPE", awsInitType); - var configurations = new PowertoolsConfigurations(new PowertoolsEnvironment()); - - var loggerConfiguration = new PowertoolsLoggerConfiguration - { - LoggerOutputCase = LoggerOutputCase.CamelCase, - LogOutput = logOutput - }; - - var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); - var logger = provider.CreateLogger("temp"); - - // Act - logger.LogInformation("Hello"); - - var outPut = logOutput.ToString(); - // Assert - Assert.Contains($"\"coldStart\":{willLog.ToString().ToLower()}", outPut); - } - - [Fact] - public void Log_WhenDuplicateKeysInState_LastValueWins() - { - // Arrange - var loggerName = Guid.NewGuid().ToString(); - var service = Guid.NewGuid().ToString(); - var logLevel = LogLevel.Information; - - var configurations = Substitute.For(); - configurations.Service.Returns(service); - configurations.LogLevel.Returns(logLevel.ToString()); - configurations.LoggerOutputCase.Returns(LoggerOutputCase.PascalCase.ToString()); - - var systemWrapper = Substitute.For(); - - var loggerConfiguration = new PowertoolsLoggerConfiguration - { - Service = service, - MinimumLogLevel = logLevel, - LoggerOutputCase = LoggerOutputCase.PascalCase, - LogOutput = systemWrapper - }; - - var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); - var logger = provider.CreateLogger(loggerName); - - // Create state with duplicate keys (simulating duplicate HTTP headers) - var stateWithDuplicates = new List> - { - new("Content-Type", "application/json"), - new("Content-Type", "application/x-www-form-urlencoded"), // This should win - new("Accept", "text/html"), - new("Accept", "*/*") // This should win - }; - - // Act - This should not throw an exception - logger.Log(logLevel, new EventId(), stateWithDuplicates, null, (state, ex) => "Test message"); - - // Assert - systemWrapper.Received(1).WriteLine(Arg.Any()); - } - - [Fact] - public void GetSafeRandom_ShouldReturnValueBetweenZeroAndOne() - { - // Act & Assert - Test multiple times to ensure consistency - for (int i = 0; i < 1000; i++) - { - var randomValue = PowertoolsLoggerConfiguration.GetSafeRandom(); - - Assert.True(randomValue >= 0.0, $"Random value {randomValue} should be >= 0.0"); - Assert.True(randomValue <= 1.0, $"Random value {randomValue} should be <= 1.0"); - } - } - - [Fact] - public void GetSafeRandom_ShouldReturnDifferentValues() - { - // Arrange - var values = new HashSet(); - - // Act - Generate multiple random values - for (int i = 0; i < 100; i++) - { - values.Add(PowertoolsLoggerConfiguration.GetSafeRandom()); - } - - // Assert - Should have generated multiple different values - Assert.True(values.Count > 50, "Should generate diverse random values"); - } - - [Fact] - public void Log_SamplingWithRealRandomGenerator_ShouldWorkCorrectly() - { - // Arrange - var service = Guid.NewGuid().ToString(); - var logLevel = LogLevel.Error; // Set high log level - var loggerSampleRate = 1.0; // 100% sampling rate to ensure activation - - var configurations = Substitute.For(); - configurations.Service.Returns(service); - configurations.LogLevel.Returns(logLevel.ToString()); - configurations.LoggerSampleRate.Returns(loggerSampleRate); - - var systemWrapper = Substitute.For(); - - var loggerConfiguration = new PowertoolsLoggerConfiguration - { - Service = service, - MinimumLogLevel = logLevel, - LogOutput = systemWrapper, - SamplingRate = loggerSampleRate - }; - - // Act - var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); - var logger = provider.CreateLogger("test"); - - // First call - skipped due to cold start protection - logger.LogError("Test1"); - - // Second call - should trigger sampling with 100% rate - logger.LogError("Test2"); - - // Assert - With 100% sampling rate, should always activate sampling - systemWrapper.Received(1).WriteLine( - Arg.Is(s => - s.Contains("Changed log level to DEBUG based on Sampling configuration") && - s.Contains($"Sampling Rate: {loggerSampleRate}") - ) - ); - } - - [Fact] - public void Log_SamplingWithZeroRate_ShouldNeverActivate() - { - // Arrange - var service = Guid.NewGuid().ToString(); - var logLevel = LogLevel.Error; - var loggerSampleRate = 0.0; // 0% sampling rate - - var configurations = Substitute.For(); - configurations.Service.Returns(service); - configurations.LogLevel.Returns(logLevel.ToString()); - configurations.LoggerSampleRate.Returns(loggerSampleRate); - - var systemWrapper = Substitute.For(); - - var loggerConfiguration = new PowertoolsLoggerConfiguration - { - Service = service, - MinimumLogLevel = logLevel, - LogOutput = systemWrapper, - SamplingRate = loggerSampleRate - }; - - // Act - var provider = new PowertoolsLoggerProvider(loggerConfiguration, configurations); - var logger = provider.CreateLogger("test"); - - // Assert - With 0% sampling rate, should never activate sampling - systemWrapper.DidNotReceive().WriteLine( - Arg.Is(s => s.Contains("Changed log level to DEBUG based on Sampling configuration")) - ); + Assert.Equal(willLog, systemWrapper.LogMethodCalled); } public void Dispose() { - // Environment.SetEnvironmentVariable("AWS_LAMBDA_INITIALIZATION_TYPE", null); - LambdaLifecycleTracker.Reset(); + PowertoolsLoggingSerializer.ClearOptions(); + LoggingAspect.ResetForTest(); } } -} +} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Sampling/EnvironmentVariableSamplingTests.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Sampling/EnvironmentVariableSamplingTests.cs deleted file mode 100644 index 03d191ecd..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Sampling/EnvironmentVariableSamplingTests.cs +++ /dev/null @@ -1,260 +0,0 @@ -using System; -using AWS.Lambda.Powertools.Common; -using AWS.Lambda.Powertools.Common.Tests; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Xunit; -using LogLevel = Microsoft.Extensions.Logging.LogLevel; - -namespace AWS.Lambda.Powertools.Logging.Tests; - -/// -/// Tests for sampling behavior when using environment variables -/// This covers the specific use case described in the GitHub issue -/// -public class EnvironmentVariableSamplingTests : IDisposable -{ - private readonly string _originalLogLevel; - private readonly string _originalSampleRate; - - public EnvironmentVariableSamplingTests() - { - // Store original environment variables - _originalLogLevel = Environment.GetEnvironmentVariable("POWERTOOLS_LOG_LEVEL"); - _originalSampleRate = Environment.GetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE"); - - // Reset logger before each test - Logger.Reset(); - } - - public void Dispose() - { - // Restore original environment variables - if (_originalLogLevel != null) - Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", _originalLogLevel); - else - Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", null); - - if (_originalSampleRate != null) - Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE", _originalSampleRate); - else - Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE", null); - - Logger.Reset(); - } - - /// - /// Creates a logger factory that properly processes environment variables - /// - private ILoggerFactory CreateLoggerFactoryWithEnvironmentVariables(TestLoggerOutput output) - { - var services = new ServiceCollection(); - - services.AddLogging(builder => - { - builder.AddPowertoolsLogger(config => - { - config.Service = "HelloWorldService"; - config.LoggerOutputCase = LoggerOutputCase.CamelCase; - config.LogEvent = true; - config.LogOutput = output; - }); - }); - - var serviceProvider = services.BuildServiceProvider(); - return serviceProvider.GetRequiredService(); - } - - /// - /// Test the exact scenario described in the GitHub issue: - /// POWERTOOLS_LOG_LEVEL=Error and POWERTOOLS_LOGGER_SAMPLE_RATE=0.9 - /// Information logs should be elevated to debug and logged when sampling is triggered - /// - [Fact] - public void EnvironmentVariables_ErrorLevelWithSampling_ShouldLogInfoWhenSamplingTriggered() - { - // Arrange - var originalLogLevel = Environment.GetEnvironmentVariable("POWERTOOLS_LOG_LEVEL"); - var originalSampleRate = Environment.GetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE"); - - try - { - Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", "Error"); - Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE", "0.9"); - - var output = new TestLoggerOutput(); - bool samplingTriggered = false; - string logOutput = ""; - - // Try multiple times to trigger sampling (90% chance each time) - for (int attempt = 0; attempt < 20 && !samplingTriggered; attempt++) - { - output.Clear(); - Logger.Reset(); - Logger.Configure(options => { options.LogOutput = output; }); - - Logger.LogError("This is an error message"); - Logger.LogInformation("Another info message"); - - logOutput = output.ToString(); - samplingTriggered = logOutput.Contains("Another info message"); - } - - // Assert - Assert.True(samplingTriggered, - $"Sampling should have been triggered within 20 attempts with 90% rate. " + - $"Last output: {logOutput}"); - - // Only verify the content if sampling was triggered - if (samplingTriggered) - { - Assert.Contains("This is an error message", logOutput); - Assert.Contains("Another info message", logOutput); - } - } - finally - { - // Cleanup - Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", originalLogLevel); - Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE", originalSampleRate); - Logger.Reset(); - } - } - - - /// - /// Test with POWERTOOLS_LOGGER_SAMPLE_RATE=1.0 (100% sampling) - /// This should always trigger sampling - guarantees the fix works - /// - [Fact] - public void EnvironmentVariables_ErrorLevelWithFullSampling_ShouldAlwaysLogInfo() - { - // Arrange - Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", "Error"); - Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE", "1.0"); - - var output = new TestLoggerOutput(); - - using var loggerFactory = CreateLoggerFactoryWithEnvironmentVariables(output); - var logger = loggerFactory.CreateLogger(); - - // Act - logger.LogError("This is an error message"); - logger.LogInformation("This is an info message — should appear with 100% sampling"); - - // Assert - var logOutput = output.ToString(); - Assert.Contains("Changed log level to DEBUG based on Sampling configuration", logOutput); - Assert.Contains("This is an error message", logOutput); - Assert.Contains("This is an info message — should appear with 100% sampling", logOutput); - } - - /// - /// Test with POWERTOOLS_LOGGER_SAMPLE_RATE=0 (no sampling) - /// Info messages should not be logged - ensures sampling is required - /// - [Fact] - public void EnvironmentVariables_ErrorLevelWithNoSampling_ShouldNotLogInfo() - { - // Arrange - Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", "Error"); - Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE", "0"); - - var output = new TestLoggerOutput(); - - using var loggerFactory = CreateLoggerFactoryWithEnvironmentVariables(output); - var logger = loggerFactory.CreateLogger(); - - // Act - logger.LogError("This is an error message"); - logger.LogInformation("This is an info message — should NOT appear with 0% sampling"); - - // Assert - var logOutput = output.ToString(); - Assert.DoesNotContain("Changed log level to DEBUG based on Sampling configuration", logOutput); - Assert.Contains("This is an error message", logOutput); - Assert.DoesNotContain("This is an info message — should NOT appear with 0% sampling", logOutput); - } - - /// - /// Test the ShouldEnableDebugSampling() method without out parameter - /// - [Fact] - public void ShouldEnableDebugSampling_WithoutOutParameter_ShouldReturnCorrectValue() - { - // Arrange - var config = new PowertoolsLoggerConfiguration - { - SamplingRate = 1.0 // 100% sampling - }; - - // Act - var result = config.ShouldEnableDebugSampling(); - - // Assert - Assert.True(result); - } - - /// - /// Test the ShouldEnableDebugSampling() method with zero sampling rate - /// - [Fact] - public void ShouldEnableDebugSampling_WithZeroSamplingRate_ShouldReturnFalse() - { - // Arrange - var config = new PowertoolsLoggerConfiguration - { - SamplingRate = 0.0 // 0% sampling - }; - - // Act - var result = config.ShouldEnableDebugSampling(); - - // Assert - Assert.False(result); - } - - /// - /// Test the RefreshSampleRateCalculation() method without out parameter - /// - [Fact] - public void RefreshSampleRateCalculation_WithoutOutParameter_ShouldReturnCorrectValue() - { - // Arrange - var config = new PowertoolsLoggerConfiguration - { - SamplingRate = 1.0, // 100% sampling - InitialLogLevel = LogLevel.Error, - MinimumLogLevel = LogLevel.Error - }; - - // Act - First call should return false due to cold start protection - var firstResult = config.RefreshSampleRateCalculation(); - - // Second call should return true with 100% sampling - var secondResult = config.RefreshSampleRateCalculation(); - - // Assert - Assert.False(firstResult); // Cold start protection - Assert.True(secondResult); // Should enable sampling - } - - /// - /// Test the RefreshSampleRateCalculation() method with zero sampling rate - /// - [Fact] - public void RefreshSampleRateCalculation_WithZeroSamplingRate_ShouldReturnFalse() - { - // Arrange - var config = new PowertoolsLoggerConfiguration - { - SamplingRate = 0.0 // 0% sampling - }; - - // Act - var result = config.RefreshSampleRateCalculation(); - - // Assert - Assert.False(result); - } -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Sampling/SamplingTests.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Sampling/SamplingTests.cs deleted file mode 100644 index 671fab4cb..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Sampling/SamplingTests.cs +++ /dev/null @@ -1,373 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using AWS.Lambda.Powertools.Common.Tests; -using Xunit; -using LogLevel = Microsoft.Extensions.Logging.LogLevel; - -namespace AWS.Lambda.Powertools.Logging.Tests.Sampling; - -public class SamplingTests : IDisposable -{ - private readonly string _originalLogLevel; - private readonly string _originalSampleRate; - - public SamplingTests() - { - // Store original environment variables - _originalLogLevel = Environment.GetEnvironmentVariable("POWERTOOLS_LOG_LEVEL"); - _originalSampleRate = Environment.GetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE"); - - // Reset logger before each test - Logger.Reset(); - } - - public void Dispose() - { - // Restore original environment variables - if (_originalLogLevel != null) - Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", _originalLogLevel); - else - Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", null); - - if (_originalSampleRate != null) - Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE", _originalSampleRate); - else - Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_SAMPLE_RATE", null); - - Logger.Reset(); - } - - [Fact] - public void SamplingRate_WhenConfigured_ShouldEnableDebugSampling() - { - // Arrange - var output = new TestLoggerOutput(); - var config = new PowertoolsLoggerConfiguration - { - SamplingRate = 1.0, // 100% sampling rate - LogOutput = output - }; - - // Act - var result = config.ShouldEnableDebugSampling(); - - // Assert - Assert.True(result); - } - - [Fact] - public void SamplingRate_WhenZero_ShouldNotEnableDebugSampling() - { - // Arrange - var config = new PowertoolsLoggerConfiguration - { - SamplingRate = 0.0 // 0% sampling rate - }; - - // Act - var result = config.ShouldEnableDebugSampling(); - - // Assert - Assert.False(result); - } - - [Fact] - public void RefreshSampleRateCalculation_FirstCall_ShouldReturnFalseDueToColdStartProtection() - { - // Arrange - var config = new PowertoolsLoggerConfiguration - { - SamplingRate = 1.0, // 100% sampling rate - InitialLogLevel = LogLevel.Error, - MinimumLogLevel = LogLevel.Error - }; - - // Act - var result = config.RefreshSampleRateCalculation(); - - // Assert - Assert.False(result); // Cold start protection should prevent sampling on first call - } - - [Fact] - public void RefreshSampleRateCalculation_SecondCall_WithFullSampling_ShouldReturnTrue() - { - // Arrange - var config = new PowertoolsLoggerConfiguration - { - SamplingRate = 1.0, // 100% sampling rate - InitialLogLevel = LogLevel.Error, - MinimumLogLevel = LogLevel.Error - }; - - // Act - var firstResult = config.RefreshSampleRateCalculation(); // Cold start protection - var secondResult = config.RefreshSampleRateCalculation(); // Should enable sampling - - // Assert - Assert.False(firstResult); // Cold start protection - Assert.True(secondResult); // Should enable sampling - Assert.Equal(LogLevel.Debug, config.MinimumLogLevel); // Should have changed to Debug - } - - [Fact] - public void RefreshSampleRateCalculation_WithZeroSampling_ShouldNeverEnableSampling() - { - // Arrange - var config = new PowertoolsLoggerConfiguration - { - SamplingRate = 0.0, // 0% sampling rate - InitialLogLevel = LogLevel.Error, - MinimumLogLevel = LogLevel.Error - }; - - // Act - var firstResult = config.RefreshSampleRateCalculation(); - var secondResult = config.RefreshSampleRateCalculation(); - - // Assert - Assert.False(firstResult); - Assert.False(secondResult); - Assert.Equal(LogLevel.Error, config.MinimumLogLevel); // Should remain unchanged - } - - [Fact] - public void Logger_WithSamplingEnabled_ShouldLogDebugWhenSamplingTriggered() - { - // Arrange - var output = new TestLoggerOutput(); - Logger.Configure(options => - { - options.Service = "TestService"; - options.SamplingRate = 1.0; // 100% sampling - options.MinimumLogLevel = LogLevel.Error; - options.LogOutput = output; - }); - - // Act - Logger.LogError("This is an error"); // Trigger first call (cold start protection) - Logger.LogInformation("This should be logged due to sampling"); // Should trigger sampling - - // Assert - var logOutput = output.ToString(); - Assert.Contains("This is an error", logOutput); - Assert.Contains("This should be logged due to sampling", logOutput); - Assert.Contains("Changed log level to DEBUG based on Sampling configuration", logOutput); - } - - [Fact] - public void Logger_WithNoSampling_ShouldNotLogDebugMessages() - { - // Arrange - var output = new TestLoggerOutput(); - Logger.Configure(options => - { - options.Service = "TestService"; - options.SamplingRate = 0.0; // 0% sampling - options.MinimumLogLevel = LogLevel.Error; - options.LogOutput = output; - }); - - // Act - Logger.LogError("This is an error"); - Logger.LogInformation("This should NOT be logged"); - - // Assert - var logOutput = output.ToString(); - Assert.Contains("This is an error", logOutput); - Assert.DoesNotContain("This should NOT be logged", logOutput); - Assert.DoesNotContain("Changed log level to DEBUG based on Sampling configuration", logOutput); - } - - [Fact] - public void ShouldEnableDebugSampling_WithOutParameter_ShouldReturnSamplerValue() - { - // Arrange - var config = new PowertoolsLoggerConfiguration - { - SamplingRate = 1.0 // 100% sampling - }; - - // Act - var result = config.ShouldEnableDebugSampling(out double samplerValue); - - // Assert - Assert.True(result); - Assert.True(samplerValue >= 0.0 && samplerValue <= 1.0); - Assert.True(samplerValue <= config.SamplingRate); - } - - [Fact] - public void RefreshSampleRateCalculation_WithOutParameter_ShouldProvideSamplerValue() - { - // Arrange - var config = new PowertoolsLoggerConfiguration - { - SamplingRate = 1.0, // 100% sampling - InitialLogLevel = LogLevel.Error, - MinimumLogLevel = LogLevel.Error - }; - - // Act - var firstResult = config.RefreshSampleRateCalculation(out double firstSamplerValue); - var secondResult = config.RefreshSampleRateCalculation(out double secondSamplerValue); - - // Assert - Assert.False(firstResult); // Cold start protection - Assert.Equal(0.0, firstSamplerValue); // Should be 0 during cold start protection - - Assert.True(secondResult); // Should enable sampling - Assert.True(secondSamplerValue >= 0.0 && secondSamplerValue <= 1.0); - } - - [Fact] - public void GetSafeRandom_ShouldReturnValueBetweenZeroAndOne() - { - // Act - var randomValue = PowertoolsLoggerConfiguration.GetSafeRandom(); - - // Assert - Assert.True(randomValue >= 0.0); - Assert.True(randomValue <= 1.0); - } - - [Fact] - public void SamplingRefreshCount_ShouldIncrementCorrectly() - { - // Arrange - var config = new PowertoolsLoggerConfiguration - { - SamplingRate = 1.0 - }; - - // Act & Assert - Assert.Equal(0, config.SamplingRefreshCount); - - config.RefreshSampleRateCalculation(); - Assert.Equal(1, config.SamplingRefreshCount); - - config.RefreshSampleRateCalculation(); - Assert.Equal(2, config.SamplingRefreshCount); - } - - [Fact] - public void RefreshSampleRateCalculation_ShouldEnableDebugLogging() - { - // Arrange - var output = new TestLoggerOutput(); - Logger.Configure(options => - { - options.Service = "TestService"; - options.SamplingRate = 1.0; // 100% sampling - options.MinimumLogLevel = LogLevel.Error; - options.LogOutput = output; - }); - - // Act - First refresh (cold start protection) - Logger.RefreshSampleRateCalculation(); - Logger.LogDebug("This should not appear"); - - // Clear output from first attempt - output.Clear(); - - // Second refresh (should enable sampling) - Logger.RefreshSampleRateCalculation(); - Logger.LogDebug("This should appear after sampling"); - - // Assert - var logOutput = output.ToString(); - Assert.Contains("This should appear after sampling", logOutput); - Assert.Contains("Changed log level to DEBUG based on Sampling configuration", logOutput); - } - - [Fact] - public void Logger_RefreshSampleRateCalculation_ShouldTriggerConfigurationUpdate() - { - // Arrange - var output = new TestLoggerOutput(); - Logger.Configure(options => - { - options.Service = "TestService"; - options.SamplingRate = 1.0; // 100% sampling - options.MinimumLogLevel = LogLevel.Warning; - options.LogOutput = output; - }); - - // Verify initial state - debug logs should not appear (sampling not yet triggered) - Logger.LogDebug("Initial debug - should not appear"); - Assert.DoesNotContain("Initial debug", output.ToString()); - - output.Clear(); - - // Act - Trigger sampling refresh - // First call is protected by cold start logic, second call should enable sampling - Logger.RefreshSampleRateCalculation(); // Cold start protection - no effect - var samplingEnabled = Logger.RefreshSampleRateCalculation(); // Should enable debug sampling - - // Verify sampling was enabled - Assert.True(samplingEnabled, "Sampling should be enabled with 100% rate"); - - // Now debug logs should appear because sampling elevated the log level - Logger.LogDebug("Debug after sampling - should appear"); - - // Assert - var logOutput = output.ToString(); - Assert.Contains("Debug after sampling - should appear", logOutput); - Assert.Contains("Changed log level to DEBUG based on Sampling configuration", logOutput); - } - - - - - - [Fact] - public void RefreshSampleRateCalculation_ShouldGenerateRandomValues_OverMultipleIterations() - { - // Arrange - var config = new PowertoolsLoggerConfiguration - { - SamplingRate = 0.5, // 50% sampling rate - MinimumLogLevel = LogLevel.Error, - InitialLogLevel = LogLevel.Error - }; - - var samplerValues = new List(); - bool samplingTriggeredAtLeastOnce = false; - bool samplingNotTriggeredAtLeastOnce = false; - - // Act - Try up to 20 times to verify random behavior - for (int i = 0; i < 20; i++) - { - // Reset for each iteration - config.SamplingRefreshCount = 1; // Skip cold start protection - - bool wasTriggered = config.RefreshSampleRateCalculation(out double samplerValue); - samplerValues.Add(samplerValue); - - if (wasTriggered) - { - samplingTriggeredAtLeastOnce = true; - } - else - { - samplingNotTriggeredAtLeastOnce = true; - } - } - - // Assert - // Verify that we got different random values (not all the same) - var uniqueValues = samplerValues.Distinct().Count(); - Assert.True(uniqueValues > 1, "Should generate different random values across iterations"); - - // With 50% sampling rate over 20 iterations, we should see both triggered and not triggered cases - // (probability of all same outcome is extremely low: 0.5^20 ≈ 0.000001) - Assert.True(samplingTriggeredAtLeastOnce, - "Sampling should have been triggered at least once in 20 iterations with 50% rate"); - Assert.True(samplingNotTriggeredAtLeastOnce, - "Sampling should have been skipped at least once in 20 iterations with 50% rate"); - - // Verify all sampler values are within valid range [0, 1] - Assert.True((bool)samplerValues.All(v => v >= 0.0 && v <= 1.0), "All sampler values should be between 0 and 1"); - } -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Serializers/PowertoolsLambdaSerializerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Serializers/PowertoolsLambdaSerializerTests.cs index 4e422a671..489504927 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Serializers/PowertoolsLambdaSerializerTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Serializers/PowertoolsLambdaSerializerTests.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using AWS.Lambda.Powertools.Logging.Serializers; using System; @@ -15,23 +30,29 @@ namespace AWS.Lambda.Powertools.Logging.Tests.Serializers; public class PowertoolsLambdaSerializerTests : IDisposable { - private readonly PowertoolsLoggingSerializer _serializer; - - public PowertoolsLambdaSerializerTests() - { - _serializer = new PowertoolsLoggingSerializer(); - } - -#if NET8_0_OR_GREATER [Fact] public void Constructor_ShouldNotThrowException() { // Arrange & Act & Assert var exception = - Record.Exception(() => _serializer.AddSerializerContext(TestJsonContext.Default)); + Record.Exception(() => PowertoolsLoggingSerializer.AddSerializerContext(TestJsonContext.Default)); Assert.Null(exception); } + [Fact] + public void Constructor_ShouldAddCustomerContext() + { + // Arrange + var customerContext = new TestJsonContext(); + + // Act + PowertoolsLoggingSerializer.AddSerializerContext(customerContext); + ; + + // Assert + Assert.True(PowertoolsLoggingSerializer.HasContext(customerContext)); + } + [Theory] [InlineData(LoggerOutputCase.CamelCase, "{\"fullName\":\"John\",\"age\":30}", "John", 30)] [InlineData(LoggerOutputCase.PascalCase, "{\"FullName\":\"Jane\",\"Age\":25}", "Jane", 25)] @@ -59,7 +80,7 @@ public void Deserialize_InvalidType_ShouldThrowInvalidOperationException() var serializer = new PowertoolsSourceGeneratorSerializer(); ; - _serializer.ConfigureNamingPolicy(LoggerOutputCase.PascalCase); + PowertoolsLoggingSerializer.ConfigureNamingPolicy(LoggerOutputCase.PascalCase); var json = "{\"FullName\":\"John\",\"Age\":30}"; var stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); @@ -187,7 +208,7 @@ public void Should_Serialize_Unknown_Type_When_Including_Outside_Context() stream.Position = 0; var outputExternalSerializer = new StreamReader(stream).ReadToEnd(); - var outptuMySerializer = _serializer.Serialize(log, typeof(LogEntry)); + var outptuMySerializer = PowertoolsLoggingSerializer.Serialize(log, typeof(LogEntry)); // Assert Assert.Equal( @@ -199,40 +220,10 @@ public void Should_Serialize_Unknown_Type_When_Including_Outside_Context() } -#endif public void Dispose() { - + PowertoolsLoggingSerializer.ConfigureNamingPolicy(LoggingConstants.DefaultLoggerOutputCase); + PowertoolsLoggingSerializer.ClearOptions(); } -#if NET6_0 - - [Fact] - public void Should_Serialize_Net6() - { - // Arrange - _serializer.ConfigureNamingPolicy(LoggingConstants.DefaultLoggerOutputCase); - var testObject = new APIGatewayProxyRequest - { - Path = "asda", - RequestContext = new APIGatewayProxyRequest.ProxyRequestContext - { - RequestId = "asdas" - } - }; - - var log = new LogEntry - { - Name = "dasda", - Message = testObject - }; - - var outptuMySerializer = _serializer.Serialize(log, null); - - // Assert - Assert.Equal( - "{\"cold_start\":false,\"x_ray_trace_id\":null,\"correlation_id\":null,\"timestamp\":\"0001-01-01T00:00:00\",\"level\":\"Trace\",\"service\":null,\"name\":\"dasda\",\"message\":{\"resource\":null,\"path\":\"asda\",\"http_method\":null,\"headers\":null,\"multi_value_headers\":null,\"query_string_parameters\":null,\"multi_value_query_string_parameters\":null,\"path_parameters\":null,\"stage_variables\":null,\"request_context\":{\"path\":null,\"account_id\":null,\"resource_id\":null,\"stage\":null,\"request_id\":\"asdas\",\"identity\":null,\"resource_path\":null,\"http_method\":null,\"api_id\":null,\"extended_request_id\":null,\"connection_id\":null,\"connected_at\":0,\"domain_name\":null,\"domain_prefix\":null,\"event_type\":null,\"message_id\":null,\"route_key\":null,\"authorizer\":null,\"operation_name\":null,\"error\":null,\"integration_latency\":null,\"message_direction\":null,\"request_time\":null,\"request_time_epoch\":0,\"status\":null},\"body\":null,\"is_base64_encoded\":false},\"sampling_rate\":null,\"extra_keys\":null,\"exception\":null,\"lambda_context\":null}", - outptuMySerializer); - } -#endif } \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Serializers/PowertoolsLoggingSerializerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Serializers/PowertoolsLoggingSerializerTests.cs index 58b42e3f6..be5830777 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Serializers/PowertoolsLoggingSerializerTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Serializers/PowertoolsLoggingSerializerTests.cs @@ -1,12 +1,25 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.Collections.Generic; -using System.IO; +using System.Runtime.CompilerServices; using System.Text.Encodings.Web; using System.Text.Json; using System.Text.Json.Serialization; -using System.Text.Json.Serialization.Metadata; using Amazon.Lambda.Serialization.SystemTextJson; -using AWS.Lambda.Powertools.Common; using AWS.Lambda.Powertools.Common.Utils; using AWS.Lambda.Powertools.Logging.Internal; using AWS.Lambda.Powertools.Logging.Internal.Converters; @@ -18,21 +31,17 @@ namespace AWS.Lambda.Powertools.Logging.Tests.Serializers; public class PowertoolsLoggingSerializerTests : IDisposable { - private readonly PowertoolsLoggingSerializer _serializer; public PowertoolsLoggingSerializerTests() { - _serializer = new PowertoolsLoggingSerializer(); - _serializer.ConfigureNamingPolicy(LoggingConstants.DefaultLoggerOutputCase); -#if NET8_0_OR_GREATER - ClearContext(); -#endif + PowertoolsLoggingSerializer.ConfigureNamingPolicy(LoggingConstants.DefaultLoggerOutputCase); + PowertoolsLoggingSerializer.ClearContext(); } - + [Fact] public void SerializerOptions_ShouldNotBeNull() { - var options = _serializer.GetSerializerOptions(); + var options = PowertoolsLoggingSerializer.GetSerializerOptions(); Assert.NotNull(options); } @@ -40,9 +49,9 @@ public void SerializerOptions_ShouldNotBeNull() public void SerializerOptions_ShouldHaveCorrectDefaultSettings() { RuntimeFeatureWrapper.SetIsDynamicCodeSupported(false); - - var options = _serializer.GetSerializerOptions(); - + + var options = PowertoolsLoggingSerializer.GetSerializerOptions(); + Assert.Collection(options.Converters, converter => Assert.IsType(converter), converter => Assert.IsType(converter), @@ -50,27 +59,21 @@ public void SerializerOptions_ShouldHaveCorrectDefaultSettings() converter => Assert.IsType(converter), converter => Assert.IsType(converter), converter => Assert.IsType(converter), -#if NET8_0_OR_GREATER converter => Assert.IsType(converter)); -#elif NET6_0 - converter => Assert.IsType(converter)); -#endif Assert.Equal(JavaScriptEncoder.UnsafeRelaxedJsonEscaping, options.Encoder); -#if NET8_0_OR_GREATER Assert.Collection(options.TypeInfoResolverChain, - resolver => Assert.IsType(resolver)); -#endif + resolver => Assert.IsType(resolver)); } - + [Fact] public void SerializerOptions_ShouldHaveCorrectDefaultSettings_WhenDynamic() { RuntimeFeatureWrapper.SetIsDynamicCodeSupported(true); - - var options = _serializer.GetSerializerOptions(); - + + var options = PowertoolsLoggingSerializer.GetSerializerOptions(); + Assert.Collection(options.Converters, converter => Assert.IsType(converter), converter => Assert.IsType(converter), @@ -78,17 +81,11 @@ public void SerializerOptions_ShouldHaveCorrectDefaultSettings_WhenDynamic() converter => Assert.IsType(converter), converter => Assert.IsType(converter), converter => Assert.IsType(converter), -#if NET8_0_OR_GREATER - converter => Assert.IsType(converter)); -#elif NET6_0 converter => Assert.IsType(converter)); -#endif Assert.Equal(JavaScriptEncoder.UnsafeRelaxedJsonEscaping, options.Encoder); -#if NET8_0_OR_GREATER Assert.Empty(options.TypeInfoResolverChain); -#endif } [Fact] @@ -121,7 +118,7 @@ public void ConfigureNamingPolicy_ShouldNotChangeWhenPassedNull() public void ConfigureNamingPolicy_ShouldNotChangeWhenPassedSameCase() { var originalJson = SerializeTestObject(LoggerOutputCase.SnakeCase); - _serializer.ConfigureNamingPolicy(LoggerOutputCase.SnakeCase); + PowertoolsLoggingSerializer.ConfigureNamingPolicy(LoggerOutputCase.SnakeCase); var newJson = SerializeTestObject(LoggerOutputCase.SnakeCase); Assert.Equal(originalJson, newJson); } @@ -129,7 +126,7 @@ public void ConfigureNamingPolicy_ShouldNotChangeWhenPassedSameCase() [Fact] public void Serialize_ShouldHandleNestedObjects() { - _serializer.ConfigureNamingPolicy(LoggerOutputCase.SnakeCase); + PowertoolsLoggingSerializer.ConfigureNamingPolicy(LoggerOutputCase.SnakeCase); var testObject = new LogEntry { @@ -140,7 +137,7 @@ public void Serialize_ShouldHandleNestedObjects() } }; - var json = JsonSerializer.Serialize(testObject, _serializer.GetSerializerOptions()); + var json = JsonSerializer.Serialize(testObject, PowertoolsLoggingSerializer.GetSerializerOptions()); Assert.Contains("\"cold_start\":true", json); Assert.Contains("\"nested_object\":{\"property_name\":\"Value\"}", json); } @@ -152,11 +149,10 @@ public void Serialize_ShouldHandleEnumValues() { Level = LogLevel.Error }; - var json = JsonSerializer.Serialize(testObject, _serializer.GetSerializerOptions()); + var json = JsonSerializer.Serialize(testObject, PowertoolsLoggingSerializer.GetSerializerOptions()); Assert.Contains("\"level\":\"Error\"", json); } -#if NET8_0_OR_GREATER [Fact] public void Serialize_UnknownType_ThrowsInvalidOperationException() { @@ -166,409 +162,47 @@ public void Serialize_UnknownType_ThrowsInvalidOperationException() RuntimeFeatureWrapper.SetIsDynamicCodeSupported(false); // Act & Assert var exception = Assert.Throws(() => - _serializer.Serialize(unknownObject, typeof(UnknownType))); + PowertoolsLoggingSerializer.Serialize(unknownObject, typeof(UnknownType))); Assert.Contains("is not known to the serializer", exception.Message); Assert.Contains(typeof(UnknownType).ToString(), exception.Message); } - + [Fact] public void Serialize_UnknownType_Should_Not_Throw_InvalidOperationException_When_Dynamic() { // Arrange - var unknownObject = new UnknownType { SomeProperty = "Hello" }; + var unknownObject = new UnknownType{ SomeProperty = "Hello"}; RuntimeFeatureWrapper.SetIsDynamicCodeSupported(true); // Act & Assert var expected = - _serializer.Serialize(unknownObject, typeof(UnknownType)); + PowertoolsLoggingSerializer.Serialize(unknownObject, typeof(UnknownType)); Assert.Equal("{\"some_property\":\"Hello\"}", expected); } - [Fact] - public void AddSerializerContext_ShouldUpdateTypeInfoResolver() - { - // Arrange - RuntimeFeatureWrapper.SetIsDynamicCodeSupported(false); - var testContext = new TestSerializerContext(new JsonSerializerOptions()); - - // Get the initial resolver - var beforeOptions = _serializer.GetSerializerOptions(); - var beforeResolver = beforeOptions.TypeInfoResolver; - - // Act - _serializer.AddSerializerContext(testContext); - - // Get the updated resolver - var afterOptions = _serializer.GetSerializerOptions(); - var afterResolver = afterOptions.TypeInfoResolver; - - // Assert - adding a context should create a new resolver - Assert.NotSame(beforeResolver, afterResolver); - Assert.IsType(afterResolver); - } - private class UnknownType { public string SomeProperty { get; set; } } - private class TestSerializerContext : JsonSerializerContext - { - private readonly JsonSerializerOptions _options; - - public TestSerializerContext(JsonSerializerOptions options) : base(options) - { - _options = options; - } - - public override JsonTypeInfo? GetTypeInfo(Type type) - { - return null; // For testing purposes only - } - - protected override JsonSerializerOptions? GeneratedSerializerOptions => _options; - } - - private void ClearContext() - { - // Create a new serializer to clear any existing contexts - _serializer.SetOptions(new JsonSerializerOptions()); - } -#endif - private string SerializeTestObject(LoggerOutputCase? outputCase) { if (outputCase.HasValue) { - _serializer.ConfigureNamingPolicy(outputCase.Value); + PowertoolsLoggingSerializer.ConfigureNamingPolicy(outputCase.Value); } LogEntry testObject = new LogEntry { ColdStart = true }; - return JsonSerializer.Serialize(testObject, _serializer.GetSerializerOptions()); - } - - [Fact] - public void ByteArrayConverter_ShouldProduceBase64EncodedString() - { - // Arrange - var testObject = new { BinaryData = new byte[] { 1, 2, 3, 4, 5 } }; - - // Act - var json = JsonSerializer.Serialize(testObject, _serializer.GetSerializerOptions()); - - // Assert - Assert.Contains("\"binary_data\":\"AQIDBAU=\"", json); - } - - [Fact] - public void ExceptionConverter_ShouldSerializeExceptionDetails() - { - // Arrange - var exception = new InvalidOperationException("Test error message", new Exception("Inner exception")); - var testObject = new { Error = exception }; - - // Act - var json = JsonSerializer.Serialize(testObject, _serializer.GetSerializerOptions()); - - // Assert - Assert.Equal("{\"error\":{\"type\":\"System.InvalidOperationException\",\"message\":\"Test error message\",\"inner_exception\":{\"type\":\"System.Exception\",\"message\":\"Inner exception\"}}}", json); - } - - [Fact] - public void MemoryStreamConverter_ShouldConvertToBase64() - { - // Arrange - var bytes = new byte[] { 10, 20, 30, 40, 50 }; - var memoryStream = new MemoryStream(bytes); - var testObject = new { Stream = memoryStream }; - - // Act - var json = JsonSerializer.Serialize(testObject, _serializer.GetSerializerOptions()); - - // Assert - Assert.Contains("\"stream\":\"ChQeKDI=\"", json); + return JsonSerializer.Serialize(testObject, PowertoolsLoggingSerializer.GetSerializerOptions()); } - [Fact] - public void ConstantClassConverter_ShouldSerializeToString() - { - // Arrange - var testObject = new { Level = LogLevel.Warning }; - - // Act - var json = JsonSerializer.Serialize(testObject, _serializer.GetSerializerOptions()); - - // Assert - Assert.Contains("\"level\":\"Warning\"", json); - } - -#if NET6_0_OR_GREATER - [Fact] - public void DateOnlyConverter_ShouldSerializeToIsoDate() - { - // Arrange - var date = new DateOnly(2023, 10, 15); - var testObject = new { Date = date }; - - // Act - var json = JsonSerializer.Serialize(testObject, _serializer.GetSerializerOptions()); - - // Assert - Assert.Contains("\"date\":\"2023-10-15\"", json); - } - - [Fact] - public void TimeOnlyConverter_ShouldSerializeToIsoTime() - { - // Arrange - var time = new TimeOnly(13, 45, 30); - var testObject = new { Time = time }; - - // Act - var json = JsonSerializer.Serialize(testObject, _serializer.GetSerializerOptions()); - - // Assert - Assert.Contains("\"time\":\"13:45:30\"", json); - } -#endif - - [Fact] - public void LogLevelJsonConverter_ShouldSerializeAllLogLevels() - { - // Arrange - var levels = new Dictionary - { - { "trace", LogLevel.Trace }, - { "debug", LogLevel.Debug }, - { "info", LogLevel.Information }, - { "warning", LogLevel.Warning }, - { "error", LogLevel.Error }, - { "critical", LogLevel.Critical } - }; - - // Act - var json = JsonSerializer.Serialize(levels, _serializer.GetSerializerOptions()); - - // Assert - Assert.Contains("\"trace\":\"Trace\"", json); - Assert.Contains("\"debug\":\"Debug\"", json); - Assert.Contains("\"info\":\"Information\"", json); - Assert.Contains("\"warning\":\"Warning\"", json); - Assert.Contains("\"error\":\"Error\"", json); - Assert.Contains("\"critical\":\"Critical\"", json); - } - - [Fact] - public void Serialize_ComplexObjectWithMultipleConverters_ShouldConvertAllProperties() - { - // Arrange - var testObject = new ComplexTestObject - { - BinaryData = new byte[] { 1, 2, 3 }, - Exception = new ArgumentException("Test argument"), - Stream = new MemoryStream(new byte[] { 4, 5, 6 }), - Level = LogLevel.Information, -#if NET6_0_OR_GREATER - Date = new DateOnly(2023, 1, 15), - Time = new TimeOnly(14, 30, 0), -#endif - }; - - // Act - var json = JsonSerializer.Serialize(testObject, _serializer.GetSerializerOptions()); - - // Assert - Assert.Contains("\"binary_data\":\"AQID\"", json); - Assert.Contains("\"exception\":{\"type\":\"System.ArgumentException\"", json); - Assert.Contains("\"stream\":\"BAUG\"", json); - Assert.Contains("\"level\":\"Information\"", json); -#if NET6_0_OR_GREATER - Assert.Contains("\"date\":\"2023-01-15\"", json); - Assert.Contains("\"time\":\"14:30:00\"", json); -#endif - } - - private class ComplexTestObject - { - public byte[] BinaryData { get; set; } - public Exception Exception { get; set; } - public MemoryStream Stream { get; set; } - public LogLevel Level { get; set; } -#if NET6_0_OR_GREATER - public DateOnly Date { get; set; } - public TimeOnly Time { get; set; } -#endif - } - - [Fact] - public void ConfigureNamingPolicy_WhenChanged_RebuildsOptions() - { - // Arrange - var serializer = new PowertoolsLoggingSerializer(); - - // Force initialization of _jsonOptions - _ = serializer.GetSerializerOptions(); - - // Act - serializer.ConfigureNamingPolicy(LoggerOutputCase.CamelCase); - var options = serializer.GetSerializerOptions(); - - // Assert - Assert.Equal(JsonNamingPolicy.CamelCase, options.PropertyNamingPolicy); - Assert.Equal(JsonNamingPolicy.CamelCase, options.DictionaryKeyPolicy); - } - - [Fact] - public void ConfigureNamingPolicy_WhenAlreadySet_DoesNothing() - { - // Arrange - var serializer = new PowertoolsLoggingSerializer(); - serializer.ConfigureNamingPolicy(LoggerOutputCase.CamelCase); - - // Get the initial options - var initialOptions = serializer.GetSerializerOptions(); - - // Act - set the same case again - serializer.ConfigureNamingPolicy(LoggerOutputCase.CamelCase); - var newOptions = serializer.GetSerializerOptions(); - - // Assert - should be the same instance - Assert.Same(initialOptions, newOptions); - } - - [Fact] - public void Serialize_WithValidObject_ReturnsJsonString() - { - // Arrange - var serializer = new PowertoolsLoggingSerializer(); - var testObj = new TestClass { Name = "Test", Value = 123 }; - - // Act - var json = serializer.Serialize(testObj, typeof(TestClass)); - - // Assert - Assert.Contains("\"name\"", json); - Assert.Contains("\"value\"", json); - Assert.Contains("123", json); - Assert.Contains("Test", json); - } - -#if NET8_0_OR_GREATER - - [Fact] - public void SetOptions_WithTypeInfoResolver_SetsCustomResolver() - { - // Arrange - var serializer = new PowertoolsLoggingSerializer(); - - // Explicitly disable dynamic code - important to set before creating options - RuntimeFeatureWrapper.SetIsDynamicCodeSupported(false); - - var context = new TestJsonContext(new JsonSerializerOptions()); - var options = new JsonSerializerOptions - { - TypeInfoResolver = context - }; - - // Act - serializer.SetOptions(options); - var serializerOptions = serializer.GetSerializerOptions(); - - // Assert - options are properly configured - Assert.NotNull(serializerOptions.TypeInfoResolver); - } -#endif - - [Fact] - public void SetOutputCase_CamelCase_SetsPoliciesCorrectly() - { - // Arrange - var serializer = new PowertoolsLoggingSerializer(); - serializer.ConfigureNamingPolicy(LoggerOutputCase.CamelCase); - - // Act - var options = serializer.GetSerializerOptions(); - - // Assert - Assert.Equal(JsonNamingPolicy.CamelCase, options.PropertyNamingPolicy); - Assert.Equal(JsonNamingPolicy.CamelCase, options.DictionaryKeyPolicy); - } - - [Fact] - public void SetOutputCase_PascalCase_SetsPoliciesCorrectly() - { - // Arrange - var serializer = new PowertoolsLoggingSerializer(); - serializer.ConfigureNamingPolicy(LoggerOutputCase.PascalCase); - - // Act - var options = serializer.GetSerializerOptions(); - - // Assert - Assert.IsType(options.PropertyNamingPolicy); - Assert.IsType(options.DictionaryKeyPolicy); - } - - [Fact] - public void SetOutputCase_SnakeCase_SetsPoliciesCorrectly() - { - // Arrange - var serializer = new PowertoolsLoggingSerializer(); - serializer.ConfigureNamingPolicy(LoggerOutputCase.SnakeCase); - - // Act - var options = serializer.GetSerializerOptions(); - -#if NET8_0_OR_GREATER - // Assert - in .NET 8 we use built-in SnakeCaseLower - Assert.Equal(JsonNamingPolicy.SnakeCaseLower, options.PropertyNamingPolicy); - Assert.Equal(JsonNamingPolicy.SnakeCaseLower, options.DictionaryKeyPolicy); -#else - // Assert - in earlier versions, we use custom SnakeCaseNamingPolicy - Assert.IsType(options.PropertyNamingPolicy); - Assert.IsType(options.DictionaryKeyPolicy); -#endif - } - - [Fact] - public void GetSerializerOptions_AddsAllConverters() - { - // Arrange - var serializer = new PowertoolsLoggingSerializer(); - - // Act - var options = serializer.GetSerializerOptions(); - - // Assert - Assert.Contains(options.Converters, c => c is ByteArrayConverter); - Assert.Contains(options.Converters, c => c is ExceptionConverter); - Assert.Contains(options.Converters, c => c is MemoryStreamConverter); - Assert.Contains(options.Converters, c => c is ConstantClassConverter); - Assert.Contains(options.Converters, c => c is DateOnlyConverter); - Assert.Contains(options.Converters, c => c is TimeOnlyConverter); -#if NET8_0_OR_GREATER || NET6_0 - Assert.Contains(options.Converters, c => c is LogLevelJsonConverter); -#endif - } - - // Test class for serialization - private class TestClass - { - public string Name { get; set; } - public int Value { get; set; } - } - - - - public void Dispose() { -#if NET8_0_OR_GREATER - ClearContext(); -#endif - _serializer.SetOptions(null); + PowertoolsLoggingSerializer.ConfigureNamingPolicy(LoggingConstants.DefaultLoggerOutputCase); + PowertoolsLoggingSerializer.ClearContext(); + PowertoolsLoggingSerializer.ClearOptions(); RuntimeFeatureWrapper.Reset(); } } \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/TestSetup.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/TestSetup.cs index f86545998..708c63c23 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/TestSetup.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/TestSetup.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System.Collections.Generic; using System.Linq; using Xunit; diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Utilities/Converters.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Utilities/Converters.cs deleted file mode 100644 index b7e975e4e..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Utilities/Converters.cs +++ /dev/null @@ -1,181 +0,0 @@ -using System; -using System.Text; -using System.Text.Json; -using System.Text.Json.Serialization; -using AWS.Lambda.Powertools.Logging.Internal.Converters; -using Xunit; - -namespace AWS.Lambda.Powertools.Logging.Tests.Utilities; - -public class ByteArrayConverterTests -{ - private readonly JsonSerializerOptions _options; - - public ByteArrayConverterTests() - { - _options = new JsonSerializerOptions(); - _options.Converters.Add(new ByteArrayConverter()); - } - - [Fact] - public void Write_WhenByteArrayIsNull_WritesNullValue() - { - // Arrange - var testObject = new TestClass { Data = null }; - - // Act - var json = JsonSerializer.Serialize(testObject, _options); - - // Assert - Assert.Contains("\"data\":null", json); - } - - [Fact] - public void Write_WithByteArray_WritesBase64String() - { - // Arrange - byte[] testData = { 1, 2, 3, 4, 5 }; - var testObject = new TestClass { Data = testData }; - var expectedBase64 = Convert.ToBase64String(testData); - - // Act - var json = JsonSerializer.Serialize(testObject, _options); - - // Assert - Assert.Contains($"\"data\":\"{expectedBase64}\"", json); - } - - [Fact] - public void Read_WithBase64String_ReturnsByteArray() - { - // Arrange - byte[] expectedData = { 1, 2, 3, 4, 5 }; - var base64 = Convert.ToBase64String(expectedData); - var json = $"{{\"data\":\"{base64}\"}}"; - - // Act - var result = JsonSerializer.Deserialize(json, _options); - - // Assert - Assert.Equal(expectedData, result.Data); - } - - [Fact] - public void Read_WithInvalidType_ThrowsJsonException() - { - // Arrange - var json = "{\"data\":123}"; - - // Act & Assert - Assert.Throws(() => - JsonSerializer.Deserialize(json, _options)); - } - - [Fact] - public void Read_WithEmptyString_ReturnsEmptyByteArray() - { - // Arrange - var json = "{\"data\":\"\"}"; - - // Act - var result = JsonSerializer.Deserialize(json, _options); - - // Assert - Assert.NotNull(result.Data); - Assert.Empty(result.Data); - } - - [Fact] - public void WriteAndRead_RoundTrip_PreservesData() - { - // Arrange - byte[] originalData = Encoding.UTF8.GetBytes("Test data with special chars: !@#$%^&*()"); - var testObject = new TestClass { Data = originalData }; - - // Act - var json = JsonSerializer.Serialize(testObject, _options); - var deserializedObject = JsonSerializer.Deserialize(json, _options); - - // Assert - Assert.Equal(originalData, deserializedObject.Data); - } - - private class TestClass - { - [JsonPropertyName("data")] public byte[] Data { get; set; } - } - - [Fact] - public void ByteArrayConverter_Write_ShouldHandleNullValue() - { - // Arrange - var converter = new ByteArrayConverter(); - var options = new JsonSerializerOptions(); - var testObject = new { Data = (byte[])null }; - - // Act - var json = JsonSerializer.Serialize(testObject, options); - - // Assert - Assert.Contains("\"Data\":null", json); - } - - [Fact] - public void ByteArrayConverter_Read_ShouldHandleNullToken() - { - // Arrange - var converter = new ByteArrayConverter(); - var json = "{\"Data\":null}"; - var options = new JsonSerializerOptions(); - options.Converters.Add(converter); - - // Act - var result = JsonSerializer.Deserialize(json, options); - - // Assert - Assert.Null(result.Data); - } - - [Fact] - public void ByteArrayConverter_Read_ShouldHandleStringToken() - { - // Arrange - var converter = new ByteArrayConverter(); - var expectedBytes = new byte[] { 1, 2, 3, 4 }; - var base64String = Convert.ToBase64String(expectedBytes); - var json = $"{{\"Data\":\"{base64String}\"}}"; - - var options = new JsonSerializerOptions(); - options.Converters.Add(converter); - - // Act - var result = JsonSerializer.Deserialize(json, options); - - // Assert - Assert.NotNull(result.Data); - Assert.Equal(expectedBytes, result.Data); - } - - [Fact] - public void ByteArrayConverter_Read_ShouldThrowOnInvalidToken() - { - // Arrange - var converter = new ByteArrayConverter(); - var json = "{\"Data\":123}"; // Number instead of string - - var options = new JsonSerializerOptions(); - options.Converters.Add(converter); - - // Act & Assert - var ex = Assert.Throws(() => - JsonSerializer.Deserialize(json, options)); - - Assert.Contains("Expected string value for byte array", ex.Message); - } - -// Helper class for testing byte array deserialization - private class TestByteArrayClass - { - public byte[] Data { get; set; } - } -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Utilities/PowertoolsConfigurationExtensionsTests.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Utilities/PowertoolsConfigurationExtensionsTests.cs index f8ee09854..6a719d1b2 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Utilities/PowertoolsConfigurationExtensionsTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Utilities/PowertoolsConfigurationExtensionsTests.cs @@ -1,6 +1,24 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using Xunit; +using NSubstitute; +using AWS.Lambda.Powertools.Common; using AWS.Lambda.Powertools.Logging.Internal; +using AWS.Lambda.Powertools.Logging.Serializers; namespace AWS.Lambda.Powertools.Logging.Tests.Utilities; @@ -13,8 +31,12 @@ public class PowertoolsConfigurationExtensionsTests : IDisposable [InlineData(LoggerOutputCase.SnakeCase, "testString", "test_string")] // Default case public void ConvertToOutputCase_ShouldConvertCorrectly(LoggerOutputCase outputCase, string input, string expected) { + // Arrange + var systemWrapper = Substitute.For(); + var configurations = new PowertoolsConfigurations(systemWrapper); + // Act - var result = input.ToCase(outputCase); + var result = configurations.ConvertToOutputCase(input, outputCase); // Assert Assert.Equal(expected, result); @@ -44,7 +66,7 @@ public void ConvertToOutputCase_ShouldConvertCorrectly(LoggerOutputCase outputCa public void ToSnakeCase_ShouldConvertCorrectly(string input, string expected) { // Act - var result = input.ToSnake(); + var result = PrivateMethod.InvokeStatic(typeof(PowertoolsConfigurationsExtension), "ToSnakeCase", input); // Assert Assert.Equal(expected, result); @@ -75,7 +97,7 @@ public void ToSnakeCase_ShouldConvertCorrectly(string input, string expected) public void ToPascalCase_ShouldConvertCorrectly(string input, string expected) { // Act - var result = input.ToPascal(); + var result = PrivateMethod.InvokeStatic(typeof(PowertoolsConfigurationsExtension), "ToPascalCase", input); // Assert Assert.Equal(expected, result); @@ -113,7 +135,7 @@ public void ToPascalCase_ShouldConvertCorrectly(string input, string expected) public void ToCamelCase_ShouldConvertCorrectly(string input, string expected) { // Act - var result = input.ToCamel(); + var result = PrivateMethod.InvokeStatic(typeof(PowertoolsConfigurationsExtension), "ToCamelCase", input); // Assert Assert.Equal(expected, result); @@ -122,6 +144,7 @@ public void ToCamelCase_ShouldConvertCorrectly(string input, string expected) public void Dispose() { LoggingAspect.ResetForTest(); + PowertoolsLoggingSerializer.ClearOptions(); } } diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Utilities/PowertoolsLoggerHelpersTests.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Utilities/PowertoolsLoggerHelpersTests.cs index f35753f88..da3dd6968 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Utilities/PowertoolsLoggerHelpersTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Utilities/PowertoolsLoggerHelpersTests.cs @@ -1,19 +1,16 @@ -#if NET8_0_OR_GREATER - using System; using System.Collections.Generic; using System.IO; using AWS.Lambda.Powertools.Common; using AWS.Lambda.Powertools.Logging.Internal.Helpers; using AWS.Lambda.Powertools.Logging.Serializers; -using Microsoft.Extensions.Logging; using NSubstitute; using Xunit; namespace AWS.Lambda.Powertools.Logging.Tests.Utilities; public class PowertoolsLoggerHelpersTests : IDisposable -{ +{ [Fact] public void ObjectToDictionary_AnonymousObjectWithSimpleProperties_ReturnsDictionary() { @@ -74,12 +71,9 @@ public void ObjectToDictionary_NullObject_Return_New_Dictionary() [Fact] public void Should_Log_With_Anonymous() { - var consoleOut = Substitute.For(); - Logger.Configure(options => - { - options.LogOutput = consoleOut; - }); - + var consoleOut = Substitute.For(); + SystemWrapper.Instance.SetOut(consoleOut); + // Act & Assert Logger.AppendKey("newKey", new { @@ -97,12 +91,9 @@ public void Should_Log_With_Anonymous() [Fact] public void Should_Log_With_Complex_Anonymous() { - var consoleOut = Substitute.For(); - Logger.Configure(options => - { - options.LogOutput = consoleOut; - }); - + var consoleOut = Substitute.For(); + SystemWrapper.Instance.SetOut(consoleOut); + // Act & Assert Logger.AppendKey("newKey", new { @@ -208,28 +199,7 @@ public void ObjectToDictionary_ObjectWithAllNullProperties_ReturnsEmptyDictionar public void Dispose() { - ResetAllState(); - } - - private static void ResetAllState() - { - // Clear environment variables - Environment.SetEnvironmentVariable("POWERTOOLS_LOGGER_CASE", null); - Environment.SetEnvironmentVariable("POWERTOOLS_SERVICE_NAME", null); - Environment.SetEnvironmentVariable("POWERTOOLS_LOG_LEVEL", null); - - // Reset all logging components - Logger.Reset(); - PowertoolsLoggingBuilderExtensions.ResetAllProviders(); - - // Force default configuration - var config = new PowertoolsLoggerConfiguration - { - MinimumLogLevel = LogLevel.Information, - LoggerOutputCase = LoggerOutputCase.SnakeCase - }; - PowertoolsLoggingBuilderExtensions.UpdateConfiguration(config); + PowertoolsLoggingSerializer.ConfigureNamingPolicy(LoggerOutputCase.Default); + PowertoolsLoggingSerializer.ClearOptions(); } } - -#endif \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Utilities/SystemWrapperMock.cs b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Utilities/SystemWrapperMock.cs new file mode 100644 index 000000000..1ab2b94ed --- /dev/null +++ b/libraries/tests/AWS.Lambda.Powertools.Logging.Tests/Utilities/SystemWrapperMock.cs @@ -0,0 +1,68 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System.IO; +using AWS.Lambda.Powertools.Common; + +namespace AWS.Lambda.Powertools.Logging.Tests.Utilities; + +public class SystemWrapperMock : ISystemWrapper +{ + private readonly IPowertoolsEnvironment _powertoolsEnvironment; + public bool LogMethodCalled { get; private set; } + public string LogMethodCalledWithArgument { get; private set; } + + public SystemWrapperMock(IPowertoolsEnvironment powertoolsEnvironment) + { + _powertoolsEnvironment = powertoolsEnvironment; + } + + public string GetEnvironmentVariable(string variable) + { + return _powertoolsEnvironment.GetEnvironmentVariable(variable); + } + + public void Log(string value) + { + LogMethodCalledWithArgument = value; + LogMethodCalled = true; + } + + public void LogLine(string value) + { + LogMethodCalledWithArgument = value; + LogMethodCalled = true; + } + + + public double GetRandom() + { + return 0.7; + } + + public void SetEnvironmentVariable(string variable, string value) + { + throw new System.NotImplementedException(); + } + + public void SetExecutionEnvironment(T type) + { + } + + public void SetOut(TextWriter writeTo) + { + + } +} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests.csproj b/libraries/tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests.csproj deleted file mode 100644 index 15ac13121..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests.csproj +++ /dev/null @@ -1,35 +0,0 @@ - - - - - AWS.Lambda.Powertools.Metrics.AspNetCore.Tests - AWS.Lambda.Powertools.Metrics.AspNetCore.Tests - net8.0 - enable - enable - - false - true - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests/MetricsEndpointExtensionsTests.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests/MetricsEndpointExtensionsTests.cs deleted file mode 100644 index 18ef4c2c7..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests/MetricsEndpointExtensionsTests.cs +++ /dev/null @@ -1,161 +0,0 @@ -using Amazon.Lambda.TestUtilities; -using AWS.Lambda.Powertools.Common; -using AWS.Lambda.Powertools.Metrics.AspNetCore.Http; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.DependencyInjection; -using NSubstitute; -using Xunit; - -namespace AWS.Lambda.Powertools.Metrics.AspNetCore.Tests; - -[Collection("Metrics")] -public class MetricsEndpointExtensionsTests : IDisposable -{ - [Fact] - public async Task When_WithMetrics_Should_Add_ColdStart() - { - // Arrange - var options = new MetricsOptions - { - CaptureColdStart = true, - Namespace = "TestNamespace" - }; - - var conf = Substitute.For(); - var consoleWrapper = Substitute.For(); - var metrics = new Metrics(conf, consoleWrapper: consoleWrapper, options: options); - var builder = WebApplication.CreateBuilder(); - builder.Services.AddSingleton(metrics); - builder.WebHost.UseTestServer(); - - var app = builder.Build(); - - app.MapGet("/test", () => Results.Ok(new { success = true })).WithMetrics(); - - await app.StartAsync(); - var client = app.GetTestClient(); - - // Act - var response = await client.GetAsync("/test"); - - // Assert - Assert.Equal(200, (int)response.StatusCode); - - // Assert metrics calls - consoleWrapper.Received(1).WriteLine( - Arg.Is(s => s.Contains("CloudWatchMetrics\":[{\"Namespace\":\"TestNamespace\",\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[[]]}]},\"ColdStart\":1}")) - ); - - - await app.StopAsync(); - } - - [Fact] - public async Task When_WithMetrics_Should_Add_ColdStart_Dimensions() - { - // Arrange - var options = new MetricsOptions - { - CaptureColdStart = true, - Namespace = "TestNamespace", - }; - - var conf = Substitute.For(); - var consoleWrapper = Substitute.For(); - var metrics = new Metrics(conf, consoleWrapper: consoleWrapper, options: options); - - var builder = WebApplication.CreateBuilder(); - builder.Services.AddSingleton(metrics); - builder.WebHost.UseTestServer(); - - - var app = builder.Build(); - app.Use(async (context, next) => - { - var lambdaContext = new TestLambdaContext - { - FunctionName = "TestFunction" - }; - context.Items["LambdaContext"] = lambdaContext; - await next(); - }); - - app.MapGet("/test", () => Results.Ok(new { success = true })).WithMetrics(); - - await app.StartAsync(); - var client = app.GetTestClient(); - - // Act - var response = await client.GetAsync("/test"); - - // Assert - Assert.Equal(200, (int)response.StatusCode); - - // Assert metrics calls - consoleWrapper.Received(1).WriteLine( - Arg.Is(s => s.Contains("CloudWatchMetrics\":[{\"Namespace\":\"TestNamespace\",\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"FunctionName\"]]}]},\"FunctionName\":\"TestFunction\",\"ColdStart\":1}")) - ); - - await app.StopAsync(); - } - - [Fact] - public async Task When_WithMetrics_Should_Add_ColdStart_Default_Dimensions() - { - // Arrange - var options = new MetricsOptions - { - CaptureColdStart = true, - Namespace = "TestNamespace", - DefaultDimensions = new Dictionary - { - { "Environment", "Prod" } - } - }; - - var conf = Substitute.For(); - var consoleWrapper = Substitute.For(); - var metrics = new Metrics(conf, consoleWrapper: consoleWrapper, options: options); - - var builder = WebApplication.CreateBuilder(); - builder.Services.AddSingleton(metrics); - builder.WebHost.UseTestServer(); - - - var app = builder.Build(); - app.Use(async (context, next) => - { - var lambdaContext = new TestLambdaContext - { - FunctionName = "TestFunction" - }; - context.Items["LambdaContext"] = lambdaContext; - await next(); - }); - - app.MapGet("/test", () => Results.Ok(new { success = true })).WithMetrics(); - - await app.StartAsync(); - var client = app.GetTestClient(); - - // Act - var response = await client.GetAsync("/test"); - - // Assert - Assert.Equal(200, (int)response.StatusCode); - - // Assert metrics calls - consoleWrapper.Received(1).WriteLine( - Arg.Is(s => s.Contains("CloudWatchMetrics\":[{\"Namespace\":\"TestNamespace\",\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Environment\",\"FunctionName\"]]}]},\"Environment\":\"Prod\",\"FunctionName\":\"TestFunction\",\"ColdStart\":1}")) - ); - - await app.StopAsync(); - } - - public void Dispose() - { - ColdStartTracker.ResetColdStart(); - } -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests/MetricsFilterTests.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests/MetricsFilterTests.cs deleted file mode 100644 index 9951034ae..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests/MetricsFilterTests.cs +++ /dev/null @@ -1,76 +0,0 @@ -using Amazon.Lambda.Core; -using AWS.Lambda.Powertools.Metrics.AspNetCore.Http; -using Microsoft.AspNetCore.Http; -using NSubstitute; -using Xunit; - -namespace AWS.Lambda.Powertools.Metrics.AspNetCore.Tests; - -[Collection("Metrics")] -public class MetricsFilterTests : IDisposable -{ - private readonly IMetrics _metrics; - private readonly EndpointFilterInvocationContext _context; - - public MetricsFilterTests() - { - ColdStartTracker.ResetColdStart(); // Reset before each test - _metrics = Substitute.For(); - _context = Substitute.For(); - var lambdaContext = Substitute.For(); - - var httpContext = new DefaultHttpContext(); - httpContext.Items["LambdaContext"] = lambdaContext; - _context.HttpContext.Returns(httpContext); - } - - [Fact] - public async Task InvokeAsync_Second_Call_DoesNotRecord_ColdStart_Metric() - { - // Arrange - var options = new MetricsOptions { CaptureColdStart = false }; - _metrics.Options.Returns(options); - - var filter = new MetricsFilter(_metrics); - var next = new EndpointFilterDelegate(_ => ValueTask.FromResult("result")); - - // Act - _ = await filter.InvokeAsync(_context, next); - var result = await filter.InvokeAsync(_context, next); - - // Assert - _metrics.Received(1).CaptureColdStartMetric(Arg.Any() ); - Assert.Equal("result", result); - } - - [Fact] - public async Task InvokeAsync_ShouldCallNextAndContinue() - { - // Arrange - var metrics = Substitute.For(); - metrics.Options.Returns(new MetricsOptions { CaptureColdStart = true }); - - var httpContext = new DefaultHttpContext(); - var context = new DefaultEndpointFilterInvocationContext(httpContext); - var filter = new MetricsFilter(metrics); - - var called = false; - EndpointFilterDelegate next = _ => - { - called = true; - return ValueTask.FromResult("result"); - }; - - // Act - var result = await filter.InvokeAsync(context, next); - - // Assert - Assert.True(called); - Assert.Equal("result", result); - } - - public void Dispose() - { - ColdStartTracker.ResetColdStart(); - } -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests/MetricsMiddlewareExtensionsTests.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests/MetricsMiddlewareExtensionsTests.cs deleted file mode 100644 index 128a5c427..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Metrics.AspNetCore.Tests/MetricsMiddlewareExtensionsTests.cs +++ /dev/null @@ -1,103 +0,0 @@ -using Amazon.Lambda.TestUtilities; -using AWS.Lambda.Powertools.Common; -using AWS.Lambda.Powertools.Metrics.AspNetCore.Http; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.DependencyInjection; -using NSubstitute; -using Xunit; - -namespace AWS.Lambda.Powertools.Metrics.AspNetCore.Tests; - -[Collection("Metrics")] -public class MetricsMiddlewareExtensionsTests : IDisposable -{ - [Fact] - public async Task When_UseMetrics_Should_Add_ColdStart() - { - // Arrange - var options = new MetricsOptions - { - CaptureColdStart = true, - Namespace = "TestNamespace", - }; - - var conf = Substitute.For(); - var consoleWrapper = Substitute.For(); - var metrics = new Metrics(conf, consoleWrapper: consoleWrapper, options: options); - - var builder = WebApplication.CreateBuilder(); - builder.Services.AddSingleton(metrics); - builder.WebHost.UseTestServer(); - - var app = builder.Build(); - app.UseMetrics(); - app.MapGet("/test", () => Results.Ok()); - - await app.StartAsync(); - var client = app.GetTestClient(); - - // Act - var response = await client.GetAsync("/test"); - - // Assert - Assert.Equal(200, (int)response.StatusCode); - consoleWrapper.Received(1).WriteLine( - Arg.Is(s => s.Contains("CloudWatchMetrics\":[{\"Namespace\":\"TestNamespace\",\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[[]]}]},\"ColdStart\":1}")) - ); - - await app.StopAsync(); - } - - [Fact] - public async Task When_UseMetrics_Should_Add_ColdStart_With_LambdaContext() - { - // Arrange - var options = new MetricsOptions - { - CaptureColdStart = true, - Namespace = "TestNamespace", - }; - - var conf = Substitute.For(); - var consoleWrapper = Substitute.For(); - var metrics = new Metrics(conf, consoleWrapper:consoleWrapper, options: options); - - var builder = WebApplication.CreateBuilder(); - builder.Services.AddSingleton(metrics); - builder.WebHost.UseTestServer(); - - var app = builder.Build(); - app.Use(async (context, next) => - { - var lambdaContext = new TestLambdaContext - { - FunctionName = "TestFunction" - }; - context.Items["LambdaContext"] = lambdaContext; - await next(); - }); - app.UseMetrics(); - app.MapGet("/test", () => Results.Ok()); - - await app.StartAsync(); - var client = app.GetTestClient(); - - // Act - var response = await client.GetAsync("/test"); - - // Assert - Assert.Equal(200, (int)response.StatusCode); - consoleWrapper.Received(1).WriteLine( - Arg.Is(s => s.Contains("CloudWatchMetrics\":[{\"Namespace\":\"TestNamespace\",\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"FunctionName\"]]}]},\"FunctionName\":\"TestFunction\",\"ColdStart\":1}")) - ); - - await app.StopAsync(); - } - - public void Dispose() - { - ColdStartTracker.ResetColdStart(); - } -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/ClearDimensionsTests.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/ClearDimensionsTests.cs index 90a3547a7..1ac09fbee 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/ClearDimensionsTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/ClearDimensionsTests.cs @@ -13,7 +13,7 @@ public void WhenClearAllDimensions_NoDimensionsInOutput() { // Arrange var consoleOut = new StringWriter(); - ConsoleWrapper.SetOut(consoleOut); + SystemWrapper.Instance.SetOut(consoleOut); // Act var handler = new FunctionHandler(); @@ -22,7 +22,7 @@ public void WhenClearAllDimensions_NoDimensionsInOutput() var metricsOutput = consoleOut.ToString(); // Assert - Assert.Contains("{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"Metric Name\",\"Unit\":\"Count\"}],\"Dimensions\":[[]]", metricsOutput); + Assert.Contains("{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"Metric Name\",\"Unit\":\"Count\"}],\"Dimensions\":[]", metricsOutput); // Reset MetricsAspect.ResetForTest(); diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/EMFValidationTests.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/EMFValidationTests.cs index f879de8bb..79c275ae2 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/EMFValidationTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/EMFValidationTests.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.Collections.Generic; using System.IO; @@ -20,7 +35,7 @@ public EmfValidationTests() { _handler = new FunctionHandler(); _consoleOut = new CustomConsoleWriter(); - ConsoleWrapper.SetOut(_consoleOut); + SystemWrapper.Instance.SetOut(_consoleOut); } [Trait("Category", value: "SchemaValidation")] @@ -64,12 +79,12 @@ public void WhenMaxMetricsAreAdded_FlushAutomatically() // flush when it reaches MaxMetrics Assert.Contains( - "{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"Metric Name 1\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 2\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 3\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 4\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 5\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 6\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 7\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 8\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 9\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 10\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 11\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 12\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 13\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 14\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 15\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 16\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 17\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 18\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 19\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 20\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 21\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 22\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 23\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 24\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 25\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 26\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 27\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 28\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 29\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 30\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 31\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 32\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 33\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 34\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 35\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 36\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 37\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 38\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 39\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 40\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 41\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 42\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 43\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 44\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 45\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 46\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 47\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 48\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 49\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 50\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 51\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 52\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 53\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 54\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 55\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 56\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 57\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 58\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 59\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 60\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 61\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 62\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 63\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 64\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 65\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 66\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 67\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 68\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 69\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 70\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 71\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 72\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 73\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 74\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 75\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 76\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 77\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 78\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 79\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 80\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 81\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 82\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 83\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 84\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 85\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 86\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 87\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 88\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 89\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 90\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 91\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 92\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 93\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 94\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 95\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 96\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 97\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 98\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 99\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 100\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\"]]", + "{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"Metric Name 1\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 2\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 3\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 4\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 5\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 6\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 7\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 8\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 9\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 10\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 11\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 12\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 13\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 14\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 15\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 16\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 17\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 18\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 19\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 20\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 21\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 22\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 23\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 24\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 25\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 26\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 27\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 28\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 29\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 30\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 31\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 32\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 33\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 34\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 35\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 36\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 37\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 38\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 39\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 40\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 41\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 42\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 43\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 44\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 45\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 46\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 47\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 48\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 49\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 50\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 51\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 52\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 53\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 54\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 55\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 56\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 57\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 58\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 59\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 60\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 61\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 62\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 63\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 64\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 65\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 66\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 67\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 68\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 69\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 70\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 71\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 72\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 73\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 74\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 75\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 76\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 77\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 78\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 79\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 80\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 81\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 82\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 83\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 84\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 85\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 86\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 87\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 88\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 89\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 90\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 91\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 92\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 93\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 94\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 95\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 96\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 97\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 98\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 99\",\"Unit\":\"Count\"},{\"Name\":\"Metric Name 100\",\"Unit\":\"Count\"}],\"Dimensions\":[\"Service\"]", metricsOutput[0]); // flush the (MaxMetrics + 1) item only Assert.Contains( - "{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"Metric Name 101\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\"]]", + "{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"Metric Name 101\",\"Unit\":\"Count\"}],\"Dimensions\":[\"Service\"]", metricsOutput[1]); } @@ -90,15 +105,15 @@ public void WhenMaxDataPointsAreAddedToTheSameMetric_FlushAutomatically() metricsOutput[0]); // flush the (MaxMetrics + 1) item only - Assert.Contains("\"Dimensions\":[[\"Service\"]]}]},\"Service\":\"testService\",\"Metric Name\":100}", metricsOutput[1]); + Assert.Contains("[\"Service\"]}]},\"Service\":\"testService\",\"Metric Name\":100}", metricsOutput[1]); } [Trait("Category", "EMFLimits")] [Fact] - public void WhenMoreThan29DimensionsAdded_ThrowArgumentOutOfRangeException() + public void WhenMoreThan9DimensionsAdded_ThrowArgumentOutOfRangeException() { // Act - var act = () => { _handler.MaxDimensions(29); }; + var act = () => { _handler.MaxDimensions(9); }; // Assert Assert.Throws(act); @@ -126,7 +141,7 @@ public void WhenDimensionsAreAdded_MustExistAsMembers() var metricsOutput = _consoleOut.ToString(); // Assert - Assert.Contains("\"Dimensions\":[[\"Service\",\"functionVersion\"]]" + Assert.Contains("\"Dimensions\":[\"Service\",\"functionVersion\"]" , metricsOutput); Assert.Contains("\"Service\":\"testService\",\"functionVersion\":\"$LATEST\"" , metricsOutput); @@ -142,71 +157,11 @@ public void When_Multiple_DimensionsAreAdded_MustExistAsMembers() var metricsOutput = _consoleOut.ToString(); // Assert - Assert.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\"]]}]},\"Service\":\"ServiceName\",\"ColdStart\":1}", metricsOutput); - Assert.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"SingleMetric1\",\"Unit\":\"Count\",\"StorageResolution\":1}],\"Dimensions\":[[\"Default1\"]]}]},\"Default1\":\"SingleMetric1\",\"SingleMetric1\":1}", metricsOutput); - Assert.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"ns2\",\"Metrics\":[{\"Name\":\"SingleMetric2\",\"Unit\":\"Count\",\"StorageResolution\":1}],\"Dimensions\":[[\"Default1\",\"Default2\"]]}]},\"Default1\":\"SingleMetric2\",\"Default2\":\"SingleMetric2\",\"SingleMetric2\":1}", metricsOutput); - Assert.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"AddMetric\",\"Unit\":\"Count\",\"StorageResolution\":1},{\"Name\":\"AddMetric2\",\"Unit\":\"Count\",\"StorageResolution\":1}],\"Dimensions\":[[\"Service\"]]}]},\"Service\":\"ServiceName\",\"AddMetric\":1,\"AddMetric2\":1}", metricsOutput); - } - - [Trait("Category", "SchemaValidation")] - [Fact] - public void When_PushSingleMetric_With_Namespace() - { - // Act - _handler.PushSingleMetricWithNamespace(); - - var metricsOutput = _consoleOut.ToString(); - - // Assert - Assert.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"ExampleApplication\",\"Metrics\":[{\"Name\":\"SingleMetric\",\"Unit\":\"Count\",\"StorageResolution\":1}],\"Dimensions\":[[\"Default\"]]}]},\"Default\":\"SingleMetric\",\"SingleMetric\":1}", metricsOutput); - } - - [Trait("Category", "SchemaValidation")] - [Fact] - public void When_PushSingleMetric_With_No_DefaultDimensions() - { - // Act - _handler.PushSingleMetricNoDefaultDimensions(); - - var metricsOutput = _consoleOut.ToString(); - - // Assert - Assert.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"ExampleApplication\",\"Metrics\":[{\"Name\":\"SingleMetric\",\"Unit\":\"Count\"}],\"Dimensions\":[[]]}]},\"SingleMetric\":1}", metricsOutput); - } - - [Trait("Category", "SchemaValidation")] - [Fact] - public void When_PushSingleMetric_With_DefaultDimensions() - { - // Act - _handler.PushSingleMetricDefaultDimensions(); - - var metricsOutput = _consoleOut.ToString(); - - // Assert - Assert.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"ExampleApplication\",\"Metrics\":[{\"Name\":\"SingleMetric\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Default\"]]}]},\"Default\":\"SingleMetric\",\"SingleMetric\":1}", metricsOutput); - } - - [Trait("Category", "SchemaValidation")] - [Fact] - public void When_PushSingleMetric_With_Env_Namespace() - { - // Arrange - Environment.SetEnvironmentVariable("POWERTOOLS_METRICS_NAMESPACE", "EnvNamespace"); + Assert.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"ns1\",\"Metrics\":[{\"Name\":\"Lambda Execute\",\"Unit\":\"Count\",\"StorageResolution\":1}],\"Dimensions\":[\"Type\",\"Service\"]}]},\"Type\":\"Start\",\"Service\":\"service_undefined\",\"Lambda Execute\":1}", metricsOutput); - // Act - _handler.PushSingleMetricWithEnvNamespace(); - - var metricsOutput = _consoleOut.ToString(); - - // Assert - Assert.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"EnvNamespace\",\"Metrics\":[{\"Name\":\"SingleMetric\",\"Unit\":\"Count\",\"StorageResolution\":1}],\"Dimensions\":[[\"Default\"]]}]},\"Default\":\"SingleMetric\",\"SingleMetric\":1}", metricsOutput); - - // assert with different service name - Assert.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"EnvNamespace\",\"Metrics\":[{\"Name\":\"SingleMetric2\",\"Unit\":\"Count\",\"StorageResolution\":1}],\"Dimensions\":[[\"Service\",\"Default\"]]}]},\"Service\":\"service1\",\"Default\":\"SingleMetric\",\"SingleMetric2\":1}", metricsOutput); + Assert.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"ns2\",\"Metrics\":[{\"Name\":\"Lambda Execute\",\"Unit\":\"Count\",\"StorageResolution\":1}],\"Dimensions\":[\"Type\",\"SessionId\",\"Service\"]}]},\"Type\":\"Start\",\"SessionId\":\"Unset\",\"Service\":\"service_undefined\",\"Lambda Execute\":1}", metricsOutput); - // assert with different service name - Assert.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"EnvNamespace\",\"Metrics\":[{\"Name\":\"SingleMetric3\",\"Unit\":\"Count\",\"StorageResolution\":1}],\"Dimensions\":[[\"Service\",\"Default\"]]}]},\"Service\":\"service2\",\"Default\":\"SingleMetric\",\"SingleMetric3\":1}", metricsOutput); + Assert.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"Lambda Execute\",\"Unit\":\"Count\",\"StorageResolution\":1}],\"Dimensions\":[\"Service\",\"Default\",\"SessionId\",\"Type\"]}]},\"Service\":\"testService\",\"Default\":\"Initial\",\"SessionId\":\"MySessionId\",\"Type\":\"Start\",\"Lambda Execute\":1}", metricsOutput); } [Trait("Category", "MetricsImplementation")] @@ -218,7 +173,7 @@ public void WhenNamespaceIsDefined_AbleToRetrieveNamespace() var metricsOutput = _consoleOut.ToString(); - var result = Metrics.Instance.Options.Namespace; + var result = Metrics.GetNamespace(); // Assert Assert.Equal("dotnet-powertools-test", result); @@ -254,7 +209,7 @@ public void WhenDefaultDimensionsSet_ValidInitialization() var result = _consoleOut.ToString(); // Assert - Assert.Contains($"\"Dimensions\":[[\"Service\",\"{key}\"]]", result); + Assert.Contains($"\"Dimensions\":[\"Service\",\"{key}\"]", result); Assert.Contains($"\"CustomDefaultDimension\":\"{value}\"", result); } @@ -291,7 +246,7 @@ public void WhenDefaultDimensionSet_IgnoreDuplicates() var result = _consoleOut.ToString(); // Assert - Assert.Contains("\"Dimensions\":[[\"Service\",\"CustomDefaultDimension\"]]", result); + Assert.Contains("\"Dimensions\":[\"Service\",\"CustomDefaultDimension\"]", result); Assert.Contains("\"CustomDefaultDimension\":\"CustomDefaultDimensionValue\"", result); } @@ -305,7 +260,7 @@ public void WhenMetricsAndMetadataAdded_ValidateOutput() // Assert Assert.Contains( - "CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"Time\",\"Unit\":\"Milliseconds\"}],\"Dimensions\":[[\"Service\",\"functionVersion\"]]}]},\"Service\":\"testService\",\"functionVersion\":\"$LATEST\",\"Time\":100.7,\"env\":\"dev\"}" + "CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"Time\",\"Unit\":\"Milliseconds\"}],\"Dimensions\":[\"Service\",\"functionVersion\"]}]},\"Service\":\"testService\",\"functionVersion\":\"$LATEST\",\"Time\":100.7,\"env\":\"dev\"}" , result); } @@ -320,7 +275,7 @@ public void When_Metrics_And_Metadata_Added_With_Same_Key_Should_Only_Write_Metr // Assert // No Metadata key was added Assert.Contains( - "CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"Time\",\"Unit\":\"Milliseconds\"}],\"Dimensions\":[[\"Service\",\"functionVersion\"]]}]},\"Service\":\"testService\",\"functionVersion\":\"$LATEST\",\"Time\":100.7}" + "CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"Time\",\"Unit\":\"Milliseconds\"}],\"Dimensions\":[\"Service\",\"functionVersion\"]}]},\"Service\":\"testService\",\"functionVersion\":\"$LATEST\",\"Time\":100.7}" , result); } @@ -382,99 +337,9 @@ public async Task WhenMetricsAsyncRaceConditionItemSameKeyExists_ValidateLock() // Assert Assert.Contains( - "{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"Metric Name\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\"]]", + "{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"Metric Name\",\"Unit\":\"Count\"}],\"Dimensions\":[\"Service\"]", metricsOutput); } - - [Trait("Category", "MetricsImplementation")] - [Fact] - public void AddDimensions_WithMultipleValues_AddsDimensionsToSameDimensionSet() - { - // Act - _handler.AddMultipleDimensionsInSameSet(); - - var result = _consoleOut.ToString(); - - // Assert - Assert.Contains("\"Dimensions\":[[\"Service\",\"Environment\",\"Region\"]]", result); - Assert.Contains("\"Service\":\"testService\",\"Environment\":\"test\",\"Region\":\"us-west-2\"", result); - } - - [Trait("Category", "MetricsImplementation")] - [Fact] - public void AddDimensions_WithEmptyArray_DoesNotAddAnyDimensions() - { - // Act - _handler.AddEmptyDimensions(); - - var result = _consoleOut.ToString(); - - // Assert - Assert.Contains("\"Dimensions\":[[\"Service\"]]", result); - Assert.DoesNotContain("\"Environment\":", result); - } - - [Trait("Category", "MetricsImplementation")] - [Fact] - public void AddDimensions_WithNullOrEmptyKey_ThrowsArgumentNullException() - { - // Act & Assert - Assert.Throws(() => _handler.AddDimensionsWithInvalidKey()); - } - - [Trait("Category", "MetricsImplementation")] - [Fact] - public void AddDimensions_WithNullOrEmptyValue_ThrowsArgumentNullException() - { - // Act & Assert - Assert.Throws(() => _handler.AddDimensionsWithInvalidValue()); - } - - [Trait("Category", "MetricsImplementation")] - [Fact] - public void AddDimensions_OverwritesExistingDimensions_LastValueWins() - { - // Act - _handler.AddDimensionsWithOverwrite(); - - var result = _consoleOut.ToString(); - - // Assert - Assert.Contains("\"Service\":\"testService\",\"dimension1\":\"B\",\"dimension2\":\"2\"", result); - Assert.DoesNotContain("\"dimension1\":\"A\"", result); - } - - [Trait("Category", "MetricsImplementation")] - [Fact] - public void AddDimensions_IncludesDefaultDimensions() - { - // Act - _handler.AddDimensionsWithDefaultDimensions(); - - var result = _consoleOut.ToString(); - - // Assert - Assert.Contains("\"Dimensions\":[[\"Service\",\"environment\",\"dimension1\",\"dimension2\"]]", result); - Assert.Contains("\"Service\":\"testService\",\"environment\":\"prod\",\"dimension1\":\"1\",\"dimension2\":\"2\"", result); - } - - [Trait("Category", "MetricsImplementation")] - [Fact] - public void AddDefaultDimensionsAtRuntime_OnlyAppliedToNewDimensionSets() - { - // Act - _handler.AddDefaultDimensionsAtRuntime(); - - var result = _consoleOut.ToString(); - - // First metric output should have original default dimensions - Assert.Contains("\"Metrics\":[{\"Name\":\"FirstMetric\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\",\"environment\",\"dimension1\",\"dimension2\"]]", result); - Assert.Contains("\"Service\":\"testService\",\"environment\":\"prod\",\"dimension1\":\"1\",\"dimension2\":\"2\",\"FirstMetric\":1", result); - - // Second metric output should have additional default dimensions - Assert.Contains("\"Metrics\":[{\"Name\":\"SecondMetric\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\",\"environment\",\"tenantId\",\"foo\",\"bar\"]]", result); - Assert.Contains("\"Service\":\"testService\",\"environment\":\"prod\",\"tenantId\":\"1\",\"foo\":\"1\",\"bar\":\"2\",\"SecondMetric\":1", result); - } #region Helpers @@ -500,9 +365,7 @@ private List AllIndexesOf(string str, string value) public void Dispose() { // need to reset instance after each test - Metrics.ResetForTest(); MetricsAspect.ResetForTest(); - Environment.SetEnvironmentVariable("POWERTOOLS_METRICS_NAMESPACE", null); } } } \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/DefaultDimensionsHandler.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/DefaultDimensionsHandler.cs deleted file mode 100644 index 95e6a9f36..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/DefaultDimensionsHandler.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.Collections.Generic; -using Amazon.Lambda.Core; - -namespace AWS.Lambda.Powertools.Metrics.Tests.Handlers; - -public class DefaultDimensionsHandler -{ - public DefaultDimensionsHandler() - { - Metrics.Configure(options => - { - options.Namespace = "dotnet-powertools-test"; - options.Service = "testService"; - options.CaptureColdStart = true; - options.DefaultDimensions = new Dictionary - { - { "Environment", "Prod" }, - { "Another", "One" } - }; - }); - } - - [Metrics] - public void Handler() - { - // Default dimensions are already set - Metrics.AddMetric("SuccessfulBooking", 1, MetricUnit.Count); - } - - [Metrics] - public void HandlerWithContext(ILambdaContext context) - { - // Default dimensions are already set and adds FunctionName dimension - Metrics.AddMetric("Memory", 10, MetricUnit.Megabytes); - } -} - -public class MetricsDependencyInjectionOptionsHandler -{ - private readonly IMetrics _metrics; - - // Allow injection of IMetrics for testing - public MetricsDependencyInjectionOptionsHandler(IMetrics metrics = null) - { - _metrics = metrics ?? Metrics.Configure(options => - { - options.DefaultDimensions = new Dictionary - { - { "Environment", "Prod" }, - { "Another", "One" } - }; - }); - } - - [Metrics(Namespace = "dotnet-powertools-test", Service = "testService", CaptureColdStart = true)] - public void Handler() - { - _metrics.AddMetric("SuccessfulBooking", 1, MetricUnit.Count); - } -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandler.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandler.cs index da949a78b..e29e99a99 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandler.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandler.cs @@ -1,9 +1,26 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; +using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading.Tasks; using Amazon.Lambda.Core; +using Amazon.Lambda.TestUtilities; namespace AWS.Lambda.Powertools.Metrics.Tests.Handlers; @@ -22,67 +39,25 @@ public void AddDimensions() Metrics.AddMetric("TestMetric", 1, MetricUnit.Count); } - [Metrics(Namespace = "dotnet-powertools-test", Service = "ServiceName", CaptureColdStart = true)] + [Metrics(Namespace = "dotnet-powertools-test", Service = "testService")] public void AddMultipleDimensions() { - Metrics.PushSingleMetric("SingleMetric1", 1, MetricUnit.Count, resolution: MetricResolution.High, - dimensions: new Dictionary { - { "Default1", "SingleMetric1" } - }); - - Metrics.PushSingleMetric("SingleMetric2", 1, MetricUnit.Count, resolution: MetricResolution.High, nameSpace: "ns2", - dimensions: new Dictionary { - { "Default1", "SingleMetric2" }, - { "Default2", "SingleMetric2" } - }); - Metrics.AddMetric("AddMetric", 1, MetricUnit.Count, MetricResolution.High); - Metrics.AddMetric("AddMetric2", 1, MetricUnit.Count, MetricResolution.High); - } - - [Metrics(Namespace = "ExampleApplication")] - public void PushSingleMetricWithNamespace() - { - Metrics.PushSingleMetric("SingleMetric", 1, MetricUnit.Count, resolution: MetricResolution.High, - dimensions: new Dictionary { - { "Default", "SingleMetric" } - }); - } - - [Metrics(Namespace = "ExampleApplication")] - public void PushSingleMetricNoDefaultDimensions() - { - Metrics.PushSingleMetric("SingleMetric", 1, MetricUnit.Count); - } - - [Metrics(Namespace = "ExampleApplication")] - public void PushSingleMetricDefaultDimensions() - { - Metrics.SetDefaultDimensions(new Dictionary - { - { "Default", "SingleMetric" } + Metrics.SetDefaultDimensions(new Dictionary { + { "Default", "Initial" } }); - Metrics.PushSingleMetric("SingleMetric", 1, MetricUnit.Count, dimensions: Metrics.DefaultDimensions ); - } - - [Metrics] - public void PushSingleMetricWithEnvNamespace() - { - Metrics.PushSingleMetric("SingleMetric", 1, MetricUnit.Count, resolution: MetricResolution.High, - dimensions: new Dictionary { - { "Default", "SingleMetric" } + Metrics.PushSingleMetric("Lambda Execute", 1, MetricUnit.Count, metricResolution: MetricResolution.High, nameSpace: "ns1", + defaultDimensions: new Dictionary { + { "Type", "Start" } }); - Metrics.PushSingleMetric("SingleMetric2", 1, MetricUnit.Count, resolution: MetricResolution.High, - service: "service1", - dimensions: new Dictionary { - { "Default", "SingleMetric" } - }); - - Metrics.PushSingleMetric("SingleMetric3", 1, MetricUnit.Count, resolution: MetricResolution.High, - service: "service2", - dimensions: new Dictionary { - { "Default", "SingleMetric" } + Metrics.PushSingleMetric("Lambda Execute", 1, MetricUnit.Count, metricResolution: MetricResolution.High, nameSpace: "ns2", + defaultDimensions: new Dictionary { + { "Type", "Start" }, + { "SessionId", "Unset" } }); + Metrics.AddMetric("Lambda Execute", 1, MetricUnit.Count, MetricResolution.High); + Metrics.AddDimension("SessionId", "MySessionId"); + Metrics.AddDimension("Type", "Start"); } [Metrics(Namespace = "dotnet-powertools-test", Service = "testService")] @@ -225,138 +200,4 @@ public void HandleWithParamAndLambdaContext(string input, ILambdaContext context { } - - [Metrics(Namespace = "ns", Service = "svc", RaiseOnEmptyMetrics = true)] - public void HandlerRaiseOnEmptyMetrics() - { - - } - - [Metrics(Namespace = "ns", Service = "svc", CaptureColdStart = true)] - public void HandleOnlyDimensionsInColdStart(ILambdaContext context) - { - Metrics.AddMetric("MyMetric", 1); - } - - [Metrics(Namespace = "ns", Service = "svc", CaptureColdStart = true, FunctionName = "MyFunction")] - public void HandleFunctionNameWithContext(ILambdaContext context) - { - - } - - [Metrics(Namespace = "ns", Service = "svc", CaptureColdStart = true, FunctionName = "MyFunction")] - public void HandleFunctionNameNoContext() - { - - } - - [Metrics(Namespace = "dotnet-powertools-test", Service = "testService", CaptureColdStart = true)] - public void AddMultipleDimensionsInSameSet() - { - // Add multiple dimensions at once - Metrics.AddDimensions( - ("Environment", "test"), - ("Region", "us-west-2") - ); - - Metrics.AddMetric("TestMetric", 1.0, MetricUnit.Count); - } - - [Metrics(Namespace = "dotnet-powertools-test", Service = "testService", CaptureColdStart = true)] - public void AddEmptyDimensions() - { - // Add empty dimensions array - Metrics.AddDimensions(); - - Metrics.AddMetric("TestMetric", 1.0, MetricUnit.Count); - } - - [Metrics(Namespace = "dotnet-powertools-test", Service = "testService", CaptureColdStart = true)] - public void AddDimensionsWithInvalidKey() - { - // Add dimension with null key - Metrics.AddDimensions(("", "value")); - } - - [Metrics(Namespace = "dotnet-powertools-test", Service = "testService", CaptureColdStart = true)] - public void AddDimensionsWithInvalidValue() - { - // Add dimension with null value - Metrics.AddDimensions(("key", "")); - } - - public void AddDimensionsWithOverwrite() - { - Metrics.SetNamespace("dotnet-powertools-test"); - Metrics.SetService("testService"); - - // Add single dimension - Metrics.AddDimension("dimension1", "A"); - - // Then add multiple dimensions, including the same key - Metrics.AddDimensions( - ("dimension1", "B"), - ("dimension2", "2") - ); - - Metrics.AddMetric("TestMetric", 1.0, MetricUnit.Count); - Metrics.Flush(); - } - - public void AddDimensionsWithDefaultDimensions() - { - Metrics.SetNamespace("dotnet-powertools-test"); - Metrics.SetService("testService"); - - // Set default dimensions - Metrics.SetDefaultDimensions(new Dictionary - { - { "environment", "prod" } - }); - - // Add multiple dimensions - Metrics.AddDimensions( - ("dimension1", "1"), - ("dimension2", "2") - ); - - Metrics.AddMetric("TestMetric", 1.0, MetricUnit.Count); - Metrics.Flush(); - } - - public void AddDefaultDimensionsAtRuntime() - { - Metrics.SetNamespace("dotnet-powertools-test"); - Metrics.SetService("testService"); - - // Set initial default dimensions - Metrics.SetDefaultDimensions(new Dictionary - { - { "environment", "prod" } - }); - - // Add first set of dimensions - Metrics.AddDimensions( - ("dimension1", "1"), - ("dimension2", "2") - ); - Metrics.AddMetric("FirstMetric", 1.0, MetricUnit.Count); - Metrics.Flush(); - - // Add more default dimensions - Metrics.SetDefaultDimensions(new Dictionary - { - { "environment", "prod" }, - { "tenantId", "1" } - }); - - // Add second set of dimensions - Metrics.AddDimensions( - ("foo", "1"), - ("bar", "2") - ); - Metrics.AddMetric("SecondMetric", 1.0, MetricUnit.Count); - - Metrics.Flush(); - } } \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandlerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandlerTests.cs index 799aefdbb..dd140b409 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandlerTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/FunctionHandlerTests.cs @@ -1,10 +1,22 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; -using System.Collections.Generic; using System.Threading.Tasks; -using Amazon.Lambda.Core; using Amazon.Lambda.TestUtilities; using AWS.Lambda.Powertools.Common; -using NSubstitute; using Xunit; namespace AWS.Lambda.Powertools.Metrics.Tests.Handlers; @@ -14,35 +26,38 @@ public class FunctionHandlerTests : IDisposable { private readonly FunctionHandler _handler; private readonly CustomConsoleWriter _consoleOut; - + public FunctionHandlerTests() { _handler = new FunctionHandler(); _consoleOut = new CustomConsoleWriter(); - ConsoleWrapper.SetOut(_consoleOut); + SystemWrapper.Instance.SetOut(_consoleOut); } - + [Fact] public async Task When_Metrics_Add_Metadata_Same_Key_Should_Ignore_Metadata() { + // Arrange + + // Act - var exception = await Record.ExceptionAsync(() => _handler.HandleSameKey("whatever")); - + var exception = await Record.ExceptionAsync( () => _handler.HandleSameKey("whatever")); + // Assert Assert.Null(exception); } - + [Fact] public async Task When_Metrics_Add_Metadata_Second_Invocation_Should_Not_Throw_Exception() { // Act - var exception = await Record.ExceptionAsync(() => _handler.HandleTestSecondCall("whatever")); + var exception = await Record.ExceptionAsync( () => _handler.HandleTestSecondCall("whatever")); Assert.Null(exception); - - exception = await Record.ExceptionAsync(() => _handler.HandleTestSecondCall("whatever")); + + exception = await Record.ExceptionAsync( () => _handler.HandleTestSecondCall("whatever")); Assert.Null(exception); } - + [Fact] public async Task When_Metrics_Add_Metadata_FromMultipleThread_Should_Not_Throw_Exception() { @@ -50,7 +65,7 @@ public async Task When_Metrics_Add_Metadata_FromMultipleThread_Should_Not_Throw_ var exception = await Record.ExceptionAsync(() => _handler.HandleMultipleThreads("whatever")); Assert.Null(exception); } - + [Fact] public void When_LambdaContext_Should_Add_FunctioName_Dimension_CaptureColdStart() { @@ -59,21 +74,21 @@ public void When_LambdaContext_Should_Add_FunctioName_Dimension_CaptureColdStart { FunctionName = "My Function with context" }; - + // Act _handler.HandleWithLambdaContext(context); var metricsOutput = _consoleOut.ToString(); - + // Assert Assert.Contains( "\"FunctionName\":\"My Function with context\"", metricsOutput); - + Assert.Contains( - "\"CloudWatchMetrics\":[{\"Namespace\":\"ns\",\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\",\"FunctionName\"]]}]},\"Service\":\"svc\",\"FunctionName\":\"My Function with context\",\"ColdStart\":1}", + "\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[\"FunctionName\",\"Service\"]}]}", metricsOutput); } - + [Fact] public void When_LambdaContext_And_Parameter_Should_Add_FunctioName_Dimension_CaptureColdStart() { @@ -82,326 +97,41 @@ public void When_LambdaContext_And_Parameter_Should_Add_FunctioName_Dimension_Ca { FunctionName = "My Function with context" }; - + // Act - _handler.HandleWithParamAndLambdaContext("Hello", context); + _handler.HandleWithParamAndLambdaContext("Hello",context); var metricsOutput = _consoleOut.ToString(); - + // Assert Assert.Contains( "\"FunctionName\":\"My Function with context\"", metricsOutput); - + Assert.Contains( - "\"CloudWatchMetrics\":[{\"Namespace\":\"ns\",\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\",\"FunctionName\"]]}]},\"Service\":\"svc\",\"FunctionName\":\"My Function with context\",\"ColdStart\":1}", + "\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[\"FunctionName\",\"Service\"]}]}", metricsOutput); } - + [Fact] public void When_No_LambdaContext_Should_Not_Add_FunctioName_Dimension_CaptureColdStart() { // Act _handler.HandleColdStartNoContext(); var metricsOutput = _consoleOut.ToString(); - + // Assert Assert.DoesNotContain( "\"FunctionName\"", metricsOutput); - - Assert.Contains( - "\"Metrics\":[{\"Name\":\"MyMetric\",\"Unit\":\"None\"}],\"Dimensions\":[[\"Service\"]]}]},\"Service\":\"svc\",\"MyMetric\":1}", - metricsOutput); - } - - [Fact] - public void DefaultDimensions_AreAppliedCorrectly() - { - // Arrange - var handler = new DefaultDimensionsHandler(); - - // Act - handler.Handler(); - - // Get the output and parse it - var metricsOutput = _consoleOut.ToString(); - - // Assert cold start - Assert.Contains( - "\"CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\",\"Environment\",\"Another\"]]}]},\"Service\":\"testService\",\"Environment\":\"Prod\",\"Another\":\"One\",\"ColdStart\":1}", - metricsOutput); - // Assert successful booking metrics - Assert.Contains( - "\"CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"SuccessfulBooking\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\",\"Environment\",\"Another\"]]}]},\"Service\":\"testService\",\"Environment\":\"Prod\",\"Another\":\"One\",\"SuccessfulBooking\":1}", - metricsOutput); - } - - [Fact] - public void DefaultDimensions_AreAppliedCorrectly_WithContext_FunctionName() - { - // Arrange - var handler = new DefaultDimensionsHandler(); - - // Act - handler.HandlerWithContext(new TestLambdaContext - { - FunctionName = "My_Function_Name" - }); - - // Get the output and parse it - var metricsOutput = _consoleOut.ToString(); - - // Assert cold start - Assert.Contains( - "\"CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\",\"Environment\",\"Another\",\"FunctionName\"]]}]},\"Service\":\"testService\",\"Environment\":\"Prod\",\"Another\":\"One\",\"FunctionName\":\"My_Function_Name\",\"ColdStart\":1}", - metricsOutput); - // Assert successful Memory metrics - Assert.Contains( - "\"CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"Memory\",\"Unit\":\"Megabytes\"}],\"Dimensions\":[[\"Service\",\"Environment\",\"Another\"]]}]},\"Service\":\"testService\",\"Environment\":\"Prod\",\"Another\":\"One\",\"Memory\":10}", - metricsOutput); - } - - [Fact] - public void Handler_WithMockedMetrics_ShouldCallAddMetric() - { - // Arrange - var metricsMock = Substitute.For(); - - metricsMock.Options.Returns(new MetricsOptions - { - CaptureColdStart = true, - Namespace = "dotnet-powertools-test", - Service = "testService", - DefaultDimensions = new Dictionary - { - { "Environment", "Prod" }, - { "Another", "One" } - } - }); - - Metrics.UseMetricsForTests(metricsMock); - - - var sut = new MetricsDependencyInjectionOptionsHandler(metricsMock); - - // Act - sut.Handler(); - - // Assert - metricsMock.Received(1).CaptureColdStartMetric(Arg.Any()); - metricsMock.Received(1).AddMetric("SuccessfulBooking", 1, MetricUnit.Count); - } - - [Fact] - public void Handler_With_Builder_Should_Configure_In_Constructor() - { - // Arrange - var handler = new MetricsnBuilderHandler(); - - // Act - handler.Handler(new TestLambdaContext - { - FunctionName = "My_Function_Name" - }); - - // Get the output and parse it - var metricsOutput = _consoleOut.ToString(); - - // Assert cold start - Assert.Contains( - "\"CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\",\"Environment\",\"Another\",\"FunctionName\"]]}]},\"Service\":\"testService\",\"Environment\":\"Prod1\",\"Another\":\"One\",\"FunctionName\":\"My_Function_Name\",\"ColdStart\":1}", - metricsOutput); - // Assert successful Memory metrics - Assert.Contains( - "\"CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"SuccessfulBooking\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\",\"Environment\",\"Another\"]]}]},\"Service\":\"testService\",\"Environment\":\"Prod1\",\"Another\":\"One\",\"SuccessfulBooking\":1}", - metricsOutput); - } - - [Fact] - public void Handler_With_Builder_Should_Configure_In_Constructor_Mock() - { - var metricsMock = Substitute.For(); - - metricsMock.Options.Returns(new MetricsOptions - { - CaptureColdStart = true, - Namespace = "dotnet-powertools-test", - Service = "testService", - DefaultDimensions = new Dictionary - { - { "Environment", "Prod" }, - { "Another", "One" } - } - }); - - Metrics.UseMetricsForTests(metricsMock); - - var sut = new MetricsnBuilderHandler(metricsMock); - - // Act - sut.Handler(new TestLambdaContext - { - FunctionName = "My_Function_Name" - }); - - metricsMock.Received(1).CaptureColdStartMetric(Arg.Any()); - metricsMock.Received(1).AddMetric("SuccessfulBooking", 1, MetricUnit.Count); - } - - [Fact] - public void When_RaiseOnEmptyMetrics_And_NoMetrics_Should_ThrowException() - { - // Act & Assert - var exception = Assert.Throws(() => _handler.HandlerRaiseOnEmptyMetrics()); - Assert.Equal("No metrics have been provided.", exception.Message); - } - - [Fact] - public void Handler_With_Builder_Should_Raise_Empty_Metrics() - { - // Arrange - var handler = new MetricsnBuilderHandler(); - - // Act & Assert - var exception = Assert.Throws(() => handler.HandlerEmpty()); - Assert.Equal("No metrics have been provided.", exception.Message); - } - - [Fact] - public void Handler_With_Builder_Push_Single_Metric_No_Dimensions() - { - // Arrange - var handler = new MetricsnBuilderHandler(); - - // Act - handler.HandlerSingleMetric(); - - // Get the output and parse it - var metricsOutput = _consoleOut.ToString(); - - Assert.Contains( - "\"CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"SuccessfulBooking\",\"Unit\":\"Count\"}],\"Dimensions\":[[]]}]},\"SuccessfulBooking\":1}", - metricsOutput); - } - - [Fact] - public void Handler_With_Builder_Push_Single_Metric_Dimensions() - { - // Arrange - var handler = new MetricsnBuilderHandler(); - - // Act - handler.HandlerSingleMetricDimensions(); - - // Get the output and parse it - var metricsOutput = _consoleOut.ToString(); - - Assert.Contains( - "\"CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"SuccessfulBooking\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\",\"Environment\",\"Another\"]]}]},\"Service\":\"testService\",\"Environment\":\"Prod1\",\"Another\":\"One\",\"SuccessfulBooking\":1}", - metricsOutput); - } - - [Fact] - public void Dimension_Only_Set_In_Cold_Start() - { - // Arrange - var handler = new FunctionHandler(); - - // Act - handler.HandleOnlyDimensionsInColdStart(new TestLambdaContext - { - FunctionName = "My_Function_Name" - }); - - // Get the output and parse it - var metricsOutput = _consoleOut.ToString(); - - // Assert cold start - Assert.Contains( - "\"CloudWatchMetrics\":[{\"Namespace\":\"ns\",\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\",\"FunctionName\"]]}]},\"Service\":\"svc\",\"FunctionName\":\"My_Function_Name\",\"ColdStart\":1}", - metricsOutput); - - // Assert successful add metric without dimensions - Assert.Contains( - "\"CloudWatchMetrics\":[{\"Namespace\":\"ns\",\"Metrics\":[{\"Name\":\"MyMetric\",\"Unit\":\"None\"}],\"Dimensions\":[[\"Service\"]]}]},\"Service\":\"svc\",\"MyMetric\":1}", - metricsOutput); - } - - [Fact] - public void When_Function_Name_Is_Set() - { - // Arrange - var handler = new FunctionHandler(); - - // Act - handler.HandleFunctionNameWithContext(new TestLambdaContext - { - FunctionName = "This_Will_Be_Overwritten" - }); - - // Get the output and parse it - var metricsOutput = _consoleOut.ToString(); - // Assert cold start function name is set MyFunction Assert.Contains( - "\"CloudWatchMetrics\":[{\"Namespace\":\"ns\",\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\",\"FunctionName\"]]}]},\"Service\":\"svc\",\"FunctionName\":\"MyFunction\",\"ColdStart\":1}", + "\"Metrics\":[{\"Name\":\"MyMetric\",\"Unit\":\"None\"}],\"Dimensions\":[\"Service\"]}]},\"Service\":\"svc\",\"MyMetric\":1}", metricsOutput); } - - [Fact] - public void When_Function_Name_Is_Set_No_Context() - { - // Arrange - var handler = new FunctionHandler(); - - // Act - handler.HandleFunctionNameNoContext(); - - // Get the output and parse it - var metricsOutput = _consoleOut.ToString(); - - // Assert cold start function name is set MyFunction - Assert.Contains( - "\"CloudWatchMetrics\":[{\"Namespace\":\"ns\",\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Service\",\"FunctionName\"]]}]},\"Service\":\"svc\",\"FunctionName\":\"MyFunction\",\"ColdStart\":1}", - metricsOutput); - } - - [Fact] - public void Handler_With_Builder_Should_Configure_FunctionName_In_Constructor_Mock() - { - var metricsMock = Substitute.For(); - - metricsMock.Options.Returns(new MetricsOptions - { - CaptureColdStart = true, - Namespace = "dotnet-powertools-test", - Service = "testService", - FunctionName = "My_Function_Custome_Name", - DefaultDimensions = new Dictionary - { - { "Environment", "Prod" }, - { "Another", "One" } - } - }); - - Metrics.UseMetricsForTests(metricsMock); - - var sut = new MetricsnBuilderHandler(metricsMock); - - // Act - sut.Handler(new TestLambdaContext - { - FunctionName = "This_Will_Be_Overwritten" - }); - - metricsMock.Received(1).CaptureColdStartMetric(Arg.Any()); - metricsMock.Received(1).AddMetric("SuccessfulBooking", 1, MetricUnit.Count); - } public void Dispose() { Metrics.ResetForTest(); MetricsAspect.ResetForTest(); - ConsoleWrapper.ResetForTest(); } } \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/MetricsnBuilderHandler.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/MetricsnBuilderHandler.cs deleted file mode 100644 index 5aae4cdc2..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Handlers/MetricsnBuilderHandler.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Collections.Generic; -using Amazon.Lambda.Core; - -namespace AWS.Lambda.Powertools.Metrics.Tests.Handlers; - -public class MetricsnBuilderHandler -{ - private readonly IMetrics _metrics; - - // Allow injection of IMetrics for testing - public MetricsnBuilderHandler(IMetrics metrics = null) - { - _metrics = metrics ?? new MetricsBuilder() - .WithCaptureColdStart(true) - .WithService("testService") - .WithNamespace("dotnet-powertools-test") - .WithRaiseOnEmptyMetrics(true) - .WithDefaultDimensions(new Dictionary - { - { "Environment", "Prod1" }, - { "Another", "One" } - }).Build(); - } - - [Metrics] - public void Handler(ILambdaContext context) - { - _metrics.AddMetric("SuccessfulBooking", 1, MetricUnit.Count); - } - - [Metrics] - public void HandlerEmpty() - { - } - - public void HandlerSingleMetric() - { - _metrics.PushSingleMetric("SuccessfulBooking", 1, MetricUnit.Count); - } - - public void HandlerSingleMetricDimensions() - { - _metrics.PushSingleMetric("SuccessfulBooking", 1, MetricUnit.Count, dimensions: _metrics.Options.DefaultDimensions); - } - -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/MetricsAttributeTests.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/MetricsAttributeTests.cs deleted file mode 100644 index 04a1f86d0..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/MetricsAttributeTests.cs +++ /dev/null @@ -1,72 +0,0 @@ -using Xunit; - -namespace AWS.Lambda.Powertools.Metrics.Tests; - -[Collection("Sequential")] -public class MetricsAttributeTests -{ - [Fact] - public void MetricsAttribute_WhenCaptureColdStartSet_ShouldSetFlag() - { - // Arrange & Act - var attribute = new MetricsAttribute - { - CaptureColdStart = true - }; - - // Assert - Assert.True(attribute.CaptureColdStart); - Assert.True(attribute.IsCaptureColdStartSet); - } - - [Fact] - public void MetricsAttribute_WhenCaptureColdStartNotSet_ShouldNotSetFlag() - { - // Arrange & Act - var attribute = new MetricsAttribute(); - - // Assert - Assert.False(attribute.CaptureColdStart); - Assert.False(attribute.IsCaptureColdStartSet); - } - - [Fact] - public void MetricsAttribute_WhenRaiseOnEmptyMetricsSet_ShouldSetFlag() - { - // Arrange & Act - var attribute = new MetricsAttribute - { - RaiseOnEmptyMetrics = true - }; - - // Assert - Assert.True(attribute.RaiseOnEmptyMetrics); - Assert.True(attribute.IsRaiseOnEmptyMetricsSet); - } - - [Fact] - public void MetricsAttribute_WhenRaiseOnEmptyMetricsNotSet_ShouldNotSetFlag() - { - // Arrange & Act - var attribute = new MetricsAttribute(); - - // Assert - Assert.False(attribute.RaiseOnEmptyMetrics); - Assert.False(attribute.IsRaiseOnEmptyMetricsSet); - } - - [Fact] - public void MetricsAttribute_ShouldSetNamespaceAndService() - { - // Arrange & Act - var attribute = new MetricsAttribute - { - Namespace = "TestNamespace", - Service = "TestService" - }; - - // Assert - Assert.Equal("TestNamespace", attribute.Namespace); - Assert.Equal("TestService", attribute.Service); - } -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/MetricsTests.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/MetricsTests.cs index 8f038dfc4..97aa5bf87 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/MetricsTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/MetricsTests.cs @@ -1,8 +1,3 @@ -using System; -using System.Collections.Generic; -using System.IO; -using Amazon.Lambda.Core; -using Amazon.Lambda.TestUtilities; using AWS.Lambda.Powertools.Common; using NSubstitute; using Xunit; @@ -17,410 +12,22 @@ public void Metrics_Set_Execution_Environment_Context() { // Arrange Metrics.ResetForTest(); - var env = new PowertoolsEnvironment(); + var assemblyName = "AWS.Lambda.Powertools.Metrics"; + var assemblyVersion = "1.0.0"; - var conf = new PowertoolsConfigurations(env); - - _ = new Metrics(conf); - - // Assert - Assert.Contains($"{Constants.FeatureContextIdentifier}/Metrics/", - env.GetEnvironmentVariable("AWS_EXECUTION_ENV")); - } - - [Fact] - public void Before_When_RaiseOnEmptyMetricsNotSet_Should_Configure_Null() - { - // Arrange - MetricsAspect.ResetForTest(); - var method = typeof(MetricsTests).GetMethod(nameof(TestMethod)); - var trigger = new MetricsAttribute(); - - var metricsAspect = new MetricsAspect(); - - // Act - metricsAspect.Before( - this, - "TestMethod", - new object[] { new TestLambdaContext() }, - typeof(MetricsTests), - method, - typeof(void), - new Attribute[] { trigger } - ); - - // Assert - var metrics = Metrics.Instance; - Assert.False(trigger.IsRaiseOnEmptyMetricsSet); - Assert.False(metrics.Options.RaiseOnEmptyMetrics); - } - - [Fact] - public void When_MetricsDisabled_Should_Not_AddMetric() - { - // Arrange - var conf = Substitute.For(); - conf.MetricsDisabled.Returns(true); - - IMetrics metrics = new Metrics(conf); - var stringWriter = new StringWriter(); - Console.SetOut(stringWriter); - - // Act - metrics.AddMetric("test", 1.0); - metrics.Flush(); - - // Assert - Assert.Empty(stringWriter.ToString()); - - // Cleanup - stringWriter.Dispose(); - Console.SetOut(new StreamWriter(Console.OpenStandardOutput())); - } - - [Fact] - public void When_MetricsDisabled_Should_Not_PushSingleMetric() - { - // Arrange - var conf = Substitute.For(); - conf.MetricsDisabled.Returns(true); - - IMetrics metrics = new Metrics(conf); - var stringWriter = new StringWriter(); - Console.SetOut(stringWriter); - - // Act - metrics.PushSingleMetric("test", 1.0, MetricUnit.Count); - - // Assert - Assert.Empty(stringWriter.ToString()); - - // Cleanup - stringWriter.Dispose(); - Console.SetOut(new StreamWriter(Console.OpenStandardOutput())); - } - - // Helper method for the tests - internal void TestMethod(ILambdaContext context) - { - } - - [Fact] - public void When_Constructor_With_Null_Namespace_And_Service_Should_Not_Set() - { - // Arrange - Substitute.For(); - var powertoolsConfigMock = Substitute.For(); - powertoolsConfigMock.MetricsNamespace.Returns((string)null); - powertoolsConfigMock.Service.Returns("service_undefined"); - - // Act - var metrics = new Metrics(powertoolsConfigMock); - - // Assert - Assert.Null(metrics.GetNamespace()); - Assert.Null(metrics.Options.Service); - } - - [Fact] - public void When_AddMetric_With_EmptyKey_Should_ThrowArgumentNullException() - { - // Arrange - Substitute.For(); - var powertoolsConfigMock = Substitute.For(); - IMetrics metrics = new Metrics(powertoolsConfigMock); - - // Act & Assert - var exception = Assert.Throws(() => metrics.AddMetric("", 1.0)); - Assert.Equal("key", exception.ParamName); - Assert.Contains("'AddMetric' method requires a valid metrics key. 'Null' or empty values are not allowed.", - exception.Message); - } - - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void When_AddMetric_With_InvalidKey_Should_ThrowArgumentNullException(string key) - { - // Arrange - // var metricsMock = Substitute.For(); - var powertoolsConfigMock = Substitute.For(); - IMetrics metrics = new Metrics(powertoolsConfigMock); - - // Act & Assert - var exception = Assert.Throws(() => metrics.AddMetric(key, 1.0)); - Assert.Equal("key", exception.ParamName); - Assert.Contains("'AddMetric' method requires a valid metrics key. 'Null' or empty values are not allowed.", - exception.Message); - } - - [Fact] - public void When_AddMetric_With_TooLongKey_Should_ThrowArgumentOutOfRangeException() - { - // Arrange - Substitute.For(); - var powertoolsConfigMock = Substitute.For(); - IMetrics metrics = new Metrics(powertoolsConfigMock); - - // Act & Assert - var exception = Assert.Throws(() => metrics.AddMetric("Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem.", 1.0)); - Assert.Equal("key", exception.ParamName); - Assert.Contains("'AddMetric' method requires a valid metrics key. Key exceeds the allowed length constraint.", - exception.Message); - } - - [Fact] - public void When_SetDefaultDimensions_With_InvalidKeyOrValue_Should_ThrowArgumentNullException() - { - // Arrange - var powertoolsConfigMock = Substitute.For(); - IMetrics metrics = new Metrics(powertoolsConfigMock); - - var invalidDimensions = new Dictionary - { - { "", "value" }, // empty key - { "key", "" }, // empty value - { " ", "value" }, // whitespace key - { "key1", " " }, // whitespace value - { "key2", null } // null value - }; - - // Act & Assert - foreach (var dimension in invalidDimensions) - { - var dimensions = new Dictionary { { dimension.Key, dimension.Value } }; - var exception = Assert.Throws(() => metrics.SetDefaultDimensions(dimensions)); - Assert.Equal("Key", exception.ParamName); - Assert.Contains( - "'SetDefaultDimensions' method requires a valid key pair. 'Null' or empty values are not allowed.", - exception.Message); - } - } - - [Fact] - public void When_PushSingleMetric_With_EmptyName_Should_ThrowArgumentNullException() - { - // Arrange - var powertoolsConfigMock = Substitute.For(); - IMetrics metrics = new Metrics(powertoolsConfigMock); - - // Act & Assert - var exception = Assert.Throws(() => metrics.PushSingleMetric("", 1.0, MetricUnit.Count)); - Assert.Equal("name", exception.ParamName); - Assert.Contains( - "'PushSingleMetric' method requires a valid metrics key. 'Null' or empty values are not allowed.", - exception.Message); - } - - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void When_PushSingleMetric_With_InvalidName_Should_ThrowArgumentNullException(string name) - { - // Arrange - var powertoolsConfigMock = Substitute.For(); - IMetrics metrics = new Metrics(powertoolsConfigMock); - - // Act & Assert - var exception = - Assert.Throws(() => metrics.PushSingleMetric(name, 1.0, MetricUnit.Count)); - Assert.Equal("name", exception.ParamName); - Assert.Contains( - "'PushSingleMetric' method requires a valid metrics key. 'Null' or empty values are not allowed.", - exception.Message); - } - - - [Fact] - public void When_ColdStart_Should_Use_DefaultDimensions_From_Options() - { - // Arrange - var options = new MetricsOptions - { - CaptureColdStart = true, - Namespace = "dotnet-powertools-test", - DefaultDimensions = new Dictionary - { - { "Environment", "Test" }, - { "Region", "us-east-1" } - } - }; - - var conf = Substitute.For(); - var consoleWrapper = Substitute.For(); - IMetrics metrics = new Metrics(conf, consoleWrapper: consoleWrapper, options: options); - - var context = new TestLambdaContext - { - FunctionName = "TestFunction" - }; - - // Act - metrics.CaptureColdStartMetric(context); - - // Assert - consoleWrapper.Received(1).WriteLine( - Arg.Is(s => s.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"Environment\",\"Region\",\"FunctionName\"]]}]},\"Environment\":\"Test\",\"Region\":\"us-east-1\",\"FunctionName\":\"TestFunction\",\"ColdStart\":1}")) - ); - } - - [Fact] - public void When_ColdStart_And_DefaultDimensions_Is_Null_Should_Only_Add_Service_And_FunctionName() - { - // Arrange - var options = new MetricsOptions - { - CaptureColdStart = true, - Namespace = "dotnet-powertools-test", - DefaultDimensions = null - }; - - var conf = Substitute.For(); - var consoleWrapper = Substitute.For(); - IMetrics metrics = new Metrics(conf, consoleWrapper: consoleWrapper, options: options); - - var context = new TestLambdaContext - { - FunctionName = "TestFunction" - }; - - // Act - metrics.CaptureColdStartMetric(context); - - // Assert - consoleWrapper.Received(1).WriteLine( - Arg.Is(s => s.Contains("\"CloudWatchMetrics\":[{\"Namespace\":\"dotnet-powertools-test\",\"Metrics\":[{\"Name\":\"ColdStart\",\"Unit\":\"Count\"}],\"Dimensions\":[[\"FunctionName\"]]}]},\"FunctionName\":\"TestFunction\",\"ColdStart\":1}")) - ); - } + var env = Substitute.For(); + env.GetAssemblyName(Arg.Any()).Returns(assemblyName); + env.GetAssemblyVersion(Arg.Any()).Returns(assemblyVersion); - [Fact] - public void Namespace_Should_Return_OptionsNamespace() - { - // Arrange - Metrics.ResetForTest(); - var metricsMock = Substitute.For(); - var optionsMock = new MetricsOptions - { - Namespace = "TestNamespace" - }; - - metricsMock.Options.Returns(optionsMock); - Metrics.UseMetricsForTests(metricsMock); - - // Act - var result = Metrics.Namespace; - - // Assert - Assert.Equal("TestNamespace", result); - } - - [Fact] - public void Service_Should_Return_OptionsService() - { - // Arrange - Metrics.ResetForTest(); - var metricsMock = Substitute.For(); - var optionsMock = new MetricsOptions - { - Service = "TestService" - }; + var conf = new PowertoolsConfigurations(new SystemWrapper(env)); - metricsMock.Options.Returns(optionsMock); - Metrics.UseMetricsForTests(metricsMock); - - // Act - var result = Metrics.Service; - + var metrics = new Metrics(conf); + // Assert - Assert.Equal("TestService", result); - } - - [Fact] - public void Namespace_Should_Return_Null_When_Not_Set() - { - // Arrange - Metrics.ResetForTest(); - var metricsMock = Substitute.For(); - var optionsMock = new MetricsOptions(); - - metricsMock.Options.Returns(optionsMock); - Metrics.UseMetricsForTests(metricsMock); - - // Act - var result = Metrics.Namespace; - - // Assert - Assert.Null(result); - } - - [Fact] - public void Service_Should_Return_Null_When_Not_Set() - { - // Arrange - Metrics.ResetForTest(); - var metricsMock = Substitute.For(); - var optionsMock = new MetricsOptions(); - - metricsMock.Options.Returns(optionsMock); - Metrics.UseMetricsForTests(metricsMock); - - // Act - var result = Metrics.Service; - - // Assert - Assert.Null(result); - } - - [Fact] - public void WithFunctionName_Should_Set_FunctionName_In_Options() - { - // Arrange - var builder = new MetricsBuilder(); - var expectedFunctionName = "TestFunction"; - - // Act - var result = builder.WithFunctionName(expectedFunctionName); - var metrics = result.Build(); - - // Assert - Assert.Equal(expectedFunctionName, metrics.Options.FunctionName); - Assert.Same(builder, result); - } - - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void WithFunctionName_Should_Allow_NullOrEmpty_FunctionName(string functionName) - { - // Arrange - var builder = new MetricsBuilder(); - - // Act - var result = builder.WithFunctionName(functionName); - var metrics = result.Build(); - - // Assert - // Assert - Assert.Null(metrics.Options.FunctionName); // All invalid values should result in null - Assert.Same(builder, result); - } - - [Fact] - public void Build_Should_Preserve_FunctionName_When_Set_Through_Builder() - { - // Arrange - var builder = new MetricsBuilder() - .WithNamespace("TestNamespace") - .WithService("TestService") - .WithFunctionName("TestFunction"); - - // Act - var metrics = builder.Build(); + env.Received(1).SetEnvironmentVariable( + "AWS_EXECUTION_ENV", $"{Constants.FeatureContextIdentifier}/Metrics/{assemblyVersion}" + ); - // Assert - Assert.Equal("TestFunction", metrics.Options.FunctionName); + env.Received(1).GetEnvironmentVariable("AWS_EXECUTION_ENV"); } } \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Utils.cs b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Utils.cs index e021dcc11..63fa1d4d0 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Utils.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Metrics.Tests/Utils.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System.Collections.Generic; using System.IO; diff --git a/libraries/tests/AWS.Lambda.Powertools.Parameters.Tests/AppConfig/AppConfigProviderTest.cs b/libraries/tests/AWS.Lambda.Powertools.Parameters.Tests/AppConfig/AppConfigProviderTest.cs index 1577b5b23..8c664e4e9 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Parameters.Tests/AppConfig/AppConfigProviderTest.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Parameters.Tests/AppConfig/AppConfigProviderTest.cs @@ -13,7 +13,6 @@ * permissions and limitations under the License. */ -using System.Diagnostics.CodeAnalysis; using System.Text; using System.Text.Json; using System.Text.Json.Nodes; @@ -33,7 +32,6 @@ namespace AWS.Lambda.Powertools.Parameters.Tests.AppConfig; -[SuppressMessage("Usage", "xUnit1030:Do not call ConfigureAwait(false) in test method")] public class AppConfigProviderTest { [Fact] diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/FullExampleHandler.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/FullExampleHandler.cs index 943b2d942..4f84cc972 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/FullExampleHandler.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/FullExampleHandler.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System.Collections.Generic; using System.Net.Http; using System.Threading.Tasks; diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/HandlerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/HandlerTests.cs index 62e4b5846..17e3af3a7 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/HandlerTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/HandlerTests.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.Collections.Generic; using System.Linq; diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/Handlers.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/Handlers.cs index 67b24c8e8..38210ac92 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/Handlers.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Handlers/Handlers.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; namespace AWS.Lambda.Powertools.Tracing.Tests; diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Serializers/PowertoolsTracingSerializerTests.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Serializers/PowertoolsTracingSerializerTests.cs index fe766be38..be4f12373 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Serializers/PowertoolsTracingSerializerTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Serializers/PowertoolsTracingSerializerTests.cs @@ -1,4 +1,18 @@ -#if NET8_0_OR_GREATER +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.Collections.Generic; using System.Text.Json; @@ -236,5 +250,4 @@ public class TestNullableObject public class TestArrayObject { public int[] Values { get; set; } -} -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Serializers/TestJsonContext.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Serializers/TestJsonContext.cs index 1145fee4d..81d9287bd 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Serializers/TestJsonContext.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Serializers/TestJsonContext.cs @@ -1,4 +1,17 @@ -#if NET8_0_OR_GREATER +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ using System.Collections.Generic; using System.Text.Json.Serialization; @@ -28,7 +41,6 @@ public class TestComplexObject public Dictionary NestedObject { get; set; } } -#endif public class TestResponse { diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Serializers/TracingSerializerExtensionsTests.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Serializers/TracingSerializerExtensionsTests.cs index 78e2adaae..999143a43 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Serializers/TracingSerializerExtensionsTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/Serializers/TracingSerializerExtensionsTests.cs @@ -1,5 +1,19 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + -#if NET8_0_OR_GREATER using Amazon.Lambda.Serialization.SystemTextJson; using AWS.Lambda.Powertools.Tracing.Serializers; using Xunit; @@ -25,5 +39,4 @@ public void WithTracing_InitializesSerializer_Successfully() var serialized = PowertoolsTracingSerializer.Serialize(testObject); Assert.Contains("\"Name\":\"Test\"", serialized); } -} -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAspectTests.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAspectTests.cs index 115032752..e373278e2 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAspectTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAspectTests.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.Text.Json; using System.Threading; @@ -8,10 +23,8 @@ using NSubstitute; using Xunit; -#if NET8_0_OR_GREATER using AWS.Lambda.Powertools.Tracing.Serializers; using AWS.Lambda.Powertools.Tracing.Tests.Serializers; -#endif namespace AWS.Lambda.Powertools.Tracing.Tests; @@ -98,7 +111,6 @@ public async Task Around_AsyncMethod_HandlesResponseAndSegmentCorrectly() _mockXRayRecorder.Received(1).EndSubsegment(); } -#if NET8_0_OR_GREATER [Fact] public void Around_SyncMethod_HandlesResponseAndSegmentCorrectly_AOT() { @@ -162,7 +174,6 @@ public async Task Around_AsyncMethod_HandlesResponseAndSegmentCorrectly_AOT() PowertoolsTracingSerializer.Serialize(result)); _mockXRayRecorder.Received(1).EndSubsegment(); } -#endif [Fact] public async Task Around_VoidAsyncMethod_HandlesSegmentCorrectly() diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAttributeTest.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAttributeTest.cs index aca8afc07..f02619c1f 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAttributeTest.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingAttributeTest.cs @@ -1,8 +1,22 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using System.Linq; using System.Text; using Amazon.XRay.Recorder.Core; -using AWS.Lambda.Powertools.Common.Core; using AWS.Lambda.Powertools.Tracing.Internal; using Xunit; @@ -36,8 +50,6 @@ public void OnEntry_WhenFirstCall_CapturesColdStart() var subSegmentCold = segmentCold.Subsegments[0]; // Warm Start Execution - // Clear just the AsyncLocal value to simulate new invocation in same container - LambdaLifecycleTracker.Reset(resetContainer: false); // Start segment var segmentWarm = AWSXRayRecorder.Instance.TraceContext.GetEntity(); _handler.Handle(); @@ -75,9 +87,6 @@ public void OnEntry_WhenFirstCall_And_Service_Not_Set_CapturesColdStart() var subSegmentCold = segmentCold.Subsegments[0]; // Warm Start Execution - // Clear just the AsyncLocal value to simulate new invocation in same container - LambdaLifecycleTracker.Reset(resetContainer: false); - // Start segment var segmentWarm = AWSXRayRecorder.Instance.TraceContext.GetEntity(); _handler.Handle(); diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingSubsegmentTests.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingSubsegmentTests.cs deleted file mode 100644 index 54300a6d3..000000000 --- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/TracingSubsegmentTests.cs +++ /dev/null @@ -1,235 +0,0 @@ -using AWS.Lambda.Powertools.Tracing.Internal; -using Xunit; -using Amazon.XRay.Recorder.Core.Internal.Entities; -using System; - -namespace AWS.Lambda.Powertools.Tracing.Tests; - -[Collection("Sequential")] -public class TracingSubsegmentTests -{ - - [Fact] - public void TracingSubsegment_Constructor_Should_Set_Name() - { - // Arrange - var name = "test-segment"; - - // Act - var subsegment = new TracingSubsegment(name); - - // Assert - Assert.Equal("test-segment", subsegment.Name); - Assert.True(Entity.IsIdValid(subsegment.Id)); - Assert.Null(subsegment.TraceId); - Assert.Null(subsegment.ParentId); - Assert.False(subsegment.IsSubsegmentsAdded); - } - - [Fact] - public void Test_Add_Ref_And_Release_With_TracingSubsegment() - { - // Arrange - var parent = new Segment("parent", TraceId.NewId()); - var child = new TracingSubsegment("child"); - - // Act - parent.AddSubsegment(child); - - // Assert - Assert.Equal(2, parent.Reference); - Assert.Equal(1, child.Reference); - - child.Release(); - Assert.Equal(1, parent.Reference); - Assert.Equal(0, child.Reference); - Assert.False(parent.IsEmittable()); - Assert.False(child.IsEmittable()); - - parent.Release(); - Assert.Equal(0, parent.Reference); - Assert.True(parent.IsEmittable()); - Assert.True(child.IsEmittable()); - } - - [Fact] - public void IsEmittable_Returns_False_Without_Parent() - { - var subsegment = new TracingSubsegment("segment"); - Assert.False(subsegment.IsEmittable()); - } - - [Fact] - public void TracingSubsegment_Is_Assignable_BaseClass() - { - // Arrange - var subsegment = new TracingSubsegment("segment"); - - // Act & Assert - Assert.IsAssignableFrom(subsegment); - } - - [Fact] - public void Tracing_WithSubsegment_Invokes_Delegate_With_TracingSubsegment() - { - // Arrange - bool delegateInvoked = false; - - void TracingSubsegmentDelegate(TracingSubsegment subsegment) - { - delegateInvoked = true; - } - - // Act - Tracing.WithSubsegment("namespace", "test", TracingSubsegmentDelegate); - - // Assert - Assert.True(delegateInvoked); - } - - [Fact] - public void WithSubsegment_WithEntity_ThrowsArgumentNullException_WhenNameIsNull() - { - // Arrange - var parent = new Segment("parent", TraceId.NewId()); - - // Act & Assert - Assert.Throws(() => - Tracing.WithSubsegment(null, null, parent, _ => { })); - } - - [Fact] - public void WithSubsegment_WithEntity_ThrowsArgumentNullException_WhenNameIsEmpty() - { - // Arrange - var parent = new Segment("parent", TraceId.NewId()); - - // Act & Assert - Assert.Throws(() => - Tracing.WithSubsegment(null, "", parent, _ => { })); - } - - [Fact] - public void WithSubsegment_WithEntity_ThrowsArgumentNullException_WhenEntityIsNull() - { - // Act & Assert - Assert.Throws(() => - Tracing.WithSubsegment(null, "test", null, _ => { })); - } - - [Fact] - public void WithSubsegment_WithEntity_CreatesSubsegmentWithCorrectName() - { - // Arrange - var parent = new Segment("parent", TraceId.NewId()); - TracingSubsegment capturedSubsegment = null; - - // Act - Tracing.WithSubsegment("test-namespace", "test-name", parent, subsegment => - { - capturedSubsegment = subsegment; - }); - - // Assert - Assert.NotNull(capturedSubsegment); - Assert.Equal("## test-name", capturedSubsegment.Name); - Assert.Equal("test-namespace", capturedSubsegment.Namespace); - } - - [Fact] - public void WithSubsegment_WithEntity_SetsSubsegmentProperties() - { - // Arrange - var parent = new Segment("parent", TraceId.NewId()); - TracingSubsegment capturedSubsegment = null; - - // Act - Tracing.WithSubsegment("test-namespace", "test-name", parent, subsegment => - { - capturedSubsegment = subsegment; - }); - - // Assert - Assert.NotNull(capturedSubsegment); - Assert.Equal(parent.Sampled, capturedSubsegment.Sampled); - Assert.False(capturedSubsegment.IsInProgress); - Assert.True(capturedSubsegment.StartTime > 0); - Assert.True(capturedSubsegment.EndTime > 0); - } - - [Fact] - public void WithSubsegment_WithEntity_AddsSubsegmentToParent() - { - // Arrange - var parent = new Segment("parent", TraceId.NewId()); - var initialSubsegmentCount = parent.Subsegments?.Count ?? 0; - - // Act - Tracing.WithSubsegment("test-namespace", "test-name", parent, _ => { }); - - // Assert - Assert.True(parent.IsSubsegmentsAdded); - Assert.Equal(initialSubsegmentCount + 1, parent.Subsegments.Count); - } - - [Fact] - public void WithSubsegment_WithEntity_InvokesActionWithSubsegment() - { - // Arrange - var parent = new Segment("parent", TraceId.NewId()); - bool actionInvoked = false; - TracingSubsegment passedSubsegment = null; - - // Act - Tracing.WithSubsegment("test-namespace", "test-name", parent, subsegment => - { - actionInvoked = true; - passedSubsegment = subsegment; - }); - - // Assert - Assert.True(actionInvoked); - Assert.NotNull(passedSubsegment); - Assert.IsType(passedSubsegment); - } - - - [Fact] - public void WithSubsegment_WithEntity_UsesDefaultNamespaceWhenNull() - { - // Arrange - var parent = new Segment("parent", TraceId.NewId()); - TracingSubsegment capturedSubsegment = null; - - // Act - Tracing.WithSubsegment(null, "test-name", parent, subsegment => - { - capturedSubsegment = subsegment; - }); - - // Assert - Assert.NotNull(capturedSubsegment); - Assert.NotNull(capturedSubsegment.Namespace); - } - - [Fact] - public void WithSubsegment_WithEntity_HandlesExceptionInAction() - { - // Arrange - var parent = new Segment("parent", TraceId.NewId()); - var expectedException = new InvalidOperationException("Test exception"); - - // Act & Assert - var actualException = Assert.Throws(() => - { - Tracing.WithSubsegment("test-namespace", "test-name", parent, subsegment => - { - throw expectedException; - }); - }); - - Assert.Equal(expectedException, actualException); - // Verify subsegment was still properly cleaned up - Assert.True(parent.IsSubsegmentsAdded); - } -} \ No newline at end of file diff --git a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/XRayRecorderTests.cs b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/XRayRecorderTests.cs index c40f44009..6a0243343 100644 --- a/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/XRayRecorderTests.cs +++ b/libraries/tests/AWS.Lambda.Powertools.Tracing.Tests/XRayRecorderTests.cs @@ -1,3 +1,18 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + using System; using Amazon.XRay.Recorder.Core; using Amazon.XRay.Recorder.Core.Internal.Entities; @@ -16,17 +31,27 @@ public class XRayRecorderTests public void Tracing_Set_Execution_Environment_Context() { // Arrange - var env = new PowertoolsEnvironment(); + var assemblyName = "AWS.Lambda.Powertools.Tracing"; + var assemblyVersion = "1.0.0"; + + var env = Substitute.For(); + env.GetAssemblyName(Arg.Any()).Returns(assemblyName); + env.GetAssemblyVersion(Arg.Any()).Returns(assemblyVersion); - var conf = new PowertoolsConfigurations(env); + var conf = new PowertoolsConfigurations(new SystemWrapper(env)); var awsXray = Substitute.For(); // Act var xRayRecorder = new XRayRecorder(awsXray, conf); // Assert - Assert.Contains($"{Constants.FeatureContextIdentifier}/Tracing/", - env.GetEnvironmentVariable("AWS_EXECUTION_ENV")); + env.Received(1).SetEnvironmentVariable( + "AWS_EXECUTION_ENV", $"{Constants.FeatureContextIdentifier}/Tracing/{assemblyVersion}" + ); + + env.Received(1).GetEnvironmentVariable( + "AWS_EXECUTION_ENV" + ); Assert.NotNull(xRayRecorder); } diff --git a/libraries/tests/Directory.Build.props b/libraries/tests/Directory.Build.props index d662fc454..26ab08bb9 100644 --- a/libraries/tests/Directory.Build.props +++ b/libraries/tests/Directory.Build.props @@ -1,6 +1,6 @@ - net6.0;net8.0 + net8.0 false diff --git a/libraries/tests/Directory.Packages.props b/libraries/tests/Directory.Packages.props index 804b073e2..e8c9a16ef 100644 --- a/libraries/tests/Directory.Packages.props +++ b/libraries/tests/Directory.Packages.props @@ -4,23 +4,21 @@ - + - - - + - + \ No newline at end of file diff --git a/libraries/tests/e2e/InfraShared/FunctionConstruct.cs b/libraries/tests/e2e/InfraShared/FunctionConstruct.cs index c3bb7d9eb..6dfeb84b9 100644 --- a/libraries/tests/e2e/InfraShared/FunctionConstruct.cs +++ b/libraries/tests/e2e/InfraShared/FunctionConstruct.cs @@ -27,7 +27,6 @@ public FunctionConstruct(Construct scope, string id, FunctionConstructProps prop Tracing = Tracing.ACTIVE, Timeout = Duration.Seconds(10), Environment = props.Environment, - LoggingFormat = LoggingFormat.TEXT, Code = Code.FromCustomCommand(distPath, [ command diff --git a/libraries/tests/e2e/functions/core/logging/AOT-Function-ILogger/src/AOT-Function-ILogger/AOT-Function-ILogger.csproj b/libraries/tests/e2e/functions/core/logging/AOT-Function-ILogger/src/AOT-Function-ILogger/AOT-Function-ILogger.csproj deleted file mode 100644 index 8655735ee..000000000 --- a/libraries/tests/e2e/functions/core/logging/AOT-Function-ILogger/src/AOT-Function-ILogger/AOT-Function-ILogger.csproj +++ /dev/null @@ -1,33 +0,0 @@ - - - Exe - net8.0 - enable - enable - Lambda - - true - - true - - true - - partial - - - - - - - - - - TestHelper.cs - - - - - - \ No newline at end of file diff --git a/libraries/tests/e2e/functions/core/logging/AOT-Function-ILogger/src/AOT-Function-ILogger/Function.cs b/libraries/tests/e2e/functions/core/logging/AOT-Function-ILogger/src/AOT-Function-ILogger/Function.cs deleted file mode 100644 index 16234c5bc..000000000 --- a/libraries/tests/e2e/functions/core/logging/AOT-Function-ILogger/src/AOT-Function-ILogger/Function.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System.Text.Json; -using Amazon.Lambda.Core; -using Amazon.Lambda.RuntimeSupport; -using System.Text.Json.Serialization; -using Amazon.Lambda.APIGatewayEvents; -using Amazon.Lambda.Serialization.SystemTextJson; -using AWS.Lambda.Powertools.Logging; -using AWS.Lambda.Powertools.Logging.Serializers; -using Helpers; - -namespace AOT_Function; - -public static class Function -{ - private static async Task Main() - { - Logger.Configure(logger => - { - logger.Service = "TestService"; - logger.LoggerOutputCase = LoggerOutputCase.PascalCase; - logger.JsonOptions = new JsonSerializerOptions - { - TypeInfoResolver = LambdaFunctionJsonSerializerContext.Default - }; - }); - - Func handler = FunctionHandler; - await LambdaBootstrapBuilder.Create(handler, new SourceGeneratorLambdaJsonSerializer()) - .Build() - .RunAsync(); - } - - [Logging(LogEvent = true, CorrelationIdPath = CorrelationIdPaths.ApiGatewayRest)] - public static APIGatewayProxyResponse FunctionHandler(APIGatewayProxyRequest apigwProxyEvent, ILambdaContext context) - { - Logger.LogInformation("Processing request started"); - - var requestContextRequestId = apigwProxyEvent.RequestContext.RequestId; - var lookupInfo = new Dictionary() - { - {"LookupInfo", new Dictionary{{ "LookupId", requestContextRequestId }}} - }; - - var customKeys = new Dictionary - { - {"test1", "value1"}, - {"test2", "value2"} - }; - - Logger.AppendKeys(lookupInfo); - Logger.AppendKeys(customKeys); - - Logger.LogWarning("Warn with additional keys"); - - Logger.RemoveKeys("test1", "test2"); - - var error = new InvalidOperationException("Parent exception message", - new ArgumentNullException(nameof(apigwProxyEvent), - new Exception("Very important nested inner exception message"))); - Logger.LogError(error, "Oops something went wrong"); - return new APIGatewayProxyResponse() - { - StatusCode = 200, - Body = apigwProxyEvent.Body.ToUpper() - }; - } -} - -[JsonSerializable(typeof(APIGatewayProxyRequest))] -[JsonSerializable(typeof(APIGatewayProxyResponse))] -public partial class LambdaFunctionJsonSerializerContext : JsonSerializerContext -{ - -} \ No newline at end of file diff --git a/libraries/tests/e2e/functions/core/logging/AOT-Function-ILogger/src/AOT-Function-ILogger/aws-lambda-tools-defaults.json b/libraries/tests/e2e/functions/core/logging/AOT-Function-ILogger/src/AOT-Function-ILogger/aws-lambda-tools-defaults.json deleted file mode 100644 index be3c7ec13..000000000 --- a/libraries/tests/e2e/functions/core/logging/AOT-Function-ILogger/src/AOT-Function-ILogger/aws-lambda-tools-defaults.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "Information": [ - "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", - "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", - "dotnet lambda help", - "All the command line options for the Lambda command can be specified in this file." - ], - "profile": "", - "region": "", - "configuration": "Release", - "function-runtime": "dotnet8", - "function-memory-size": 512, - "function-timeout": 30, - "function-handler": "AOT-Function", - "msbuild-parameters": "--self-contained true" -} \ No newline at end of file diff --git a/libraries/tests/e2e/functions/core/logging/AOT-Function/src/AOT-Function/AOT-Function.csproj b/libraries/tests/e2e/functions/core/logging/AOT-Function/src/AOT-Function/AOT-Function.csproj index 8655735ee..b2636d6bc 100644 --- a/libraries/tests/e2e/functions/core/logging/AOT-Function/src/AOT-Function/AOT-Function.csproj +++ b/libraries/tests/e2e/functions/core/logging/AOT-Function/src/AOT-Function/AOT-Function.csproj @@ -17,7 +17,7 @@ partial - + diff --git a/libraries/tests/e2e/functions/core/logging/Function/src/Function/Function.cs b/libraries/tests/e2e/functions/core/logging/Function/src/Function/Function.cs index 958f36ff8..8a4d3a8b0 100644 --- a/libraries/tests/e2e/functions/core/logging/Function/src/Function/Function.cs +++ b/libraries/tests/e2e/functions/core/logging/Function/src/Function/Function.cs @@ -2,191 +2,24 @@ using Amazon.Lambda.Core; using AWS.Lambda.Powertools.Logging; using Helpers; -using Microsoft.Extensions.Logging; // Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class. [assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))] -namespace Function -{ - public class Function - { - [Logging(LogEvent = true, LoggerOutputCase = LoggerOutputCase.PascalCase, Service = "TestService", - CorrelationIdPath = CorrelationIdPaths.ApiGatewayRest)] - public APIGatewayProxyResponse FunctionHandler(APIGatewayProxyRequest apigwProxyEvent, ILambdaContext context) - { - TestHelper.TestMethod(apigwProxyEvent); - - return new APIGatewayProxyResponse() - { - StatusCode = 200, - Body = apigwProxyEvent.Body.ToUpper() - }; - } - } -} - -namespace StaticConfiguration -{ - public class Function - { - public Function() - { - Logger.Configure(config => - { - config.Service = "TestService"; - config.LoggerOutputCase = LoggerOutputCase.PascalCase; - }); - } - - [Logging(LogEvent = true, CorrelationIdPath = CorrelationIdPaths.ApiGatewayRest)] - public APIGatewayProxyResponse FunctionHandler(APIGatewayProxyRequest apigwProxyEvent, ILambdaContext context) - { - TestHelper.TestMethod(apigwProxyEvent); - - return new APIGatewayProxyResponse() - { - StatusCode = 200, - Body = apigwProxyEvent.Body.ToUpper() - }; - } - } -} +namespace Function; -namespace StaticILoggerConfiguration +public class Function { - public class Function - { - public Function() - { - LoggerFactory.Create(builder => - { - builder.AddPowertoolsLogger(config => - { - config.Service = "TestService"; - config.LoggerOutputCase = LoggerOutputCase.PascalCase; - }); - }); - } - - [Logging(LogEvent = true, CorrelationIdPath = CorrelationIdPaths.ApiGatewayRest)] - public APIGatewayProxyResponse FunctionHandler(APIGatewayProxyRequest apigwProxyEvent, ILambdaContext context) - { - TestHelper.TestMethod(apigwProxyEvent); - - return new APIGatewayProxyResponse() - { - StatusCode = 200, - Body = apigwProxyEvent.Body.ToUpper() - }; - } - } -} - -namespace ILoggerConfiguration -{ - public class Function - { - private readonly ILogger _logger; - - public Function() - { - _logger = LoggerFactory.Create(builder => - { - builder.AddPowertoolsLogger(config => - { - config.Service = "TestService"; - config.LoggerOutputCase = LoggerOutputCase.PascalCase; - }); - }).CreatePowertoolsLogger(); - } - - [Logging(LogEvent = true, CorrelationIdPath = CorrelationIdPaths.ApiGatewayRest)] - public APIGatewayProxyResponse FunctionHandler(APIGatewayProxyRequest apigwProxyEvent, ILambdaContext context) - { - _logger.LogInformation("Processing request started"); - - var requestContextRequestId = apigwProxyEvent.RequestContext.RequestId; - var lookupInfo = new Dictionary() - { - {"LookupInfo", new Dictionary{{ "LookupId", requestContextRequestId }}} - }; - - var customKeys = new Dictionary - { - {"test1", "value1"}, - {"test2", "value2"} - }; - - _logger.AppendKeys(lookupInfo); - _logger.AppendKeys(customKeys); - - _logger.LogWarning("Warn with additional keys"); - - _logger.RemoveKeys("test1", "test2"); - - var error = new InvalidOperationException("Parent exception message", - new ArgumentNullException(nameof(apigwProxyEvent), - new Exception("Very important nested inner exception message"))); - _logger.LogError(error, "Oops something went wrong"); - - return new APIGatewayProxyResponse() - { - StatusCode = 200, - Body = apigwProxyEvent.Body.ToUpper() - }; - } - } -} - -namespace ILoggerBuilder -{ - public class Function + [Logging(LogEvent = true, LoggerOutputCase = LoggerOutputCase.PascalCase, Service = "TestService", + CorrelationIdPath = CorrelationIdPaths.ApiGatewayRest)] + public APIGatewayProxyResponse FunctionHandler(APIGatewayProxyRequest apigwProxyEvent, ILambdaContext context) { - private readonly ILogger _logger; - - public Function() - { - _logger = new PowertoolsLoggerBuilder() - .WithService("TestService") - .WithOutputCase(LoggerOutputCase.PascalCase) - .Build(); - } + TestHelper.TestMethod(apigwProxyEvent); - [Logging(LogEvent = true, CorrelationIdPath = CorrelationIdPaths.ApiGatewayRest)] - public APIGatewayProxyResponse FunctionHandler(APIGatewayProxyRequest apigwProxyEvent, ILambdaContext context) + return new APIGatewayProxyResponse() { - _logger.LogInformation("Processing request started"); - - var requestContextRequestId = apigwProxyEvent.RequestContext.RequestId; - var lookupInfo = new Dictionary() - { - {"LookupInfo", new Dictionary{{ "LookupId", requestContextRequestId }}} - }; - - var customKeys = new Dictionary - { - {"test1", "value1"}, - {"test2", "value2"} - }; - - _logger.AppendKeys(lookupInfo); - _logger.AppendKeys(customKeys); - - _logger.LogWarning("Warn with additional keys"); - - _logger.RemoveKeys("test1", "test2"); - - var error = new InvalidOperationException("Parent exception message", - new ArgumentNullException(nameof(apigwProxyEvent), - new Exception("Very important nested inner exception message"))); - _logger.LogError(error, "Oops something went wrong"); - - return new APIGatewayProxyResponse() - { - StatusCode = 200, - Body = apigwProxyEvent.Body.ToUpper() - }; - } + StatusCode = 200, + Body = apigwProxyEvent.Body.ToUpper() + }; } } \ No newline at end of file diff --git a/libraries/tests/e2e/functions/core/logging/Function/src/Function/Function.csproj b/libraries/tests/e2e/functions/core/logging/Function/src/Function/Function.csproj index 2e08d5e4a..80be7fd59 100644 --- a/libraries/tests/e2e/functions/core/logging/Function/src/Function/Function.csproj +++ b/libraries/tests/e2e/functions/core/logging/Function/src/Function/Function.csproj @@ -1,6 +1,6 @@ - net6.0;net8.0 + net8.0 enable enable true diff --git a/libraries/tests/e2e/functions/core/logging/Function/test/Function.Tests/FunctionTests.cs b/libraries/tests/e2e/functions/core/logging/Function/test/Function.Tests/FunctionTests.cs index f4d7c1e4c..ca3a857a6 100644 --- a/libraries/tests/e2e/functions/core/logging/Function/test/Function.Tests/FunctionTests.cs +++ b/libraries/tests/e2e/functions/core/logging/Function/test/Function.Tests/FunctionTests.cs @@ -5,7 +5,6 @@ using Amazon.Lambda.Model; using TestUtils; using Xunit.Abstractions; -using Environment = Amazon.Lambda.Model.Environment; namespace Function.Tests; @@ -23,21 +22,10 @@ public FunctionTests(ITestOutputHelper testOutputHelper) [Trait("Category", "AOT")] [Theory] - [InlineData("E2ETestLambda_X64_AOT_NET8_logging_AOT-Function")] - [InlineData("E2ETestLambda_ARM_AOT_NET8_logging_AOT-Function")] + [InlineData("E2ETestLambda_X64_AOT_NET8_logging")] + [InlineData("E2ETestLambda_ARM_AOT_NET8_logging")] public async Task AotFunctionTest(string functionName) { - // await ResetFunction(functionName); - await TestFunction(functionName); - } - - [Trait("Category", "AOT")] - [Theory] - [InlineData("E2ETestLambda_X64_AOT_NET8_logging_AOT-Function-ILogger")] - [InlineData("E2ETestLambda_ARM_AOT_NET8_logging_AOT-Function-ILogger")] - public async Task AotILoggerFunctionTest(string functionName) - { - // await ResetFunction(functionName); await TestFunction(functionName); } @@ -48,51 +36,6 @@ public async Task AotILoggerFunctionTest(string functionName) [InlineData("E2ETestLambda_ARM_NET8_logging")] public async Task FunctionTest(string functionName) { - await UpdateFunctionHandler(functionName, "Function::Function.Function::FunctionHandler"); - await TestFunction(functionName); - } - - [Theory] - [InlineData("E2ETestLambda_X64_NET6_logging")] - [InlineData("E2ETestLambda_ARM_NET6_logging")] - [InlineData("E2ETestLambda_X64_NET8_logging")] - [InlineData("E2ETestLambda_ARM_NET8_logging")] - public async Task StaticConfigurationFunctionTest(string functionName) - { - await UpdateFunctionHandler(functionName, "Function::StaticConfiguration.Function::FunctionHandler"); - await TestFunction(functionName); - } - - [Theory] - [InlineData("E2ETestLambda_X64_NET6_logging")] - [InlineData("E2ETestLambda_ARM_NET6_logging")] - [InlineData("E2ETestLambda_X64_NET8_logging")] - [InlineData("E2ETestLambda_ARM_NET8_logging")] - public async Task StaticILoggerConfigurationFunctionTest(string functionName) - { - await UpdateFunctionHandler(functionName, "Function::StaticILoggerConfiguration.Function::FunctionHandler"); - await TestFunction(functionName); - } - - [Theory] - [InlineData("E2ETestLambda_X64_NET6_logging")] - [InlineData("E2ETestLambda_ARM_NET6_logging")] - [InlineData("E2ETestLambda_X64_NET8_logging")] - [InlineData("E2ETestLambda_ARM_NET8_logging")] - public async Task ILoggerConfigurationFunctionTest(string functionName) - { - await UpdateFunctionHandler(functionName, "Function::ILoggerConfiguration.Function::FunctionHandler"); - await TestFunction(functionName); - } - - [Theory] - [InlineData("E2ETestLambda_X64_NET6_logging")] - [InlineData("E2ETestLambda_ARM_NET6_logging")] - [InlineData("E2ETestLambda_X64_NET8_logging")] - [InlineData("E2ETestLambda_ARM_NET8_logging")] - public async Task ILoggerBuilderFunctionTest(string functionName) - { - await UpdateFunctionHandler(functionName, "Function::ILoggerBuilder.Function::FunctionHandler"); await TestFunction(functionName); } @@ -173,17 +116,41 @@ private void AssertEventLog(string functionName, bool isColdStart, string output Assert.True(messageElement.TryGetProperty("HttpMethod", out JsonElement httpMethodElement)); Assert.Equal("POST", httpMethodElement.GetString()); - + + Assert.True(messageElement.TryGetProperty("Headers", out JsonElement headersElement)); + Assert.True(headersElement.TryGetProperty("Accept-Encoding", out JsonElement acceptEncodingElement)); + Assert.Equal("gzip, deflate, sdch", acceptEncodingElement.GetString()); + + Assert.True(headersElement.TryGetProperty("Accept-Language", out JsonElement acceptLanguageElement)); + Assert.Equal("en-US,en;q=0.8", acceptLanguageElement.GetString()); + + Assert.True(headersElement.TryGetProperty("Cache-Control", out JsonElement cacheControlElement)); + Assert.Equal("max-age=0", cacheControlElement.GetString()); + + Assert.True( + messageElement.TryGetProperty("QueryStringParameters", out JsonElement queryStringParametersElement)); + Assert.True(queryStringParametersElement.TryGetProperty("Foo", out JsonElement fooElement)); + Assert.Equal("bar", fooElement.GetString()); + Assert.True(messageElement.TryGetProperty("RequestContext", out JsonElement requestContextElement)); Assert.True(requestContextElement.TryGetProperty("Path", out JsonElement requestContextPathElement)); Assert.Equal("/prod/path/to/resource", requestContextPathElement.GetString()); + Assert.True(requestContextElement.TryGetProperty("AccountId", out JsonElement accountIdElement)); + Assert.Equal("123456789012", accountIdElement.GetString()); + Assert.True(requestContextElement.TryGetProperty("ResourceId", out JsonElement resourceIdElement)); Assert.Equal("123456", resourceIdElement.GetString()); - + + Assert.True(requestContextElement.TryGetProperty("Stage", out JsonElement stageElement)); + Assert.Equal("prod", stageElement.GetString()); + Assert.True(requestContextElement.TryGetProperty("RequestId", out JsonElement requestIdElement)); Assert.Equal("c6af9ac6-7b61-11e6-9a41-93e8deadbeef", requestIdElement.GetString()); - + + Assert.True(requestContextElement.TryGetProperty("ResourcePath", out JsonElement resourcePathElement)); + Assert.Equal("/{proxy+}", resourcePathElement.GetString()); + Assert.True( requestContextElement.TryGetProperty("HttpMethod", out JsonElement requestContextHttpMethodElement)); Assert.Equal("POST", requestContextHttpMethodElement.GetString()); @@ -191,6 +158,12 @@ private void AssertEventLog(string functionName, bool isColdStart, string output Assert.True(requestContextElement.TryGetProperty("ApiId", out JsonElement apiIdElement)); Assert.Equal("1234567890", apiIdElement.GetString()); + Assert.True(requestContextElement.TryGetProperty("RequestTime", out JsonElement requestTimeElement)); + Assert.Equal("09/Apr/2015:12:34:56 +0000", requestTimeElement.GetString()); + + Assert.True(requestContextElement.TryGetProperty("RequestTimeEpoch", out JsonElement requestTimeEpochElement)); + Assert.Equal(1428582896000, requestTimeEpochElement.GetInt64()); + Assert.True(messageElement.TryGetProperty("Body", out JsonElement bodyElement)); Assert.Equal("hello world", bodyElement.GetString()); @@ -270,48 +243,4 @@ private void AssertExceptionLog(string functionName, bool isColdStart, string ou Assert.False(root.TryGetProperty("Test1", out JsonElement _)); Assert.False(root.TryGetProperty("Test2", out JsonElement _)); } - - private async Task UpdateFunctionHandler(string functionName, string handler) - { - var updateRequest = new UpdateFunctionConfigurationRequest - { - FunctionName = functionName, - Handler = handler - }; - - var updateResponse = await _lambdaClient.UpdateFunctionConfigurationAsync(updateRequest); - - if (updateResponse.HttpStatusCode == System.Net.HttpStatusCode.OK) - { - Console.WriteLine($"Successfully updated the handler for function {functionName} to {handler}"); - } - else - { - Assert.Fail( - $"Failed to update the handler for function {functionName}. Status code: {updateResponse.HttpStatusCode}"); - } - - //wait a few seconds for the changes to take effect - await Task.Delay(1000); - } - - private async Task ResetFunction(string functionName) - { - var updateRequest = new UpdateFunctionConfigurationRequest - { - FunctionName = functionName, - Environment = new Environment - { - Variables = - { - {"Updated", DateTime.UtcNow.ToString("G")} - } - } - }; - - await _lambdaClient.UpdateFunctionConfigurationAsync(updateRequest); - - //wait a few seconds for the changes to take effect - await Task.Delay(1000); - } } \ No newline at end of file diff --git a/libraries/tests/e2e/functions/core/metrics/Function/src/Function/Function.csproj b/libraries/tests/e2e/functions/core/metrics/Function/src/Function/Function.csproj index 1664858ed..ed95bbca4 100644 --- a/libraries/tests/e2e/functions/core/metrics/Function/src/Function/Function.csproj +++ b/libraries/tests/e2e/functions/core/metrics/Function/src/Function/Function.csproj @@ -1,6 +1,6 @@ - net6.0;net8.0 + net8.0 enable enable true diff --git a/libraries/tests/e2e/functions/core/metrics/Function/src/Function/TestHelper.cs b/libraries/tests/e2e/functions/core/metrics/Function/src/Function/TestHelper.cs index 38cb7438b..b7059a62c 100644 --- a/libraries/tests/e2e/functions/core/metrics/Function/src/Function/TestHelper.cs +++ b/libraries/tests/e2e/functions/core/metrics/Function/src/Function/TestHelper.cs @@ -17,7 +17,6 @@ public static void TestMethod(APIGatewayProxyRequest apigwProxyEvent, ILambdaCon Metrics.SetDefaultDimensions(DefaultDimensions); Metrics.AddMetric("Invocation", 1, MetricUnit.Count); - Metrics.AddDimension("FunctionName", context.FunctionName); Metrics.AddDimension("Memory","MemoryLimitInMB"); Metrics.AddMetric("Memory with Environment dimension", context.MemoryLimitInMB, MetricUnit.Megabytes); @@ -33,14 +32,14 @@ public static void TestMethod(APIGatewayProxyRequest apigwProxyEvent, ILambdaCon Metrics.AddMetadata("RequestId", apigwProxyEvent.RequestContext.RequestId); Metrics.PushSingleMetric( - name: "SingleMetric", + metricName: "SingleMetric", value: 1, unit: MetricUnit.Count, nameSpace: "Test", service: "Test", - dimensions: new Dictionary + defaultDimensions: new Dictionary { - {"FunctionName", context.FunctionName} + {"FunctionContext", "$LATEST"} }); } } \ No newline at end of file diff --git a/libraries/tests/e2e/functions/core/metrics/Function/test/Function.Tests/Function.Tests.csproj b/libraries/tests/e2e/functions/core/metrics/Function/test/Function.Tests/Function.Tests.csproj index aa3f3cb89..6881f4cb1 100644 --- a/libraries/tests/e2e/functions/core/metrics/Function/test/Function.Tests/Function.Tests.csproj +++ b/libraries/tests/e2e/functions/core/metrics/Function/test/Function.Tests/Function.Tests.csproj @@ -10,7 +10,6 @@ - diff --git a/libraries/tests/e2e/functions/core/metrics/Function/test/Function.Tests/FunctionTests.cs b/libraries/tests/e2e/functions/core/metrics/Function/test/Function.Tests/FunctionTests.cs index f0afeef3d..1670dceb8 100644 --- a/libraries/tests/e2e/functions/core/metrics/Function/test/Function.Tests/FunctionTests.cs +++ b/libraries/tests/e2e/functions/core/metrics/Function/test/Function.Tests/FunctionTests.cs @@ -1,14 +1,10 @@ using System.Text.Json; -using Amazon.CDK.AWS.CodeDeploy; -using Amazon.CloudWatch; -using Amazon.CloudWatch.Model; using Amazon.Lambda; using Amazon.Lambda.APIGatewayEvents; using Xunit; using Amazon.Lambda.Model; using TestUtils; using Xunit.Abstractions; -using Environment = Amazon.Lambda.Model.Environment; namespace Function.Tests; @@ -17,7 +13,6 @@ public class FunctionTests { private readonly ITestOutputHelper _testOutputHelper; private readonly AmazonLambdaClient _lambdaClient; - private string? _functionName; public FunctionTests(ITestOutputHelper testOutputHelper) { @@ -27,13 +22,11 @@ public FunctionTests(ITestOutputHelper testOutputHelper) [Trait("Category", "AOT")] [Theory] - [InlineData("E2ETestLambda_X64_AOT_NET8_metrics_AOT-Function")] - [InlineData("E2ETestLambda_ARM_AOT_NET8_metrics_AOT-Function")] + [InlineData("E2ETestLambda_X64_AOT_NET8_metrics")] + [InlineData("E2ETestLambda_ARM_AOT_NET8_metrics")] public async Task AotFunctionTest(string functionName) { - _functionName = functionName; - await ForceColdStart(); - await TestFunction(); + await TestFunction(functionName); } [Theory] @@ -43,147 +36,61 @@ public async Task AotFunctionTest(string functionName) [InlineData("E2ETestLambda_ARM_NET8_metrics")] public async Task FunctionTest(string functionName) { - _functionName = functionName; - await ForceColdStart(); - await TestFunction(); + await TestFunction(functionName); } - internal async Task TestFunction() + internal async Task TestFunction(string functionName) { var request = new InvokeRequest { - FunctionName = _functionName, + FunctionName = functionName, InvocationType = InvocationType.RequestResponse, Payload = await File.ReadAllTextAsync("../../../../../../../../payload.json"), LogType = LogType.Tail }; - // Test cold start - var coldStartResponse = await _lambdaClient.InvokeAsync(request); - ValidateResponse(coldStartResponse, true); + // run twice for cold and warm start + for (int i = 0; i < 2; i++) + { + var response = await _lambdaClient.InvokeAsync(request); - // Test warm start - var warmStartResponse = await _lambdaClient.InvokeAsync(request); - ValidateResponse(warmStartResponse, false); + if (string.IsNullOrEmpty(response.LogResult)) + { + Assert.Fail("No LogResult field returned in the response of Lambda invocation."); + } - // Assert cloudwatch - await AssertCloudWatch(); - } + var payload = System.Text.Encoding.UTF8.GetString(response.Payload.ToArray()); + var parsedPayload = JsonSerializer.Deserialize(payload); - private void ValidateResponse(InvokeResponse response, bool isColdStart) - { - if (string.IsNullOrEmpty(response.LogResult)) - { - Assert.Fail("No LogResult field returned in the response of Lambda invocation."); - } + if (parsedPayload == null) + { + Assert.Fail("Failed to parse payload."); + } - var payload = System.Text.Encoding.UTF8.GetString(response.Payload.ToArray()); - var parsedPayload = JsonSerializer.Deserialize(payload); + Assert.Equal(200, parsedPayload.StatusCode); + Assert.Equal("HELLO WORLD", parsedPayload.Body); - if (parsedPayload == null) - { - Assert.Fail("Failed to parse payload."); + // Assert Output log from Lambda execution + AssertOutputLog(response); } - - Assert.Equal(200, parsedPayload.StatusCode); - Assert.Equal("HELLO WORLD", parsedPayload.Body); - - // Assert Output log from Lambda execution - AssertOutputLog(response, isColdStart); } - private void AssertOutputLog(InvokeResponse response, bool expectedColdStart) + private void AssertOutputLog(InvokeResponse response) { + // Extract and parse log var logResult = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(response.LogResult)); _testOutputHelper.WriteLine(logResult); var output = OutputLogParser.ParseLogSegments(logResult, out var report); var isColdStart = report.initDuration != "N/A"; - - Assert.Equal(expectedColdStart, isColdStart); - + var index = 0; if (isColdStart) { - AssertColdStart(output[0]); - AssertSingleMetric(output[1]); - AssertMetricsDimensionsMetadata(output[2]); - } - else - { - AssertSingleMetric(output[0]); - AssertMetricsDimensionsMetadata(output[1]); + AssertColdStart(output[index]); + index += 1; } - } - private async Task AssertCloudWatch() - { - using var cloudWatchClient = new AmazonCloudWatchClient(); - var request = new ListMetricsRequest - { - Namespace = "Test", - Dimensions = - [ - new DimensionFilter - { - Name = "Service", - Value = "Test" - }, - - new DimensionFilter - { - Name = "FunctionName", - Value = _functionName - } - ] - }; - - // retry n amount of times to ensure metrics are available - var response = new ListMetricsResponse(); - for (int i = 0; i < 5; i++) - { - try - { - response = await cloudWatchClient.ListMetricsAsync(request); - if (response.Metrics.Count > 6) - { - break; - } - } - catch (Exception ex) - { - _testOutputHelper.WriteLine($"Attempt {i + 1}: Failed to list metrics: {ex.Message}"); - } - - await Task.Delay(5000); // wait for 5 seconds before retrying - } - - Assert.Equal(7, response.Metrics.Count); - - foreach (var metric in response.Metrics) - { - Assert.Equal("Test", metric.Namespace); - - switch (metric.MetricName) - { - case "ColdStart": - case "SingleMetric": - Assert.Equal(2, metric.Dimensions.Count); - Assert.Contains(metric.Dimensions, d => d.Name == "Service" && d.Value == "Test"); - Assert.Contains(metric.Dimensions, d => d.Name == "FunctionName" && d.Value == _functionName); - break; - case "Invocation": - case "Memory with Environment dimension": - case "Standard resolution": - case "High resolution": - case "Default resolution": - Assert.Equal(5, metric.Dimensions.Count); - Assert.Contains(metric.Dimensions, d => d.Name == "Service" && d.Value == "Test"); - Assert.Contains(metric.Dimensions, d => d.Name == "FunctionName" && d.Value == _functionName); - Assert.Contains(metric.Dimensions, d => d.Name == "Memory" && d.Value == "MemoryLimitInMB"); - Assert.Contains(metric.Dimensions, d => d.Name == "Environment" && d.Value == "Prod"); - Assert.Contains(metric.Dimensions, d => d.Name == "Another" && d.Value == "One"); - break; - } - } + AssertSingleMetric(output[index]); + AssertMetricsDimensionsMetadata(output[index + 1]); } private void AssertMetricsDimensionsMetadata(string output) @@ -229,11 +136,10 @@ private void AssertMetricsDimensionsMetadata(string output) Assert.Equal("Count", unitElement5.GetString()); Assert.True(cloudWatchMetricsElement[0].TryGetProperty("Dimensions", out JsonElement dimensionsElement)); - Assert.Equal("Service", dimensionsElement[0][0].GetString()); - Assert.Equal("Environment", dimensionsElement[0][1].GetString()); - Assert.Equal("Another", dimensionsElement[0][2].GetString()); - Assert.Equal("FunctionName", dimensionsElement[0][3].GetString()); - Assert.Equal("Memory", dimensionsElement[0][4].GetString()); + Assert.Equal("Service", dimensionsElement[0].GetString()); + Assert.Equal("Environment", dimensionsElement[1].GetString()); + Assert.Equal("Another", dimensionsElement[2].GetString()); + Assert.Equal("Memory", dimensionsElement[3].GetString()); Assert.True(root.TryGetProperty("Service", out JsonElement serviceElement)); Assert.Equal("Test", serviceElement.GetString()); @@ -244,9 +150,6 @@ private void AssertMetricsDimensionsMetadata(string output) Assert.True(root.TryGetProperty("Another", out JsonElement anotherElement)); Assert.Equal("One", anotherElement.GetString()); - Assert.True(root.TryGetProperty("FunctionName", out JsonElement functionNameElement)); - Assert.Equal(_functionName, functionNameElement.GetString()); - Assert.True(root.TryGetProperty("Memory", out JsonElement memoryElement)); Assert.Equal("MemoryLimitInMB", memoryElement.GetString()); @@ -288,11 +191,11 @@ private void AssertSingleMetric(string output) Assert.Equal("Count", unitElement.GetString()); Assert.True(cloudWatchMetricsElement[0].TryGetProperty("Dimensions", out JsonElement dimensionsElement)); - Assert.Equal("Service", dimensionsElement[0][0].GetString()); - Assert.Equal("FunctionName", dimensionsElement[0][1].GetString()); + Assert.Equal("FunctionContext", dimensionsElement[0].GetString()); + Assert.Equal("Service", dimensionsElement[1].GetString()); - Assert.True(root.TryGetProperty("FunctionName", out JsonElement functionNameElement)); - Assert.Equal(_functionName, functionNameElement.GetString()); + Assert.True(root.TryGetProperty("FunctionContext", out JsonElement functionContextElement)); + Assert.Equal("$LATEST", functionContextElement.GetString()); Assert.True(root.TryGetProperty("Service", out JsonElement serviceElement)); Assert.Equal("Test", serviceElement.GetString()); @@ -318,23 +221,4 @@ private void AssertColdStart(string output) Assert.True(root.TryGetProperty("ColdStart", out JsonElement coldStartElement)); Assert.Equal(1, coldStartElement.GetInt32()); } - - private async Task ForceColdStart() - { - var updateRequest = new UpdateFunctionConfigurationRequest - { - FunctionName = _functionName, - Environment = new Environment - { - Variables = new Dictionary - { - { "ForceColdStart", Guid.NewGuid().ToString() } - } - } - }; - - _ = await _lambdaClient.UpdateFunctionConfigurationAsync(updateRequest); - - await Task.Delay(15000); - } } \ No newline at end of file diff --git a/libraries/tests/e2e/functions/core/tracing/AOT-Function/src/AOT-Function/AOT-Function.csproj b/libraries/tests/e2e/functions/core/tracing/AOT-Function/src/AOT-Function/AOT-Function.csproj index 111b59c2e..85b41ba2b 100644 --- a/libraries/tests/e2e/functions/core/tracing/AOT-Function/src/AOT-Function/AOT-Function.csproj +++ b/libraries/tests/e2e/functions/core/tracing/AOT-Function/src/AOT-Function/AOT-Function.csproj @@ -17,7 +17,7 @@ partial - + diff --git a/libraries/tests/e2e/functions/core/tracing/Function/src/Function/Function.csproj b/libraries/tests/e2e/functions/core/tracing/Function/src/Function/Function.csproj index c8c004582..e8d65b046 100644 --- a/libraries/tests/e2e/functions/core/tracing/Function/src/Function/Function.csproj +++ b/libraries/tests/e2e/functions/core/tracing/Function/src/Function/Function.csproj @@ -1,6 +1,6 @@ - net6.0;net8.0 + net8.0 enable enable true diff --git a/libraries/tests/e2e/functions/core/tracing/Function/test/Function.Tests/FunctionTests.cs b/libraries/tests/e2e/functions/core/tracing/Function/test/Function.Tests/FunctionTests.cs index 919fd3745..aa1c0b394 100644 --- a/libraries/tests/e2e/functions/core/tracing/Function/test/Function.Tests/FunctionTests.cs +++ b/libraries/tests/e2e/functions/core/tracing/Function/test/Function.Tests/FunctionTests.cs @@ -25,8 +25,8 @@ public FunctionTests(ITestOutputHelper testOutputHelper) [Trait("Category", "AOT")] [Theory] - [InlineData("E2ETestLambda_X64_AOT_NET8_tracing_AOT-Function")] - [InlineData("E2ETestLambda_ARM_AOT_NET8_tracing_AOT-Function")] + [InlineData("E2ETestLambda_X64_AOT_NET8_tracing")] + [InlineData("E2ETestLambda_ARM_AOT_NET8_tracing")] public async Task AotFunctionTest(string functionName) { await TestFunction(functionName); diff --git a/libraries/tests/e2e/functions/idempotency/Function/src/Function/Function.csproj b/libraries/tests/e2e/functions/idempotency/Function/src/Function/Function.csproj index 0dedeaeab..827690848 100644 --- a/libraries/tests/e2e/functions/idempotency/Function/src/Function/Function.csproj +++ b/libraries/tests/e2e/functions/idempotency/Function/src/Function/Function.csproj @@ -1,6 +1,6 @@ - net6.0;net8.0 + net8.0 enable enable true diff --git a/libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/FunctionTests.cs b/libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/FunctionTests.cs index b7bbe28c9..3f5c7cc7a 100644 --- a/libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/FunctionTests.cs +++ b/libraries/tests/e2e/functions/idempotency/Function/test/Function.Tests/FunctionTests.cs @@ -186,7 +186,7 @@ private async Task IdempotencyHandler(string functionName, string? keyPrefix = n // Assert DynamoDB await AssertDynamoDbData( - $"{key}#24e83361c8bd544887aa99ab26395d54", + $"{key}#35973cf447e6cc11008d603c791a232f", guid1); } @@ -323,7 +323,7 @@ private async Task AssertDynamoDbData(string id, string requestId, bool isSavedD return await ExecuteRequest(request); } - + private async Task<(APIGatewayProxyResponse Response, string Guid)> ExecuteRequest(InvokeRequest request) { var response = await _lambdaClient.InvokeAsync(request); diff --git a/libraries/tests/e2e/functions/payload.json b/libraries/tests/e2e/functions/payload.json index 656968825..9f23a4b7b 100644 --- a/libraries/tests/e2e/functions/payload.json +++ b/libraries/tests/e2e/functions/payload.json @@ -4,12 +4,23 @@ "path": "/path/to/resource", "httpMethod": "POST", "isBase64Encoded": false, + "queryStringParameters": { + "foo": "bar" + }, + "headers": { + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0" + }, "requestContext": { "accountId": "123456789012", "resourceId": "123456", "stage": "prod", "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "requestTime": "09/Apr/2015:12:34:56 +0000", + "requestTimeEpoch": 1428582896000, "path": "/prod/path/to/resource", + "resourcePath": "/{proxy+}", "httpMethod": "POST", "apiId": "1234567890", "protocol": "HTTP/1.1" diff --git a/libraries/tests/e2e/infra-aot/CoreAotStack.cs b/libraries/tests/e2e/infra-aot/CoreAotStack.cs index d2ebcc5cd..4387892cf 100644 --- a/libraries/tests/e2e/infra-aot/CoreAotStack.cs +++ b/libraries/tests/e2e/infra-aot/CoreAotStack.cs @@ -6,18 +6,6 @@ namespace InfraAot; -public class ConstructArgs -{ - public Construct Scope { get; set; } - public string Id { get; set; } - public Runtime Runtime { get; set; } - public Architecture Architecture { get; set; } - public string Name { get; set; } - public string SourcePath { get; set; } - public string DistPath { get; set; } - public string Handler { get; set; } -} - public class CoreAotStack : Stack { private readonly Architecture _architecture; @@ -27,42 +15,31 @@ internal CoreAotStack(Construct scope, string id, PowertoolsDefaultStackProps pr if (props != null) _architecture = props.ArchitectureString == "arm64" ? Architecture.ARM_64 : Architecture.X86_64; CreateFunctionConstructs("logging"); - CreateFunctionConstructs("logging", "AOT-Function-ILogger"); CreateFunctionConstructs("metrics"); CreateFunctionConstructs("tracing"); } - private void CreateFunctionConstructs(string utility, string function = "AOT-Function" ) + private void CreateFunctionConstructs(string utility) { - var baseAotPath = $"../functions/core/{utility}/{function}/src/{function}"; - var distAotPath = $"../functions/core/{utility}/{function}/dist/{function}"; + var baseAotPath = $"../functions/core/{utility}/AOT-Function/src/AOT-Function"; + var distAotPath = $"../functions/core/{utility}/AOT-Function/dist"; var arch = _architecture == Architecture.X86_64 ? "X64" : "ARM"; - var construct = new ConstructArgs - { - Scope = this, - Id = $"{utility}_{arch}_aot_net8_{function}", - Runtime = Runtime.DOTNET_8, - Architecture = _architecture, - Name = $"E2ETestLambda_{arch}_AOT_NET8_{utility}_{function}", - SourcePath = baseAotPath, - DistPath = distAotPath, - Handler = function - }; - - CreateFunctionConstruct(construct); + CreateFunctionConstruct(this, $"{utility}_{arch}_aot_net8", Runtime.DOTNET_8, _architecture, + $"E2ETestLambda_{arch}_AOT_NET8_{utility}", baseAotPath, distAotPath); } - private void CreateFunctionConstruct(ConstructArgs constructArgs) + private void CreateFunctionConstruct(Construct scope, string id, Runtime runtime, Architecture architecture, + string name, string sourcePath, string distPath) { - _ = new FunctionConstruct(constructArgs.Scope, constructArgs.Id, new FunctionConstructProps + _ = new FunctionConstruct(scope, id, new FunctionConstructProps { - Runtime = constructArgs.Runtime, - Architecture = constructArgs.Architecture, - Name = constructArgs.Name, - Handler = constructArgs.Handler, - SourcePath = constructArgs.SourcePath, - DistPath = constructArgs.DistPath, + Runtime = runtime, + Architecture = architecture, + Name = name, + Handler = "AOT-Function", + SourcePath = sourcePath, + DistPath = distPath, IsAot = true }); } diff --git a/libraries/tests/e2e/infra/CoreStack.cs b/libraries/tests/e2e/infra/CoreStack.cs index 15f3fd6da..d77c725ac 100644 --- a/libraries/tests/e2e/infra/CoreStack.cs +++ b/libraries/tests/e2e/infra/CoreStack.cs @@ -6,28 +6,6 @@ namespace Infra { - public class ConstructArgs - { - public ConstructArgs(Construct scope, string id, Runtime runtime, Architecture architecture, string name, string sourcePath, string distPath) - { - Scope = scope; - Id = id; - Runtime = runtime; - Architecture = architecture; - Name = name; - SourcePath = sourcePath; - DistPath = distPath; - } - - public Construct Scope { get; private set; } - public string Id { get; private set; } - public Runtime Runtime { get; private set; } - public Architecture Architecture { get; private set; } - public string Name { get; private set; } - public string SourcePath { get; private set; } - public string DistPath { get; private set; } - } - public class CoreStack : Stack { internal CoreStack(Construct scope, string id, IStackProps props = null) : base(scope, id, props) @@ -42,22 +20,27 @@ private void CreateFunctionConstructs(string utility) var basePath = $"../functions/core/{utility}/Function/src/Function"; var distPath = $"../functions/core/{utility}/Function/dist"; - CreateFunctionConstruct(new ConstructArgs(this, $"{utility}_X64_net8", Runtime.DOTNET_8, Architecture.X86_64, $"E2ETestLambda_X64_NET8_{utility}", basePath, distPath)); - CreateFunctionConstruct(new ConstructArgs(this, $"{utility}_arm_net8", Runtime.DOTNET_8, Architecture.ARM_64, $"E2ETestLambda_ARM_NET8_{utility}", basePath, distPath)); - CreateFunctionConstruct(new ConstructArgs(this, $"{utility}_X64_net6", Runtime.DOTNET_6, Architecture.X86_64, $"E2ETestLambda_X64_NET6_{utility}", basePath, distPath)); - CreateFunctionConstruct(new ConstructArgs(this, $"{utility}_arm_net6", Runtime.DOTNET_6, Architecture.ARM_64, $"E2ETestLambda_ARM_NET6_{utility}", basePath, distPath)); + CreateFunctionConstruct(this, $"{utility}_X64_net8", Runtime.DOTNET_8, Architecture.X86_64, + $"E2ETestLambda_X64_NET8_{utility}", basePath, distPath); + CreateFunctionConstruct(this, $"{utility}_arm_net8", Runtime.DOTNET_8, Architecture.ARM_64, + $"E2ETestLambda_ARM_NET8_{utility}", basePath, distPath); + CreateFunctionConstruct(this, $"{utility}_X64_net6", Runtime.DOTNET_6, Architecture.X86_64, + $"E2ETestLambda_X64_NET6_{utility}", basePath, distPath); + CreateFunctionConstruct(this, $"{utility}_arm_net6", Runtime.DOTNET_6, Architecture.ARM_64, + $"E2ETestLambda_ARM_NET6_{utility}", basePath, distPath); } - private void CreateFunctionConstruct(ConstructArgs constructArgs) + private void CreateFunctionConstruct(Construct scope, string id, Runtime runtime, Architecture architecture, + string name, string sourcePath, string distPath) { - _ = new FunctionConstruct(constructArgs.Scope, constructArgs.Id, new FunctionConstructProps + _ = new FunctionConstruct(scope, id, new FunctionConstructProps { - Runtime = constructArgs.Runtime, - Architecture = constructArgs.Architecture, - Name = constructArgs.Name, + Runtime = runtime, + Architecture = architecture, + Name = name, Handler = "Function::Function.Function::FunctionHandler", - SourcePath = constructArgs.SourcePath, - DistPath = constructArgs.DistPath, + SourcePath = sourcePath, + DistPath = distPath, }); } } diff --git a/mkdocs.yml b/mkdocs.yml index 81cf8be88..c804a353a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -3,38 +3,25 @@ site_description: Powertools for AWS Lambda (.NET) site_author: Amazon Web Services repo_url: https://github.com/aws-powertools/powertools-lambda-dotnet edit_uri: edit/develop/docs -site_url: https://docs.powertools.aws.dev/lambda/dotnet/ nav: - - Homepage: - - index.md - - References: references.md - - Changelog: changelog.md - - Roadmap: roadmap.md - - We Made This (Community): we_made_this.md - - Workshop 🆕: https://s12d.com/powertools-for-aws-lambda-workshop" target="_blank - - Getting started: - - Logging: - - getting-started/logger/simple.md - - getting-started/logger/aspnet.md - - getting-started/logger/aot.md - - Features: - - core/logging.md - - core/metrics.md - - core/tracing.md - - utilities/idempotency.md - - utilities/batch-processing.md - - Event Handler: - - core/event_handler/appsync_events.md - - core/event_handler/bedrock_agent_function.md - - utilities/parameters.md - - utilities/jmespath-functions.md - - utilities/kafka.md - - Resources: - - "llms.txt": ./llms.txt - - "llms.txt (full version)": ./llms-full.txt + - Homepage: index.md + - References: references.md + - Changelog: changelog.md + - Roadmap: roadmap.md - API Reference: api/" target="_blank - + - We Made This (Community): we_made_this.md + - Workshop 🆕: https://s12d.com/powertools-for-aws-lambda-workshop" target="_blank + - Core utilities: + - core/logging.md + - core/metrics.md + - core/tracing.md + - Utilities: + - utilities/parameters.md + - utilities/idempotency.md + - utilities/batch-processing.md + - utilities/jmespath-functions.md + theme: name: material font: @@ -55,16 +42,14 @@ theme: features: - header.autohide - navigation.sections + - navigation.expand - navigation.top - - navigation.tabs - navigation.instant - navigation.indexes - navigation.tracking - content.code.annotate - - content.code.copy - toc.follow - announce.dismiss - - content.tabs.link icon: repo: fontawesome/brands/github logo: media/aws-logo-light.svg @@ -100,43 +85,13 @@ markdown_extensions: format: !!python/name:pymdownx.superfences.fence_code_format - md_in_html -copyright: | - +copyright: Copyright © 2024 Amazon Web Services plugins: - privacy - git-revision-date - search - - llmstxt: - markdown_description: Powertools for AWS Lambda (.NET) is a developer toolkit to implement Serverless best practices and increase developer velocity. It provides a suite of utilities for AWS Lambda Functions that makes tracing with AWS X-Ray, structured logging and creating custom metrics asynchronously easier. - full_output: llms-full.txt - sections: - Project Overview: - - index.md - - changelog.md - - roadmap.md - Core Utilities: - - core/logging.md - - core/metrics.md - - core/tracing.md - Utilities: - - utilities/idempotency.md - - utilities/batch-processing.md - - utilities/parameters.md - - utilities/jmespath-functions.md - - core/event_handler/appsync_events.md - - core/event_handler/bedrock_agent_function.md - - utilities/kafka.md - Getting Started: - - getting-started/logger/simple.md - - getting-started/logger/aspnet.md - - getting-started/logger/aot.md + extra_css: - stylesheets/extra.css extra_javascript: diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 5ed858e82..000000000 --- a/package-lock.json +++ /dev/null @@ -1,447 +0,0 @@ -{ - "name": "powertools-lambda-dotnet", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "powertools-lambda-dotnet", - "version": "1.0.0", - "license": "MIT", - "dependencies": { - "aws-cdk": "^2.1000.2", - "aws-cdk-lib": "^2.189.1" - } - }, - "node_modules/@aws-cdk/asset-awscli-v1": { - "version": "2.2.230", - "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.230.tgz", - "integrity": "sha512-kUnhKIYu42hqBa6a8x2/7o29ObpJgjYGQy28lZDq9awXyvpR62I2bRxrNKNR3uFUQz3ySuT9JXhGHhuZPdbnFw==", - "license": "Apache-2.0" - }, - "node_modules/@aws-cdk/asset-node-proxy-agent-v6": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.1.0.tgz", - "integrity": "sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A==" - }, - "node_modules/@aws-cdk/cloud-assembly-schema": { - "version": "41.2.0", - "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-41.2.0.tgz", - "integrity": "sha512-JaulVS6z9y5+u4jNmoWbHZRs9uGOnmn/ktXygNWKNu1k6lF3ad4so3s18eRu15XCbUIomxN9WPYT6Ehh7hzONw==", - "bundleDependencies": [ - "jsonschema", - "semver" - ], - "license": "Apache-2.0", - "dependencies": { - "jsonschema": "~1.4.1", - "semver": "^7.7.1" - }, - "engines": { - "node": ">= 14.15.0" - } - }, - "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": { - "version": "1.4.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": { - "version": "7.7.1", - "inBundle": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/aws-cdk": { - "version": "2.1000.2", - "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.1000.2.tgz", - "integrity": "sha512-QsXqJhGWjHNqP7etgE3sHOTiDBXItmSKdFKgsm1qPMBabCMyFfmWZnEeUxfZ4sMaIoxvLpr3sqoWSNeLuUk4sg==", - "bin": { - "cdk": "bin/cdk" - }, - "engines": { - "node": ">= 16.0.0" - }, - "optionalDependencies": { - "fsevents": "2.3.2" - } - }, - "node_modules/aws-cdk-lib": { - "version": "2.189.1", - "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.189.1.tgz", - "integrity": "sha512-9JU0yUr2iRTJ1oCPrHyx7hOtBDWyUfyOcdb6arlumJnMcQr2cyAMASY8HuAXHc8Y10ipVp8dRTW+J4/132IIYA==", - "bundleDependencies": [ - "@balena/dockerignore", - "case", - "fs-extra", - "ignore", - "jsonschema", - "minimatch", - "punycode", - "semver", - "table", - "yaml", - "mime-types" - ], - "license": "Apache-2.0", - "dependencies": { - "@aws-cdk/asset-awscli-v1": "^2.2.229", - "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.0", - "@aws-cdk/cloud-assembly-schema": "^41.0.0", - "@balena/dockerignore": "^1.0.2", - "case": "1.6.3", - "fs-extra": "^11.3.0", - "ignore": "^5.3.2", - "jsonschema": "^1.5.0", - "mime-types": "^2.1.35", - "minimatch": "^3.1.2", - "punycode": "^2.3.1", - "semver": "^7.7.1", - "table": "^6.9.0", - "yaml": "1.10.2" - }, - "engines": { - "node": ">= 14.15.0" - }, - "peerDependencies": { - "constructs": "^10.0.0" - } - }, - "node_modules/aws-cdk-lib/node_modules/@balena/dockerignore": { - "version": "1.0.2", - "inBundle": true, - "license": "Apache-2.0" - }, - "node_modules/aws-cdk-lib/node_modules/ajv": { - "version": "8.17.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/aws-cdk-lib/node_modules/ansi-regex": { - "version": "5.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/aws-cdk-lib/node_modules/ansi-styles": { - "version": "4.3.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/aws-cdk-lib/node_modules/astral-regex": { - "version": "2.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/aws-cdk-lib/node_modules/balanced-match": { - "version": "1.0.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/aws-cdk-lib/node_modules/brace-expansion": { - "version": "1.1.11", - "inBundle": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/aws-cdk-lib/node_modules/case": { - "version": "1.6.3", - "inBundle": true, - "license": "(MIT OR GPL-3.0-or-later)", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/aws-cdk-lib/node_modules/color-convert": { - "version": "2.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/aws-cdk-lib/node_modules/color-name": { - "version": "1.1.4", - "inBundle": true, - "license": "MIT" - }, - "node_modules/aws-cdk-lib/node_modules/concat-map": { - "version": "0.0.1", - "inBundle": true, - "license": "MIT" - }, - "node_modules/aws-cdk-lib/node_modules/emoji-regex": { - "version": "8.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/aws-cdk-lib/node_modules/fast-deep-equal": { - "version": "3.1.3", - "inBundle": true, - "license": "MIT" - }, - "node_modules/aws-cdk-lib/node_modules/fast-uri": { - "version": "3.0.6", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "inBundle": true, - "license": "BSD-3-Clause" - }, - "node_modules/aws-cdk-lib/node_modules/fs-extra": { - "version": "11.3.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, - "node_modules/aws-cdk-lib/node_modules/graceful-fs": { - "version": "4.2.11", - "inBundle": true, - "license": "ISC" - }, - "node_modules/aws-cdk-lib/node_modules/ignore": { - "version": "5.3.2", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/aws-cdk-lib/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/aws-cdk-lib/node_modules/json-schema-traverse": { - "version": "1.0.0", - "inBundle": true, - "license": "MIT" - }, - "node_modules/aws-cdk-lib/node_modules/jsonfile": { - "version": "6.1.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/aws-cdk-lib/node_modules/jsonschema": { - "version": "1.5.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/aws-cdk-lib/node_modules/lodash.truncate": { - "version": "4.4.2", - "inBundle": true, - "license": "MIT" - }, - "node_modules/aws-cdk-lib/node_modules/mime-db": { - "version": "1.52.0", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/aws-cdk-lib/node_modules/mime-types": { - "version": "2.1.35", - "inBundle": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/aws-cdk-lib/node_modules/minimatch": { - "version": "3.1.2", - "inBundle": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/aws-cdk-lib/node_modules/punycode": { - "version": "2.3.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/aws-cdk-lib/node_modules/require-from-string": { - "version": "2.0.2", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/aws-cdk-lib/node_modules/semver": { - "version": "7.7.1", - "inBundle": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/aws-cdk-lib/node_modules/slice-ansi": { - "version": "4.0.0", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/aws-cdk-lib/node_modules/string-width": { - "version": "4.2.3", - "inBundle": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/aws-cdk-lib/node_modules/strip-ansi": { - "version": "6.0.1", - "inBundle": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/aws-cdk-lib/node_modules/table": { - "version": "6.9.0", - "inBundle": true, - "license": "BSD-3-Clause", - "dependencies": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/aws-cdk-lib/node_modules/universalify": { - "version": "2.0.1", - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/aws-cdk-lib/node_modules/yaml": { - "version": "1.10.2", - "inBundle": true, - "license": "ISC", - "engines": { - "node": ">= 6" - } - }, - "node_modules/constructs": { - "version": "10.4.2", - "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.4.2.tgz", - "integrity": "sha512-wsNxBlAott2qg8Zv87q3eYZYgheb9lchtBfjHzzLHtXbttwSrHPs1NNQbBrmbb1YZvYg2+Vh0Dor76w4mFxJkA==", - "peer": true - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - } - } -} diff --git a/package.json b/package.json deleted file mode 100644 index e402ead58..000000000 --- a/package.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "powertools-lambda-dotnet", - "version": "1.0.0", - "description": "Powertools for AWS Lambda (.NET)", - "main": "index.js", - "directories": { - "doc": "docs", - "example": "examples" - }, - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "author": "", - "license": "MIT", - "dependencies": { - "aws-cdk": "^2.1000.2", - "aws-cdk-lib": "^2.189.1" - } -} diff --git a/poetry.lock b/poetry.lock index 80a21cfb6..b54242f51 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,144 +1,14 @@ -# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. - -[[package]] -name = "babel" -version = "2.17.0" -description = "Internationalization utilities" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"}, - {file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"}, -] - -[package.extras] -dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata ; sys_platform == \"win32\""] - -[[package]] -name = "certifi" -version = "2025.1.31" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, - {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, -] - -[[package]] -name = "charset-normalizer" -version = "3.4.1" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765"}, - {file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"}, - {file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"}, -] +# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. [[package]] name = "click" -version = "8.1.8" +version = "8.1.3" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ - {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, - {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, ] [package.dependencies] @@ -150,7 +20,6 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["main"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -162,7 +31,6 @@ version = "2.1.0" description = "Copy your docs directly to the gh-pages branch." optional = false python-versions = "*" -groups = ["main"] files = [ {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, @@ -176,14 +44,13 @@ dev = ["flake8", "markdown", "twine", "wheel"] [[package]] name = "gitdb" -version = "4.0.12" +version = "4.0.10" description = "Git Object Database" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ - {file = "gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf"}, - {file = "gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571"}, + {file = "gitdb-4.0.10-py3-none-any.whl", hash = "sha256:c286cf298426064079ed96a9e4a9d39e7f3e9bf15ba60701e95f5492f28415c7"}, + {file = "gitdb-4.0.10.tar.gz", hash = "sha256:6eb990b69df4e15bad899ea868dc46572c3f75339735663b81de79b06f17eb9a"}, ] [package.dependencies] @@ -191,73 +58,49 @@ smmap = ">=3.0.1,<6" [[package]] name = "gitpython" -version = "3.1.44" +version = "3.1.41" description = "GitPython is a Python library used to interact with Git repositories" optional = false python-versions = ">=3.7" -groups = ["main"] files = [ - {file = "GitPython-3.1.44-py3-none-any.whl", hash = "sha256:9e0e10cda9bed1ee64bc9a6de50e7e38a9c9943241cd7f585f6df3ed28011110"}, - {file = "gitpython-3.1.44.tar.gz", hash = "sha256:c87e30b26253bf5418b01b0660f818967f3c503193838337fe5e573331249269"}, + {file = "GitPython-3.1.41-py3-none-any.whl", hash = "sha256:c36b6634d069b3f719610175020a9aed919421c87552185b085e04fbbdb10b7c"}, + {file = "GitPython-3.1.41.tar.gz", hash = "sha256:ed66e624884f76df22c8e16066d567aaa5a37d5b5fa19db2c6df6f7156db9048"}, ] [package.dependencies] gitdb = ">=4.0.1,<5" [package.extras] -doc = ["sphinx (>=7.1.2,<7.2)", "sphinx-autodoc-typehints", "sphinx_rtd_theme"] -test = ["coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock ; python_version < \"3.8\"", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "typing-extensions ; python_version < \"3.11\""] - -[[package]] -name = "idna" -version = "3.10" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.6" -groups = ["main"] -files = [ - {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, - {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, -] - -[package.extras] -all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] +test = ["black", "coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest (>=7.3.1)", "pytest-cov", "pytest-instafail", "pytest-mock", "pytest-sugar", "sumtypes"] [[package]] name = "importlib-metadata" -version = "8.6.1" +version = "5.2.0" description = "Read metadata from Python packages" optional = false -python-versions = ">=3.9" -groups = ["main"] -markers = "python_version < \"3.10\"" +python-versions = ">=3.7" files = [ - {file = "importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e"}, - {file = "importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580"}, + {file = "importlib_metadata-5.2.0-py3-none-any.whl", hash = "sha256:0eafa39ba42bf225fc00e67f701d71f85aead9f878569caf13c3724f704b970f"}, + {file = "importlib_metadata-5.2.0.tar.gz", hash = "sha256:404d48d62bba0b7a77ff9d405efd91501bef2e67ff4ace0bed40a0cf28c3c7cd"}, ] [package.dependencies] -zipp = ">=3.20" +zipp = ">=0.5" [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -enabler = ["pytest-enabler (>=2.2)"] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] -test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] -type = ["pytest-mypy"] +testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] [[package]] name = "jinja2" -version = "3.1.6" +version = "3.1.5" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" -groups = ["main"] files = [ - {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, - {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, + {file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"}, + {file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"}, ] [package.dependencies] @@ -268,92 +111,68 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "markdown" -version = "3.7" -description = "Python implementation of John Gruber's Markdown." +version = "3.3.7" +description = "Python implementation of Markdown." optional = false -python-versions = ">=3.8" -groups = ["main"] +python-versions = ">=3.6" files = [ - {file = "Markdown-3.7-py3-none-any.whl", hash = "sha256:7eb6df5690b81a1d7942992c97fad2938e956e79df20cbc6186e9c3a77b1c803"}, - {file = "markdown-3.7.tar.gz", hash = "sha256:2ae2471477cfd02dbbf038d5d9bc226d40def84b4fe2986e49b59b6b472bbed2"}, + {file = "Markdown-3.3.7-py3-none-any.whl", hash = "sha256:f5da449a6e1c989a4cea2631aa8ee67caa5a2ef855d551c88f9e309f4634c621"}, + {file = "Markdown-3.3.7.tar.gz", hash = "sha256:cbb516f16218e643d8e0a95b309f77eb118cb138d39a4f27851e6a63581db874"}, ] [package.dependencies] importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} [package.extras] -docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] testing = ["coverage", "pyyaml"] [[package]] name = "markupsafe" -version = "3.0.2" +version = "2.1.1" description = "Safely add untrusted strings to HTML/XML markup." optional = false -python-versions = ">=3.9" -groups = ["main"] +python-versions = ">=3.7" files = [ - {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, - {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, - {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, - {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, - {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, - {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, - {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, - {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, + {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, ] [[package]] @@ -362,7 +181,6 @@ version = "1.3.4" description = "A deep merge function for 🐍." optional = false python-versions = ">=3.6" -groups = ["main"] files = [ {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, @@ -374,7 +192,6 @@ version = "1.1.2" description = "Manage multiple versions of your MkDocs-powered documentation" optional = false python-versions = "*" -groups = ["main"] files = [ {file = "mike-1.1.2-py3-none-any.whl", hash = "sha256:4c307c28769834d78df10f834f57f810f04ca27d248f80a75f49c6fa2d1527ca"}, {file = "mike-1.1.2.tar.gz", hash = "sha256:56c3f1794c2d0b5fdccfa9b9487beb013ca813de2e3ad0744724e9d34d40b77b"}, @@ -392,53 +209,31 @@ test = ["coverage", "flake8 (>=3.0)", "shtab"] [[package]] name = "mkdocs" -version = "1.6.1" +version = "1.4.2" description = "Project documentation with Markdown." optional = false -python-versions = ">=3.8" -groups = ["main"] +python-versions = ">=3.7" files = [ - {file = "mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e"}, - {file = "mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2"}, + {file = "mkdocs-1.4.2-py3-none-any.whl", hash = "sha256:c8856a832c1e56702577023cd64cc5f84948280c1c0fcc6af4cd39006ea6aa8c"}, + {file = "mkdocs-1.4.2.tar.gz", hash = "sha256:8947af423a6d0facf41ea1195b8e1e8c85ad94ac95ae307fe11232e0424b11c5"}, ] [package.dependencies] click = ">=7.0" colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} ghp-import = ">=1.0" -importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} +importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""} jinja2 = ">=2.11.1" -markdown = ">=3.3.6" -markupsafe = ">=2.0.1" +markdown = ">=3.2.1,<3.4" mergedeep = ">=1.3.4" -mkdocs-get-deps = ">=0.2.0" packaging = ">=20.5" -pathspec = ">=0.11.1" pyyaml = ">=5.1" pyyaml-env-tag = ">=0.1" watchdog = ">=2.0" [package.extras] i18n = ["babel (>=2.9.0)"] -min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4) ; platform_system == \"Windows\"", "ghp-import (==1.0)", "importlib-metadata (==4.4) ; python_version < \"3.10\"", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"] - -[[package]] -name = "mkdocs-get-deps" -version = "0.2.0" -description = "MkDocs extension that lists all dependencies according to a mkdocs.yml file" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134"}, - {file = "mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c"}, -] - -[package.dependencies] -importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""} -mergedeep = ">=1.3.4" -platformdirs = ">=2.2.0" -pyyaml = ">=5.1" +min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.3)", "jinja2 (==2.11.1)", "markdown (==3.2.1)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "packaging (==20.5)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "typing-extensions (==3.10)", "watchdog (==2.0)"] [[package]] name = "mkdocs-git-revision-date-plugin" @@ -446,7 +241,6 @@ version = "0.3.2" description = "MkDocs plugin for setting revision date from git per markdown file." optional = false python-versions = ">=3.4" -groups = ["main"] files = [ {file = "mkdocs_git_revision_date_plugin-0.3.2-py3-none-any.whl", hash = "sha256:2e67956cb01823dd2418e2833f3623dee8604cdf223bddd005fe36226a56f6ef"}, ] @@ -458,147 +252,83 @@ mkdocs = ">=0.17" [[package]] name = "mkdocs-material" -version = "9.6.5" -description = "Documentation that simply works" +version = "7.3.6" +description = "A Material Design theme for MkDocs" optional = false -python-versions = ">=3.8" -groups = ["main"] +python-versions = "*" files = [ - {file = "mkdocs_material-9.6.5-py3-none-any.whl", hash = "sha256:aad3e6fb860c20870f75fb2a69ef901f1be727891e41adb60b753efcae19453b"}, - {file = "mkdocs_material-9.6.5.tar.gz", hash = "sha256:b714679a8c91b0ffe2188e11ed58c44d2523e9c2ae26a29cc652fa7478faa21f"}, + {file = "mkdocs-material-7.3.6.tar.gz", hash = "sha256:1b1dbd8ef2508b358d93af55a5c5db3f141c95667fad802301ec621c40c7c217"}, + {file = "mkdocs_material-7.3.6-py2.py3-none-any.whl", hash = "sha256:1b6b3e9e09f922c2d7f1160fe15c8f43d4adc0d6fb81aa6ff0cbc7ef5b78ec75"}, ] [package.dependencies] -babel = ">=2.10,<3.0" -colorama = ">=0.4,<1.0" -jinja2 = ">=3.0,<4.0" -markdown = ">=3.2,<4.0" -mkdocs = ">=1.6,<2.0" -mkdocs-material-extensions = ">=1.3,<2.0" -paginate = ">=0.5,<1.0" -pygments = ">=2.16,<3.0" -pymdown-extensions = ">=10.2,<11.0" -regex = ">=2022.4" -requests = ">=2.26,<3.0" - -[package.extras] -git = ["mkdocs-git-committers-plugin-2 (>=1.1,<3)", "mkdocs-git-revision-date-localized-plugin (>=1.2.4,<2.0)"] -imaging = ["cairosvg (>=2.6,<3.0)", "pillow (>=10.2,<11.0)"] -recommended = ["mkdocs-minify-plugin (>=0.7,<1.0)", "mkdocs-redirects (>=1.2,<2.0)", "mkdocs-rss-plugin (>=1.6,<2.0)"] +jinja2 = ">=2.11.1" +markdown = ">=3.2" +mkdocs = ">=1.2.3" +mkdocs-material-extensions = ">=1.0" +pygments = ">=2.10" +pymdown-extensions = ">=9.0" [[package]] name = "mkdocs-material-extensions" -version = "1.3.1" +version = "1.1.1" description = "Extension pack for Python Markdown and MkDocs Material." optional = false -python-versions = ">=3.8" -groups = ["main"] +python-versions = ">=3.7" files = [ - {file = "mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31"}, - {file = "mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443"}, + {file = "mkdocs_material_extensions-1.1.1-py3-none-any.whl", hash = "sha256:e41d9f38e4798b6617ad98ca8f7f1157b1e4385ac1459ca1e4ea219b556df945"}, + {file = "mkdocs_material_extensions-1.1.1.tar.gz", hash = "sha256:9c003da71e2cc2493d910237448c672e00cefc800d3d6ae93d2fc69979e3bd93"}, ] [[package]] name = "packaging" -version = "24.2" +version = "22.0" description = "Core utilities for Python packages" optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, - {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, -] - -[[package]] -name = "paginate" -version = "0.5.7" -description = "Divides large result sets into pages for easier browsing" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591"}, - {file = "paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945"}, -] - -[package.extras] -dev = ["pytest", "tox"] -lint = ["black"] - -[[package]] -name = "pathspec" -version = "0.12.1" -description = "Utility library for gitignore style pattern matching of file paths." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, - {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, -] - -[[package]] -name = "platformdirs" -version = "4.3.6" -description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." -optional = false -python-versions = ">=3.8" -groups = ["main"] +python-versions = ">=3.7" files = [ - {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, - {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, + {file = "packaging-22.0-py3-none-any.whl", hash = "sha256:957e2148ba0e1a3b282772e791ef1d8083648bc131c8ab0c1feba110ce1146c3"}, + {file = "packaging-22.0.tar.gz", hash = "sha256:2198ec20bd4c017b8f9717e00f0c8714076fc2fd93816750ab48e2c41de2cfd3"}, ] -[package.extras] -docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] -type = ["mypy (>=1.11.2)"] - [[package]] name = "pygments" -version = "2.19.1" +version = "2.15.0" description = "Pygments is a syntax highlighting package written in Python." optional = false -python-versions = ">=3.8" -groups = ["main"] +python-versions = ">=3.7" files = [ - {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, - {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, + {file = "Pygments-2.15.0-py3-none-any.whl", hash = "sha256:77a3299119af881904cd5ecd1ac6a66214b6e9bed1f2db16993b54adede64094"}, + {file = "Pygments-2.15.0.tar.gz", hash = "sha256:f7e36cffc4c517fbc252861b9a6e4644ca0e5abadf9a113c72d1358ad09b9500"}, ] [package.extras] -windows-terminal = ["colorama (>=0.4.6)"] +plugins = ["importlib-metadata"] [[package]] name = "pymdown-extensions" -version = "10.14.3" +version = "10.0" description = "Extension pack for Python Markdown." optional = false -python-versions = ">=3.8" -groups = ["main"] +python-versions = ">=3.7" files = [ - {file = "pymdown_extensions-10.14.3-py3-none-any.whl", hash = "sha256:05e0bee73d64b9c71a4ae17c72abc2f700e8bc8403755a00580b49a4e9f189e9"}, - {file = "pymdown_extensions-10.14.3.tar.gz", hash = "sha256:41e576ce3f5d650be59e900e4ceff231e0aed2a88cf30acaee41e02f063a061b"}, + {file = "pymdown_extensions-10.0-py3-none-any.whl", hash = "sha256:e6cbe8ace7d8feda30bc4fd6a21a073893a9a0e90c373e92d69ce5b653051f55"}, + {file = "pymdown_extensions-10.0.tar.gz", hash = "sha256:9a77955e63528c2ee98073a1fb3207c1a45607bc74a34ef21acd098f46c3aa8a"}, ] [package.dependencies] -markdown = ">=3.6" +markdown = ">=3.2" pyyaml = "*" -[package.extras] -extra = ["pygments (>=2.19.1)"] - [[package]] name = "python-dateutil" -version = "2.9.0.post0" +version = "2.8.2" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main"] files = [ - {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, - {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] [package.dependencies] @@ -606,65 +336,51 @@ six = ">=1.5" [[package]] name = "pyyaml" -version = "6.0.2" +version = "6.0" description = "YAML parser and emitter for Python" optional = false -python-versions = ">=3.8" -groups = ["main"] +python-versions = ">=3.6" files = [ - {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, - {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, - {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, - {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, - {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, - {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, - {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, - {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, - {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, - {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, - {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, - {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, - {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, - {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, - {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, - {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, - {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, + {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, + {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, + {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, ] [[package]] @@ -673,7 +389,6 @@ version = "0.1" description = "A custom YAML tag for referencing environment variables in YAML files. " optional = false python-versions = ">=3.6" -groups = ["main"] files = [ {file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"}, {file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"}, @@ -682,181 +397,34 @@ files = [ [package.dependencies] pyyaml = "*" -[[package]] -name = "regex" -version = "2024.11.6" -description = "Alternative regular expression module, to replace re." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"}, - {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"}, - {file = "regex-2024.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86"}, - {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67"}, - {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d"}, - {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2"}, - {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008"}, - {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62"}, - {file = "regex-2024.11.6-cp310-cp310-win32.whl", hash = "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e"}, - {file = "regex-2024.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519"}, - {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638"}, - {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7"}, - {file = "regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20"}, - {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114"}, - {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3"}, - {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f"}, - {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0"}, - {file = "regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55"}, - {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89"}, - {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d"}, - {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34"}, - {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d"}, - {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45"}, - {file = "regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9"}, - {file = "regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60"}, - {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a"}, - {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9"}, - {file = "regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2"}, - {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4"}, - {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577"}, - {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3"}, - {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e"}, - {file = "regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe"}, - {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e"}, - {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29"}, - {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39"}, - {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51"}, - {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad"}, - {file = "regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54"}, - {file = "regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b"}, - {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84"}, - {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4"}, - {file = "regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0"}, - {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0"}, - {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7"}, - {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7"}, - {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c"}, - {file = "regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3"}, - {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07"}, - {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e"}, - {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6"}, - {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4"}, - {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d"}, - {file = "regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff"}, - {file = "regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a"}, - {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3a51ccc315653ba012774efca4f23d1d2a8a8f278a6072e29c7147eee7da446b"}, - {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ad182d02e40de7459b73155deb8996bbd8e96852267879396fb274e8700190e3"}, - {file = "regex-2024.11.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba9b72e5643641b7d41fa1f6d5abda2c9a263ae835b917348fc3c928182ad467"}, - {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40291b1b89ca6ad8d3f2b82782cc33807f1406cf68c8d440861da6304d8ffbbd"}, - {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdf58d0e516ee426a48f7b2c03a332a4114420716d55769ff7108c37a09951bf"}, - {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a36fdf2af13c2b14738f6e973aba563623cb77d753bbbd8d414d18bfaa3105dd"}, - {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1cee317bfc014c2419a76bcc87f071405e3966da434e03e13beb45f8aced1a6"}, - {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50153825ee016b91549962f970d6a4442fa106832e14c918acd1c8e479916c4f"}, - {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea1bfda2f7162605f6e8178223576856b3d791109f15ea99a9f95c16a7636fb5"}, - {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:df951c5f4a1b1910f1a99ff42c473ff60f8225baa1cdd3539fe2819d9543e9df"}, - {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:072623554418a9911446278f16ecb398fb3b540147a7828c06e2011fa531e773"}, - {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f654882311409afb1d780b940234208a252322c24a93b442ca714d119e68086c"}, - {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:89d75e7293d2b3e674db7d4d9b1bee7f8f3d1609428e293771d1a962617150cc"}, - {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:f65557897fc977a44ab205ea871b690adaef6b9da6afda4790a2484b04293a5f"}, - {file = "regex-2024.11.6-cp38-cp38-win32.whl", hash = "sha256:6f44ec28b1f858c98d3036ad5d7d0bfc568bdd7a74f9c24e25f41ef1ebfd81a4"}, - {file = "regex-2024.11.6-cp38-cp38-win_amd64.whl", hash = "sha256:bb8f74f2f10dbf13a0be8de623ba4f9491faf58c24064f32b65679b021ed0001"}, - {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5704e174f8ccab2026bd2f1ab6c510345ae8eac818b613d7d73e785f1310f839"}, - {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:220902c3c5cc6af55d4fe19ead504de80eb91f786dc102fbd74894b1551f095e"}, - {file = "regex-2024.11.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7e351589da0850c125f1600a4c4ba3c722efefe16b297de54300f08d734fbf"}, - {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5056b185ca113c88e18223183aa1a50e66507769c9640a6ff75859619d73957b"}, - {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e34b51b650b23ed3354b5a07aab37034d9f923db2a40519139af34f485f77d0"}, - {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5670bce7b200273eee1840ef307bfa07cda90b38ae56e9a6ebcc9f50da9c469b"}, - {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08986dce1339bc932923e7d1232ce9881499a0e02925f7402fb7c982515419ef"}, - {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93c0b12d3d3bc25af4ebbf38f9ee780a487e8bf6954c115b9f015822d3bb8e48"}, - {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:764e71f22ab3b305e7f4c21f1a97e1526a25ebdd22513e251cf376760213da13"}, - {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f056bf21105c2515c32372bbc057f43eb02aae2fda61052e2f7622c801f0b4e2"}, - {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:69ab78f848845569401469da20df3e081e6b5a11cb086de3eed1d48f5ed57c95"}, - {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:86fddba590aad9208e2fa8b43b4c098bb0ec74f15718bb6a704e3c63e2cef3e9"}, - {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:684d7a212682996d21ca12ef3c17353c021fe9de6049e19ac8481ec35574a70f"}, - {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a03e02f48cd1abbd9f3b7e3586d97c8f7a9721c436f51a5245b3b9483044480b"}, - {file = "regex-2024.11.6-cp39-cp39-win32.whl", hash = "sha256:41758407fc32d5c3c5de163888068cfee69cb4c2be844e7ac517a52770f9af57"}, - {file = "regex-2024.11.6-cp39-cp39-win_amd64.whl", hash = "sha256:b2837718570f95dd41675328e111345f9b7095d821bac435aac173ac80b19983"}, - {file = "regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519"}, -] - -[[package]] -name = "requests" -version = "2.32.4" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"}, - {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"}, -] - -[package.dependencies] -certifi = ">=2017.4.17" -charset_normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - [[package]] name = "six" -version = "1.17.0" +version = "1.16.0" description = "Python 2 and 3 compatibility utilities" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main"] +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ - {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, - {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] [[package]] name = "smmap" -version = "5.0.2" +version = "5.0.0" description = "A pure Python implementation of a sliding window memory map manager" optional = false -python-versions = ">=3.7" -groups = ["main"] -files = [ - {file = "smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e"}, - {file = "smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5"}, -] - -[[package]] -name = "urllib3" -version = "2.5.0" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=3.9" -groups = ["main"] +python-versions = ">=3.6" files = [ - {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, - {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, + {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, + {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, ] -[package.extras] -brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] -h2 = ["h2 (>=4,<5)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] - [[package]] name = "verspec" version = "0.1.0" description = "Flexible version handling" optional = false python-versions = "*" -groups = ["main"] files = [ {file = "verspec-0.1.0-py3-none-any.whl", hash = "sha256:741877d5633cc9464c45a469ae2a31e801e6dbbaa85b9675d481cda100f11c31"}, {file = "verspec-0.1.0.tar.gz", hash = "sha256:c4504ca697b2056cdb4bfa7121461f5a0e81809255b41c03dda4ba823637c01e"}, @@ -867,42 +435,39 @@ test = ["coverage", "flake8 (>=3.7)", "mypy", "pretend", "pytest"] [[package]] name = "watchdog" -version = "6.0.0" +version = "2.2.0" description = "Filesystem events monitoring" optional = false -python-versions = ">=3.9" -groups = ["main"] +python-versions = ">=3.6" files = [ - {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26"}, - {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112"}, - {file = "watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3"}, - {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c"}, - {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2"}, - {file = "watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c"}, - {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948"}, - {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860"}, - {file = "watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0"}, - {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c"}, - {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134"}, - {file = "watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b"}, - {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8"}, - {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a"}, - {file = "watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c"}, - {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881"}, - {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11"}, - {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa"}, - {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2"}, - {file = "watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a"}, - {file = "watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680"}, - {file = "watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f"}, - {file = "watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282"}, + {file = "watchdog-2.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ed91c3ccfc23398e7aa9715abf679d5c163394b8cad994f34f156d57a7c163dc"}, + {file = "watchdog-2.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:76a2743402b794629a955d96ea2e240bd0e903aa26e02e93cd2d57b33900962b"}, + {file = "watchdog-2.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:920a4bda7daa47545c3201a3292e99300ba81ca26b7569575bd086c865889090"}, + {file = "watchdog-2.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ceaa9268d81205876bedb1069f9feab3eccddd4b90d9a45d06a0df592a04cae9"}, + {file = "watchdog-2.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1893d425ef4fb4f129ee8ef72226836619c2950dd0559bba022b0818c63a7b60"}, + {file = "watchdog-2.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9e99c1713e4436d2563f5828c8910e5ff25abd6ce999e75f15c15d81d41980b6"}, + {file = "watchdog-2.2.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a5bd9e8656d07cae89ac464ee4bcb6f1b9cecbedc3bf1334683bed3d5afd39ba"}, + {file = "watchdog-2.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3a048865c828389cb06c0bebf8a883cec3ae58ad3e366bcc38c61d8455a3138f"}, + {file = "watchdog-2.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e722755d995035dd32177a9c633d158f2ec604f2a358b545bba5bed53ab25bca"}, + {file = "watchdog-2.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:af4b5c7ba60206759a1d99811b5938ca666ea9562a1052b410637bb96ff97512"}, + {file = "watchdog-2.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:619d63fa5be69f89ff3a93e165e602c08ed8da402ca42b99cd59a8ec115673e1"}, + {file = "watchdog-2.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1f2b0665c57358ce9786f06f5475bc083fea9d81ecc0efa4733fd0c320940a37"}, + {file = "watchdog-2.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:441024df19253bb108d3a8a5de7a186003d68564084576fecf7333a441271ef7"}, + {file = "watchdog-2.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1a410dd4d0adcc86b4c71d1317ba2ea2c92babaf5b83321e4bde2514525544d5"}, + {file = "watchdog-2.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:28704c71afdb79c3f215c90231e41c52b056ea880b6be6cee035c6149d658ed1"}, + {file = "watchdog-2.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2ac0bd7c206bb6df78ef9e8ad27cc1346f2b41b1fef610395607319cdab89bc1"}, + {file = "watchdog-2.2.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:27e49268735b3c27310883012ab3bd86ea0a96dcab90fe3feb682472e30c90f3"}, + {file = "watchdog-2.2.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:2af1a29fd14fc0a87fb6ed762d3e1ae5694dcde22372eebba50e9e5be47af03c"}, + {file = "watchdog-2.2.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:c7bd98813d34bfa9b464cf8122e7d4bec0a5a427399094d2c17dd5f70d59bc61"}, + {file = "watchdog-2.2.0-py3-none-manylinux2014_i686.whl", hash = "sha256:56fb3f40fc3deecf6e518303c7533f5e2a722e377b12507f6de891583f1b48aa"}, + {file = "watchdog-2.2.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:74535e955359d79d126885e642d3683616e6d9ab3aae0e7dcccd043bd5a3ff4f"}, + {file = "watchdog-2.2.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:cf05e6ff677b9655c6e9511d02e9cc55e730c4e430b7a54af9c28912294605a4"}, + {file = "watchdog-2.2.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:d6ae890798a3560688b441ef086bb66e87af6b400a92749a18b856a134fc0318"}, + {file = "watchdog-2.2.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:e5aed2a700a18c194c39c266900d41f3db0c1ebe6b8a0834b9995c835d2ca66e"}, + {file = "watchdog-2.2.0-py3-none-win32.whl", hash = "sha256:d0fb5f2b513556c2abb578c1066f5f467d729f2eb689bc2db0739daf81c6bb7e"}, + {file = "watchdog-2.2.0-py3-none-win_amd64.whl", hash = "sha256:1f8eca9d294a4f194ce9df0d97d19b5598f310950d3ac3dd6e8d25ae456d4c8a"}, + {file = "watchdog-2.2.0-py3-none-win_ia64.whl", hash = "sha256:ad0150536469fa4b693531e497ffe220d5b6cd76ad2eda474a5e641ee204bbb6"}, + {file = "watchdog-2.2.0.tar.gz", hash = "sha256:83cf8bc60d9c613b66a4c018051873d6273d9e45d040eed06d6a96241bd8ec01"}, ] [package.extras] @@ -910,26 +475,20 @@ watchmedo = ["PyYAML (>=3.10)"] [[package]] name = "zipp" -version = "3.21.0" +version = "3.19.1" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false -python-versions = ">=3.9" -groups = ["main"] -markers = "python_version < \"3.10\"" +python-versions = ">=3.8" files = [ - {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, - {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, + {file = "zipp-3.19.1-py3-none-any.whl", hash = "sha256:2828e64edb5386ea6a52e7ba7cdb17bb30a73a858f5eb6eb93d8d36f5ea26091"}, + {file = "zipp-3.19.1.tar.gz", hash = "sha256:35427f6d5594f4acf82d25541438348c26736fa9b3afa2754bcd63cdb99d8e8f"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] -cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -enabler = ["pytest-enabler (>=2.2)"] -test = ["big-O", "importlib-resources ; python_version < \"3.9\"", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] -type = ["pytest-mypy"] +test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [metadata] -lock-version = "2.1" +lock-version = "2.0" python-versions = "^3.9" -content-hash = "7aaff1ad012ab8384fc464aee9558ca91c70ae990fbef27af1a3fb0ce416cfa2" +content-hash = "f082f3c26f0efb74521bfc86a3242f15f24e7108ee3f1bf455a08f11594cda75" diff --git a/pyproject.toml b/pyproject.toml index c1fa378fb..da01f9225 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,8 +7,8 @@ authors = ["Amazon Web Services"] [tool.poetry.dependencies] python = "^3.9" mike = "^1.0.1" +mkdocs-material = "^7.2.4" mkdocs-git-revision-date-plugin = "^0.3.1" -mkdocs-material = "^9.6.5" [tool.poetry.dev-dependencies] diff --git a/version.json b/version.json index c2735039c..d52ea67c7 100644 --- a/version.json +++ b/version.json @@ -1,18 +1,12 @@ { - "Core": { - "Logging": "2.0.2", - "Metrics": "2.1.1", - "Tracing": "1.6.2", - "Metrics.AspNetCore": "0.1.0" - }, - "Utilities": { - "Parameters": "1.3.1", - "Idempotency": "1.4.0", - "BatchProcessing": "1.2.1", - "EventHandler": "1.0.1", - "EventHandler.Resolvers.BedrockAgentFunction": "1.0.1", - "Kafka.Json": "1.0.2", - "Kafka.Avro": "1.0.2", - "Kafka.Protobuf": "1.0.2" - } + "Core": { + "Logging": "1.6.4", + "Metrics": "1.8.0", + "Tracing": "1.6.1" + }, + "Utilities": { + "Parameters": "1.3.0", + "Idempotency": "1.3.0", + "BatchProcessing": "1.2.0" + } }