diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6ff36476..1ea3b08a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: 1.67.0 + toolchain: 1.76.0 override: true - name: Install deps run: sudo apt-get install libssl-dev @@ -47,10 +47,5 @@ jobs: with: path: target key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} - - name: Setup DynamoDB Local - uses: rrainn/dynamodb-action@v2.0.0 - with: - port: 8000 - cors: "*" - name: test - run: AWS_ACCESS_KEY_ID=dummy AWS_SECRET_ACCESS_KEY=dummy make test + run: make test diff --git a/.gitignore b/.gitignore index 5feb9078..c31107e1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ +.vscode +node_modules target -node_modules \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 8c0b60c6..99bf9bbb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -54,9 +54,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.74" +version = "0.1.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" dependencies = [ "proc-macro2", "quote", @@ -69,6 +69,328 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "aws-config" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2a89e0000cde82447155d64eeb71720b933b4396a6fbbebad3f8b4f88ca7b54" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sdk-sso", + "aws-sdk-ssooidc", + "aws-sdk-sts", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "hex", + "http 0.2.11", + "hyper", + "ring 0.17.8", + "time 0.3.36", + "tokio", + "tracing", + "url", + "zeroize", +] + +[[package]] +name = "aws-credential-types" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16838e6c9e12125face1c1eff1343c75e3ff540de98ff7ebd61874a89bcfeb9" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "zeroize", +] + +[[package]] +name = "aws-runtime" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75588e7ee5e8496eed939adac2035a6dbab9f7eb2acdd9ab2d31856dab6f3955" +dependencies = [ + "aws-credential-types", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.11", + "http-body 0.4.6", + "percent-encoding", + "pin-project-lite", + "tracing", + "uuid", +] + +[[package]] +name = "aws-sdk-dynamodb" +version = "1.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1da0290e57949a362d3f106285bb539e8a282e6c1b0053f6e02b3fc14f2d730" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.11", + "once_cell", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-sso" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fef2d9ca2b43051224ed326ed9960a85e277b7d554a2cd0397e57c0553d86e64" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "http 0.2.11", + "once_cell", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-ssooidc" +version = "1.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c869d1f5c4ee7437b79c3c1664ddbf7a60231e893960cf82b2b299a5ccf2cc5d" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "http 0.2.11", + "once_cell", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-sts" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e2b4a632a59e4fab7abf1db0d94a3136ad7871aba46bebd1fdb95c7054afcdb" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-query", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "http 0.2.11", + "once_cell", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sigv4" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58b56f1cbe6fd4d0c2573df72868f20ab1c125ca9c9dbce17927a463433a2e57" +dependencies = [ + "aws-credential-types", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "form_urlencoded", + "hex", + "hmac 0.12.1", + "http 0.2.11", + "http 1.0.0", + "once_cell", + "percent-encoding", + "sha2 0.10.8", + "time 0.3.36", + "tracing", +] + +[[package]] +name = "aws-smithy-async" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62220bc6e97f946ddd51b5f1361f78996e704677afc518a4ff66b7a72ea1378c" +dependencies = [ + "futures-util", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "aws-smithy-http" +version = "0.60.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a7de001a1b9a25601016d8057ea16e31a45fdca3751304c8edf4ad72e706c08" +dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.11", + "http-body 0.4.6", + "once_cell", + "percent-encoding", + "pin-project-lite", + "pin-utils", + "tracing", +] + +[[package]] +name = "aws-smithy-json" +version = "0.60.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4683df9469ef09468dad3473d129960119a0d3593617542b7d52086c8486f2d6" +dependencies = [ + "aws-smithy-types", +] + +[[package]] +name = "aws-smithy-query" +version = "0.60.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fbd61ceb3fe8a1cb7352e42689cec5335833cd9f94103a61e98f9bb61c64bb" +dependencies = [ + "aws-smithy-types", + "urlencoding", +] + +[[package]] +name = "aws-smithy-runtime" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "607e8b53aeb2bc23fb332159d72a69650cd9643c161d76cd3b7f88ac00b5a1bb" +dependencies = [ + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "fastrand", + "h2", + "http 0.2.11", + "http-body 0.4.6", + "http-body 1.0.0", + "hyper", + "hyper-rustls 0.24.2", + "once_cell", + "pin-project-lite", + "pin-utils", + "rustls 0.21.10", + "tokio", + "tracing", +] + +[[package]] +name = "aws-smithy-runtime-api" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b7d790d553d163c7d80a4e06e2906bf24b9172c9ebe045fc3a274e9358ab7bb" +dependencies = [ + "aws-smithy-async", + "aws-smithy-types", + "bytes", + "http 0.2.11", + "http 1.0.0", + "pin-project-lite", + "tokio", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-types" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b6764ba7e1c5ede1c9f9e4046645534f06c2581402461c559b481a420330a83" +dependencies = [ + "base64-simd", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.11", + "http 1.0.0", + "http-body 0.4.6", + "http-body 1.0.0", + "http-body-util", + "itoa", + "num-integer", + "pin-project-lite", + "pin-utils", + "ryu", + "serde", + "time 0.3.36", + "tokio", + "tokio-util", +] + +[[package]] +name = "aws-smithy-xml" +version = "0.60.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d123fbc2a4adc3c301652ba8e149bf4bc1d1725affb9784eb20c953ace06bf55" +dependencies = [ + "xmlparser", +] + +[[package]] +name = "aws-types" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02fa328e19c849b20ef7ada4c9b581dd12351ff35ecc7642d06e69de4f98407c" +dependencies = [ + "aws-credential-types", + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "http 0.2.11", + "rustc_version", + "tracing", +] + [[package]] name = "backtrace" version = "0.3.69" @@ -92,9 +414,25 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.5" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref", + "vsimd", +] [[package]] name = "bitflags" @@ -104,9 +442,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.1" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" [[package]] name = "block-buffer" @@ -117,11 +455,20 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b" [[package]] name = "bytes" @@ -133,14 +480,21 @@ dependencies = [ ] [[package]] -name = "cc" -version = "1.0.83" +name = "bytes-utils" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" dependencies = [ - "libc", + "bytes", + "either", ] +[[package]] +name = "cc" +version = "1.0.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02f341c093d19155a6e41631ce5971aac4e9a868262212153124c15fa22d1cdc" + [[package]] name = "cfg-if" version = "1.0.0" @@ -149,15 +503,15 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" dependencies = [ "android-tzdata", "iana-time-zone", "num-traits", "serde", - "windows-targets", + "windows-targets 0.52.4", ] [[package]] @@ -177,9 +531,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", @@ -187,33 +541,43 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cpufeatures" -version = "0.2.9" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" dependencies = [ "cfg-if", ] +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "crypto-mac" -version = "0.11.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +checksum = "25fab6889090c8133f3deb8f73ba3c65a7f456f66436fc012a1b1e272b1e103e" dependencies = [ "generic-array", "subtle", @@ -221,9 +585,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.9" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", ] @@ -243,6 +607,17 @@ dependencies = [ "generic-array", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common", + "subtle", +] + [[package]] name = "dirs-next" version = "2.0.0" @@ -264,14 +639,26 @@ dependencies = [ "winapi", ] +[[package]] +name = "either" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" -version = "0.3.5" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -301,6 +688,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + [[package]] name = "fuchsia-cprng" version = "0.1.1" @@ -309,9 +705,9 @@ checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" [[package]] name = "futures" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", @@ -324,9 +720,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", @@ -334,15 +730,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" dependencies = [ "futures-core", "futures-task", @@ -351,15 +747,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", @@ -368,21 +764,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", @@ -425,9 +821,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", "libc", @@ -436,22 +832,22 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "h2" -version = "0.3.21" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" +checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" dependencies = [ "bytes", "fnv", "futures-core", "futures-sink", "futures-util", - "http", + "http 0.2.11", "indexmap", "slab", "tokio", @@ -461,15 +857,15 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.12.3" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] name = "hermit-abi" -version = "0.3.3" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hex" @@ -484,14 +880,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" dependencies = [ "crypto-mac", - "digest", + "digest 0.9.0", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", ] [[package]] name = "http" -version = "0.2.9" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea" dependencies = [ "bytes", "fnv", @@ -500,12 +916,35 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.5" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.11", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" dependencies = [ "bytes", - "http", + "http 1.0.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +dependencies = [ + "bytes", + "futures-core", + "http 1.0.0", + "http-body 1.0.0", "pin-project-lite", ] @@ -523,22 +962,22 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "0.14.27" +version = "0.14.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" dependencies = [ "bytes", "futures-channel", "futures-core", "futures-util", "h2", - "http", - "http-body", + "http 0.2.11", + "http-body 0.4.6", "httparse", "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.9", + "socket2", "tokio", "tower-service", "tracing", @@ -551,13 +990,46 @@ version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" dependencies = [ - "http", + "http 0.2.11", + "hyper", + "log", + "rustls 0.20.9", + "rustls-native-certs 0.6.3", + "tokio", + "tokio-rustls 0.23.4", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.11", + "hyper", + "log", + "rustls 0.21.10", + "rustls-native-certs 0.6.3", + "tokio", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "hyper-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "399c78f9338483cb7e630c8474b07268983c6bd5acee012e4211f9f7bb21b070" +dependencies = [ + "futures-util", + "http 0.2.11", "hyper", "log", - "rustls", - "rustls-native-certs", + "rustls 0.22.4", + "rustls-native-certs 0.7.0", + "rustls-pki-types", "tokio", - "tokio-rustls", + "tokio-rustls 0.25.0", ] [[package]] @@ -575,9 +1047,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.58" +version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -602,13 +1074,23 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "indexmap" -version = "1.9.3" +version = "2.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +checksum = "967d6dd42f16dbf0eb8040cb9e477933562684d3918f7d253f2ff9087fb3e7a3" dependencies = [ - "autocfg", + "equivalent", "hashbrown", ] @@ -623,15 +1105,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "js-sys" -version = "0.3.64" +version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" dependencies = [ "wasm-bindgen", ] @@ -644,15 +1126,26 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.149" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "libredox" +version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" +checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +dependencies = [ + "bitflags 2.4.2", + "libc", + "redox_syscall 0.4.1", +] [[package]] name = "linux-raw-sys" -version = "0.4.10" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "lock_api" @@ -666,9 +1159,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "matchers" @@ -685,35 +1178,35 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15" dependencies = [ - "block-buffer", - "digest", + "block-buffer 0.9.0", + "digest 0.9.0", "opaque-debug", ] [[package]] name = "memchr" -version = "2.6.4" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -750,11 +1243,20 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", ] @@ -771,18 +1273,18 @@ dependencies = [ [[package]] name = "object" -version = "0.32.1" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "opaque-debug" @@ -792,11 +1294,11 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.57" +version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bac25ee399abb46215765b1cb35bc0212377e58a061560d8b29b024fd0430e7c" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "cfg-if", "foreign-types", "libc", @@ -824,9 +1326,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.93" +version = "0.9.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db4d56a4c0478783083cfafcc42493dd4a981d41669da64b4572a2a089b51b1d" +checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff" dependencies = [ "cc", "libc", @@ -834,6 +1336,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "outref" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4030760ffd992bef45b0ae3f10ce1aba99e33464c90d14dd7c039884963ddc7a" + [[package]] name = "overload" version = "0.1.1" @@ -865,11 +1373,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "percent-encoding" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" @@ -885,9 +1399,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.27" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "powerfmt" @@ -919,9 +1433,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.83" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" +checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6" dependencies = [ "unicode-ident", ] @@ -964,7 +1478,14 @@ name = "raiden" version = "0.1.0" dependencies = [ "again", - "base64 0.21.5", + "aws-config", + "aws-credential-types", + "aws-sdk-dynamodb", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "base64 0.22.1", + "hyper-rustls 0.25.0", + "paste", "pretty_assertions", "raiden", "raiden-derive", @@ -1094,33 +1615,33 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.3.5" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "redox_users" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" dependencies = [ - "getrandom 0.2.10", - "redox_syscall 0.2.16", + "getrandom 0.2.12", + "libredox", "thiserror", ] [[package]] name = "regex" -version = "1.10.2" +version = "1.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.3", + "regex-automata 0.4.5", "regex-syntax 0.8.2", ] @@ -1135,15 +1656,21 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" dependencies = [ "aho-corasick", "memchr", "regex-syntax 0.8.2", ] +[[package]] +name = "regex-lite" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b661b2f27137bdbc16f00eda72866a92bb28af1753ffbd56744fb6e2e9cd8e" + [[package]] name = "regex-syntax" version = "0.6.29" @@ -1173,16 +1700,17 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.4" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fce3045ffa7c981a6ee93f640b538952e155f1ae3a1a02b84547fc7a56b7059a" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", - "getrandom 0.2.10", + "cfg-if", + "getrandom 0.2.12", "libc", "spin 0.9.8", "untrusted 0.9.0", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -1196,9 +1724,9 @@ dependencies = [ "bytes", "crc32fast", "futures", - "http", + "http 0.2.11", "hyper", - "hyper-rustls", + "hyper-rustls 0.23.2", "hyper-tls", "lazy_static", "log", @@ -1252,11 +1780,11 @@ dependencies = [ "base64 0.13.1", "bytes", "chrono", - "digest", + "digest 0.9.0", "futures", "hex", - "hmac", - "http", + "hmac 0.11.0", + "http 0.2.11", "hyper", "log", "md-5", @@ -1265,7 +1793,7 @@ dependencies = [ "rusoto_credential", "rustc_version", "serde", - "sha2", + "sha2 0.9.9", "tokio", ] @@ -1290,9 +1818,9 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustc-serialize" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" +checksum = "fe834bc780604f4674073badbad26d7219cadfb4a2275802db12cbae17498401" [[package]] name = "rustc_version" @@ -1305,15 +1833,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.19" +version = "0.38.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "745ecfa778e66b2b63c88a61cb36e0eea109e803b0b86bf9879fbc77c70e86ed" +checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -1328,6 +1856,32 @@ dependencies = [ "webpki", ] +[[package]] +name = "rustls" +version = "0.21.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +dependencies = [ + "log", + "ring 0.17.8", + "rustls-webpki 0.101.7", + "sct", +] + +[[package]] +name = "rustls" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +dependencies = [ + "log", + "ring 0.17.8", + "rustls-pki-types", + "rustls-webpki 0.102.4", + "subtle", + "zeroize", +] + [[package]] name = "rustls-native-certs" version = "0.6.3" @@ -1335,25 +1889,75 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" dependencies = [ "openssl-probe", - "rustls-pemfile", + "rustls-pemfile 1.0.4", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-native-certs" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +dependencies = [ + "openssl-probe", + "rustls-pemfile 2.1.2", + "rustls-pki-types", "schannel", "security-framework", ] [[package]] name = "rustls-pemfile" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64 0.21.5", + "base64 0.21.7", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64 0.22.1", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring 0.17.8", + "untrusted 0.9.0", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" +dependencies = [ + "ring 0.17.8", + "rustls-pki-types", + "untrusted 0.9.0", ] [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] name = "safe-builder" @@ -1377,11 +1981,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -1392,12 +1996,12 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sct" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.16.20", - "untrusted 0.7.1", + "ring 0.17.8", + "untrusted 0.9.0", ] [[package]] @@ -1425,24 +2029,24 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" [[package]] name = "serde" -version = "1.0.203" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", @@ -1451,9 +2055,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.117" +version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" dependencies = [ "itoa", "ryu", @@ -1466,13 +2070,24 @@ version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ - "block-buffer", + "block-buffer 0.9.0", "cfg-if", "cpufeatures", - "digest", + "digest 0.9.0", "opaque-debug", ] +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -1484,9 +2099,9 @@ dependencies = [ [[package]] name = "shlex" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" @@ -1508,28 +2123,18 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.1" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" [[package]] name = "socket2" -version = "0.4.9" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" dependencies = [ "libc", - "winapi", -] - -[[package]] -name = "socket2" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" -dependencies = [ - "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -1546,9 +2151,9 @@ checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "subtle" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" @@ -1574,31 +2179,30 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.8.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", - "redox_syscall 0.3.5", "rustix", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" dependencies = [ "proc-macro2", "quote", @@ -1607,9 +2211,9 @@ dependencies = [ [[package]] name = "thread_local" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ "cfg-if", "once_cell", @@ -1657,11 +2261,26 @@ dependencies = [ "time-core", ] +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" -version = "1.38.0" +version = "1.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" +checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" dependencies = [ "backtrace", "bytes", @@ -1670,16 +2289,16 @@ dependencies = [ "num_cpus", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.5", + "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] name = "tokio-macros" -version = "2.3.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", @@ -1702,16 +2321,37 @@ version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ - "rustls", + "rustls 0.20.9", "tokio", "webpki", ] +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.10", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls 0.22.4", + "rustls-pki-types", + "tokio", +] + [[package]] name = "tokio-util" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ "bytes", "futures-core", @@ -1791,9 +2431,9 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" @@ -1801,17 +2441,32 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-segmentation" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "untrusted" @@ -1825,13 +2480,30 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "uuid" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" dependencies = [ - "getrandom 0.2.10", + "getrandom 0.2.12", ] [[package]] @@ -1852,6 +2524,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + [[package]] name = "want" version = "0.3.1" @@ -1881,9 +2559,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.87" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1891,9 +2569,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.87" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" dependencies = [ "bumpalo", "log", @@ -1906,9 +2584,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.37" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97" dependencies = [ "cfg-if", "js-sys", @@ -1918,9 +2596,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.87" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1928,9 +2606,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" dependencies = [ "proc-macro2", "quote", @@ -1941,9 +2619,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" [[package]] name = "wasm-timer" @@ -1962,9 +2640,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.64" +version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" dependencies = [ "js-sys", "wasm-bindgen", @@ -1976,7 +2654,7 @@ version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" dependencies = [ - "ring 0.17.4", + "ring 0.17.8", "untrusted 0.9.0", ] @@ -2004,11 +2682,11 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" -version = "0.51.1" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets", + "windows-targets 0.52.4", ] [[package]] @@ -2017,7 +2695,16 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.4", ] [[package]] @@ -2026,13 +2713,28 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +dependencies = [ + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", ] [[package]] @@ -2041,48 +2743,96 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" + [[package]] name = "xml-rs" version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a" +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + [[package]] name = "yansi" version = "0.5.1" @@ -2091,6 +2841,6 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "zeroize" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" diff --git a/README.md b/README.md index 9de866a7..040b8585 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,53 @@ ## Examples -### get_item example +You can see more examples [here](https://github.com/raiden-rs/raiden-dynamo/tree/master/raiden/examples) + +### Generating client + +`raiden` uses `aws-sdk-dynamodb` or `rusoto_dynamodb` as internal client. + +#### With aws-sdk-dynamodb (`aws-sdk` is enabled) + +```rust +use raiden::*; -``` rust #[derive(Raiden)] #[raiden(table_name = "user")] -pub struct User { +struct User { + #[raiden(partition_key)] + id: String, + name: String, +} + +#[tokio::main] +async fn main() { + // Simply, specify the region. + let client = User::client(config::Region::from_static("us-east-1")); + + // You can also specify aws-sdk-dynamodb client. + let client = { + let sdk_config = aws_config::defaults(aws_config::BehaviorVersion::latest()) + .region(raiden::config::Region::from_static("us-east-1")) + .load() + .await; + let sdk_client = raiden::Client::new(&sdk_config); + + User::client_with(sdk_client) + }; + + // Run operations... +} +``` + +#### With rusoto_dynamodb ( `rusoto` or `rusoto_rustls` or `rustls` is enabled) + +```rust +use raiden::*; + +#[derive(Raiden)] +#[raiden(table_name = "user")] +struct User { #[raiden(partition_key)] id: String, name: String, @@ -23,14 +64,107 @@ pub struct User { #[tokio::main] async fn main() { + // Simply, specify the region. let client = User::client(Region::UsEast1); + + // You can also specify rusoto_core client. + let client = User::client_with(Client::shared(), Region::UsEast1); + + // Run operations... +} +``` + +#### Set prefix/suffix to the table name + +```rust +use raiden::*; + +#[derive(Raiden)] +#[raiden(table_name = "user")] +struct User { + #[raiden(partition_key)] + id: String, + name: String, +} + +#[tokio::main] +async fn main() { + let client = User::client(config::Region::from_static("us-east-1")) + .table_prefix("prefix-") + .table_suffix("-suffix"); + + // Print `prefix-user-suffix` + println!("{}", client.table_name()); +} +``` + +#### Configure retry strategy + +NOTE: Default retry strategy differs between `aws-sdk` and `rusoto` ( or `rusoto_rustls` ) + +- `aws-sdk` ... Not retry in raiden by default. Because you can configure retry strategy using `aws_config`. Or you can configure your own strategy like next example. +- `rusoto` or `rusoto_rustls` ... Enabled retrying in raiden by default. See detail [here](https://github.com/mythrnr/raiden-dynamo/blob/master/raiden/src/retry/mod.rs). + +```rust +use raiden::*; + +#[derive(Raiden)] +#[raiden(table_name = "user")] +struct User { + #[raiden(partition_key)] + id: String, + name: String, +} + +// Force retry 3 times. +struct MyRetryStrategy; + +impl RetryStrategy for MyRetryStrategy { + fn should_retry(&self, _error: &RaidenError) -> bool { + true + } + + fn policy(&self) -> Policy { + Policy::Limit(3) + } +} + +#[tokio::main] +async fn main() { + let client = User::client(config::Region::from_static("us-east-1")) + .with_retries(Box::new(MyRetryStrategy)); + + // Run operations... +} +``` + +### Running operations + +#### get_item + +```rust +use raiden::*; + +#[derive(Raiden)] +#[raiden(table_name = "user")] +struct User { + #[raiden(partition_key)] + id: String, + name: String, +} + +#[tokio::main] +async fn main() { + let client = /* generate client */; let _res = client.get("user_primary_key").run().await; } ``` -### put_item example +#### put_item + +```rust +use raiden::*; -``` rust #[derive(Raiden)] #[raiden(table_name = "user")] pub struct User { @@ -41,18 +175,20 @@ pub struct User { #[tokio::main] async fn main() { - let client = User::client(Region::UsEast1); + let client = /* generate client */; let input = User::put_item_builder() .id("foo".to_owned()) .name("bokuweb".to_owned()) .build(); - let res = client.put(&input).run().await; + let _res = client.put(&input).run().await; } ``` -### batch_get_item example +#### batch_get_item + +```rust +use raiden::*; -``` rust #[derive(Raiden, Debug, PartialEq)] pub struct User { #[raiden(partition_key)] @@ -63,7 +199,7 @@ pub struct User { #[tokio::main] async fn main() { - let client = User::client(Region::UsEast1); + let client = /* generate client */; let keys: Vec<(&str, usize)> = vec![("Alice", 1992), ("Bob", 1976), ("Charlie", 2002)]; let res = client.batch_get(keys).run().await; } @@ -89,31 +225,25 @@ tracing = "0.1" ### Requirements -- Rust +- Rust (1.76.0+) - Deno (1.13.2+) - GNU Make - Docker Engine -### Setup - -``` -AWS_ACCESS_KEY_ID=awsdummy AWS_SECRET_ACCESS_KEY=awsdummy make dynamo -``` - -This starts up DynamoDB on Docker container, and then arranges test fixtures. - -### Test +### Run tests ``` -AWS_ACCESS_KEY_ID=awsdummy AWS_SECRET_ACCESS_KEY=awsdummy make test +make test ``` NOTE: Don't recommend to use `cargo test` because our test suite doesn't support running tests in parallel. Use `cargo test -- --test-threads=1` instead of it. -### Example +### Run examples ``` -AWS_ACCESS_KEY_ID=awsdummy AWS_SECRET_ACCESS_KEY=awsdummy cargo run --example EXAMPLE_NAME +make dynamo + +AWS_ACCESS_KEY_ID=dummy AWS_SECRET_ACCESS_KEY=dummy cargo run --example EXAMPLE_NAME ``` ### Utility diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 00000000..7097b32d --- /dev/null +++ b/compose.yaml @@ -0,0 +1,19 @@ +name: "raiden-rs-raiden-dynamo" + +services: + dynamodb: + image: "amazon/dynamodb-local:latest" + command: "-jar DynamoDBLocal.jar -sharedDb" + ports: + - "127.0.0.1:8000:8000" + + aws-cli: + image: "amazon/aws-cli:latest" + command: "dynamodb list-tables --endpoint-url http://dynamodb:8000" + depends_on: + - "dynamodb" + environment: + AWS_ACCESS_KEY_ID: "dummy" + AWS_SECRET_ACCESS_KEY: "dummy" + AWS_REGION: "ap-northeast-1" + restart: "on-failure" diff --git a/makefile b/makefile index 85def913..388a4fb4 100644 --- a/makefile +++ b/makefile @@ -1,26 +1,33 @@ +export AWS_ACCESS_KEY_ID := dummy +export AWS_SECRET_ACCESS_KEY := dummy + .PHONY: dynamo dynamo: - - docker rm -f dynamodb - - docker stop dynamodb - docker run --rm -d --name dynamodb -p 8000:8000 amazon/dynamodb-local:latest + docker compose down --volumes + docker compose up -d --wait dynamodb + docker compose up aws-cli deno run --allow-net=localhost:8000 --allow-env --no-check ./setup/setup.ts .PHONY: test test: make dynamo - cargo test -- --test-threads=1 + cargo test --no-default-features --features aws-sdk -- --test-threads=1 + make dynamo + cargo test --no-default-features --features rusoto -- --test-threads=1 .PHONY: lint lint: - cargo clippy --all-targets -- -D warnings - cargo clippy --all-targets --no-default-features --features rustls -- -D warnings + cargo clippy --all-targets --no-default-features --features aws-sdk -- -D warnings + cargo clippy --all-targets --no-default-features --features rusoto -- -D warnings + cargo clippy --all-targets --no-default-features --features rusoto_rustls -- -D warnings cargo clippy --all-targets --features tracing -- -D warnings .PHONY: check-deps check-deps: - cargo machete || echo - cargo +nightly udeps --all-targets - cargo +nightly udeps --all-targets --no-default-features --features rustls + cargo machete + cargo +nightly udeps --all-targets --no-default-features --features aws-sdk + cargo +nightly udeps --all-targets --no-default-features --features rusoto + cargo +nightly udeps --all-targets --no-default-features --features rusoto_rustls cargo +nightly udeps --all-targets --features tracing .PHONY: licenses diff --git a/raiden-derive/Cargo.toml b/raiden-derive/Cargo.toml index cb669cb9..d11fad3a 100644 --- a/raiden-derive/Cargo.toml +++ b/raiden-derive/Cargo.toml @@ -13,10 +13,12 @@ proc-macro = true [dependencies] convert_case = "^0.6.0" ident_case = "^1.0.1" -proc-macro2 = "^1.0.71" -quote = "^1.0.33" -syn = "^2.0.42" +proc-macro2 = "^1.0.84" +quote = "^1.0.36" +syn = "^2.0.66" [features] -default = [] +default = ["rusoto"] +aws-sdk = [] +rusoto = [] tracing = [] diff --git a/raiden-derive/src/aws_sdk/client.rs b/raiden-derive/src/aws_sdk/client.rs new file mode 100644 index 00000000..ce5d5b2e --- /dev/null +++ b/raiden-derive/src/aws_sdk/client.rs @@ -0,0 +1,91 @@ +use quote::*; +use syn::*; + +pub(crate) fn expand_client_constructor( + struct_name: &Ident, + client_name: &Ident, + dynamodb_client_name: &Ident, + table_name: &str, + fields: &FieldsNamed, + rename_all_type: &crate::rename::RenameAllType, +) -> proc_macro2::TokenStream { + let insertion_attribute_name = fields.named.iter().map(|f| { + let ident = &f.ident.clone().unwrap(); + let renamed = crate::finder::find_rename_value(&f.attrs); + let result = crate::create_renamed(ident.to_string(), renamed, *rename_all_type); + quote! { + names.insert( + format!("#{}", #result.clone()), + #result.to_string(), + ); + } + }); + + quote! { + impl #client_name { + pub fn new(region: ::raiden::aws_sdk::config::Region) -> Self { + let config = ::raiden::aws_sdk::config::Config::builder() + .behavior_version(::raiden::aws_sdk::config::BehaviorVersion::latest()) + .region(region) + .build(); + let client = ::raiden::#dynamodb_client_name::from_conf(config); + Self::new_with_dynamo_db_client(client) + } + + pub fn new_with_client(client: ::raiden::#dynamodb_client_name) -> Self { + Self::new_with_dynamo_db_client(client) + } + + fn new_with_dynamo_db_client(client: ::raiden::#dynamodb_client_name) -> Self { + let names = { + let mut names: ::raiden::AttributeNames = std::collections::HashMap::new(); + #(#insertion_attribute_name)* + names + }; + let projection_expression = Some(names.keys().map(|v| v.to_string()).collect::>().join(", ")); + + Self { + table_name: #table_name, + table_prefix: "".to_owned(), + table_suffix: "".to_owned(), + client, + // NOTE: + // Since the AWS SDK provides a retry option, + // configure it to not retry by default. + retry_condition: ::raiden::RetryCondition::never(), + attribute_names: Some(names), + projection_expression + } + } + + pub fn with_retries(mut self, s: Box) -> Self { + self.retry_condition.strategy = s; + self + } + + pub fn table_prefix(mut self, prefix: impl Into) -> Self { + self.table_prefix = prefix.into(); + self + } + + pub fn table_suffix(mut self, suffix: impl Into) -> Self { + self.table_suffix = suffix.into(); + self + } + + pub fn table_name(&self) -> String { + format!("{}{}{}", self.table_prefix, self.table_name.to_string(), self.table_suffix) + } + } + + impl #struct_name { + pub fn client(region: ::raiden::aws_sdk::config::Region) -> #client_name { + #client_name::new(region) + } + + pub fn client_with(client: ::raiden::#dynamodb_client_name) -> #client_name { + #client_name::new_with_client(client) + } + } + } +} diff --git a/raiden-derive/src/aws_sdk/mod.rs b/raiden-derive/src/aws_sdk/mod.rs new file mode 100644 index 00000000..c5a66c24 --- /dev/null +++ b/raiden-derive/src/aws_sdk/mod.rs @@ -0,0 +1,2 @@ +pub mod client; +pub mod ops; diff --git a/raiden-derive/src/aws_sdk/ops/batch_delete.rs b/raiden-derive/src/aws_sdk/ops/batch_delete.rs new file mode 100644 index 00000000..abb3d04f --- /dev/null +++ b/raiden-derive/src/aws_sdk/ops/batch_delete.rs @@ -0,0 +1,194 @@ +use quote::*; +use syn::*; + +pub(crate) fn expand_batch_delete( + partition_key: &(Ident, Type), + sort_key: &Option<(Ident, Type)>, + struct_name: &Ident, +) -> proc_macro2::TokenStream { + let trait_name = format_ident!("{}BatchDelete", struct_name); + let client_name = format_ident!("{}Client", struct_name); + let builder_name = format_ident!("{}BatchDeleteBuilder", struct_name); + let (partition_key_ident, partition_key_type) = partition_key; + + let client_trait = if let Some(sort_key) = sort_key { + let (sort_key_ident, sort_key_type) = sort_key; + quote! { + pub trait #trait_name { + fn batch_delete(&self, keys: std::vec::Vec<(impl Into<#partition_key_type>, impl Into<#sort_key_type>)>) -> #builder_name; + } + + impl #trait_name for #client_name { + fn batch_delete(&self, keys: std::vec::Vec<(impl Into<#partition_key_type>, impl Into<#sort_key_type>)>) -> #builder_name { + use ::std::iter::FromIterator; + + let write_requests = { + let mut write_requests = vec![]; + for (pk, sk) in keys.into_iter() { + let pk_attr_value = pk.into().into_attr(); + let sk_attr_value = sk.into().into_attr(); + let write_request = { + let delete_request = ::raiden::aws_sdk::types::DeleteRequest::builder() + .set_key(Some(::std::collections::HashMap::from_iter([ + (stringify!(#partition_key_ident).to_string(), pk_attr_value), + (stringify!(#sort_key_ident).to_string(), sk_attr_value), + ]))) + .build() + .expect("should be built"); + + ::raiden::aws_sdk::types::WriteRequest::builder() + .delete_request(delete_request) + .build() + }; + write_requests.push(write_request); + } + + write_requests + }; + + #builder_name { + client: &self.client, + write_requests, + table_name: self.table_name(), + } + } + } + } + } else { + quote! { + pub trait #trait_name { + fn batch_delete(&self, keys: std::vec::Vec>) -> #builder_name; + } + + impl #trait_name for #client_name { + fn batch_delete(&self, keys: std::vec::Vec>) -> #builder_name { + use ::std::iter::FromIterator; + + let write_requests = { + let mut write_requests = vec![]; + for pk in keys.into_iter() { + let pk_attr_value = pk.into().into_attr(); + let write_request = { + let delete_request = ::raiden::aws_sdk::types::DeleteRequest::builder() + .set_key(Some(::std::collections::HashMap::from_iter([ + (stringify!(#partition_key_ident).to_string(), pk_attr_value), + ]))) + .build() + .expect("should be built"); + + ::raiden::aws_sdk::types::WriteRequest::builder() + .delete_request(delete_request) + .build() + }; + write_requests.push(write_request); + } + + write_requests + }; + + #builder_name { + client: &self.client, + write_requests, + table_name: self.table_name(), + } + } + } + } + }; + + let api_call_token = super::api_call_token!("batch_write_item"); + let (call_inner_run, inner_run_args) = if cfg!(feature = "tracing") { + ( + quote! { #builder_name::inner_run(&self.table_name, &self.client, builder).await? }, + quote! { table_name: &str, }, + ) + } else { + ( + quote! { #builder_name::inner_run(&self.client, builder).await? }, + quote! {}, + ) + }; + + quote! { + #client_trait + + pub struct #builder_name<'a> { + pub client: &'a ::raiden::Client, + pub write_requests: ::std::vec::Vec<::raiden::aws_sdk::types::WriteRequest>, + pub table_name: String, + } + + impl<'a> #builder_name<'a> { + pub async fn run(mut self) -> Result<::raiden::batch_delete::BatchDeleteOutput, ::raiden::RaidenError> { + // TODO: set the number of retry to 5 for now, which should be made more flexible + const RETRY: usize = 5; + const MAX_ITEMS_PER_REQUEST: usize = 25; + + for _ in 0..RETRY { + loop { + let len = self.write_requests.len(); + + // len == 0 means there are no items to be processed anymore + if len == 0 { + break; + } + + let start = len.saturating_sub(MAX_ITEMS_PER_REQUEST); + let end = std::cmp::min(len, start + MAX_ITEMS_PER_REQUEST); + // take requests up to 25 from the request buffer + let req = self.write_requests.drain(start..end).collect::>(); + let request_items = vec![(self.table_name.clone(), req)] + .into_iter() + .collect::>(); + let builder = ::raiden::aws_sdk::operation::batch_write_item::BatchWriteItemInput::builder() + .set_request_items(Some(request_items)); + + let result = #call_inner_run; + + let mut unprocessed_items = match result.unprocessed_items { + None => { + // move on to the next iteration to check if there are unprocessed + // requests + continue; + } + Some(unprocessed_items) => { + if unprocessed_items.is_empty() { + // move on to the next iteration to check if there are unprocessed + // requests + continue; + } + + unprocessed_items + }, + }; + + let unprocessed_requests = unprocessed_items + .remove(&self.table_name) + .expect("request_items hashmap must have a value for the table name"); + // push unprocessed requests back to the request buffer + self.write_requests.extend(unprocessed_requests); + } + } + + // when retry is done the specified times, treat it as success even if there are + // still unprocessed items + let unprocessed_items = self.write_requests + .into_iter() + .filter_map(|write_request| write_request.delete_request) + .collect::>(); + Ok(::raiden::batch_delete::BatchDeleteOutput { + consumed_capacity: None, + unprocessed_items, + }) + } + + async fn inner_run( + #inner_run_args + client: &::raiden::Client, + builder: ::raiden::operation::batch_write_item::builders::BatchWriteItemInputBuilder, + ) -> Result<::raiden::operation::batch_write_item::BatchWriteItemOutput, ::raiden::RaidenError> { + Ok(#api_call_token?) + } + } + } +} diff --git a/raiden-derive/src/aws_sdk/ops/batch_get.rs b/raiden-derive/src/aws_sdk/ops/batch_get.rs new file mode 100644 index 00000000..b32de9a3 --- /dev/null +++ b/raiden-derive/src/aws_sdk/ops/batch_get.rs @@ -0,0 +1,218 @@ +use proc_macro2::*; +use quote::*; +use syn::*; + +use crate::rename::*; + +pub(crate) fn expand_batch_get( + partition_key: &(Ident, Type), + sort_key: &Option<(Ident, Type)>, + struct_name: &Ident, + fields: &FieldsNamed, + rename_all_type: RenameAllType, +) -> proc_macro2::TokenStream { + let trait_name = format_ident!("{}BatchGetItem", struct_name); + let client_name = format_ident!("{}Client", struct_name); + let builder_name = format_ident!("{}BatchGetItemBuilder", struct_name); + let from_item = super::expand_attr_to_item(format_ident!("res_item"), fields, rename_all_type); + let (partition_key_ident, partition_key_type) = partition_key; + + let builder_keys_type = if sort_key.is_none() { + quote! { std::vec::Vec<::raiden::aws_sdk::types::AttributeValue> } + } else { + quote! { std::vec::Vec<(::raiden::aws_sdk::types::AttributeValue, ::raiden::aws_sdk::types::AttributeValue)> } + }; + + let insertion_attribute_name = fields.named.iter().map(|f| { + let ident = &f.ident.clone().unwrap(); + let renamed = crate::finder::find_rename_value(&f.attrs); + let result = create_renamed(ident.to_string(), renamed, rename_all_type); + quote! { + names.insert( + format!("#{}", #result.clone()), + #result.to_string(), + ); + } + }); + + let builder_init = quote! { + let names = { + let mut names: ::raiden::AttributeNames = std::collections::HashMap::new(); + #(#insertion_attribute_name)* + names + }; + let projection_expression = Some(names.keys().map(|v| v.to_string()).collect::>().join(", ")); + + #builder_name { + client: &self.client, + table_name: self.table_name(), + keys: key_attrs, + attribute_names: Some(names), + projection_expression + } + }; + + let client_trait = if sort_key.is_none() { + quote! { + pub trait #trait_name { + fn batch_get(&self, keys: std::vec::Vec>) -> #builder_name; + } + + impl #trait_name for #client_name { + fn batch_get(&self, keys: std::vec::Vec>) -> #builder_name { + let key_attrs = keys.into_iter().map(|v| v.into().into_attr()).collect(); + + #builder_init + } + } + } + } else { + let (_, sort_key_type) = sort_key.clone().unwrap(); + quote! { + pub trait #trait_name { + fn batch_get(&self, keys: std::vec::Vec<(impl Into<#partition_key_type>, impl Into<#sort_key_type>)>) -> #builder_name; + } + + impl #trait_name for #client_name { + fn batch_get(&self, keys: std::vec::Vec<(impl Into<#partition_key_type>, impl Into<#sort_key_type>)>) -> #builder_name { + let key_attrs = keys.into_iter().map(|(pk, sk)| (pk.into().into_attr(), sk.into().into_attr())).collect(); + + #builder_init + } + } + } + }; + + let convert_to_external_proc = if let Some(sort_key) = sort_key { + let (sort_key_ident, _sort_key_type) = sort_key; + quote! { + for (pk_attr, sk_attr) in keys.into_iter() { + let key_val: std::collections::HashMap = ::std::collections::HashMap::from_iter([ + (stringify!(#partition_key_ident).to_owned(), pk_attr), + (stringify!(#sort_key_ident).to_owned(), sk_attr), + ]); + + item_builder = item_builder.keys(key_val); + } + } + } else { + quote! { + for key_attr in keys.into_iter() { + let key_val: std::collections::HashMap = ::std::collections::HashMap::from_iter([ + (stringify!(#partition_key_ident).to_owned(), key_attr), + ]); + + item_builder = item_builder.keys(key_val); + } + } + }; + + let api_call_token = super::api_call_token!("batch_get_item"); + let (call_inner_run, inner_run_args) = if cfg!(feature = "tracing") { + ( + quote! { #builder_name::inner_run(&self.table_name, &self.client, builder).await? }, + quote! { table_name: &str, }, + ) + } else { + ( + quote! { #builder_name::inner_run(&self.client, builder).await? }, + quote! {}, + ) + }; + + quote! { + #client_trait + + pub struct #builder_name<'a> { + pub client: &'a ::raiden::Client, + pub table_name: String, + pub keys: #builder_keys_type, + pub attribute_names: Option<::raiden::AttributeNames>, + pub projection_expression: Option + } + + impl<'a> #builder_name<'a> { + + #![allow(clippy::field_reassign_with_default)] + pub async fn run(mut self) -> Result<::raiden::batch_get::BatchGetOutput<#struct_name>, ::raiden::RaidenError> { + use ::std::iter::FromIterator; + + let mut items: std::vec::Vec<#struct_name> = vec![]; + let mut unprocessed_keys = ::raiden::aws_sdk::types::KeysAndAttributes::builder() + .set_keys(Some(vec![])) + .build() + .expect("should be built"); + + // TODO: for now set 5, however we should make it more flexible. + let mut unprocessed_retry = 5; + loop { + let unprocessed_key_len = unprocessed_keys.keys().len(); + let mut item_builder = ::raiden::aws_sdk::types::KeysAndAttributes::builder() + .set_expression_attribute_names(self.attribute_names.clone()) + .set_projection_expression(self.projection_expression.clone()) + .set_keys(Some(unprocessed_keys.keys)); + + if unprocessed_key_len < 100 { + let keys = self.keys.drain(0..std::cmp::min(100 - unprocessed_key_len, self.keys.len())); + #convert_to_external_proc + } + + let builder = ::raiden::aws_sdk::operation::batch_get_item::BatchGetItemInput::builder() + .request_items( + self.table_name.to_string(), + item_builder.build().expect("should be built"), + ); + + let res = #call_inner_run; + + if self.keys.is_empty() { + unprocessed_retry -= 1; + } + + if let Some(res_responses) = res.responses { + let mut res_responses = res_responses; + if let Some(res_items) = (&mut res_responses).remove(&self.table_name) { + for res_item in res_items.into_iter() { + let mut res_item = res_item; + items.push(#struct_name { + #(#from_item)* + }) + } + } else { + return Err(::raiden::RaidenError::ResourceNotFound(format!("'{}' table not found or not active", &self.table_name))); + } + } else { + return Err(::raiden::RaidenError::ResourceNotFound("resource not found".to_owned())); + } + + unprocessed_keys.keys = vec![]; + + if let Some(keys_by_table) = res.unprocessed_keys { + if let Some(keys_attrs) = keys_by_table.get(&self.table_name) { + unprocessed_keys.keys = keys_attrs.keys.clone(); + } + } + + if ( + self.keys.is_empty() && unprocessed_keys.keys.is_empty() + ) || unprocessed_retry == 0 + { + return Ok(::raiden::batch_get::BatchGetOutput { + consumed_capacity: res.consumed_capacity, + items, + unprocessed_keys: Some(unprocessed_keys), + }) + } + } + } + + async fn inner_run( + #inner_run_args + client: &::raiden::Client, + builder: ::raiden::aws_sdk::operation::batch_get_item::builders::BatchGetItemInputBuilder, + ) -> Result<::raiden::aws_sdk::operation::batch_get_item::BatchGetItemOutput, ::raiden::RaidenError> { + Ok(#api_call_token?) + } + } + } +} diff --git a/raiden-derive/src/aws_sdk/ops/delete.rs b/raiden-derive/src/aws_sdk/ops/delete.rs new file mode 100644 index 00000000..395de6b9 --- /dev/null +++ b/raiden-derive/src/aws_sdk/ops/delete.rs @@ -0,0 +1,142 @@ +use proc_macro2::*; +use quote::*; +use syn::*; + +pub(crate) fn expand_delete_item( + partition_key: &(Ident, Type), + sort_key: &Option<(Ident, Type)>, + struct_name: &Ident, +) -> TokenStream { + let trait_name = format_ident!("{}DeleteItem", struct_name); + let client_name = format_ident!("{}Client", struct_name); + let builder_name = format_ident!("{}DeleteItemBuilder", struct_name); + let condition_token_name = format_ident!("{}ConditionToken", struct_name); + let (partition_key_ident, partition_key_type) = partition_key; + + let client_trait = if let Some(sort_key) = sort_key { + let (sort_key_ident, sort_key_type) = sort_key; + quote! { + pub trait #trait_name { + fn delete(&self, pk: impl Into<#partition_key_type>, sk: impl Into<#sort_key_type>) -> #builder_name; + } + + impl #trait_name for #client_name { + fn delete(&self, pk: impl Into<#partition_key_type>, sk: impl Into<#sort_key_type>) -> #builder_name { + use ::std::iter::FromIterator; + + let pk_attr: ::raiden::aws_sdk::types::AttributeValue = pk.into().into_attr(); + let sk_attr: ::raiden::aws_sdk::types::AttributeValue = sk.into().into_attr(); + let key_set: std::collections::HashMap = + std::collections::HashMap::from_iter([ + (stringify!(#partition_key_ident).to_owned(), pk_attr), + (stringify!(#sort_key_ident).to_owned(), sk_attr), + ]); + + let mut builder = ::raiden::aws_sdk::operation::delete_item::DeleteItemInput::builder() + .set_key(Some(key_set)) + .table_name(self.table_name()); + + #builder_name { + client: &self.client, + builder, + } + } + } + } + } else { + quote! { + pub trait #trait_name { + fn delete(&self, key: impl Into<#partition_key_type>) -> #builder_name; + } + + impl #trait_name for #client_name { + fn delete(&self, key: impl Into<#partition_key_type>) -> #builder_name { + use ::std::iter::FromIterator; + + let key_attr: ::raiden::aws_sdk::types::AttributeValue = key.into().into_attr(); + let key_set: std::collections::HashMap = + std::collections::HashMap::from_iter([ + (stringify!(#partition_key_ident).to_owned(), key_attr), + ]); + + let mut builder = ::raiden::aws_sdk::operation::delete_item::DeleteItemInput::builder() + .set_key(Some(key_set)) + .table_name(self.table_name()); + + #builder_name { + client: &self.client, + builder, + } + } + } + } + }; + + let api_call_token = super::api_call_token!("delete_item"); + let (call_inner_run, inner_run_args) = if cfg!(feature = "tracing") { + ( + quote! { + let table_name = self + .builder + .get_table_name() + .clone() + .expect("table name should be set"); + + #builder_name::inner_run(&table_name, &self.client, self.builder).await? + }, + quote! { table_name: &str, }, + ) + } else { + ( + quote! { #builder_name::inner_run(&self.client, self.builder).await? }, + quote! {}, + ) + }; + + quote! { + #client_trait + + pub struct #builder_name<'a> { + pub client: &'a ::raiden::Client, + pub builder: ::raiden::aws_sdk::operation::delete_item::builders::DeleteItemInputBuilder, + } + + impl<'a> #builder_name<'a> { + pub fn raw_input(mut self, builder: ::raiden::aws_sdk::operation::delete_item::builders::DeleteItemInputBuilder) -> Self { + self.builder = builder; + self + } + + pub fn condition(mut self, cond: impl ::raiden::condition::ConditionBuilder<#condition_token_name>) -> Self { + let (cond_str, attr_names, attr_values) = cond.build(); + + if !attr_names.is_empty() { + self.builder = self.builder + .set_expression_attribute_names(Some(attr_names)); + } + + if !attr_values.is_empty() { + self.builder = self.builder + .set_expression_attribute_values(Some(attr_values)); + } + + self.builder = self.builder.condition_expression(cond_str); + self + } + + pub async fn run(self) -> Result<(), ::raiden::RaidenError> { + #call_inner_run; + Ok(()) + } + + async fn inner_run( + #inner_run_args + client: &::raiden::Client, + builder: ::raiden::aws_sdk::operation::delete_item::builders::DeleteItemInputBuilder, + ) -> Result<(), ::raiden::RaidenError> { + #api_call_token?; + Ok(()) + } + } + } +} diff --git a/raiden-derive/src/aws_sdk/ops/get.rs b/raiden-derive/src/aws_sdk/ops/get.rs new file mode 100644 index 00000000..ccebda57 --- /dev/null +++ b/raiden-derive/src/aws_sdk/ops/get.rs @@ -0,0 +1,156 @@ +use proc_macro2::*; +use quote::*; +use syn::*; + +pub(crate) fn expand_get_item( + partition_key: &(Ident, Type), + sort_key: &Option<(Ident, Type)>, + struct_name: &Ident, + fields: &syn::FieldsNamed, + rename_all_type: crate::rename::RenameAllType, +) -> TokenStream { + let trait_name = format_ident!("{}GetItem", struct_name); + let client_name = format_ident!("{}Client", struct_name); + let builder_name = format_ident!("{}GetItemBuilder", struct_name); + let from_item = super::expand_attr_to_item(format_ident!("res_item"), fields, rename_all_type); + let (partition_key_ident, partition_key_type) = partition_key; + + let client_trait = if let Some(sort_key) = sort_key { + let (sort_key_ident, sort_key_type) = sort_key; + quote! { + pub trait #trait_name { + fn get(&self, pk: impl Into<#partition_key_type>, sk: impl Into<#sort_key_type>) -> #builder_name; + } + + impl #trait_name for #client_name { + fn get(&self, pk: impl Into<#partition_key_type>, sk: impl Into<#sort_key_type>) -> #builder_name { + use ::std::iter::FromIterator; + + let pk_attr: ::raiden::aws_sdk::types::AttributeValue = pk.into().into_attr(); + let sk_attr: ::raiden::aws_sdk::types::AttributeValue = sk.into().into_attr(); + let key_set: std::collections::HashMap = std::collections::HashMap::from_iter([ + (stringify!(#partition_key_ident).to_owned(), pk_attr), + (stringify!(#sort_key_ident).to_owned(), sk_attr), + ]); + + let mut builder = ::raiden::aws_sdk::operation::get_item::GetItemInput::builder() + .set_expression_attribute_names(self.attribute_names.clone()) + .set_key(Some(key_set)) + .table_name(self.table_name()); + + if let Some(ref v) = self.projection_expression { + builder = builder.projection_expression(v.clone()); + } + + #builder_name { + client: &self.client, + builder, + policy: self.retry_condition.strategy.policy(), + condition: &self.retry_condition, + } + } + } + } + } else { + quote! { + pub trait #trait_name { + fn get(&self, key: impl Into<#partition_key_type>) -> #builder_name; + } + + impl #trait_name for #client_name { + fn get(&self, key: impl Into<#partition_key_type>) -> #builder_name { + use ::std::iter::FromIterator; + + let key_attr: ::raiden::aws_sdk::types::AttributeValue = key.into().into_attr(); + let key_set: std::collections::HashMap = std::collections::HashMap::from_iter([ + (stringify!(#partition_key_ident).to_owned(), key_attr), + ]); + + let mut builder = ::raiden::aws_sdk::operation::get_item::GetItemInput::builder() + .set_expression_attribute_names(self.attribute_names.clone()) + .set_key(Some(key_set)) + .table_name(self.table_name()); + + if let Some(ref v) = self.projection_expression { + builder = builder.projection_expression(v.clone()); + } + + #builder_name { + client: &self.client, + builder, + policy: self.retry_condition.strategy.policy(), + condition: &self.retry_condition, + } + } + } + } + }; + + let api_call_token = super::api_call_token!("get_item"); + let (call_inner_run, inner_run_args) = if cfg!(feature = "tracing") { + ( + quote! { + let table_name = builder + .get_table_name() + .clone() + .expect("table name should be set"); + + #builder_name::inner_run(table_name, client, builder).await + }, + quote! { table_name: String, }, + ) + } else { + ( + quote! { #builder_name::inner_run(client, builder).await }, + quote! {}, + ) + }; + + quote! { + #client_trait + + pub struct #builder_name<'a> { + pub client: &'a ::raiden::Client, + pub builder: ::raiden::aws_sdk::operation::get_item::builders::GetItemInputBuilder, + pub policy: ::raiden::Policy, + pub condition: &'a ::raiden::retry::RetryCondition, + } + + impl<'a> #builder_name<'a> { + pub fn consistent(mut self) -> Self { + self.builder = self.builder.consistent_read(true); + self + } + + pub async fn run(self) -> Result<::raiden::get::GetOutput<#struct_name>, ::raiden::RaidenError> { + let policy: ::raiden::RetryPolicy = self.policy.into(); + let client = self.client; + let builder = self.builder; + policy.retry_if(move || { + let client = client.clone(); + let builder = builder.clone(); + async { #call_inner_run } + }, self.condition).await + } + + async fn inner_run( + #inner_run_args + client: ::raiden::Client, + builder: ::raiden::aws_sdk::operation::get_item::builders::GetItemInputBuilder, + ) -> Result<::raiden::get::GetOutput<#struct_name>, ::raiden::RaidenError> { + let res = #api_call_token?; + if res.item.is_none() { + return Err(::raiden::RaidenError::ResourceNotFound("resource not found".to_owned())); + }; + let mut res_item = res.item.unwrap(); + let item = #struct_name { + #(#from_item)* + }; + Ok(::raiden::get::GetOutput { + item, + consumed_capacity: res.consumed_capacity, + }) + } + } + } +} diff --git a/raiden-derive/src/ops/mod.rs b/raiden-derive/src/aws_sdk/ops/mod.rs similarity index 100% rename from raiden-derive/src/ops/mod.rs rename to raiden-derive/src/aws_sdk/ops/mod.rs diff --git a/raiden-derive/src/aws_sdk/ops/put.rs b/raiden-derive/src/aws_sdk/ops/put.rs new file mode 100644 index 00000000..bbf403d6 --- /dev/null +++ b/raiden-derive/src/aws_sdk/ops/put.rs @@ -0,0 +1,229 @@ +use crate::rename::*; +use proc_macro2::*; +use quote::*; + +pub(crate) fn expand_put_item( + struct_name: &Ident, + fields: &syn::FieldsNamed, + rename_all_type: crate::rename::RenameAllType, +) -> TokenStream { + let item_input_name = format_ident!("{}PutItemInput", struct_name); + let item_input_builder_name = format_ident!("{}PutItemInputBuilder", struct_name); + let item_output_name = format_ident!("{}PutItemOutput", struct_name); + let trait_name = format_ident!("{}PutItem", struct_name); + let client_name = format_ident!("{}Client", struct_name); + let builder_name = format_ident!("{}PutItemBuilder", struct_name); + let condition_token_name = format_ident!("{}ConditionToken", struct_name); + + let input_fields = fields + .named + .iter() + .filter(|f| !crate::finder::include_unary_attr(&f.attrs, "uuid")) + .map(|f| { + let ident = &f.ident.clone().unwrap(); + let ty = &f.ty; + quote! { + pub #ident: #ty, + } + }); + + let output_fields = fields.named.iter().map(|f| { + let ident = &f.ident.clone().unwrap(); + let ty = &f.ty; + quote! { + pub #ident: #ty, + } + }); + + let output_values = fields.named.iter().map(|f| { + let ident = &f.ident.clone().unwrap(); + let renamed = crate::finder::find_rename_value(&f.attrs); + let attr_key = create_renamed(ident.to_string(), renamed, rename_all_type); + if crate::finder::include_unary_attr(&f.attrs, "uuid") { + quote! { + #ident: uuid_map.get(#attr_key).cloned().unwrap().into(), + } + } else { + quote! { + #ident: item.#ident, + } + } + }); + + let input_items = { + let insertion = fields.named.iter().map(|f| { + let ident = &f.ident.clone().unwrap(); + let renamed = crate::finder::find_rename_value(&f.attrs); + let attr_key = create_renamed(ident.to_string(), renamed, rename_all_type); + if crate::finder::include_unary_attr(&f.attrs, "uuid") { + quote! { + let id = #struct_name::gen(); + input_item.insert( + #attr_key.to_string(), + id.clone().into_attr(), + ); + uuid_map.insert( + #attr_key.to_string(), + id, + ); + } + } else { + quote! { + let value = item.#ident.clone().into_attr(); + if !::raiden::is_attr_value_empty(&value) { + input_item.insert( + #attr_key.to_string(), + value, + ); + } + } + } + }); + + quote! { + let mut input_item: std::collections::HashMap = std::collections::HashMap::new(); + #(#insertion)* + } + }; + + // Create default type variables for PutItemBuilder, i.e. XXXPutItemBuilder<(), (), ()> + let required_field_idents: Vec = fields + .named + .iter() + .filter(|f| !crate::finder::include_unary_attr(&f.attrs, "uuid")) + .filter(|f| !crate::finder::is_option(&f.ty)) + .map(|f| f.ident.clone().unwrap()) + .collect(); + let default_types = expand_default_type_variables(&required_field_idents); + + let api_call_token = super::api_call_token!("put_item"); + let (call_inner_run, inner_run_args) = if cfg!(feature = "tracing") { + ( + quote! { + let table_name = builder + .get_table_name() + .clone() + .expect("table name should be set"); + + #builder_name::inner_run(table_name, client, builder).await + }, + quote! { table_name: String, }, + ) + } else { + ( + quote! { #builder_name::inner_run(client, builder).await }, + quote! {}, + ) + }; + + quote! { + #[derive(Debug, Clone, PartialEq, ::raiden::Builder)] + pub struct #item_input_name { + #(#input_fields)* + } + + impl #struct_name { + pub fn put_item_builder() -> #item_input_builder_name<#(#default_types)*> { + #item_input_name::builder() + } + } + + #[derive(Debug, Clone, PartialEq)] + pub struct #item_output_name { + #(#output_fields)* + } + + pub trait #trait_name { + fn put(&self, item: #item_input_name) -> #builder_name; + } + + impl #trait_name for #client_name { + fn put(&self, item: #item_input_name) -> #builder_name{ + let mut uuid_map: std::collections::HashMap = std::collections::HashMap::new(); + + #input_items + + let output_item = #item_output_name { + #(#output_values)* + }; + + let builder = ::raiden::aws_sdk::operation::put_item::PutItemInput::builder() + .set_item(Some(input_item)) + .table_name(self.table_name()); + + #builder_name { + client: &self.client, + builder, + item: output_item, + policy: self.retry_condition.strategy.policy(), + condition: &self.retry_condition, + } + } + } + + pub struct #builder_name<'a> { + pub client: &'a ::raiden::Client, + pub builder: ::raiden::aws_sdk::operation::put_item::builders::PutItemInputBuilder, + pub item: #item_output_name, + pub policy: ::raiden::Policy, + pub condition: &'a ::raiden::retry::RetryCondition, + } + + impl<'a> #builder_name<'a> { + + pub fn raw_input(mut self, builder: ::raiden::aws_sdk::operation::put_item::builders::PutItemInputBuilder) -> Self { + self.builder = builder; + self + } + + pub fn condition(mut self, cond: impl ::raiden::condition::ConditionBuilder<#condition_token_name>) -> Self { + let (cond_str, attr_names, attr_values) = cond.build(); + + if !attr_names.is_empty() { + self.builder = self.builder + .set_expression_attribute_names(Some(attr_names)); + } + + if !attr_values.is_empty() { + self.builder = self.builder + .set_expression_attribute_values(Some(attr_values)); + } + + self.builder = self.builder.condition_expression(cond_str); + self + } + + pub async fn run(self) -> Result<::raiden::put::PutOutput<#item_output_name>, ::raiden::RaidenError> { + let builder = self.builder.clone(); + let client = self.client.clone(); + let policy: ::raiden::RetryPolicy = self.policy.into(); + + let res = policy.retry_if(move || { + let builder = builder.clone(); + let client = client.clone(); + async { #call_inner_run } + }, self.condition).await?; + + Ok(::raiden::put::PutOutput { + item: self.item, + consumed_capacity: res.consumed_capacity, + }) + } + + async fn inner_run( + #inner_run_args + client: ::raiden::Client, + builder: ::raiden::aws_sdk::operation::put_item::builders::PutItemInputBuilder, + ) -> Result<::raiden::aws_sdk::operation::put_item::PutItemOutput, ::raiden::RaidenError> { + Ok(#api_call_token?) + } + } + } +} + +#[allow(clippy::ptr_arg)] +fn expand_default_type_variables(idents: &Vec) -> impl Iterator { + idents.clone().into_iter().map(|_ident| { + quote! { (), } + }) +} diff --git a/raiden-derive/src/aws_sdk/ops/query.rs b/raiden-derive/src/aws_sdk/ops/query.rs new file mode 100644 index 00000000..85362175 --- /dev/null +++ b/raiden-derive/src/aws_sdk/ops/query.rs @@ -0,0 +1,223 @@ +use quote::*; + +pub(crate) fn expand_query( + struct_name: &proc_macro2::Ident, + fields: &syn::FieldsNamed, + rename_all_type: crate::rename::RenameAllType, +) -> proc_macro2::TokenStream { + let trait_name = format_ident!("{}Query", struct_name); + let client_name = format_ident!("{}Client", struct_name); + let builder_name = format_ident!("{}QueryBuilder", struct_name); + let query_output_item = format_ident!("{}QueryOutput", struct_name); + + let filter_expression_token_name = format_ident!("{}FilterExpressionToken", struct_name); + let key_condition_token_name = format_ident!("{}KeyConditionToken", struct_name); + + let from_item = super::expand_attr_to_item(format_ident!("res_item"), fields, rename_all_type); + let api_call_token = super::api_call_token!("query"); + let (call_inner_run, inner_run_args) = if cfg!(feature = "tracing") { + ( + quote! { + let table_name = builder + .get_table_name() + .clone() + .expect("table name should be set"); + + #builder_name::inner_run(table_name, client, builder).await + }, + quote! { table_name: String, }, + ) + } else { + ( + quote! { #builder_name::inner_run(client, builder).await }, + quote! {}, + ) + }; + + quote! { + pub trait #trait_name { + fn query(&self) -> #builder_name; + } + + pub struct #builder_name<'a> { + pub client: &'a ::raiden::Client, + pub builder: ::raiden::aws_sdk::operation::query::builders::QueryInputBuilder, + pub next_token: Option<::raiden::NextToken>, + pub limit: Option, + pub policy: ::raiden::Policy, + pub condition: &'a ::raiden::retry::RetryCondition, + } + + struct #query_output_item { + consumed_capacity: Option<::raiden::aws_sdk::types::ConsumedCapacity>, + count: Option, + items: Option>>, + last_evaluated_key: Option<::std::collections::HashMap>, + scanned_count: Option, + } + + impl #trait_name for #client_name { + + #![allow(clippy::field_reassign_with_default)] + fn query(&self) -> #builder_name { + let builder = ::raiden::aws_sdk::operation::query::QueryInput::builder() + .table_name(self.table_name()) + .set_projection_expression(self.projection_expression.clone()) + .set_expression_attribute_names(self.attribute_names.clone()); + + #builder_name { + client: &self.client, + builder, + next_token: None, + limit: None, + policy: self.retry_condition.strategy.policy(), + condition: &self.retry_condition, + } + } + } + + impl<'a> #builder_name<'a> { + pub fn index(mut self, index: impl Into) -> Self { + self.builder = self.builder.index_name(index.into()); + self + } + + pub fn consistent(mut self) -> Self { + self.builder = self.builder.consistent_read(true); + self + } + + pub fn next_token(mut self, token: ::raiden::NextToken) -> Self { + self.next_token = Some(token); + self + } + + pub fn desc(mut self) -> Self { + self.builder = self.builder.scan_index_forward(false); + self + } + + pub fn asc(mut self) -> Self { + self.builder = self.builder.scan_index_forward(true); + self + } + + pub fn limit(mut self, limit: usize) -> Self { + self.limit = Some(limit as i64); + self + } + + pub fn filter(mut self, cond: impl ::raiden::filter_expression::FilterExpressionBuilder<#filter_expression_token_name>) -> Self { + let (cond_str, attr_names, attr_values) = cond.build(); + + if !attr_values.is_empty() { + if let Some(v) = self.builder.get_expression_attribute_values().clone() { + self.builder = self.builder + .set_expression_attribute_values(Some(::raiden::merge_map(attr_values, v))); + } else { + self.builder = self.builder + .set_expression_attribute_values(Some(attr_values)); + } + } + + self.builder = self.builder.filter_expression(cond_str); + self + } + + pub fn key_condition(mut self, cond: impl ::raiden::key_condition::KeyConditionBuilder<#key_condition_token_name>) -> Self { + let (cond_str, attr_names, attr_values) = cond.build(); + + if !attr_values.is_empty() { + if let Some(v) = self.builder.get_expression_attribute_values().clone() { + self.builder = self + .builder + .set_expression_attribute_values(Some(::raiden::merge_map(attr_values, v))); + } else { + self.builder = self + .builder + .set_expression_attribute_values(Some(attr_values)); + } + } + + self.builder = self.builder.key_condition_expression(cond_str); + self + } + + pub async fn run(mut self) -> Result<::raiden::query::QueryOutput<#struct_name>, ::raiden::RaidenError> { + if let Some(token) = self.next_token { + self.builder = self.builder + .set_exclusive_start_key(Some(token.into_attr_values()?)); + } + + let mut items: Vec<#struct_name> = vec![]; + let policy: ::raiden::RetryPolicy = self.policy.into(); + let client = self.client; + + loop { + if let Some(limit) = self.limit { + self.builder = self.builder.limit(limit as i32); + } + + let builder = self.builder.clone(); + let client = self.client.clone(); + + let res: #query_output_item = policy.retry_if(move || { + let builder = builder.clone(); + let client = client.clone(); + async { #call_inner_run } + }, self.condition).await?; + + if let Some(res_items) = res.items { + for res_item in res_items.into_iter() { + let mut res_item = res_item; + items.push(#struct_name { + #(#from_item)* + }) + } + }; + + let scanned = res.scanned_count.unwrap_or(0); + + let mut has_next = true; + if let Some(limit) = self.limit { + has_next = limit - scanned > 0; + self.limit = Some(limit - scanned); + } + + if res.last_evaluated_key.is_none() || !has_next { + let next_token = if res.last_evaluated_key.is_some() { + Some(::raiden::NextToken::from_attr(&res.last_evaluated_key.unwrap())) + } else { + None + }; + return Ok(::raiden::query::QueryOutput { + consumed_capacity: res.consumed_capacity, + count: res.count, + items, + next_token, + scanned_count: res.scanned_count, + }) + } + + self.builder = self.builder + .set_exclusive_start_key(res.last_evaluated_key); + } + } + + async fn inner_run( + #inner_run_args + client: ::raiden::Client, + builder: ::raiden::aws_sdk::operation::query::builders::QueryInputBuilder, + ) -> Result<#query_output_item, ::raiden::RaidenError> { + let res = #api_call_token?; + Ok(#query_output_item { + consumed_capacity: res.consumed_capacity, + count: Some(res.count as i64), + items: res.items, + last_evaluated_key: res.last_evaluated_key, + scanned_count: Some(res.scanned_count as i64), + }) + } + } + } +} diff --git a/raiden-derive/src/aws_sdk/ops/scan.rs b/raiden-derive/src/aws_sdk/ops/scan.rs new file mode 100644 index 00000000..dfe4fddc --- /dev/null +++ b/raiden-derive/src/aws_sdk/ops/scan.rs @@ -0,0 +1,152 @@ +use quote::*; + +pub(crate) fn expand_scan( + struct_name: &proc_macro2::Ident, + fields: &syn::FieldsNamed, + rename_all_type: crate::rename::RenameAllType, +) -> proc_macro2::TokenStream { + let trait_name = format_ident!("{}Scan", struct_name); + let client_name = format_ident!("{}Client", struct_name); + let builder_name = format_ident!("{}ScanBuilder", struct_name); + + let filter_expression_token_name = format_ident!("{}FilterExpressionToken", struct_name); + let from_item = super::expand_attr_to_item(format_ident!("res_item"), fields, rename_all_type); + let api_call_token = super::api_call_token!("scan"); + let (call_inner_run, inner_run_args) = if cfg!(feature = "tracing") { + ( + quote! { + let table_name = self.builder.get_table_name().clone().expect("table name should be set"); + #builder_name::inner_run(&table_name, &self.client, self.builder.clone()).await? + }, + quote! { table_name: &str, }, + ) + } else { + ( + quote! { #builder_name::inner_run(&self.client, self.builder.clone()).await? }, + quote! {}, + ) + }; + + quote! { + pub trait #trait_name { + fn scan(&self) -> #builder_name; + } + + pub struct #builder_name<'a> { + pub client: &'a ::raiden::Client, + pub builder: ::raiden::aws_sdk::operation::scan::builders::ScanInputBuilder, + pub next_token: Option<::raiden::NextToken>, + pub limit: Option + } + + impl #trait_name for #client_name { + #![allow(clippy::field_reassign_with_default)] + fn scan(&self) -> #builder_name { + let builder = ::raiden::aws_sdk::operation::scan::ScanInput::builder() + .table_name(self.table_name()) + .set_projection_expression(self.projection_expression.clone()) + .set_expression_attribute_names(self.attribute_names.clone()); + + #builder_name { + client: &self.client, + builder, + next_token: None, + limit: None, + } + } + } + + impl<'a> #builder_name<'a> { + pub fn index(mut self, index: impl Into) -> Self { + self.builder = self.builder.index_name(index.into()); + self + } + + pub fn consistent(mut self) -> Self { + self.builder = self.builder.consistent_read(true); + self + } + + pub fn filter(mut self, cond: impl ::raiden::filter_expression::FilterExpressionBuilder<#filter_expression_token_name>) -> Self { + let (cond_str, attr_names, attr_values) = cond.build(); + + if !attr_values.is_empty() { + if let Some(v) = self.builder.get_expression_attribute_values().clone() { + self.builder = self.builder + .set_expression_attribute_values(Some(::raiden::merge_map(attr_values, v))); + } else { + self.builder = self.builder + .set_expression_attribute_values(Some(attr_values)); + } + } + + self.builder = self.builder.filter_expression(cond_str); + self + } + + pub fn next_token(mut self, token: ::raiden::NextToken) -> Self { + self.next_token = Some(token); + self + } + + pub fn limit(mut self, limit: usize) -> Self { + self.limit = Some(limit as i64); + self + } + + pub async fn run(mut self) -> Result<::raiden::scan::ScanOutput<#struct_name>, ::raiden::RaidenError> { + if let Some(token) = self.next_token { + self.builder = self.builder + .set_exclusive_start_key(Some(token.into_attr_values()?)); + } + + let mut items: Vec<#struct_name> = vec![]; + + loop { + if let Some(limit) = self.limit { + self.builder = self.builder.limit(limit as i32); + } + + let res = { #call_inner_run }; + + if let Some(res_items) = res.items { + for res_item in res_items.into_iter() { + let mut res_item = res_item; + items.push(#struct_name { + #(#from_item)* + }) + } + }; + + let scanned = res.scanned_count as i64; + + let mut has_next = true; + if let Some(limit) = self.limit { + has_next = limit - scanned > 0; + self.limit = Some(limit - scanned); + } + if res.last_evaluated_key.is_none() || !has_next { + return Ok(::raiden::scan::ScanOutput { + consumed_capacity: res.consumed_capacity, + count: Some(res.count as i64), + items, + last_evaluated_key: res.last_evaluated_key, + scanned_count: Some(res.scanned_count as i64), + }) + } + + self.builder = self.builder + .set_exclusive_start_key(res.last_evaluated_key); + } + } + + async fn inner_run( + #inner_run_args + client: &::raiden::Client, + builder: ::raiden::aws_sdk::operation::scan::builders::ScanInputBuilder, + ) -> Result<::raiden::aws_sdk::operation::scan::ScanOutput, ::raiden::RaidenError> { + Ok(#api_call_token?) + } + } + } +} diff --git a/raiden-derive/src/aws_sdk/ops/shared.rs b/raiden-derive/src/aws_sdk/ops/shared.rs new file mode 100644 index 00000000..cd7f0459 --- /dev/null +++ b/raiden-derive/src/aws_sdk/ops/shared.rs @@ -0,0 +1,110 @@ +use quote::*; + +pub(crate) fn expand_attr_to_item( + item_ident: proc_macro2::Ident, + fields: &syn::FieldsNamed, + rename_all_type: crate::rename::RenameAllType, +) -> Vec { + fields.named.iter().map(|f| { + let ident = &f.ident.clone().unwrap(); + let use_default = crate::finder::include_unary_attr(&f.attrs, "use_default"); + let renamed = crate::finder::find_rename_value(&f.attrs); + let attr_key = if let Some(renamed) = renamed { + renamed + } else if rename_all_type != crate::rename::RenameAllType::None { + crate::rename::rename(rename_all_type, ident.to_string()) + } else { + ident.to_string() + }; + let ty = &f.ty; + + let item = quote! { + let item = <#ty as ::raiden::ResolveAttribute>::resolve_attr(&#attr_key, &mut #item_ident); + }; + if crate::finder::is_option(ty) { + quote! { + #ident: { + #item + if item.is_none() { + None + } else { + let converted = ::raiden::FromAttribute::from_attr(item); + if converted.is_err() { + return Err(::raiden::RaidenError::AttributeConvertError{ attr_name: #attr_key.to_string() }); + } + converted.unwrap() + } + }, + } + } else if use_default { + quote! { + #ident: { + #item + if item.is_none() { + Default::default() + } else { + let item = item.unwrap(); + // If null is true, use default value. + if item.is_null() { + Default::default() + } else { + let converted = ::raiden::FromAttribute::from_attr(Some(item)); + if converted.is_err() { + // TODO: improve error handling. + return Err(::raiden::RaidenError::AttributeConvertError{ attr_name: #attr_key.to_string() }); + } + converted.unwrap() + } + } + }, + } + } else { + quote! { + #ident: { + #item + let converted = ::raiden::FromAttribute::from_attr(item); + if converted.is_err() { + // TODO: improve error handling. + return Err(::raiden::RaidenError::AttributeConvertError{ attr_name: #attr_key.to_string() }); + } + converted.unwrap() + }, + } + } + }).collect() +} + +macro_rules! api_call_token { + ($operation: literal) => { + $crate::ops::api_call_token!("table_name", "client", $operation, "builder") + }; + ($table_name: literal, $client: literal, $operation: literal, $builder: literal) => {{ + let table_name = ::quote::format_ident!($table_name); + let client = ::quote::format_ident!($client); + let operation = ::quote::format_ident!($operation); + let builder = ::quote::format_ident!($builder); + + let span_token = if cfg!(feature = "tracing") { + ::quote::quote! { + use tracing::Instrument; + let fut = fut.instrument(::tracing::debug_span!( + "dynamodb::action", + table = #table_name, + api = std::stringify!(#operation), + )); + } + } else { + ::quote::quote! {} + }; + + ::quote::quote! {{ + let fut = #builder.send_with(&#client); + + #span_token + + fut.await + }} + }}; +} + +pub(super) use api_call_token; diff --git a/raiden-derive/src/aws_sdk/ops/transact_write.rs b/raiden-derive/src/aws_sdk/ops/transact_write.rs new file mode 100644 index 00000000..340a49de --- /dev/null +++ b/raiden-derive/src/aws_sdk/ops/transact_write.rs @@ -0,0 +1,437 @@ +use proc_macro2::*; +use quote::*; +use syn::*; + +use crate::rename::*; + +pub(crate) fn expand_transact_write( + struct_name: &Ident, + partition_key: &(Ident, Type), + _sort_key: &Option<(Ident, Type)>, // TODO: Support sort key + fields: &FieldsNamed, + attr_enum_name: &Ident, + rename_all_type: RenameAllType, + table_name: &str, +) -> TokenStream { + let item_input_name = format_ident!("{}PutItemInput", struct_name); + let put_builder = format_ident!("{}TransactPutItemBuilder", struct_name); + let update_builder = format_ident!("{}TransactUpdateItemBuilder", struct_name); + let delete_builder = format_ident!("{}TransactDeleteItemBuilder", struct_name); + let condition_check_builder = format_ident!("{}TransactConditionCheckBuilder", struct_name); + let condition_token_name = format_ident!("{}ConditionToken", struct_name); + let (partition_key_ident, partition_key_type) = partition_key; + + let input_items = { + let insertion = fields.named.iter().map(|f| { + let ident = &f.ident.clone().unwrap(); + let renamed = crate::finder::find_rename_value(&f.attrs); + let attr_key = create_renamed(ident.to_string(), renamed, rename_all_type); + if crate::finder::include_unary_attr(&f.attrs, "uuid") { + quote! { + let id = #struct_name::gen(); + input_item.insert( + #attr_key.to_string(), + id.clone().into_attr(), + ); + uuid_map.insert( + #attr_key.to_string(), + id, + ); + } + } else { + quote! { + let value = item.#ident.into_attr(); + if !::raiden::is_attr_value_empty(&value) { + input_item.insert( + #attr_key.to_string(), + value, + ); + } + } + } + }); + + quote! { + let mut input_item: std::collections::HashMap = std::collections::HashMap::new(); + #(#insertion)* + } + }; + + quote! { + impl #struct_name { + pub fn put(item: #item_input_name) -> #put_builder { + let mut attribute_names: std::collections::HashMap = std::collections::HashMap::new(); + let mut attribute_values: std::collections::HashMap = std::collections::HashMap::new(); + let mut uuid_map: std::collections::HashMap = std::collections::HashMap::new(); + + #input_items + + let builder = ::raiden::aws_sdk::types::Put::builder().set_item(Some(input_item)); + + #put_builder { + builder, + table_name: #table_name.to_owned(), + table_prefix: "".to_owned(), + table_suffix: "".to_owned(), + } + } + + // TODO: Support sort key + pub fn condition_check(key: impl Into<#partition_key_type>) -> #condition_check_builder { + use std::iter::FromIterator; + + let key_attr: ::raiden::aws_sdk::types::AttributeValue = key.into().into_attr(); + let key_set: std::collections::HashMap = std::collections::HashMap::from_iter([ + (stringify!(#partition_key_ident).to_owned(), key_attr), + ]); + let builder = ::raiden::aws_sdk::types::ConditionCheck::builder() + .set_key(Some(key_set)); + + #condition_check_builder { + builder, + table_name: #table_name.to_owned(), + table_prefix: "".to_owned(), + table_suffix: "".to_owned(), + } + } + + // TODO: Support sort key + pub fn delete(key: impl Into<#partition_key_type>) -> #delete_builder { + use std::iter::FromIterator; + + let key_attr: ::raiden::aws_sdk::types::AttributeValue = key.into().into_attr(); + let key_set: std::collections::HashMap = std::collections::HashMap::from_iter([ + (stringify!(#partition_key_ident).to_owned(), key_attr), + ]); + let builder = ::raiden::aws_sdk::types::Delete::builder().set_key(Some(key_set)); + + #delete_builder { + builder, + table_name: #table_name.to_owned(), + table_prefix: "".to_owned(), + table_suffix: "".to_owned(), + } + } + + // TODO: Support sort key + pub fn update(key: impl Into<#partition_key_type>) -> #update_builder { + use std::iter::FromIterator; + + let key_attr: ::raiden::aws_sdk::types::AttributeValue = key.into().into_attr(); + let key_set: std::collections::HashMap = std::collections::HashMap::from_iter([ + (stringify!(#partition_key_ident).to_owned(), key_attr), + ]); + let builder = ::raiden::aws_sdk::types::Update::builder().set_key(Some(key_set)); + + #update_builder { + builder, + table_name: #table_name.to_owned(), + table_prefix: "".to_owned(), + table_suffix: "".to_owned(), + // item: output_item, + add_items: vec![], + set_items: vec![], + remove_items: vec![], + delete_items: vec![], + } + } + } + + pub struct #put_builder { + pub table_name: String, + pub table_prefix: String, + pub table_suffix: String, + pub builder: ::raiden::aws_sdk::types::builders::PutBuilder, + } + + impl ::raiden::TransactWritePutBuilder for #put_builder { + fn build(self) -> ::raiden::aws_sdk::types::Put { + self.builder + .table_name(format!("{}{}{}", self.table_prefix, self.table_name, self.table_suffix)) + .build() + .expect("should be built") + } + } + + impl #put_builder { + + pub fn table_prefix(mut self, s: impl Into) -> Self { + self.table_prefix = s.into(); + self + } + + pub fn table_suffix(mut self, s: impl Into) -> Self { + self.table_suffix = s.into(); + self + } + + pub fn condition(mut self, cond: impl ::raiden::condition::ConditionBuilder<#condition_token_name>) -> Self { + let (cond_str, attr_names, attr_values) = cond.build(); + + if !attr_names.is_empty() { + self.builder = self.builder + .set_expression_attribute_names(Some(attr_names)); + } + + if !attr_values.is_empty() { + self.builder = self.builder + .set_expression_attribute_values(Some(attr_values)); + } + + self.builder = self.builder.condition_expression(cond_str); + self + } + } + + pub struct #update_builder { + pub table_name: String, + pub table_prefix: String, + pub table_suffix: String, + pub builder: ::raiden::aws_sdk::types::builders::UpdateBuilder, + + pub add_items: Vec<(String, ::raiden::AttributeNames, ::raiden::AttributeValues)>, + pub set_items: Vec<::raiden::update_expression::SetOrRemove>, + pub remove_items: Vec<#attr_enum_name>, + pub delete_items: Vec<(#attr_enum_name, ::raiden::AttributeValue)>, + } + + impl ::raiden::TransactWriteUpdateBuilder for #update_builder { + fn build(mut self) -> ::raiden::aws_sdk::types::Update { + // TODO: Refactor later + let mut attr_names: ::raiden::AttributeNames = std::collections::HashMap::new(); + let mut attr_values: ::raiden::AttributeValues = std::collections::HashMap::new(); + + let add_items = std::mem::replace(&mut self.add_items, vec![]); + let set_items = std::mem::replace(&mut self.set_items, vec![]); + let remove_items = std::mem::replace(&mut self.remove_items, vec![]); + let delete_items = std::mem::replace(&mut self.delete_items, vec![]); + + let mut remove_expressions = remove_items.into_iter().map(|name| { + let placeholder = format!(":value{}", ::raiden::generate_value_id()); + let attr_name = format!("#{}", name.into_attr_name()); + let val = format!("{}", attr_name); + attr_names.insert(attr_name, name.into_attr_name()); + val + }).collect::>(); + + let mut set_expressions = vec![]; + for set_item in set_items { + match set_item { + raiden::update_expression::SetOrRemove::Set(expression, names, values) => { + attr_names = ::raiden::merge_map(attr_names, names); + attr_values = ::raiden::merge_map(attr_values, values); + set_expressions.push(expression); + } + // https://github.com/raiden-rs/raiden-dynamo/issues/64 + // If empty set detected, replace it to remove expression. + raiden::update_expression::SetOrRemove::Remove(expression, names) => { + attr_names = ::raiden::merge_map(attr_names, names); + remove_expressions.push(expression); + } + } + } + + let set_expression = set_expressions.join(", "); + let remove_expression = remove_expressions.join(", "); + + let mut add_expressions = vec![]; + for add_item in add_items { + let (expression, names, values) = add_item; + attr_names = ::raiden::merge_map(attr_names, names); + attr_values = ::raiden::merge_map(attr_values, values); + add_expressions.push(expression); + } + let add_expression = add_expressions.join(", "); + + let delete_expression = delete_items.into_iter().map(|(name, value)| { + let placeholder = format!(":value{}", ::raiden::generate_value_id()); + let attr_name = format!("#{}", name.into_attr_name()); + let val = format!("{} {}", attr_name, placeholder); + attr_names.insert(attr_name, name.into_attr_name()); + attr_values.insert(placeholder, value); + val + }).collect::>().join(", "); + + let mut update_expressions: Vec = vec![]; + if !add_expression.is_empty() { + update_expressions.push(format!("ADD {}", add_expression)); + } + if !set_expression.is_empty() { + update_expressions.push(format!("SET {}", set_expression)); + } + if !remove_expression.is_empty() { + update_expressions.push(format!("REMOVE {}", remove_expression)); + } + if !delete_expression.is_empty() { + update_expressions.push(format!("DELETE {}", delete_expression)); + } + let update_expression = update_expressions.join(" "); + + if self.builder.get_expression_attribute_names().is_none() { + self.builder = self.builder + .set_expression_attribute_names(Some(attr_names)); + } else { + let v = self.builder.get_expression_attribute_names().clone().unwrap(); + self.builder = self.builder + .set_expression_attribute_names(Some(::raiden::merge_map(v, attr_names))); + } + + if self.builder.get_expression_attribute_values().is_none() { + self.builder = self.builder + .set_expression_attribute_values(Some(attr_values)); + } else { + let v = self.builder.get_expression_attribute_values().clone().unwrap(); + self.builder = self.builder + .set_expression_attribute_values(Some(::raiden::merge_map(v, attr_values))); + } + + self.builder = self.builder.update_expression(update_expression); + self.builder = self.builder + .table_name(format!("{}{}{}", self.table_prefix, self.table_name, self.table_suffix)); + + self.builder.build().expect("should be built") + } + } + + impl #update_builder { + + pub fn table_prefix(mut self, s: impl Into) -> Self { + self.table_prefix = s.into(); + self + } + + pub fn table_suffix(mut self, s: impl Into) -> Self { + self.table_suffix = s.into(); + self + } + + pub fn add(mut self, add: impl ::raiden::update_expression::UpdateAddExpressionBuilder) -> Self { + self.add_items.push(add.build()); + self + } + + pub fn set(mut self, set: impl ::raiden::update_expression::UpdateSetExpressionBuilder) -> Self { + self.set_items.push(set.build()); + self + } + + pub fn remove(mut self, attr: #attr_enum_name) -> Self { + self.remove_items.push(attr); + self + } + + pub fn delete(mut self, attr: #attr_enum_name, value: impl ::raiden::IntoAttribute) -> Self { + self.delete_items.push((attr, value.into_attr())); + self + } + + pub fn condition(mut self, cond: impl ::raiden::condition::ConditionBuilder<#condition_token_name>) -> Self { + let (cond_str, attr_names, attr_values) = cond.build(); + + if !attr_names.is_empty() { + self.builder = self.builder + .set_expression_attribute_names(Some(attr_names)); + } + + if !attr_values.is_empty() { + self.builder = self.builder + .set_expression_attribute_values(Some(attr_values)); + } + + self.builder = self.builder.condition_expression(cond_str); + self + } + } + + pub struct #delete_builder { + pub table_name: String, + pub table_prefix: String, + pub table_suffix: String, + pub builder: ::raiden::aws_sdk::types::builders::DeleteBuilder, + } + + impl ::raiden::TransactWriteDeleteBuilder for #delete_builder { + fn build(self) -> ::raiden::aws_sdk::types::Delete { + self.builder + .table_name(format!("{}{}{}", self.table_prefix, self.table_name, self.table_suffix)) + .build() + .expect("should be built") + } + } + + impl #delete_builder { + pub fn table_prefix(mut self, s: impl Into) -> Self { + self.table_prefix = s.into(); + self + } + + pub fn table_suffix(mut self, s: impl Into) -> Self { + self.table_suffix = s.into(); + self + } + + pub fn condition(mut self, cond: impl ::raiden::condition::ConditionBuilder<#condition_token_name>) -> Self { + let (cond_str, attr_names, attr_values) = cond.build(); + + if !attr_names.is_empty() { + self.builder = self.builder + .set_expression_attribute_names(Some(attr_names)); + } + + if !attr_values.is_empty() { + self.builder = self.builder + .set_expression_attribute_values(Some(attr_values)); + } + + self.builder = self.builder.condition_expression(cond_str); + self + } + } + + pub struct #condition_check_builder { + pub table_name: String, + pub table_prefix: String, + pub table_suffix: String, + pub builder: ::raiden::aws_sdk::types::builders::ConditionCheckBuilder, + } + + impl ::raiden::TransactWriteConditionCheckBuilder for #condition_check_builder { + fn build(self) -> ::raiden::aws_sdk::types::ConditionCheck { + self.builder + .table_name(format!("{}{}{}", self.table_prefix, self.table_name, self.table_suffix)) + .build() + .expect("should be built") + } + } + + impl #condition_check_builder { + pub fn table_prefix(mut self, s: impl Into) -> Self { + self.table_prefix = s.into(); + self + } + + pub fn table_suffix(mut self, s: impl Into) -> Self { + self.table_suffix = s.into(); + self + } + + pub fn condition(mut self, cond: impl ::raiden::condition::ConditionBuilder<#condition_token_name>) -> Self { + let (cond_str, attr_names, attr_values) = cond.build(); + + if !attr_names.is_empty() { + self.builder = self.builder + .set_expression_attribute_names(Some(attr_names)); + } + + if !attr_values.is_empty() { + self.builder = self.builder + .set_expression_attribute_values(Some(attr_values)); + } + + self.builder = self.builder.condition_expression(cond_str); + self + } + } + + } +} diff --git a/raiden-derive/src/aws_sdk/ops/update.rs b/raiden-derive/src/aws_sdk/ops/update.rs new file mode 100644 index 00000000..8ea84b42 --- /dev/null +++ b/raiden-derive/src/aws_sdk/ops/update.rs @@ -0,0 +1,360 @@ +use proc_macro2::*; +use quote::*; +use syn::*; + +pub(crate) fn expand_update_item( + partition_key: &(Ident, Type), + sort_key: &Option<(Ident, Type)>, + fields: &FieldsNamed, + attr_enum_name: &Ident, + struct_name: &Ident, + rename_all_type: crate::rename::RenameAllType, +) -> TokenStream { + let item_output_name = format_ident!("{}UpdateItemOutput", struct_name); + let trait_name = format_ident!("{}UpdateItem", struct_name); + let update_expression_name = format_ident!("{}UpdateExpression", struct_name); + let client_name = format_ident!("{}Client", struct_name); + let builder_name = format_ident!("{}UpdateItemBuilder", struct_name); + let from_item = super::expand_attr_to_item(format_ident!("res_item"), fields, rename_all_type); + let condition_token_name = format_ident!("{}ConditionToken", struct_name); + let (partition_key_ident, partition_key_type) = partition_key; + + let client_trait = if let Some(sort_key) = sort_key { + let (sort_key_ident, sort_key_type) = sort_key; + quote! { + pub trait #trait_name { + fn update(&self, pk: impl Into<#partition_key_type>, sk: impl Into<#sort_key_type>) -> #builder_name; + } + + impl #trait_name for #client_name { + fn update(&self, pk: impl Into<#partition_key_type>, sk: impl Into<#sort_key_type>) -> #builder_name { + use std::iter::FromIterator; + + let pk_attr: ::raiden::aws_sdk::types::AttributeValue = pk.into().into_attr(); + let sk_attr: ::raiden::aws_sdk::types::AttributeValue = sk.into().into_attr(); + let key_set: std::collections::HashMap = std::collections::HashMap::from_iter([ + (stringify!(#partition_key_ident).to_owned(), pk_attr), + (stringify!(#sort_key_ident).to_owned(), sk_attr), + ]); + + let builder = ::raiden::aws_sdk::operation::update_item::UpdateItemInput::builder() + .set_key(Some(key_set)) + .table_name(self.table_name()); + + #builder_name { + client: &self.client, + builder, + set_items: vec![], + add_items: vec![], + remove_items: vec![], + delete_items: vec![], + policy: self.retry_condition.strategy.policy(), + condition: &self.retry_condition, + } + } + } + } + } else { + quote! { + pub trait #trait_name { + fn update(&self, key: impl Into<#partition_key_type>) -> #builder_name; + } + + impl #trait_name for #client_name { + fn update(&self, key: impl Into<#partition_key_type>) -> #builder_name { + use std::iter::FromIterator; + + let key_attr: ::raiden::aws_sdk::types::AttributeValue = key.into().into_attr(); + let key_set: std::collections::HashMap = std::collections::HashMap::from_iter([ + (stringify!(#partition_key_ident).to_owned(), key_attr), + ]); + + let builder = ::raiden::aws_sdk::operation::update_item::UpdateItemInput::builder() + .set_key(Some(key_set)) + .table_name(self.table_name()); + + #builder_name { + client: &self.client, + builder, + set_items: vec![], + add_items: vec![], + remove_items: vec![], + delete_items: vec![], + policy: self.retry_condition.strategy.policy(), + condition: &self.retry_condition, + } + } + } + } + }; + + let api_call_token = super::api_call_token!("update_item"); + let (call_inner_run, inner_run_args) = if cfg!(feature = "tracing") { + ( + quote! { + let table_name = builder.get_table_name().clone().expect("table name should be set"); + #builder_name::inner_run(table_name, client, builder).await + }, + quote! { table_name: String, }, + ) + } else { + ( + quote! { #builder_name::inner_run(client, builder).await }, + quote! {}, + ) + }; + + quote! { + #[derive(Debug, Clone, PartialEq)] + pub struct #item_output_name { + // #(#output_fields)* + } + + pub struct #update_expression_name; + + impl #struct_name { + pub fn update_expression() -> #update_expression_name { + #update_expression_name + } + } + + impl #update_expression_name { + pub fn set(&self, attr: #attr_enum_name) -> ::raiden::update_expression::Set<#attr_enum_name> { + ::raiden::update_expression::Set::new(attr) + } + + pub fn add(&self, attr: #attr_enum_name) -> ::raiden::update_expression::Add<#attr_enum_name> { + ::raiden::update_expression::Add::new(attr) + } + + pub fn delete(&self, attr: #attr_enum_name) -> ::raiden::update_expression::Delete<#attr_enum_name> { + ::raiden::update_expression::Delete::new(attr) + } + } + + #client_trait + + pub struct #builder_name<'a> { + pub client: &'a ::raiden::Client, + pub builder: ::raiden::aws_sdk::operation::update_item::builders::UpdateItemInputBuilder, + pub add_items: Vec<(String, ::raiden::AttributeNames, ::raiden::AttributeValues)>, + pub set_items: Vec<::raiden::update_expression::SetOrRemove>, + pub remove_items: Vec<#attr_enum_name>, + pub delete_items: Vec<(String, ::raiden::AttributeNames, ::raiden::AttributeValues)>, + pub policy: ::raiden::Policy, + pub condition: &'a ::raiden::retry::RetryCondition, + } + + impl<'a> #builder_name<'a> { + pub fn raw_input(mut self, builder: ::raiden::aws_sdk::operation::update_item::builders::UpdateItemInputBuilder) -> Self { + self.builder = builder; + self + } + + pub fn add(mut self, add: impl ::raiden::update_expression::UpdateAddExpressionBuilder) -> Self { + self.add_items.push(add.build()); + self + } + + pub fn set(mut self, set: impl ::raiden::update_expression::UpdateSetExpressionBuilder) -> Self { + self.set_items.push(set.build()); + self + } + + pub fn remove(mut self, attr: #attr_enum_name) -> Self { + self.remove_items.push(attr); + self + } + + pub fn delete(mut self, set: impl ::raiden::update_expression::UpdateDeleteExpressionBuilder) -> Self { + self.delete_items.push(set.build()); + self + } + + // INFO: raiden supports only none, all_old and all_new to map response to struct. + pub fn return_all_old(mut self) -> Self { + self.builder = self.builder.return_values(::raiden::aws_sdk::types::ReturnValue::AllOld); + self + } + + // INFO: raiden supports only none, all_old and all_new to map response to struct. + pub fn return_all_new(mut self) -> Self { + self.builder = self.builder.return_values(::raiden::aws_sdk::types::ReturnValue::AllNew); + self + } + + pub fn condition(mut self, cond: impl ::raiden::condition::ConditionBuilder<#condition_token_name>) -> Self { + let (cond_str, attr_names, attr_values) = cond.build(); + + if !attr_names.is_empty() { + self.builder = self.builder + .set_expression_attribute_names(Some(attr_names)); + } + + if !attr_values.is_empty() { + self.builder = self.builder + .set_expression_attribute_values(Some(attr_values)); + } + + self.builder = self.builder.condition_expression(cond_str); + self + } + + fn build_expression(&mut self) -> (String, ::raiden::AttributeNames , ::raiden::AttributeValues) { + let mut attr_names: ::raiden::AttributeNames = std::collections::HashMap::new(); + let mut attr_values: ::raiden::AttributeValues = std::collections::HashMap::new(); + + let add_items = std::mem::replace(&mut self.add_items, vec![]); + let set_items = std::mem::replace(&mut self.set_items, vec![]); + let remove_items = std::mem::replace(&mut self.remove_items, vec![]); + let delete_items = std::mem::replace(&mut self.delete_items, vec![]); + + let mut remove_expressions = remove_items.into_iter().map(|name| { + let placeholder = format!(":value{}", ::raiden::generate_value_id()); + let attr_name = format!("#{}", name.into_attr_name()); + let val = format!("{}", attr_name); + attr_names.insert(attr_name, name.into_attr_name()); + val + }).collect::>(); + + let mut set_expressions = vec![]; + for set_item in set_items { + match set_item { + raiden::update_expression::SetOrRemove::Set(expression, names, values) => { + attr_names = ::raiden::merge_map(attr_names, names); + attr_values = ::raiden::merge_map(attr_values, values); + set_expressions.push(expression); + } + // https://github.com/raiden-rs/raiden-dynamo/issues/64 + // If empty set detected, replace it to remove expression. + raiden::update_expression::SetOrRemove::Remove(expression, names) => { + attr_names = ::raiden::merge_map(attr_names, names); + remove_expressions.push(expression); + } + } + } + let set_expression = set_expressions.join(", "); + + let mut add_expressions = vec![]; + for add_item in add_items { + let (expression, names, values) = add_item; + if expression != "" { + attr_names = ::raiden::merge_map(attr_names, names); + attr_values = ::raiden::merge_map(attr_values, values); + add_expressions.push(expression); + } + } + let add_expression = add_expressions.join(", "); + + let remove_expression = remove_expressions.join(", "); + + let mut delete_expressions = vec![]; + for delete_item in delete_items { + let (expression, names, values) = delete_item; + if expression != "" { + attr_names = ::raiden::merge_map(attr_names, names); + attr_values = ::raiden::merge_map(attr_values, values); + delete_expressions.push(expression); + } + } + let delete_expression = delete_expressions.join(", "); + + let mut update_expressions: Vec = vec![]; + if !add_expression.is_empty() { + update_expressions.push(format!("ADD {}", add_expression)); + } + if !set_expression.is_empty() { + update_expressions.push(format!("SET {}", set_expression)); + } + if !remove_expression.is_empty() { + update_expressions.push(format!("REMOVE {}", remove_expression)); + } + if !delete_expression.is_empty() { + update_expressions.push(format!("DELETE {}", delete_expression)); + } + let update_expression = update_expressions.join(" "); + (update_expression, attr_names, attr_values) + } + + pub async fn run(mut self) -> Result<::raiden::update::UpdateOutput<#struct_name>, ::raiden::RaidenError> { + let (expression, names, values) = self.build_expression(); + + if self.builder.get_expression_attribute_names().is_none() { + if names.is_empty() { + self.builder = self.builder + .set_expression_attribute_names(None); + } else { + self.builder = self.builder + .set_expression_attribute_names(Some(names)); + } + } else { + let v = self + .builder + .get_expression_attribute_names() + .clone() + .unwrap(); + self.builder = self.builder + .set_expression_attribute_names(Some(::raiden::merge_map(v, names))); + } + + if self.builder.get_expression_attribute_values().is_none() { + if values.is_empty() { + self.builder = self.builder + .set_expression_attribute_values(None); + } else { + self.builder = self.builder + .set_expression_attribute_values(Some(values)); + } + } else { + let v = self + .builder + .get_expression_attribute_values() + .clone() + .unwrap(); + self.builder = self.builder + .set_expression_attribute_values(Some(::raiden::merge_map(v, values))); + } + + if expression != "" { + self.builder = self.builder.update_expression(expression); + } + + let has_return_values = self.builder.get_return_values().is_some(); + let builder = self.builder.clone(); + let client = self.client.clone(); + let policy: ::raiden::RetryPolicy = self.policy.into(); + + let res = policy.retry_if(move || { + let builder = builder.clone(); + let client = client.clone(); + async { #call_inner_run } + }, self.condition).await?; + + + let item = if has_return_values { + let mut res_item = res.attributes.unwrap(); + Some(#struct_name { + #(#from_item)* + }) + } else { + None + }; + + Ok(::raiden::update::UpdateOutput { + item, + consumed_capacity: res.consumed_capacity, + item_collection_metrics: res.item_collection_metrics, + }) + } + + async fn inner_run( + #inner_run_args + client: ::raiden::Client, + builder: ::raiden::aws_sdk::operation::update_item::builders::UpdateItemInputBuilder, + ) -> Result<::raiden::aws_sdk::operation::update_item::UpdateItemOutput, ::raiden::RaidenError> { + Ok(#api_call_token?) + } + } + + } +} diff --git a/raiden-derive/src/condition/builder.rs b/raiden-derive/src/condition/builder.rs index da96ce3c..4aa6ae10 100644 --- a/raiden-derive/src/condition/builder.rs +++ b/raiden-derive/src/condition/builder.rs @@ -8,6 +8,13 @@ pub fn expand_condition_builder( let condition_name = format_ident!("{}Condition", struct_name); let condition_token_name = format_ident!("{}ConditionToken", struct_name); let wait_attr_op_name = format_ident!("{}LeftAttrAndWaitOp", struct_name); + let attribute_value_path = if cfg!(feature = "rusoto") { + quote! { ::raiden::AttributeValue } + } else if cfg!(feature = "aws-sdk") { + quote! { ::raiden::aws_sdk::types::AttributeValue } + } else { + unreachable!(); + }; quote! { @@ -96,7 +103,7 @@ pub fn expand_condition_builder( pub struct #wait_attr_op_name { not: bool, attr_or_placeholder: ::raiden::AttrOrPlaceholder, - attr_value: Option<::raiden::AttributeValue> + attr_value: Option<#attribute_value_path>, } impl #wait_attr_op_name { diff --git a/raiden-derive/src/finder/mod.rs b/raiden-derive/src/finder/mod.rs index 4b2243fd..26fcad15 100644 --- a/raiden-derive/src/finder/mod.rs +++ b/raiden-derive/src/finder/mod.rs @@ -105,28 +105,28 @@ pub(crate) fn find_partition_key_field(fields: &syn::FieldsNamed) -> Option = fields .named .iter() - .cloned() .filter(|f| include_unary_attr(&f.attrs, "partition_key")) + .cloned() .collect(); if fields.len() > 1 { panic!("partition key should be only one.") } - fields.get(0).cloned() + fields.first().cloned() } pub(crate) fn find_sort_key_field(fields: &syn::FieldsNamed) -> Option { let fields: Vec = fields .named .iter() - .cloned() .filter(|f| include_unary_attr(&f.attrs, "sort_key")) + .cloned() .collect(); if fields.len() > 1 { panic!("sort key should be only one.") } - fields.get(0).cloned() + fields.first().cloned() } pub(crate) fn is_option(ty: &syn::Type) -> bool { diff --git a/raiden-derive/src/lib.rs b/raiden-derive/src/lib.rs index 99cdce53..4dc5b003 100644 --- a/raiden-derive/src/lib.rs +++ b/raiden-derive/src/lib.rs @@ -1,3 +1,6 @@ +#[cfg(all(feature = "aws-sdk", feature = "rusoto"))] +compile_error!("feature \"aws-sdk\" and \"rusoto\" cannot be enabled at the same time."); + use proc_macro::TokenStream; use quote::*; @@ -10,9 +13,18 @@ mod finder; mod helpers; mod key; mod key_condition; -mod ops; mod rename; +#[cfg(feature = "rusoto")] +mod rusoto; +#[cfg(feature = "rusoto")] +use rusoto::*; + +#[cfg(feature = "aws-sdk")] +mod aws_sdk; +#[cfg(feature = "aws-sdk")] +use aws_sdk::*; + use crate::rename::*; use std::str::FromStr; @@ -20,6 +32,17 @@ use std::str::FromStr; pub fn derive_raiden(input: TokenStream) -> TokenStream { let input = syn::parse_macro_input!(input as DeriveInput); + let (dynamodb_client_name, use_dynamodb_trait) = if cfg!(feature = "rusoto") { + ( + format_ident!("DynamoDbClient"), + Some(quote! { use ::raiden::DynamoDb as _; }), + ) + } else if cfg!(feature = "aws-sdk") { + (format_ident!("Client"), None) + } else { + unreachable!(); + }; + let struct_name = input.ident; let client_name = format_ident!("{}Client", struct_name); @@ -56,7 +79,7 @@ pub fn derive_raiden(input: TokenStream) -> TokenStream { let client_field = format_ident!("client"); let n = vec![ quote! { #table_name_field: &'static str }, - quote! { #client_field: ::raiden::DynamoDbClient }, + quote! { #client_field: ::raiden::#dynamodb_client_name }, ]; // let struct_fields = fields.named.iter().map(|f| { @@ -125,22 +148,19 @@ pub fn derive_raiden(input: TokenStream) -> TokenStream { &table_name, ); - let insertion_attribute_name = fields.named.iter().map(|f| { - let ident = &f.ident.clone().unwrap(); - let renamed = crate::finder::find_rename_value(&f.attrs); - let result = create_renamed(ident.to_string(), renamed, rename_all_type); - quote! { - names.insert( - format!("#{}", #result.clone()), - #result.to_string(), - ); - } - }); + let client_constructor = client::expand_client_constructor( + &struct_name, + &client_name, + &dynamodb_client_name, + &table_name, + &fields, + &rename_all_type, + ); let expanded = quote! { use ::raiden::IntoAttribute as _; use ::raiden::IntoAttrName as _; - use ::raiden::DynamoDb as _; + #use_dynamodb_trait pub struct #client_name { #( @@ -179,103 +199,10 @@ pub fn derive_raiden(input: TokenStream) -> TokenStream { #transact_write - impl #client_name { - - pub fn new(region: ::raiden::Region) -> Self { - let client = ::raiden::DynamoDbClient::new(region); - Self::new_with_dynamo_db_client(client) - } - - pub fn new_with_client(client: ::raiden::Client, region: ::raiden::Region) -> Self { - let client = ::raiden::DynamoDbClient::new_with_client(client, region); - Self::new_with_dynamo_db_client(client) - } - - fn new_with_dynamo_db_client(client: ::raiden::DynamoDbClient) -> Self { - let names = { - let mut names: ::raiden::AttributeNames = std::collections::HashMap::new(); - #(#insertion_attribute_name)* - names - }; - let projection_expression = Some(names.keys().map(|v| v.to_string()).collect::>().join(", ")); - - Self { - table_name: #table_name, - table_prefix: "".to_owned(), - table_suffix: "".to_owned(), - client, - retry_condition: ::raiden::RetryCondition::new(), - attribute_names: Some(names), - projection_expression - } - } - - pub fn with_retries(mut self, s: Box) -> Self { - self.retry_condition.strategy = s; - self - } - - pub fn table_prefix(mut self, prefix: impl Into) -> Self { - self.table_prefix = prefix.into(); - self - } - - pub fn table_suffix(mut self, suffix: impl Into) -> Self { - self.table_suffix = suffix.into(); - self - } - - pub fn table_name(&self) -> String { - format!("{}{}{}", self.table_prefix, self.table_name.to_string(), self.table_suffix) - } - } - - impl #struct_name { - pub fn client(region: ::raiden::Region) -> #client_name { - #client_name::new(region) - } - pub fn client_with(client: ::raiden::Client, region: ::raiden::Region) -> #client_name { - #client_name::new_with_client(client, region) - } - } + #client_constructor impl ::raiden::IdGenerator for #struct_name {} }; // Hand the output tokens back to the compiler. proc_macro::TokenStream::from(expanded) } - -// fn fetch_raiden_field(fields: &syn::FieldsNamed) -> Vec { -// let fields: Vec = fields -// .named -// .iter() -// .cloned() -// .filter(|f| { -// f.attrs.len() > 0 -// && f.attrs -// .iter() -// .any(|attr| attr.path.segments[0].ident == "raiden") -// }) -// .collect(); -// dbg!(&fields.len()); -// fields -// } - -// fn check_attr_of( -// name: &str, -// tokens: &mut proc_macro2::token_stream::IntoIter, -// ) -> Option { -// dbg!(&name); -// let mut tokens = match tokens.next() { -// Some(proc_macro2::TokenTree::Group(g)) => g.stream().into_iter(), -// _ => return None, -// }; -// dbg!(&name); -// -// match tokens.next() { -// Some(proc_macro2::TokenTree::Ident(ref ident)) if *ident == name => { -// return Some(tokens); -// } -// _ => return None, -// }; -// } diff --git a/raiden-derive/src/rusoto/client.rs b/raiden-derive/src/rusoto/client.rs new file mode 100644 index 00000000..a9caa1e0 --- /dev/null +++ b/raiden-derive/src/rusoto/client.rs @@ -0,0 +1,85 @@ +use quote::*; +use syn::*; + +pub(crate) fn expand_client_constructor( + struct_name: &Ident, + client_name: &Ident, + dynamodb_client_name: &Ident, + table_name: &str, + fields: &FieldsNamed, + rename_all_type: &crate::rename::RenameAllType, +) -> proc_macro2::TokenStream { + let insertion_attribute_name = fields.named.iter().map(|f| { + let ident = &f.ident.clone().unwrap(); + let renamed = crate::finder::find_rename_value(&f.attrs); + let result = crate::create_renamed(ident.to_string(), renamed, *rename_all_type); + quote! { + names.insert( + format!("#{}", #result.clone()), + #result.to_string(), + ); + } + }); + + quote! { + impl #client_name { + + pub fn new(region: ::raiden::Region) -> Self { + let client = ::raiden::#dynamodb_client_name::new(region); + Self::new_with_dynamo_db_client(client) + } + + pub fn new_with_client(client: ::raiden::Client, region: ::raiden::Region) -> Self { + let client = ::raiden::#dynamodb_client_name::new_with_client(client, region); + Self::new_with_dynamo_db_client(client) + } + + fn new_with_dynamo_db_client(client: ::raiden::#dynamodb_client_name) -> Self { + let names = { + let mut names: ::raiden::AttributeNames = std::collections::HashMap::new(); + #(#insertion_attribute_name)* + names + }; + let projection_expression = Some(names.keys().map(|v| v.to_string()).collect::>().join(", ")); + + Self { + table_name: #table_name, + table_prefix: "".to_owned(), + table_suffix: "".to_owned(), + client, + retry_condition: ::raiden::RetryCondition::new(), + attribute_names: Some(names), + projection_expression + } + } + + pub fn with_retries(mut self, s: Box) -> Self { + self.retry_condition.strategy = s; + self + } + + pub fn table_prefix(mut self, prefix: impl Into) -> Self { + self.table_prefix = prefix.into(); + self + } + + pub fn table_suffix(mut self, suffix: impl Into) -> Self { + self.table_suffix = suffix.into(); + self + } + + pub fn table_name(&self) -> String { + format!("{}{}{}", self.table_prefix, self.table_name.to_string(), self.table_suffix) + } + } + + impl #struct_name { + pub fn client(region: ::raiden::Region) -> #client_name { + #client_name::new(region) + } + pub fn client_with(client: ::raiden::Client, region: ::raiden::Region) -> #client_name { + #client_name::new_with_client(client, region) + } + } + } +} diff --git a/raiden-derive/src/rusoto/mod.rs b/raiden-derive/src/rusoto/mod.rs new file mode 100644 index 00000000..c5a66c24 --- /dev/null +++ b/raiden-derive/src/rusoto/mod.rs @@ -0,0 +1,2 @@ +pub mod client; +pub mod ops; diff --git a/raiden-derive/src/ops/batch_delete.rs b/raiden-derive/src/rusoto/ops/batch_delete.rs similarity index 99% rename from raiden-derive/src/ops/batch_delete.rs rename to raiden-derive/src/rusoto/ops/batch_delete.rs index f1b0fb48..6f8d49d5 100644 --- a/raiden-derive/src/ops/batch_delete.rs +++ b/raiden-derive/src/rusoto/ops/batch_delete.rs @@ -162,7 +162,7 @@ pub(crate) fn expand_batch_delete( let unprocessed_requests = unprocessed_items .remove(&self.table_name) - .expect("reqeust_items hashmap must have a value for the table name"); + .expect("request_items hashmap must have a value for the table name"); // push unprocessed requests back to the request buffer self.write_requests.extend(unprocessed_requests); } diff --git a/raiden-derive/src/ops/batch_get.rs b/raiden-derive/src/rusoto/ops/batch_get.rs similarity index 100% rename from raiden-derive/src/ops/batch_get.rs rename to raiden-derive/src/rusoto/ops/batch_get.rs diff --git a/raiden-derive/src/ops/delete.rs b/raiden-derive/src/rusoto/ops/delete.rs similarity index 91% rename from raiden-derive/src/ops/delete.rs rename to raiden-derive/src/rusoto/ops/delete.rs index 23a7cacb..5d71d984 100644 --- a/raiden-derive/src/ops/delete.rs +++ b/raiden-derive/src/rusoto/ops/delete.rs @@ -33,6 +33,8 @@ pub(crate) fn expand_delete_item( #builder_name { client: &self.client, input, + policy: self.retry_condition.strategy.policy(), + condition: &self.retry_condition, } } } @@ -54,6 +56,8 @@ pub(crate) fn expand_delete_item( #builder_name { client: &self.client, input, + policy: self.retry_condition.strategy.policy(), + condition: &self.retry_condition, } } } @@ -63,12 +67,12 @@ pub(crate) fn expand_delete_item( let api_call_token = super::api_call_token!("delete_item"); let (call_inner_run, inner_run_args) = if cfg!(feature = "tracing") { ( - quote! { #builder_name::inner_run(&self.input.table_name.clone(), &self.client, self.input).await? }, - quote! { table_name: &str, }, + quote! { #builder_name::inner_run(input.table_name.clone(), client, input).await }, + quote! { table_name: String, }, ) } else { ( - quote! { #builder_name::inner_run(&self.client, self.input).await? }, + quote! { #builder_name::inner_run(client, input).await }, quote! {}, ) }; @@ -79,6 +83,8 @@ pub(crate) fn expand_delete_item( pub struct #builder_name<'a> { pub client: &'a ::raiden::DynamoDbClient, pub input: ::raiden::DeleteItemInput, + pub policy: ::raiden::Policy, + pub condition: &'a ::raiden::retry::RetryCondition, } impl<'a> #builder_name<'a> { @@ -100,13 +106,21 @@ pub(crate) fn expand_delete_item( } pub async fn run(self) -> Result<(), ::raiden::RaidenError> { - #call_inner_run; + let policy: ::raiden::RetryPolicy = self.policy.into(); + let client = self.client; + let input = self.input; + policy.retry_if(move || { + let client = client.clone(); + let input = input.clone(); + async { #call_inner_run } + }, self.condition).await?; + Ok(()) } async fn inner_run( #inner_run_args - client: &::raiden::DynamoDbClient, + client: ::raiden::DynamoDbClient, input: ::raiden::DeleteItemInput, ) -> Result<(), ::raiden::RaidenError> { #api_call_token?; diff --git a/raiden-derive/src/ops/get.rs b/raiden-derive/src/rusoto/ops/get.rs similarity index 100% rename from raiden-derive/src/ops/get.rs rename to raiden-derive/src/rusoto/ops/get.rs diff --git a/raiden-derive/src/rusoto/ops/mod.rs b/raiden-derive/src/rusoto/ops/mod.rs new file mode 100644 index 00000000..f05976c0 --- /dev/null +++ b/raiden-derive/src/rusoto/ops/mod.rs @@ -0,0 +1,21 @@ +mod batch_delete; +mod batch_get; +mod delete; +mod get; +mod put; +mod query; +mod scan; +mod shared; +mod transact_write; +mod update; + +pub(crate) use batch_delete::*; +pub(crate) use batch_get::*; +pub(crate) use delete::*; +pub(crate) use get::*; +pub(crate) use put::*; +pub(crate) use query::*; +pub(crate) use scan::*; +pub(crate) use shared::*; +pub(crate) use transact_write::*; +pub(crate) use update::*; diff --git a/raiden-derive/src/ops/put.rs b/raiden-derive/src/rusoto/ops/put.rs similarity index 100% rename from raiden-derive/src/ops/put.rs rename to raiden-derive/src/rusoto/ops/put.rs diff --git a/raiden-derive/src/ops/query.rs b/raiden-derive/src/rusoto/ops/query.rs similarity index 100% rename from raiden-derive/src/ops/query.rs rename to raiden-derive/src/rusoto/ops/query.rs diff --git a/raiden-derive/src/ops/scan.rs b/raiden-derive/src/rusoto/ops/scan.rs similarity index 95% rename from raiden-derive/src/ops/scan.rs rename to raiden-derive/src/rusoto/ops/scan.rs index 039c7b9f..987d11df 100644 --- a/raiden-derive/src/ops/scan.rs +++ b/raiden-derive/src/rusoto/ops/scan.rs @@ -14,12 +14,12 @@ pub(crate) fn expand_scan( let api_call_token = super::api_call_token!("scan"); let (call_inner_run, inner_run_args) = if cfg!(feature = "tracing") { ( - quote! { #builder_name::inner_run(&self.input.table_name, &self.client, self.input.clone()).await? }, - quote! { table_name: &str, }, + quote! { #builder_name::inner_run(input.table_name.clone(), client, input).await }, + quote! { table_name: String, }, ) } else { ( - quote! { #builder_name::inner_run(&self.client, self.input.clone()).await? }, + quote! { #builder_name::inner_run(client, input).await }, quote! {}, ) }; @@ -32,6 +32,8 @@ pub(crate) fn expand_scan( pub struct #builder_name<'a> { pub client: &'a ::raiden::DynamoDbClient, pub input: ::raiden::ScanInput, + pub policy: ::raiden::Policy, + pub condition: &'a ::raiden::retry::RetryCondition, pub next_token: Option<::raiden::NextToken>, pub limit: Option } @@ -46,6 +48,8 @@ pub(crate) fn expand_scan( #builder_name { client: &self.client, input, + policy: self.retry_condition.strategy.policy(), + condition: &self.retry_condition, next_token: None, limit: None, } @@ -98,7 +102,16 @@ pub(crate) fn expand_scan( self.input.limit = Some(limit); } - let res = #call_inner_run; + let res = { + let policy: ::raiden::RetryPolicy = self.policy.clone().into(); + let client = self.client; + let input = self.input.clone(); + policy.retry_if(move || { + let client = client.clone(); + let input = input.clone(); + async { #call_inner_run } + }, self.condition).await? + }; if let Some(res_items) = res.items { for res_item in res_items.into_iter() { @@ -131,7 +144,7 @@ pub(crate) fn expand_scan( async fn inner_run( #inner_run_args - client: &::raiden::DynamoDbClient, + client: ::raiden::DynamoDbClient, input: ::raiden::ScanInput, ) -> Result<::raiden::ScanOutput, ::raiden::RaidenError> { Ok(#api_call_token?) diff --git a/raiden-derive/src/ops/shared.rs b/raiden-derive/src/rusoto/ops/shared.rs similarity index 54% rename from raiden-derive/src/ops/shared.rs rename to raiden-derive/src/rusoto/ops/shared.rs index dce4da44..48038689 100644 --- a/raiden-derive/src/ops/shared.rs +++ b/raiden-derive/src/rusoto/ops/shared.rs @@ -19,57 +19,58 @@ pub(crate) fn expand_attr_to_item( let ty = &f.ty; let item = quote! { - let item = <#ty as ResolveAttribute>::resolve_attr(&#attr_key, &mut #item_ident); + let item = <#ty as ::raiden::ResolveAttribute>::resolve_attr(&#attr_key, &mut #item_ident); }; + if crate::finder::is_option(ty) { quote! { - #ident: { - #item - if item.is_none() { - None - } else { - let converted = ::raiden::FromAttribute::from_attr(item); - if converted.is_err() { - return Err(::raiden::RaidenError::AttributeConvertError{ attr_name: #attr_key.to_string() }); + #ident: { + #item + if item.is_none() { + None + } else { + let converted = ::raiden::FromAttribute::from_attr(item); + if converted.is_err() { + return Err(::raiden::RaidenError::AttributeConvertError{ attr_name: #attr_key.to_string() }); + } + converted.unwrap() } - converted.unwrap() - } - }, + }, } } else if use_default { quote! { - #ident: { - #item - if item.is_none() { - Default::default() - } else { - let item = item.unwrap(); - // If null is true, use default value. - if let Some(true) = item.null { - Default::default() - } else { - let converted = ::raiden::FromAttribute::from_attr(Some(item)); - if converted.is_err() { - // TODO: improve error handling. - return Err(::raiden::RaidenError::AttributeConvertError{ attr_name: #attr_key.to_string() }); + #ident: { + #item + if item.is_none() { + Default::default() + } else { + let item = item.unwrap(); + // If null is true, use default value. + if let Some(true) = item.null { + Default::default() + } else { + let converted = ::raiden::FromAttribute::from_attr(Some(item)); + if converted.is_err() { + // TODO: improve error handling. + return Err(::raiden::RaidenError::AttributeConvertError{ attr_name: #attr_key.to_string() }); + } + converted.unwrap() + } } - converted.unwrap() - } - } - }, + }, } } else { quote! { #ident: { - #item - let converted = ::raiden::FromAttribute::from_attr(item); - if converted.is_err() { - // TODO: improve error handling. - return Err(::raiden::RaidenError::AttributeConvertError{ attr_name: #attr_key.to_string() }); - } - converted.unwrap() + #item + let converted = ::raiden::FromAttribute::from_attr(item); + if converted.is_err() { + // TODO: improve error handling. + return Err(::raiden::RaidenError::AttributeConvertError{ attr_name: #attr_key.to_string() }); + } + converted.unwrap() }, - } + } } }).collect() } @@ -86,12 +87,12 @@ macro_rules! api_call_token { let span_token = if cfg!(feature = "tracing") { ::quote::quote! { - use tracing::Instrument; - let fut = fut.instrument(::tracing::debug_span!( - "dynamodb::action", - table = #table_name, - api = std::stringify!(#operation), - )); + use tracing::Instrument; + let fut = fut.instrument(::tracing::debug_span!( + "dynamodb::action", + table = #table_name, + api = std::stringify!(#operation), + )); } } else { ::quote::quote! {} diff --git a/raiden-derive/src/ops/transact_write.rs b/raiden-derive/src/rusoto/ops/transact_write.rs similarity index 100% rename from raiden-derive/src/ops/transact_write.rs rename to raiden-derive/src/rusoto/ops/transact_write.rs diff --git a/raiden-derive/src/ops/update.rs b/raiden-derive/src/rusoto/ops/update.rs similarity index 100% rename from raiden-derive/src/ops/update.rs rename to raiden-derive/src/rusoto/ops/update.rs diff --git a/raiden/Cargo.toml b/raiden/Cargo.toml index 6536459c..723f9c9b 100644 --- a/raiden/Cargo.toml +++ b/raiden/Cargo.toml @@ -9,37 +9,62 @@ license.workspace = true [dependencies] again = "0.1" -base64 = "^0.21.5" -raiden-derive = { version = "*", path = "../raiden-derive" } -rusoto_core_default = { package = "rusoto_core", version = "0.48", optional = true } -rusoto_core_rustls = { package = "rusoto_core", version = "0.48", default_features = false, features = [ - "rustls", -], optional = true } -rusoto_credential = "0.48" -rusoto_dynamodb_default = { package = "rusoto_dynamodb", version = "0.48", features = [ - "serialize_structs", -], optional = true } -rusoto_dynamodb_rustls = { package = "rusoto_dynamodb", version = "0.48", default_features = false, features = [ - "rustls", +aws-config = { version = "^1", optional = true } +aws-sdk-dynamodb = { version = "^1", optional = true } +aws-smithy-runtime-api = { version = "^1", optional = true } +base64 = "^0.22" +paste = { version = "1.0.15", optional = true } +raiden-derive = { version = "*", path = "../raiden-derive", default_features = false } +rusoto_core = { package = "rusoto_core", version = "0.48", default_features = false, optional = true } +rusoto_credential = { version = "0.48", optional = true } +rusoto_dynamodb = { package = "rusoto_dynamodb", version = "0.48", default_features = false, features = [ "serialize_structs", ], optional = true } rust-crypto = "^0.2.36" safe-builder = { tag = "0.0.6", git = "https://github.com/raiden-rs/safe-builder.git" } -serde = { version = "^1.0.193", features = ["derive"] } -serde_derive = "^1.0.193" +serde = { version = "^1", features = ["derive"] } +serde_derive = "^1" serde_json = "^1" -thiserror = "^1.0.51" +thiserror = "^1" tracing = { version = "0.1", optional = true } -uuid = { version = "^1.4.1", features = ["v4"] } +uuid = { version = "^1.8", features = ["v4"] } [dev-dependencies] +aws-credential-types = { version = "^1" } +aws-smithy-runtime = { version = "^1" } +hyper-rustls = { version = "=0.25.0", features = ["http2"] } pretty_assertions = "1.4.0" raiden = { path = "./", features = ["tracing"], default_features = false } -time = "0.3.31" -tokio = "1.34.0" -tracing-subscriber = { version = "0.3.18", features = ["env-filter", "time"] } +time = "0.3.36" +tokio = { version = "^1", features = ["rt-multi-thread", "macros"] } +tracing-subscriber = { version = "0.3", features = ["env-filter", "time"] } [features] -default = ["rusoto_core_default", "rusoto_dynamodb_default"] -rustls = ["rusoto_core_rustls", "rusoto_dynamodb_rustls"] +default = ["rusoto"] +aws-sdk = [ + "dep:aws-config", + "dep:aws-sdk-dynamodb", + "dep:aws-smithy-runtime-api", + "dep:paste", + "raiden-derive/aws-sdk", +] +rusoto = [ + "rusoto_core/default", + "rusoto_credential", + "rusoto_dynamodb/default", + "raiden-derive/rusoto", +] +rusoto_rustls = [ + "rusoto_core/rustls", + "rusoto_credential", + "rusoto_dynamodb/rustls", + "raiden-derive/rusoto", +] +rustls = ["rusoto_rustls"] tracing = ["dep:tracing", "raiden-derive/tracing"] + +[package.metadata.cargo-machete] +ignored = ["rust-crypto"] + +[package.metadata.cargo-udeps.ignore] +development = ["aws-credential-types", "aws-smithy-runtime", "hyper-rustls"] diff --git a/raiden/examples/delete.rs b/raiden/examples/delete.rs index 65d2bdf4..a0fe621f 100644 --- a/raiden/examples/delete.rs +++ b/raiden/examples/delete.rs @@ -4,9 +4,8 @@ use tracing_subscriber::{ EnvFilter, }; -#[derive(Raiden)] +#[derive(Raiden, Debug, Clone)] #[raiden(table_name = "QueryTestData0")] -#[derive(Debug, Clone)] #[allow(dead_code)] pub struct Test { #[raiden(partition_key)] @@ -16,6 +15,33 @@ pub struct Test { year: usize, } +#[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] +async fn example() { + let client = Test::client(raiden::Region::Custom { + endpoint: "http://localhost:8000".into(), + name: "ap-northeast-1".into(), + }); + let res = client.delete("id1", 2003_usize).run().await; + + dbg!(&res); + assert!(res.is_ok()); +} + +#[cfg(feature = "aws-sdk")] +async fn example() { + let sdk_config = aws_config::defaults(aws_config::BehaviorVersion::latest()) + .endpoint_url("http://localhost:8000") + .region(raiden::config::Region::from_static("ap-northeast-1")) + .load() + .await; + let sdk_client = raiden::Client::new(&sdk_config); + let client = Test::client_with(sdk_client); + let res = client.delete("id1", 2003_usize).run().await; + + dbg!(&res); + assert!(res.is_ok()); +} + fn main() { tracing_subscriber::fmt() .with_env_filter(EnvFilter::new("delete=debug,info")) @@ -26,15 +52,5 @@ fn main() { .with_timer(UtcTime::rfc_3339()) .init(); - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = Test::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - - let res = client.delete("id1", 2003_usize).run().await; - dbg!(&res); - } - rt.block_on(example()); + tokio::runtime::Runtime::new().unwrap().block_on(example()); } diff --git a/raiden/examples/get_with_reserved.rs b/raiden/examples/get_with_reserved.rs index 53228283..1c4425a7 100644 --- a/raiden/examples/get_with_reserved.rs +++ b/raiden/examples/get_with_reserved.rs @@ -4,7 +4,7 @@ use tracing_subscriber::{ EnvFilter, }; -#[derive(Raiden)] +#[derive(Raiden, Debug)] #[raiden(table_name = "ReservedTestData0")] pub struct Reserved { #[raiden(partition_key)] @@ -12,6 +12,35 @@ pub struct Reserved { pub r#type: String, } +#[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] +async fn example() { + let client = Reserved::client(Region::Custom { + endpoint: "http://localhost:8000".into(), + name: "ap-northeast-1".into(), + }); + let res = client.get("id0").run().await; + + dbg!(&res); + assert!(res.is_err()); +} + +#[cfg(feature = "aws-sdk")] +async fn example() { + let sdk_config = ::raiden::aws_sdk::aws_config::defaults( + ::raiden::aws_sdk::config::BehaviorVersion::latest(), + ) + .endpoint_url("http://localhost:8000") + .region(::raiden::config::Region::from_static("ap-northeast-1")) + .load() + .await; + let sdk_client = ::raiden::Client::new(&sdk_config); + let client = Reserved::client_with(sdk_client); + let res = client.get("id0").run().await; + + dbg!(&res); + assert!(res.is_err()); +} + fn main() { tracing_subscriber::fmt() .with_env_filter(EnvFilter::new("get_with_reserved=debug,info")) @@ -22,13 +51,5 @@ fn main() { .with_timer(UtcTime::rfc_3339()) .init(); - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = Reserved::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let _ = client.get("id0").run().await; - } - rt.block_on(example()); + tokio::runtime::Runtime::new().unwrap().block_on(example()); } diff --git a/raiden/examples/get_with_retries.rs b/raiden/examples/get_with_retries.rs index 370e8714..568b0277 100644 --- a/raiden/examples/get_with_retries.rs +++ b/raiden/examples/get_with_retries.rs @@ -4,7 +4,7 @@ use tracing_subscriber::{ EnvFilter, }; -#[derive(Raiden)] +#[derive(Raiden, Debug)] #[raiden(table_name = "user")] pub struct User { #[raiden(partition_key)] @@ -23,6 +23,43 @@ impl RetryStrategy for MyRetryStrategy { } } +#[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] +async fn example() { + let client = User::client(Region::Custom { + endpoint: "http://localhost:8000".into(), + name: "ap-northeast-1".into(), + }); + let res = client + .with_retries(Box::new(MyRetryStrategy)) + .get("anonymous") + .run() + .await; + + dbg!(&res); + assert!(res.is_err()); +} + +#[cfg(feature = "aws-sdk")] +async fn example() { + let sdk_config = ::raiden::aws_sdk::aws_config::defaults( + ::raiden::aws_sdk::config::BehaviorVersion::latest(), + ) + .endpoint_url("http://localhost:8000") + .region(::raiden::config::Region::from_static("ap-northeast-1")) + .load() + .await; + let sdk_client = ::raiden::Client::new(&sdk_config); + let client = User::client_with(sdk_client); + let res = client + .with_retries(Box::new(MyRetryStrategy)) + .get("anonymous") + .run() + .await; + + dbg!(&res); + assert!(res.is_err()); +} + fn main() { tracing_subscriber::fmt() .with_env_filter(EnvFilter::new("get_with_retries=debug,info")) @@ -33,17 +70,5 @@ fn main() { .with_timer(UtcTime::rfc_3339()) .init(); - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = User::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let _ = client - .with_retries(Box::new(MyRetryStrategy)) - .get("anonymous") - .run() - .await; - } - rt.block_on(example()); + tokio::runtime::Runtime::new().unwrap().block_on(example()); } diff --git a/raiden/examples/hello.rs b/raiden/examples/hello.rs index d7cbba29..7918fb95 100644 --- a/raiden/examples/hello.rs +++ b/raiden/examples/hello.rs @@ -4,16 +4,55 @@ use tracing_subscriber::{ EnvFilter, }; -#[derive(Raiden)] -#[raiden(table_name = "user")] -pub struct User { +#[derive(Raiden, Debug)] +#[raiden(table_name = "hello")] +pub struct Hello { #[raiden(partition_key)] pub id: String, #[raiden(sort_key)] pub year: usize, - #[raiden(uuid)] - pub uuid: String, - pub name: String, +} + +#[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] +async fn example() { + let client = Hello::client(Region::Custom { + endpoint: "http://localhost:8000".into(), + name: "ap-northeast-1".into(), + }); + //let user = HelloPutItemInput { + // id: "a".to_owned(), + // name: "bokuweb".to_owned(), + // // uuid: "aa".to_owned(), + //}; + //let cond = Hello::condition() + // .attr(Hello::name()) + // .eq_attr(Hello::name()); + // + //// let cond = Hello::condition().not().attr_type(Hello::name(), AttributeType::N); + //// .and(Hello::condition().not().attribute_exists(Hello::id())); + let keys: Vec<(&str, usize)> = vec![("bokuweb", 2019), ("raiden", 2020)]; + let res = client.batch_get(keys).run().await; + + dbg!(&res); + assert!(res.is_ok()); +} + +#[cfg(feature = "aws-sdk")] +async fn example() { + let sdk_config = ::raiden::aws_sdk::aws_config::defaults( + ::raiden::aws_sdk::config::BehaviorVersion::latest(), + ) + .endpoint_url("http://localhost:8000") + .region(::raiden::config::Region::from_static("ap-northeast-1")) + .load() + .await; + let sdk_client = ::raiden::Client::new(&sdk_config); + let client = Hello::client_with(sdk_client); + let keys: Vec<(&str, usize)> = vec![("bokuweb", 2019), ("raiden", 2020)]; + let res = client.batch_get(keys).run().await; + + dbg!(&res); + assert!(res.is_ok()); } fn main() { @@ -26,25 +65,5 @@ fn main() { .with_timer(UtcTime::rfc_3339()) .init(); - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = User::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - //let user = UserPutItemInput { - // id: "a".to_owned(), - // name: "bokuweb".to_owned(), - // // uuid: "aa".to_owned(), - //}; - //let cond = User::condition() - // .attr(User::name()) - // .eq_attr(User::name()); - // - //// let cond = User::condition().not().attr_type(User::name(), AttributeType::N); - //// .and(User::condition().not().attribute_exists(User::id())); - let keys: Vec<(&str, usize)> = vec![("bokuweb", 2019), ("raiden", 2020)]; - let _ = client.batch_get(keys).run().await; - } - rt.block_on(example()); + tokio::runtime::Runtime::new().unwrap().block_on(example()); } diff --git a/raiden/examples/last_key.rs b/raiden/examples/last_key.rs index 94a15872..0d340d28 100644 --- a/raiden/examples/last_key.rs +++ b/raiden/examples/last_key.rs @@ -4,13 +4,101 @@ use tracing_subscriber::{ EnvFilter, }; -#[derive(Raiden)] +#[derive(Raiden, Debug)] #[raiden(table_name = "LastEvaluateKeyData")] pub struct Test { #[raiden(partition_key)] pub id: String, pub ref_id: String, - pub long_text: String, + pub long_text: LongText, +} + +#[derive(Clone, PartialEq)] +pub struct LongText(String); + +#[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] +impl IntoAttribute for LongText { + fn into_attr(self) -> AttributeValue { + AttributeValue { + s: Some(self.0), + ..AttributeValue::default() + } + } +} + +#[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] +impl FromAttribute for LongText { + fn from_attr(value: Option) -> Result { + Ok(Self(value.unwrap().s.unwrap())) + } +} + +#[cfg(feature = "aws-sdk")] +impl raiden::IntoAttribute for LongText { + fn into_attr(self) -> raiden::AttributeValue { + raiden::AttributeValue::S(self.0) + } +} + +#[cfg(feature = "aws-sdk")] +impl raiden::FromAttribute for LongText { + fn from_attr(value: Option) -> Result { + if let Some(raiden::AttributeValue::S(v)) = value { + Ok(Self(v)) + } else { + unimplemented!(); + } + } +} + +impl std::fmt::Debug for LongText { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Long long text") + } +} + +#[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] +async fn example() { + let client = Test::client(Region::Custom { + endpoint: "http://localhost:8000".into(), + name: "ap-northeast-1".into(), + }); + let cond = Test::key_condition(Test::ref_id()).eq("id0"); + let res = client + .query() + .index("testGSI") + .limit(5) + .key_condition(cond) + .run() + .await; + + dbg!(&res); + assert!(res.is_ok()); +} + +#[cfg(feature = "aws-sdk")] +async fn example() { + let sdk_config = ::raiden::aws_sdk::aws_config::defaults( + ::raiden::aws_sdk::config::BehaviorVersion::latest(), + ) + .endpoint_url("http://localhost:8000") + .region(::raiden::config::Region::from_static("ap-northeast-1")) + .load() + .await; + let sdk_client = ::raiden::Client::new(&sdk_config); + + let client = Test::client_with(sdk_client); + let cond = Test::key_condition(Test::ref_id()).eq("id0"); + let res = client + .query() + .index("testGSI") + .limit(5) + .key_condition(cond) + .run() + .await; + + dbg!(&res); + assert!(res.is_ok()); } fn main() { @@ -23,21 +111,5 @@ fn main() { .with_timer(UtcTime::rfc_3339()) .init(); - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = Test::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let cond = Test::key_condition(Test::ref_id()).eq("id0"); - let res = client - .query() - .index("testGSI") - .limit(5) - .key_condition(cond) - .run() - .await; - dbg!(&res.unwrap().items.len()); - } - rt.block_on(example()); + tokio::runtime::Runtime::new().unwrap().block_on(example()); } diff --git a/raiden/examples/put.rs b/raiden/examples/put.rs index 8fde2ac7..372aadeb 100644 --- a/raiden/examples/put.rs +++ b/raiden/examples/put.rs @@ -13,6 +13,7 @@ impl From for CustomId { } } +#[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] impl raiden::IntoAttribute for CustomId { fn into_attr(self) -> raiden::AttributeValue { raiden::AttributeValue { @@ -22,13 +23,32 @@ impl raiden::IntoAttribute for CustomId { } } +#[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] impl raiden::FromAttribute for CustomId { fn from_attr(value: Option) -> Result { Ok(CustomId(value.unwrap().s.unwrap())) } } -#[derive(Raiden)] +#[cfg(feature = "aws-sdk")] +impl raiden::IntoAttribute for CustomId { + fn into_attr(self) -> raiden::AttributeValue { + raiden::AttributeValue::S(self.0) + } +} + +#[cfg(feature = "aws-sdk")] +impl raiden::FromAttribute for CustomId { + fn from_attr(value: Option) -> Result { + if let Some(raiden::AttributeValue::S(v)) = value { + Ok(CustomId(v)) + } else { + unimplemented!(); + } + } +} + +#[derive(Raiden, Debug)] #[raiden(table_name = "user")] pub struct User { #[raiden(partition_key)] @@ -38,6 +58,43 @@ pub struct User { pub name: String, } +#[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] +async fn example() { + let client = User::client(Region::Custom { + endpoint: "http://localhost:8000".into(), + name: "ap-northeast-1".into(), + }); + let input = User::put_item_builder() + .id("testId".to_owned()) + .name("bokuweb".to_owned()) + .build(); + let res = client.put(input).run().await; + + dbg!(&res); + assert!(res.is_ok()); +} + +#[cfg(feature = "aws-sdk")] +async fn example() { + let sdk_config = ::raiden::aws_sdk::aws_config::defaults( + ::raiden::aws_sdk::config::BehaviorVersion::latest(), + ) + .endpoint_url("http://localhost:8000") + .region(::raiden::config::Region::from_static("ap-northeast-1")) + .load() + .await; + let sdk_client = ::raiden::Client::new(&sdk_config); + let client = User::client_with(sdk_client); + let input = User::put_item_builder() + .id("testId".to_owned()) + .name("bokuweb".to_owned()) + .build(); + let res = client.put(input).run().await; + + dbg!(&res); + assert!(res.is_ok()); +} + fn main() { tracing_subscriber::fmt() .with_env_filter(EnvFilter::new("put=debug,info")) @@ -48,17 +105,5 @@ fn main() { .with_timer(UtcTime::rfc_3339()) .init(); - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = User::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let input = User::put_item_builder() - .id("testId".to_owned()) - .name("bokuweb".to_owned()) - .build(); - let _ = client.put(input).run().await; - } - rt.block_on(example()); + tokio::runtime::Runtime::new().unwrap().block_on(example()); } diff --git a/raiden/examples/query.rs b/raiden/examples/query.rs index ac3826d8..96caefd0 100644 --- a/raiden/examples/query.rs +++ b/raiden/examples/query.rs @@ -15,17 +15,8 @@ pub struct QueryTestData0 { num: usize, } -#[tokio::main] -async fn main() { - tracing_subscriber::fmt() - .with_env_filter(EnvFilter::new("query=debug,info")) - .with_file(true) - .with_line_number(true) - .with_span_events(FmtSpan::CLOSE) - .with_target(true) - .with_timer(UtcTime::rfc_3339()) - .init(); - +#[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] +async fn example() { let client = QueryTestData0::client(Region::Custom { endpoint: "http://localhost:8000".into(), name: "ap-northeast-1".into(), @@ -34,13 +25,17 @@ async fn main() { .eq("id0") .and(QueryTestData0::key_condition(QueryTestData0::year()).eq(1999)); let res = client.query().key_condition(cond).run().await; + dbg!(&res); + assert!(res.is_ok()); let cond = QueryTestData0::key_condition(QueryTestData0::id()) .eq("id0") .and(QueryTestData0::key_condition(QueryTestData0::year()).eq(1999)); let res = client.query().key_condition(cond).run().await; + dbg!(&res); + assert!(res.is_ok()); let cond = QueryTestData0::key_condition(QueryTestData0::id()).eq("id0"); let filter = QueryTestData0::filter_expression(QueryTestData0::num()).eq(1000); @@ -50,5 +45,60 @@ async fn main() { .filter(filter) .run() .await; + dbg!(&res); + assert!(res.is_ok()); +} + +#[cfg(feature = "aws-sdk")] +async fn example() { + let sdk_config = ::raiden::aws_sdk::aws_config::defaults( + ::raiden::aws_sdk::config::BehaviorVersion::latest(), + ) + .endpoint_url("http://localhost:8000") + .region(::raiden::config::Region::from_static("ap-northeast-1")) + .load() + .await; + let sdk_client = ::raiden::Client::new(&sdk_config); + let client = QueryTestData0::client_with(sdk_client); + let cond = QueryTestData0::key_condition(QueryTestData0::id()) + .eq("id0") + .and(QueryTestData0::key_condition(QueryTestData0::year()).eq(1999)); + let res = client.query().key_condition(cond).run().await; + + dbg!(&res); + assert!(res.is_ok()); + + let cond = QueryTestData0::key_condition(QueryTestData0::id()) + .eq("id0") + .and(QueryTestData0::key_condition(QueryTestData0::year()).eq(1999)); + let res = client.query().key_condition(cond).run().await; + + dbg!(&res); + assert!(res.is_ok()); + + let cond = QueryTestData0::key_condition(QueryTestData0::id()).eq("id0"); + let filter = QueryTestData0::filter_expression(QueryTestData0::num()).eq(1000); + let res = client + .query() + .key_condition(cond) + .filter(filter) + .run() + .await; + + dbg!(&res); + assert!(res.is_ok()); +} + +fn main() { + tracing_subscriber::fmt() + .with_env_filter(EnvFilter::new("query=debug,info")) + .with_file(true) + .with_line_number(true) + .with_span_events(FmtSpan::CLOSE) + .with_target(true) + .with_timer(UtcTime::rfc_3339()) + .init(); + + tokio::runtime::Runtime::new().unwrap().block_on(example()); } diff --git a/raiden/examples/query_rename.rs b/raiden/examples/query_rename.rs index 43c22ef1..68718ce3 100644 --- a/raiden/examples/query_rename.rs +++ b/raiden/examples/query_rename.rs @@ -4,7 +4,7 @@ use tracing_subscriber::{ EnvFilter, }; -#[derive(Raiden)] +#[derive(Raiden, Debug)] #[raiden(table_name = "Project")] #[raiden(rename_all = "camelCase")] pub struct Project { @@ -14,6 +14,49 @@ pub struct Project { pub updated_at: String, } +#[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] +async fn example() { + let client = Project::client(Region::Custom { + endpoint: "http://localhost:8000".into(), + name: "ap-northeast-1".into(), + }); + let cond = Project::key_condition(Project::org_id()).eq("myOrg"); + let res = client + .query() + .index("orgIndex") + .limit(11) + .key_condition(cond) + .run() + .await; + + dbg!(&res); + assert!(res.is_ok()); +} + +#[cfg(feature = "aws-sdk")] +async fn example() { + let sdk_config = ::raiden::aws_sdk::aws_config::defaults( + ::raiden::aws_sdk::config::BehaviorVersion::latest(), + ) + .endpoint_url("http://localhost:8000") + .region(::raiden::config::Region::from_static("ap-northeast-1")) + .load() + .await; + let sdk_client = ::raiden::Client::new(&sdk_config); + let client = Project::client_with(sdk_client); + let cond = Project::key_condition(Project::org_id()).eq("myOrg"); + let res = client + .query() + .index("orgIndex") + .limit(11) + .key_condition(cond) + .run() + .await; + + dbg!(&res); + assert!(res.is_ok()); +} + fn main() { tracing_subscriber::fmt() .with_env_filter(EnvFilter::new("query_rename=debug,info")) @@ -24,20 +67,5 @@ fn main() { .with_timer(UtcTime::rfc_3339()) .init(); - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = Project::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let cond = Project::key_condition(Project::org_id()).eq("myOrg"); - let _res = client - .query() - .index("orgIndex") - .limit(11) - .key_condition(cond) - .run() - .await; - } - rt.block_on(example()); + tokio::runtime::Runtime::new().unwrap().block_on(example()); } diff --git a/raiden/examples/scan.rs b/raiden/examples/scan.rs index b31fccc3..33e941f4 100644 --- a/raiden/examples/scan.rs +++ b/raiden/examples/scan.rs @@ -15,8 +15,36 @@ pub struct ScanTestData0 { num: usize, } -#[tokio::main] -async fn main() { +#[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] +async fn example() { + let client = ScanTestData0::client(Region::Custom { + endpoint: "http://localhost:8000".into(), + name: "ap-northeast-1".into(), + }); + let res = client.scan().run().await; + + dbg!(&res); + assert!(res.is_ok()); +} + +#[cfg(feature = "aws-sdk")] +async fn example() { + let sdk_config = ::raiden::aws_sdk::aws_config::defaults( + ::raiden::aws_sdk::config::BehaviorVersion::latest(), + ) + .endpoint_url("http://localhost:8000") + .region(::raiden::config::Region::from_static("ap-northeast-1")) + .load() + .await; + let sdk_client = ::raiden::Client::new(&sdk_config); + let client = ScanTestData0::client_with(sdk_client); + let res = client.scan().run().await; + + dbg!(&res); + assert!(res.is_ok()); +} + +fn main() { tracing_subscriber::fmt() .with_env_filter(EnvFilter::new("scan=debug,info")) .with_file(true) @@ -26,10 +54,5 @@ async fn main() { .with_timer(UtcTime::rfc_3339()) .init(); - let client = ScanTestData0::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let res = client.scan().run().await; - dbg!(&res); + tokio::runtime::Runtime::new().unwrap().block_on(example()); } diff --git a/raiden/examples/scan_with_filter.rs b/raiden/examples/scan_with_filter.rs index 83f92ba8..f5fee17e 100644 --- a/raiden/examples/scan_with_filter.rs +++ b/raiden/examples/scan_with_filter.rs @@ -15,8 +15,40 @@ pub struct Scan { num: usize, } -#[tokio::main] -async fn main() { +#[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] +async fn example() { + let client = Scan::client(Region::Custom { + endpoint: "http://localhost:8000".into(), + name: "ap-northeast-1".into(), + }); + let filter = Scan::filter_expression(Scan::num()).eq(1000); + let res = client.scan().filter(filter).run().await; + + dbg!(&res); + assert!(res.is_ok()); + assert_eq!(res.unwrap().items.len(), 50); +} + +#[cfg(feature = "aws-sdk")] +async fn example() { + let sdk_config = ::raiden::aws_sdk::aws_config::defaults( + ::raiden::aws_sdk::config::BehaviorVersion::latest(), + ) + .endpoint_url("http://localhost:8000") + .region(::raiden::config::Region::from_static("ap-northeast-1")) + .load() + .await; + let sdk_client = ::raiden::Client::new(&sdk_config); + let client = Scan::client_with(sdk_client); + let filter = Scan::filter_expression(Scan::num()).eq(1000); + let res = client.scan().filter(filter).run().await; + + dbg!(&res); + assert!(res.is_ok()); + assert_eq!(res.unwrap().items.len(), 50); +} + +fn main() { tracing_subscriber::fmt() .with_env_filter(EnvFilter::new("scan_with_filter=debug,info")) .with_file(true) @@ -26,11 +58,5 @@ async fn main() { .with_timer(UtcTime::rfc_3339()) .init(); - let client = Scan::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let filter = Scan::filter_expression(Scan::num()).eq(1000); - let res = client.scan().filter(filter).run().await.unwrap(); - assert_eq!(res.items.len(), 50); + tokio::runtime::Runtime::new().unwrap().block_on(example()); } diff --git a/raiden/examples/transact_write.rs b/raiden/examples/transact_write.rs index c435ef75..b9fb8202 100644 --- a/raiden/examples/transact_write.rs +++ b/raiden/examples/transact_write.rs @@ -12,6 +12,62 @@ pub struct User { pub name: String, } +#[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] +async fn example() { + let tx = ::raiden::WriteTx::new(Region::Custom { + endpoint: "http://localhost:8000".into(), + name: "ap-northeast-1".into(), + }); + let cond = User::condition().attr_not_exists(User::id()); + let input = User::put_item_builder() + .id("testId".to_owned()) + .name("bokuweb".to_owned()) + .build(); + let input2 = User::put_item_builder() + .id("testId2".to_owned()) + .name("bokuweb".to_owned()) + .build(); + let res = tx + .put(User::put(input).condition(cond)) + .put(User::put(input2)) + .run() + .await; + + dbg!(&res); + assert!(res.is_ok()); +} + +#[cfg(feature = "aws-sdk")] +async fn example() { + let sdk_config = ::raiden::aws_sdk::aws_config::defaults( + ::raiden::aws_sdk::config::BehaviorVersion::latest(), + ) + .endpoint_url("http://localhost:8000") + .region(::raiden::config::Region::from_static("ap-northeast-1")) + .load() + .await; + let sdk_client = ::raiden::Client::new(&sdk_config); + + let tx = ::raiden::WriteTx::new_with_client(sdk_client); + let cond = User::condition().attr_not_exists(User::id()); + let input = User::put_item_builder() + .id("testId".to_owned()) + .name("bokuweb".to_owned()) + .build(); + let input2 = User::put_item_builder() + .id("testId2".to_owned()) + .name("bokuweb".to_owned()) + .build(); + let res = tx + .put(User::put(input).condition(cond)) + .put(User::put(input2)) + .run() + .await; + + dbg!(&res); + assert!(res.is_ok()); +} + fn main() { tracing_subscriber::fmt() .with_env_filter(EnvFilter::new("raiden=debug,info")) @@ -22,26 +78,5 @@ fn main() { .with_timer(UtcTime::rfc_3339()) .init(); - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let tx = ::raiden::WriteTx::new(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let cond = User::condition().attr_not_exists(User::id()); - let input = User::put_item_builder() - .id("testId".to_owned()) - .name("bokuweb".to_owned()) - .build(); - let input2 = User::put_item_builder() - .id("testId2".to_owned()) - .name("bokuweb".to_owned()) - .build(); - tx.put(User::put(input).condition(cond)) - .put(User::put(input2)) - .run() - .await - .unwrap(); - } - rt.block_on(example()); + tokio::runtime::Runtime::new().unwrap().block_on(example()); } diff --git a/raiden/examples/transact_write_with_http_client.rs b/raiden/examples/transact_write_with_http_client.rs index 2a79bc45..4e6675dd 100644 --- a/raiden/examples/transact_write_with_http_client.rs +++ b/raiden/examples/transact_write_with_http_client.rs @@ -12,6 +12,84 @@ pub struct User { pub name: String, } +#[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] +async fn example() { + let dispatcher = + raiden::request::HttpClient::new().expect("failed to create request dispatcher"); + let credentials_provider = raiden::credential::DefaultCredentialsProvider::new() + .expect("failed to create credentials provider"); + let core_client = raiden::Client::new_with(credentials_provider, dispatcher); + let tx = ::raiden::WriteTx::new_with_client( + core_client, + Region::Custom { + endpoint: "http://localhost:8000".into(), + name: "ap-northeast-1".into(), + }, + ); + let cond = User::condition().attr_not_exists(User::id()); + let input = User::put_item_builder() + .id("testId".to_owned()) + .name("bokuweb".to_owned()) + .build(); + let input2 = User::put_item_builder() + .id("testId2".to_owned()) + .name("bokuweb".to_owned()) + .build(); + let res = tx + .put(User::put(input).condition(cond)) + .put(User::put(input2)) + .run() + .await; + + dbg!(&res); + assert!(res.is_ok()); +} + +#[cfg(feature = "aws-sdk")] +async fn example() { + let https_connector = hyper_rustls::HttpsConnectorBuilder::new() + .with_native_roots() + .expect("should be success") + .https_or_http() + .enable_http1() + .enable_http2() + .build(); + let http_client = aws_smithy_runtime::client::http::hyper_014::HyperClientBuilder::new() + .build(https_connector); + let sdk_config = ::raiden::aws_sdk::aws_config::defaults( + ::raiden::aws_sdk::config::BehaviorVersion::latest(), + ) + .endpoint_url("http://localhost:8000") + .http_client(http_client) + .region(raiden::config::Region::from_static("ap-northeast-1")) + .timeout_config( + raiden::config::timeout::TimeoutConfig::builder() + .connect_timeout(std::time::Duration::from_secs(5)) + .build(), + ) + .load() + .await; + let sdk_client = ::raiden::Client::new(&sdk_config); + let tx = ::raiden::WriteTx::new_with_client(sdk_client); + let cond = User::condition().attr_not_exists(User::id()); + let input = User::put_item_builder() + .id("testId".to_owned()) + .name("bokuweb".to_owned()) + .build(); + let input2 = User::put_item_builder() + .id("testId2".to_owned()) + .name("bokuweb".to_owned()) + .build(); + let res = tx + .put(User::put(input).condition(cond)) + .put(User::put(input2)) + .run() + .await; + + dbg!(&res); + assert!(res.is_ok()); +} + fn main() { tracing_subscriber::fmt() .with_env_filter(EnvFilter::new("raiden=debug,info")) @@ -22,35 +100,5 @@ fn main() { .with_timer(UtcTime::rfc_3339()) .init(); - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let dispatcher = - raiden::request::HttpClient::new().expect("failed to create request dispatcher"); - let credentials_provider = raiden::credential::DefaultCredentialsProvider::new() - .expect("failed to create credentials provider"); - let core_client = raiden::Client::new_with(credentials_provider, dispatcher); - - let tx = ::raiden::WriteTx::new_with_client( - core_client, - Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }, - ); - let cond = User::condition().attr_not_exists(User::id()); - let input = User::put_item_builder() - .id("testId".to_owned()) - .name("bokuweb".to_owned()) - .build(); - let input2 = User::put_item_builder() - .id("testId2".to_owned()) - .name("bokuweb".to_owned()) - .build(); - tx.put(User::put(input).condition(cond)) - .put(User::put(input2)) - .run() - .await - .unwrap(); - } - rt.block_on(example()); + tokio::runtime::Runtime::new().unwrap().block_on(example()); } diff --git a/raiden/examples/update.rs b/raiden/examples/update.rs index 9197d371..d59f982b 100644 --- a/raiden/examples/update.rs +++ b/raiden/examples/update.rs @@ -14,6 +14,41 @@ pub struct Example { age: u8, } +#[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] +async fn example() { + let client = Example::client(Region::Custom { + endpoint: "http://localhost:8000".into(), + name: "ap-northeast-1".into(), + }); + let set_expression = Example::update_expression() + .set(Example::name()) + .value("updated!!"); + let res = client.update("id0").set(set_expression).run().await; + + dbg!(&res); + assert!(res.is_ok()); +} + +#[cfg(feature = "aws-sdk")] +async fn example() { + let sdk_config = ::raiden::aws_sdk::aws_config::defaults( + ::raiden::aws_sdk::config::BehaviorVersion::latest(), + ) + .endpoint_url("http://localhost:8000") + .region(::raiden::config::Region::from_static("ap-northeast-1")) + .load() + .await; + let sdk_client = ::raiden::Client::new(&sdk_config); + let client = Example::client_with(sdk_client); + let set_expression = Example::update_expression() + .set(Example::name()) + .value("updated!!"); + let res = client.update("id0").set(set_expression).run().await; + + dbg!(&res); + assert!(res.is_ok()); +} + fn main() { tracing_subscriber::fmt() .with_env_filter(EnvFilter::new("update=debug,info")) @@ -24,22 +59,5 @@ fn main() { .with_timer(UtcTime::rfc_3339()) .init(); - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = Example::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let set_expression = Example::update_expression() - .set(Example::name()) - .value("updated!!"); - let res = client - .update("id0") - .set(set_expression) - .run() - .await - .unwrap(); - dbg!(res.item); - } - rt.block_on(example()); + tokio::runtime::Runtime::new().unwrap().block_on(example()); } diff --git a/raiden/examples/with_http_client.rs b/raiden/examples/with_http_client.rs index 7380acc1..71cdc7e5 100644 --- a/raiden/examples/with_http_client.rs +++ b/raiden/examples/with_http_client.rs @@ -4,16 +4,67 @@ use tracing_subscriber::{ EnvFilter, }; -#[derive(Raiden)] -#[raiden(table_name = "user")] +#[derive(Raiden, Debug)] +#[raiden(table_name = "hello")] pub struct User { #[raiden(partition_key)] pub id: String, #[raiden(sort_key)] pub year: usize, - #[raiden(uuid)] - pub uuid: String, - pub name: String, +} + +#[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] +async fn example() { + let dispatcher = + raiden::request::HttpClient::new().expect("failed to create request dispatcher"); + let credentials_provider = raiden::credential::DefaultCredentialsProvider::new() + .expect("failed to create credentials provider"); + let core_client = raiden::Client::new_with(credentials_provider, dispatcher); + let client = User::client_with( + core_client, + Region::Custom { + endpoint: "http://localhost:8000".into(), + name: "ap-northeast-1".into(), + }, + ); + let keys: Vec<(&str, usize)> = vec![("bokuweb", 2019), ("raiden", 2020)]; + let res = client.batch_get(keys).run().await; + + dbg!(&res); + assert!(res.is_ok()); +} + +#[cfg(feature = "aws-sdk")] +async fn example() { + let https_connector = hyper_rustls::HttpsConnectorBuilder::new() + .with_native_roots() + .expect("should be success") + .https_or_http() + .enable_http1() + .enable_http2() + .build(); + let http_client = aws_smithy_runtime::client::http::hyper_014::HyperClientBuilder::new() + .build(https_connector); + let sdk_config = ::raiden::aws_sdk::aws_config::defaults( + ::raiden::aws_sdk::config::BehaviorVersion::latest(), + ) + .endpoint_url("http://localhost:8000") + .http_client(http_client) + .region(raiden::config::Region::from_static("ap-northeast-1")) + .timeout_config( + raiden::config::timeout::TimeoutConfig::builder() + .connect_timeout(std::time::Duration::from_secs(5)) + .build(), + ) + .load() + .await; + let sdk_client = ::raiden::Client::new(&sdk_config); + let client = User::client_with(sdk_client); + let keys: Vec<(&str, usize)> = vec![("bokuweb", 2019), ("raiden", 2020)]; + let res = client.batch_get(keys).run().await; + + dbg!(&res); + assert!(res.is_ok()); } fn main() { @@ -26,24 +77,5 @@ fn main() { .with_timer(UtcTime::rfc_3339()) .init(); - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let dispatcher = - raiden::request::HttpClient::new().expect("failed to create request dispatcher"); - let credentials_provider = raiden::credential::DefaultCredentialsProvider::new() - .expect("failed to create credentials provider"); - let core_client = raiden::Client::new_with(credentials_provider, dispatcher); - - let client = User::client_with( - core_client, - Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }, - ); - - let keys: Vec<(&str, usize)> = vec![("bokuweb", 2019), ("raiden", 2020)]; - let _ = client.batch_get(keys).run().await; - } - rt.block_on(example()); + tokio::runtime::Runtime::new().unwrap().block_on(example()); } diff --git a/raiden/src/aws_sdk/errors.rs b/raiden/src/aws_sdk/errors.rs new file mode 100644 index 00000000..42ff02b3 --- /dev/null +++ b/raiden/src/aws_sdk/errors.rs @@ -0,0 +1,333 @@ +use crate::{ + aws_sdk::{ + error::{ProvideErrorMetadata, SdkError}, + operation::{ + batch_get_item::BatchGetItemError, batch_write_item::BatchWriteItemError, + delete_item::DeleteItemError, get_item::GetItemError, put_item::PutItemError, + query::QueryError, scan::ScanError, transact_write_items::TransactWriteItemsError, + update_item::UpdateItemError, + }, + }, + RaidenError, RaidenTransactionCancellationReasons, +}; + +fn into_raiden_error(error: SdkError) -> RaidenError +where + E: std::fmt::Debug + ProvideErrorMetadata, +{ + let (code, message) = ( + error.meta().code().map(|v| v.to_owned()), + error.meta().message().map(|v| v.to_owned()), + ); + + match error { + SdkError::ConstructionFailure(err) => RaidenError::Construction(err), + SdkError::TimeoutError(err) => RaidenError::Timeout(err), + SdkError::DispatchFailure(err) => RaidenError::HttpDispatch(err), + SdkError::ResponseError(err) => RaidenError::Unknown(err.into_raw()), + // This pattern is only for handling ServiceError::Unhandled. + // Other patterns should be handled in each ServiceError. + SdkError::ServiceError(err) => { + let Some(code) = code else { + return RaidenError::Unknown(err.into_raw()); + }; + + // https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Programming.Errors.html + match code.as_str() { + "ValidationException" => RaidenError::Validation(if let Some(message) = message { + message + } else { + format!("{err:?}") + }), + _ => RaidenError::Unknown(err.into_raw()), + } + } + _ => unreachable!( + "Unexpected variant of SdkError detected. Raiden must be handle this variant." + ), + } +} + +impl From> for RaidenError { + fn from(error: SdkError) -> Self { + match &error { + SdkError::ServiceError(err) => match err.err() { + BatchGetItemError::InternalServerError(err) => { + RaidenError::InternalServerError(err.to_string()) + } + BatchGetItemError::InvalidEndpointException(err) => { + RaidenError::InternalServerError(err.to_string()) + } + BatchGetItemError::ProvisionedThroughputExceededException(err) => { + RaidenError::ProvisionedThroughputExceeded(err.to_string()) + } + BatchGetItemError::RequestLimitExceeded(err) => { + RaidenError::RequestLimitExceeded(err.to_string()) + } + BatchGetItemError::ResourceNotFoundException(err) => { + RaidenError::ResourceNotFound(err.to_string()) + } + _ => into_raiden_error(error), + }, + _ => into_raiden_error(error), + } + } +} + +impl From> for RaidenError { + fn from(error: SdkError) -> Self { + match &error { + SdkError::ServiceError(err) => match err.err() { + BatchWriteItemError::InternalServerError(err) => { + RaidenError::InternalServerError(err.to_string()) + } + BatchWriteItemError::InvalidEndpointException(err) => { + RaidenError::InternalServerError(err.to_string()) + } + BatchWriteItemError::ItemCollectionSizeLimitExceededException(err) => { + RaidenError::ItemCollectionSizeLimitExceeded(err.to_string()) + } + BatchWriteItemError::ProvisionedThroughputExceededException(err) => { + RaidenError::ProvisionedThroughputExceeded(err.to_string()) + } + BatchWriteItemError::RequestLimitExceeded(err) => { + RaidenError::RequestLimitExceeded(err.to_string()) + } + BatchWriteItemError::ResourceNotFoundException(err) => { + RaidenError::ResourceNotFound(err.to_string()) + } + _ => into_raiden_error(error), + }, + _ => into_raiden_error(error), + } + } +} + +impl From> for RaidenError { + fn from(error: SdkError) -> Self { + match &error { + SdkError::ServiceError(err) => match err.err() { + DeleteItemError::ConditionalCheckFailedException(err) => { + RaidenError::ConditionalCheckFailed(err.to_string()) + } + DeleteItemError::InternalServerError(err) => { + RaidenError::InternalServerError(err.to_string()) + } + DeleteItemError::InvalidEndpointException(err) => { + RaidenError::InternalServerError(err.to_string()) + } + DeleteItemError::ItemCollectionSizeLimitExceededException(err) => { + RaidenError::ItemCollectionSizeLimitExceeded(err.to_string()) + } + DeleteItemError::ProvisionedThroughputExceededException(err) => { + RaidenError::ProvisionedThroughputExceeded(err.to_string()) + } + DeleteItemError::RequestLimitExceeded(err) => { + RaidenError::RequestLimitExceeded(err.to_string()) + } + DeleteItemError::ResourceNotFoundException(err) => { + RaidenError::ResourceNotFound(err.to_string()) + } + DeleteItemError::TransactionConflictException(err) => { + RaidenError::TransactionConflict(err.to_string()) + } + _ => into_raiden_error(error), + }, + _ => into_raiden_error(error), + } + } +} + +impl From> for RaidenError { + fn from(error: SdkError) -> Self { + match &error { + SdkError::ServiceError(err) => match err.err() { + GetItemError::InternalServerError(err) => { + RaidenError::InternalServerError(err.to_string()) + } + GetItemError::InvalidEndpointException(err) => { + RaidenError::InternalServerError(err.to_string()) + } + GetItemError::ProvisionedThroughputExceededException(err) => { + RaidenError::ProvisionedThroughputExceeded(err.to_string()) + } + GetItemError::RequestLimitExceeded(err) => { + RaidenError::RequestLimitExceeded(err.to_string()) + } + GetItemError::ResourceNotFoundException(err) => { + RaidenError::ResourceNotFound(err.to_string()) + } + _ => into_raiden_error(error), + }, + _ => into_raiden_error(error), + } + } +} + +impl From> for RaidenError { + fn from(error: SdkError) -> Self { + match &error { + SdkError::ServiceError(err) => match err.err() { + PutItemError::ConditionalCheckFailedException(err) => { + RaidenError::ConditionalCheckFailed(err.to_string()) + } + PutItemError::InternalServerError(err) => { + RaidenError::InternalServerError(err.to_string()) + } + PutItemError::InvalidEndpointException(err) => { + RaidenError::InternalServerError(err.to_string()) + } + PutItemError::ItemCollectionSizeLimitExceededException(err) => { + RaidenError::ItemCollectionSizeLimitExceeded(err.to_string()) + } + PutItemError::ProvisionedThroughputExceededException(err) => { + RaidenError::ProvisionedThroughputExceeded(err.to_string()) + } + PutItemError::RequestLimitExceeded(err) => { + RaidenError::RequestLimitExceeded(err.to_string()) + } + PutItemError::ResourceNotFoundException(err) => { + RaidenError::ResourceNotFound(err.to_string()) + } + PutItemError::TransactionConflictException(err) => { + RaidenError::TransactionConflict(err.to_string()) + } + _ => into_raiden_error(error), + }, + _ => into_raiden_error(error), + } + } +} + +impl From> for RaidenError { + fn from(error: SdkError) -> Self { + match &error { + SdkError::ServiceError(err) => match err.err() { + QueryError::InternalServerError(err) => { + RaidenError::InternalServerError(err.to_string()) + } + QueryError::InvalidEndpointException(err) => { + RaidenError::InternalServerError(err.to_string()) + } + QueryError::ProvisionedThroughputExceededException(err) => { + RaidenError::ProvisionedThroughputExceeded(err.to_string()) + } + QueryError::RequestLimitExceeded(err) => { + RaidenError::RequestLimitExceeded(err.to_string()) + } + QueryError::ResourceNotFoundException(err) => { + RaidenError::ResourceNotFound(err.to_string()) + } + _ => into_raiden_error(error), + }, + _ => into_raiden_error(error), + } + } +} + +impl From> for RaidenError { + fn from(error: SdkError) -> Self { + match &error { + SdkError::ServiceError(err) => match err.err() { + ScanError::InternalServerError(err) => { + RaidenError::InternalServerError(err.to_string()) + } + ScanError::InvalidEndpointException(err) => { + RaidenError::InternalServerError(err.to_string()) + } + ScanError::ProvisionedThroughputExceededException(err) => { + RaidenError::ProvisionedThroughputExceeded(err.to_string()) + } + ScanError::RequestLimitExceeded(err) => { + RaidenError::RequestLimitExceeded(err.to_string()) + } + ScanError::ResourceNotFoundException(err) => { + RaidenError::ResourceNotFound(err.to_string()) + } + _ => into_raiden_error(error), + }, + _ => into_raiden_error(error), + } + } +} + +impl From> for RaidenError { + fn from(error: SdkError) -> Self { + match &error { + SdkError::ServiceError(err) => match err.err() { + TransactWriteItemsError::IdempotentParameterMismatchException(err) => { + RaidenError::IdempotentParameterMismatch(err.to_string()) + } + TransactWriteItemsError::InternalServerError(err) => { + RaidenError::InternalServerError(err.to_string()) + } + TransactWriteItemsError::InvalidEndpointException(err) => { + RaidenError::InternalServerError(err.to_string()) + } + TransactWriteItemsError::ProvisionedThroughputExceededException(err) => { + RaidenError::ProvisionedThroughputExceeded(err.to_string()) + } + TransactWriteItemsError::RequestLimitExceeded(err) => { + RaidenError::RequestLimitExceeded(err.to_string()) + } + TransactWriteItemsError::ResourceNotFoundException(err) => { + RaidenError::ResourceNotFound(err.to_string()) + } + TransactWriteItemsError::TransactionCanceledException(err) => { + let reasons = RaidenTransactionCancellationReasons::from_str( + err.message + .clone() + .unwrap_or_else(|| "transaction canceled".to_owned()) + .as_str(), + ); + let raw_reasons = err.cancellation_reasons.clone().unwrap_or_default(); + + RaidenError::TransactionCanceled { + reasons, + raw_reasons, + } + } + TransactWriteItemsError::TransactionInProgressException(err) => { + RaidenError::TransactionInProgress(err.to_string()) + } + _ => into_raiden_error(error), + }, + _ => into_raiden_error(error), + } + } +} + +impl From> for RaidenError { + fn from(error: SdkError) -> Self { + match &error { + SdkError::ServiceError(err) => match err.err() { + UpdateItemError::ConditionalCheckFailedException(err) => { + RaidenError::ConditionalCheckFailed(err.to_string()) + } + UpdateItemError::InternalServerError(err) => { + RaidenError::InternalServerError(err.to_string()) + } + UpdateItemError::InvalidEndpointException(err) => { + RaidenError::InternalServerError(err.to_string()) + } + UpdateItemError::ItemCollectionSizeLimitExceededException(err) => { + RaidenError::ItemCollectionSizeLimitExceeded(err.to_string()) + } + UpdateItemError::ProvisionedThroughputExceededException(err) => { + RaidenError::ProvisionedThroughputExceeded(err.to_string()) + } + UpdateItemError::RequestLimitExceeded(err) => { + RaidenError::RequestLimitExceeded(err.to_string()) + } + UpdateItemError::ResourceNotFoundException(err) => { + RaidenError::ResourceNotFound(err.to_string()) + } + UpdateItemError::TransactionConflictException(err) => { + RaidenError::TransactionConflict(err.to_string()) + } + _ => into_raiden_error(error), + }, + _ => into_raiden_error(error), + } + } +} diff --git a/raiden/src/aws_sdk/mod.rs b/raiden/src/aws_sdk/mod.rs new file mode 100644 index 00000000..3f20c745 --- /dev/null +++ b/raiden/src/aws_sdk/mod.rs @@ -0,0 +1,324 @@ +mod errors; +mod ops; +pub(crate) mod serialize; + +use std::collections::{BTreeSet, HashMap, HashSet}; + +pub use self::ops::*; +pub use aws_config; +pub use aws_sdk_dynamodb::*; + +use crate::{ + aws_sdk::types::AttributeValue, AttributeType, AttributeValues, ConversionError, FromAttribute, + FromStringSetItem, IntoAttribute, IntoStringSetItem, RaidenError, +}; + +impl IntoAttribute for AttributeType { + fn into_attr(self) -> AttributeValue { + AttributeValue::S(self.to_string()) + } +} + +impl IntoAttribute for String { + fn into_attr(self) -> AttributeValue { + AttributeValue::S(self) + } +} + +impl FromAttribute for String { + fn from_attr(value: Option) -> Result { + match value { + Some(v) if v.is_null() => Ok("".to_owned()), + Some(AttributeValue::S(s)) => Ok(s), + _ => Err(ConversionError::ValueIsNone), + } + } +} + +impl IntoAttribute for &'_ str { + fn into_attr(self) -> AttributeValue { + if self.is_empty() { + AttributeValue::Null(true) + } else { + AttributeValue::S(self.to_owned()) + } + } +} + +impl<'a> IntoAttribute for std::borrow::Cow<'a, str> { + fn into_attr(self) -> AttributeValue { + let s = match self { + std::borrow::Cow::Owned(o) => o, + std::borrow::Cow::Borrowed(b) => b.to_owned(), + }; + + if s.is_empty() { + AttributeValue::Null(true) + } else { + AttributeValue::S(s) + } + } +} + +impl<'a> FromAttribute for std::borrow::Cow<'a, str> { + fn from_attr(value: Option) -> Result { + match value { + Some(v) if v.is_null() => Ok(std::borrow::Cow::Owned("".to_owned())), + Some(AttributeValue::S(s)) => Ok(std::borrow::Cow::Owned(s)), + _ => Err(ConversionError::ValueIsNone), + } + } +} + +macro_rules! default_attr_for_num { + ($to: ty) => { + impl IntoAttribute for $to { + fn into_attr(self) -> AttributeValue { + AttributeValue::N(format!("{self}")) + } + } + + impl FromAttribute for $to { + fn from_attr(value: Option) -> Result { + if let Some(AttributeValue::N(n)) = value { + Ok(n.parse().unwrap()) + } else { + Err(ConversionError::ValueIsNone) + } + } + } + }; +} + +pub(crate) use default_attr_for_num; + +impl IntoAttribute for Option { + fn into_attr(self) -> AttributeValue { + if let Some(value) = self { + value.into_attr() + } else { + AttributeValue::Null(true) + } + } +} + +impl FromAttribute for Option { + fn from_attr(value: Option) -> Result { + match value { + None => Ok(None), + Some(v) if v.is_null() => Ok(None), + _ => Ok(Some(FromAttribute::from_attr(value)?)), + } + } +} + +impl IntoAttribute for bool { + fn into_attr(self) -> AttributeValue { + AttributeValue::Bool(self) + } +} + +impl FromAttribute for bool { + fn from_attr(value: Option) -> Result { + if let Some(AttributeValue::Bool(v)) = value { + Ok(v) + } else { + Err(ConversionError::ValueIsNone) + } + } +} + +impl IntoAttribute for Vec { + fn into_attr(mut self) -> AttributeValue { + if self.is_empty() { + // See. https://github.com/raiden-rs/raiden/issues/57 + AttributeValue::Null(true) + } else { + AttributeValue::L(self.drain(..).map(|s| s.into_attr()).collect()) + } + } +} + +impl FromAttribute for Vec { + fn from_attr(value: Option) -> Result { + match value { + Some(AttributeValue::L(v)) => { + v.into_iter().map(|item| A::from_attr(Some(item))).collect() + } + // See. https://github.com/raiden-rs/raiden/issues/57 + Some(v) if v.is_null() => Ok(vec![]), + None => Ok(vec![]), + _ => Err(ConversionError::ValueIsNone), + } + } +} + +macro_rules! default_number_hash_set_convertor { + ($to: ty) => { + impl IntoAttribute for std::collections::HashSet<$to> { + fn into_attr(self) -> AttributeValue { + if self.is_empty() { + // See. https://github.com/raiden-rs/raiden/issues/57 + // https://github.com/raiden-rs/raiden-dynamo/issues/64 + AttributeValue::Ns(Default::default()) + } else { + AttributeValue::Ns(self.into_iter().map(|s| s.to_string()).collect()) + } + } + } + + impl FromAttribute for std::collections::HashSet<$to> { + fn from_attr(value: Option) -> Result { + match value { + Some(AttributeValue::Ns(mut nums)) => { + let mut results: Vec> = nums + .drain(..) + .map(|ns| ns.parse().map_err(|_| ConversionError::ParseInt)) + .collect(); + + results.drain(..).collect() + } + // See. https://github.com/raiden-rs/raiden/issues/57 + Some(v) if v.is_null() => Ok(std::collections::HashSet::new()), + None => Ok(std::collections::HashSet::new()), + _ => Err(ConversionError::ValueIsNone), + } + } + } + }; +} + +pub(crate) use default_number_hash_set_convertor; + +macro_rules! default_number_btree_set_convertor { + ($to: ty) => { + impl IntoAttribute for std::collections::BTreeSet<$to> { + fn into_attr(self) -> AttributeValue { + if self.is_empty() { + // See. https://github.com/raiden-rs/raiden/issues/57 + // https://github.com/raiden-rs/raiden-dynamo/issues/64 + AttributeValue::Ns(Default::default()) + } else { + AttributeValue::Ns(self.into_iter().map(|s| s.to_string()).collect()) + } + } + } + + impl FromAttribute for std::collections::BTreeSet<$to> { + fn from_attr(value: Option) -> Result { + match value { + Some(AttributeValue::Ns(mut nums)) => { + let mut results: Vec> = nums + .drain(..) + .map(|ns| ns.parse().map_err(|_| ConversionError::ParseInt)) + .collect(); + + results.drain(..).collect() + } + // See. https://github.com/raiden-rs/raiden/issues/57 + Some(v) if v.is_null() => Ok(std::collections::BTreeSet::new()), + None => Ok(std::collections::BTreeSet::new()), + _ => Err(ConversionError::ValueIsNone), + } + } + } + }; +} + +pub(crate) use default_number_btree_set_convertor; + +impl IntoAttribute for HashSet { + fn into_attr(self) -> AttributeValue { + if self.is_empty() { + // See. https://github.com/raiden-rs/raiden/issues/57 + // https://github.com/raiden-rs/raiden-dynamo/issues/64 + AttributeValue::Ss(Default::default()) + } else { + AttributeValue::Ss(self.into_iter().map(|s| s.into_ss_item()).collect()) + } + } +} + +impl FromAttribute for HashSet { + fn from_attr(value: Option) -> Result { + match value { + Some(AttributeValue::Ss(mut ss)) => ss.drain(..).map(A::from_ss_item).collect(), + // See. https://github.com/raiden-rs/raiden/issues/57 + Some(v) if v.is_null() => Ok(HashSet::new()), + None => Ok(HashSet::new()), + _ => Err(ConversionError::ValueIsNone), + } + } +} + +impl IntoAttribute for BTreeSet { + fn into_attr(self) -> AttributeValue { + if self.is_empty() { + // See. https://github.com/raiden-rs/raiden/issues/57 + // https://github.com/raiden-rs/raiden-dynamo/issues/64 + AttributeValue::Ss(Default::default()) + } else { + AttributeValue::Ss(self.into_iter().map(|s| s.into_ss_item()).collect()) + } + } +} + +impl FromAttribute for BTreeSet { + fn from_attr(value: Option) -> Result { + match value { + Some(AttributeValue::Ss(mut ss)) => ss.drain(..).map(A::from_ss_item).collect(), + // See. https://github.com/raiden-rs/raiden/issues/57 + Some(v) if v.is_null() => Ok(BTreeSet::new()), + None => Ok(BTreeSet::new()), + _ => Err(ConversionError::ValueIsNone), + } + } +} + +pub fn is_attr_value_empty(a: &AttributeValue) -> bool { + match &a { + &AttributeValue::B(_) + | &AttributeValue::Bool(_) + | &AttributeValue::M(_) + | &AttributeValue::N(_) + | &AttributeValue::Null(_) + | &AttributeValue::S(_) => false, + &AttributeValue::Bs(v) => v.is_empty(), + &AttributeValue::L(v) => v.is_empty(), + &AttributeValue::Ns(v) => v.is_empty(), + &AttributeValue::Ss(v) => v.is_empty(), + _ => true, + } +} + +pub(crate) fn deserialize_attr_value(s: &str) -> Result { + let values: HashMap = match serde_json::from_str(s) { + Ok(v) => v, + Err(_) => return Err(RaidenError::NextTokenDecodeError), + }; + + let mut deserialized: HashMap = HashMap::new(); + + for (k, v) in values { + let v = crate::aws_sdk::serialize::value_to_attribute_value(v) + .map_err(|_| RaidenError::NextTokenDecodeError)?; + + deserialized.insert(k, v); + } + + Ok(deserialized) +} + +pub(crate) fn serialize_attr_values(value: &AttributeValues) -> String { + let m: HashMap = value + .iter() + .map(|(k, v)| { + ( + k.to_owned(), + crate::aws_sdk::serialize::attribute_value_to_value(v), + ) + }) + .collect(); + + serde_json::to_string(&m).expect("should serialize") +} diff --git a/raiden/src/aws_sdk/ops/batch_delete.rs b/raiden/src/aws_sdk/ops/batch_delete.rs new file mode 100644 index 00000000..7638a0fb --- /dev/null +++ b/raiden/src/aws_sdk/ops/batch_delete.rs @@ -0,0 +1,123 @@ +use serde::de::{self, Deserialize, Deserializer, MapAccess, Visitor}; +use serde::ser::{Serialize, SerializeStruct, Serializer}; + +use crate::ops::batch_delete::BatchDeleteOutput; + +impl Serialize for BatchDeleteOutput { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut state = serializer.serialize_struct("BatchDeleteOutput", 2)?; + state.serialize_field( + "consumed_capacity", + &self.consumed_capacity.as_ref().map(|v| { + v.iter() + .map(crate::aws_sdk::serialize::consumed_capacity_to_value) + .collect::>() + }), + )?; + state.serialize_field( + "unprocessed_items", + &self + .unprocessed_items + .iter() + .map(crate::aws_sdk::serialize::delete_request_to_value) + .collect::>(), + )?; + state.end() + } +} + +impl<'de> Deserialize<'de> for BatchDeleteOutput { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(serde::Deserialize)] + #[serde(field_identifier, rename_all = "snake_case")] + enum Field { + ConsumedCapacity, + UnprocessedItems, + } + + const FIELDS: &[&str] = &["consumed_capacity", "unprocessed_items"]; + + struct BatchDeleteOutputVisitor; + + impl<'de> Visitor<'de> for BatchDeleteOutputVisitor { + type Value = BatchDeleteOutput; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("struct BatchDeleteOutput") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut consumed_capacity = None; + let mut unprocessed_items = None; + + while let Some(key) = map.next_key()? { + match key { + Field::ConsumedCapacity => { + if consumed_capacity.is_some() { + return Err(de::Error::duplicate_field("consumed_capacity")); + } + + let vs: Option> = map.next_value()?; + + consumed_capacity = if let Some(vs) = vs { + let mut values = vec![]; + + for v in vs { + values.push( + crate::aws_sdk::serialize::value_to_consumed_capacity(v) + .map_err(de::Error::custom)?, + ); + } + + Some(values) + } else { + None + }; + } + Field::UnprocessedItems => { + if unprocessed_items.is_some() { + return Err(de::Error::duplicate_field("unprocessed_items")); + } + + let vs: Option> = map.next_value()?; + + unprocessed_items = if let Some(vs) = vs { + let mut values = vec![]; + + for v in vs { + values.push( + crate::aws_sdk::serialize::value_to_delete_request(v) + .map_err(de::Error::custom)?, + ); + } + + Some(values) + } else { + None + }; + } + } + } + + let unprocessed_items = unprocessed_items + .ok_or_else(|| de::Error::missing_field("unprocessed_items"))?; + + Ok(BatchDeleteOutput { + consumed_capacity, + unprocessed_items, + }) + } + } + + deserializer.deserialize_struct("BatchDeleteOutput", FIELDS, BatchDeleteOutputVisitor) + } +} diff --git a/raiden/src/aws_sdk/ops/batch_get.rs b/raiden/src/aws_sdk/ops/batch_get.rs new file mode 100644 index 00000000..7de8e8f8 --- /dev/null +++ b/raiden/src/aws_sdk/ops/batch_get.rs @@ -0,0 +1,148 @@ +use serde::de::{self, Deserialize, Deserializer, MapAccess, Visitor}; +use serde::ser::{Serialize, SerializeStruct, Serializer}; + +use crate::ops::batch_get::BatchGetOutput; + +impl Serialize for BatchGetOutput +where + T: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut state = serializer.serialize_struct("BatchGetOutput", 3)?; + state.serialize_field( + "consumed_capacity", + &self.consumed_capacity.as_ref().map(|v| { + v.iter() + .map(crate::aws_sdk::serialize::consumed_capacity_to_value) + .collect::>() + }), + )?; + state.serialize_field("items", &self.items)?; + state.serialize_field( + "unprocessed_keys", + &self + .unprocessed_keys + .as_ref() + .map(crate::aws_sdk::serialize::keys_and_attributes_to_value), + )?; + state.end() + } +} + +impl<'de, T> Deserialize<'de> for BatchGetOutput +where + T: Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(serde::Deserialize)] + #[serde(field_identifier, rename_all = "snake_case")] + enum Field { + ConsumedCapacity, + Items, + UnprocessedKeys, + } + + const FIELDS: &[&str] = &["consumed_capacity", "items", "unprocessed_keys"]; + + struct BatchGetOutputVisitor<'de, T> + where + T: Deserialize<'de>, + { + marker: std::marker::PhantomData>, + lifetime: std::marker::PhantomData<&'de ()>, + } + + impl<'de, T> Visitor<'de> for BatchGetOutputVisitor<'de, T> + where + T: Deserialize<'de>, + { + type Value = BatchGetOutput; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("struct BatchGetOutput") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut consumed_capacity = None; + let mut items = None; + let mut unprocessed_keys = None; + + while let Some(key) = map.next_key()? { + match key { + Field::ConsumedCapacity => { + if consumed_capacity.is_some() { + return Err(de::Error::duplicate_field("consumed_capacity")); + } + + let vs: Option> = map.next_value()?; + + consumed_capacity = if let Some(vs) = vs { + let mut values = vec![]; + + for v in vs { + values.push( + crate::aws_sdk::serialize::value_to_consumed_capacity(v) + .map_err(de::Error::custom)?, + ); + } + + Some(values) + } else { + None + }; + } + Field::Items => { + if items.is_some() { + return Err(de::Error::duplicate_field("items")); + } + + items = Some(map.next_value()?); + } + Field::UnprocessedKeys => { + if unprocessed_keys.is_some() { + return Err(de::Error::duplicate_field("unprocessed_keys")); + } + + let v: Option = map.next_value()?; + + unprocessed_keys = if let Some(v) = v { + Some( + crate::aws_sdk::serialize::value_to_keys_and_attributes(v) + .map_err(de::Error::custom)?, + ) + } else { + None + }; + } + } + } + + let items = items.ok_or_else(|| de::Error::missing_field("items"))?; + + Ok(BatchGetOutput { + consumed_capacity, + items, + unprocessed_keys, + }) + } + } + + deserializer.deserialize_struct( + "BatchGetOutput", + FIELDS, + BatchGetOutputVisitor { + marker: std::marker::PhantomData::>, + lifetime: std::marker::PhantomData, + }, + ) + } +} diff --git a/raiden/src/aws_sdk/ops/get.rs b/raiden/src/aws_sdk/ops/get.rs new file mode 100644 index 00000000..99adf48f --- /dev/null +++ b/raiden/src/aws_sdk/ops/get.rs @@ -0,0 +1,115 @@ +use serde::de::{self, Deserialize, Deserializer, MapAccess, Visitor}; +use serde::ser::{Serialize, SerializeStruct, Serializer}; + +use crate::ops::get::GetOutput; + +impl Serialize for GetOutput +where + T: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut state = serializer.serialize_struct("GetOutput", 2)?; + state.serialize_field( + "consumed_capacity", + &self + .consumed_capacity + .as_ref() + .map(crate::aws_sdk::serialize::consumed_capacity_to_value), + )?; + state.serialize_field("item", &self.item)?; + state.end() + } +} + +impl<'de, T> Deserialize<'de> for GetOutput +where + T: Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(serde::Deserialize)] + #[serde(field_identifier, rename_all = "snake_case")] + enum Field { + ConsumedCapacity, + Item, + } + + const FIELDS: &[&str] = &["consumed_capacity", "item"]; + + struct GetOutputVisitor<'de, T> + where + T: Deserialize<'de>, + { + marker: std::marker::PhantomData>, + lifetime: std::marker::PhantomData<&'de ()>, + } + + impl<'de, T> Visitor<'de> for GetOutputVisitor<'de, T> + where + T: Deserialize<'de>, + { + type Value = GetOutput; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("struct GetOutput") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut consumed_capacity = None; + let mut item = None; + + while let Some(key) = map.next_key()? { + match key { + Field::ConsumedCapacity => { + if consumed_capacity.is_some() { + return Err(de::Error::duplicate_field("consumed_capacity")); + } + + let v: Option = map.next_value()?; + + consumed_capacity = if let Some(v) = v { + Some( + crate::aws_sdk::serialize::value_to_consumed_capacity(v) + .map_err(de::Error::custom)?, + ) + } else { + None + }; + } + Field::Item => { + if item.is_some() { + return Err(de::Error::duplicate_field("item")); + } + + item = Some(map.next_value()?); + } + } + } + + let item = item.ok_or_else(|| de::Error::missing_field("item"))?; + + Ok(GetOutput { + consumed_capacity, + item, + }) + } + } + + deserializer.deserialize_struct( + "GetOutput", + FIELDS, + GetOutputVisitor { + marker: std::marker::PhantomData::>, + lifetime: std::marker::PhantomData, + }, + ) + } +} diff --git a/raiden/src/aws_sdk/ops/mod.rs b/raiden/src/aws_sdk/ops/mod.rs new file mode 100644 index 00000000..868c061c --- /dev/null +++ b/raiden/src/aws_sdk/ops/mod.rs @@ -0,0 +1,10 @@ +mod batch_delete; +mod batch_get; +mod get; +mod put; +mod query; +mod scan; +mod transact_write; +mod update; + +pub use transact_write::*; diff --git a/raiden/src/aws_sdk/ops/put.rs b/raiden/src/aws_sdk/ops/put.rs new file mode 100644 index 00000000..8026c548 --- /dev/null +++ b/raiden/src/aws_sdk/ops/put.rs @@ -0,0 +1,115 @@ +use serde::de::{self, Deserialize, Deserializer, MapAccess, Visitor}; +use serde::ser::{Serialize, SerializeStruct, Serializer}; + +use crate::ops::put::PutOutput; + +impl Serialize for PutOutput +where + T: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut state = serializer.serialize_struct("PutOutput", 2)?; + state.serialize_field( + "consumed_capacity", + &self + .consumed_capacity + .as_ref() + .map(crate::aws_sdk::serialize::consumed_capacity_to_value), + )?; + state.serialize_field("item", &self.item)?; + state.end() + } +} + +impl<'de, T> Deserialize<'de> for PutOutput +where + T: Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(serde::Deserialize)] + #[serde(field_identifier, rename_all = "snake_case")] + enum Field { + ConsumedCapacity, + Item, + } + + const FIELDS: &[&str] = &["consumed_capacity", "item"]; + + struct PutOutputVisitor<'de, T> + where + T: Deserialize<'de>, + { + marker: std::marker::PhantomData>, + lifetime: std::marker::PhantomData<&'de ()>, + } + + impl<'de, T> Visitor<'de> for PutOutputVisitor<'de, T> + where + T: Deserialize<'de>, + { + type Value = PutOutput; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("struct PutOutput") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut consumed_capacity = None; + let mut item = None; + + while let Some(key) = map.next_key()? { + match key { + Field::ConsumedCapacity => { + if consumed_capacity.is_some() { + return Err(de::Error::duplicate_field("consumed_capacity")); + } + + let v: Option = map.next_value()?; + + consumed_capacity = if let Some(v) = v { + Some( + crate::aws_sdk::serialize::value_to_consumed_capacity(v) + .map_err(de::Error::custom)?, + ) + } else { + None + }; + } + Field::Item => { + if item.is_some() { + return Err(de::Error::duplicate_field("item")); + } + + item = Some(map.next_value()?); + } + } + } + + let item = item.ok_or_else(|| de::Error::missing_field("item"))?; + + Ok(PutOutput { + consumed_capacity, + item, + }) + } + } + + deserializer.deserialize_struct( + "PutOutput", + FIELDS, + PutOutputVisitor { + marker: std::marker::PhantomData::>, + lifetime: std::marker::PhantomData, + }, + ) + } +} diff --git a/raiden/src/aws_sdk/ops/query.rs b/raiden/src/aws_sdk/ops/query.rs new file mode 100644 index 00000000..641bb29b --- /dev/null +++ b/raiden/src/aws_sdk/ops/query.rs @@ -0,0 +1,154 @@ +use serde::de::{self, Deserialize, Deserializer, MapAccess, Visitor}; +use serde::ser::{Serialize, SerializeStruct, Serializer}; + +use crate::ops::query::QueryOutput; + +impl Serialize for QueryOutput +where + T: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut state = serializer.serialize_struct("QueryOutput", 5)?; + state.serialize_field( + "consumed_capacity", + &self + .consumed_capacity + .as_ref() + .map(crate::aws_sdk::serialize::consumed_capacity_to_value), + )?; + state.serialize_field("items", &self.items)?; + state.serialize_field("count", &self.count)?; + state.serialize_field("next_token", &self.next_token)?; + state.serialize_field("scanned_count", &self.scanned_count)?; + state.end() + } +} + +impl<'de, T> Deserialize<'de> for QueryOutput +where + T: Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(serde::Deserialize)] + #[serde(field_identifier, rename_all = "snake_case")] + enum Field { + ConsumedCapacity, + Items, + Count, + NextToken, + ScannedCount, + } + + const FIELDS: &[&str] = &[ + "consumed_capacity", + "items", + "count", + "next_token", + "scanned_count", + ]; + + struct QueryOutputVisitor<'de, T> + where + T: Deserialize<'de>, + { + marker: std::marker::PhantomData>, + lifetime: std::marker::PhantomData<&'de ()>, + } + + impl<'de, T> Visitor<'de> for QueryOutputVisitor<'de, T> + where + T: Deserialize<'de>, + { + type Value = QueryOutput; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("struct QueryOutput") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut consumed_capacity = None; + let mut items = None; + let mut count = None; + let mut next_token = None; + let mut scanned_count = None; + + while let Some(key) = map.next_key()? { + match key { + Field::ConsumedCapacity => { + if consumed_capacity.is_some() { + return Err(de::Error::duplicate_field("consumed_capacity")); + } + + let v: Option = map.next_value()?; + + consumed_capacity = if let Some(v) = v { + Some( + crate::aws_sdk::serialize::value_to_consumed_capacity(v) + .map_err(de::Error::custom)?, + ) + } else { + None + }; + } + Field::Items => { + if items.is_some() { + return Err(de::Error::duplicate_field("items")); + } + + items = Some(map.next_value()?); + } + Field::Count => { + if count.is_some() { + return Err(de::Error::duplicate_field("count")); + } + + count = map.next_value()?; + } + Field::NextToken => { + if next_token.is_some() { + return Err(de::Error::duplicate_field("next_token")); + } + + next_token = map.next_value()?; + } + Field::ScannedCount => { + if scanned_count.is_some() { + return Err(de::Error::duplicate_field("scanned_count")); + } + + scanned_count = map.next_value()?; + } + } + } + + let items = items.ok_or_else(|| de::Error::missing_field("items"))?; + + Ok(QueryOutput { + consumed_capacity, + items, + count, + next_token, + scanned_count, + }) + } + } + + deserializer.deserialize_struct( + "QueryOutput", + FIELDS, + QueryOutputVisitor { + marker: std::marker::PhantomData::>, + lifetime: std::marker::PhantomData, + }, + ) + } +} diff --git a/raiden/src/aws_sdk/ops/scan.rs b/raiden/src/aws_sdk/ops/scan.rs new file mode 100644 index 00000000..0bf736aa --- /dev/null +++ b/raiden/src/aws_sdk/ops/scan.rs @@ -0,0 +1,185 @@ +use std::collections::HashMap; + +use serde::de::{self, Deserialize, Deserializer, MapAccess, Visitor}; +use serde::ser::{Serialize, SerializeStruct, Serializer}; + +use crate::ops::scan::ScanOutput; +use crate::serialize::value_to_attribute_value; + +impl Serialize for ScanOutput +where + T: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut state = serializer.serialize_struct("ScanOutput", 5)?; + state.serialize_field( + "consumed_capacity", + &self + .consumed_capacity + .as_ref() + .map(crate::aws_sdk::serialize::consumed_capacity_to_value), + )?; + state.serialize_field("items", &self.items)?; + state.serialize_field("count", &self.count)?; + state.serialize_field( + "last_evaluated_key", + &self.last_evaluated_key.as_ref().map(|v| { + v.iter() + .map(|(k, v)| (k, crate::aws_sdk::serialize::attribute_value_to_value(v))) + .collect::>() + }), + )?; + state.serialize_field("scanned_count", &self.scanned_count)?; + state.end() + } +} + +impl<'de, T> Deserialize<'de> for ScanOutput +where + T: Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(serde::Deserialize)] + #[serde(field_identifier, rename_all = "snake_case")] + enum Field { + ConsumedCapacity, + Items, + Count, + LastEvaluatedKey, + ScannedCount, + } + + const FIELDS: &[&str] = &[ + "consumed_capacity", + "items", + "count", + "last_evaluated_key", + "scanned_count", + ]; + + struct ScanOutputVisitor<'de, T> + where + T: Deserialize<'de>, + { + marker: std::marker::PhantomData>, + lifetime: std::marker::PhantomData<&'de ()>, + } + + impl<'de, T> Visitor<'de> for ScanOutputVisitor<'de, T> + where + T: Deserialize<'de>, + { + type Value = ScanOutput; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("struct ScanOutput") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut consumed_capacity = None; + let mut items = None; + let mut count = None; + let mut last_evaluated_key = None; + let mut scanned_count = None; + + while let Some(key) = map.next_key()? { + match key { + Field::ConsumedCapacity => { + if consumed_capacity.is_some() { + return Err(de::Error::duplicate_field("consumed_capacity")); + } + + let v: Option = map.next_value()?; + + consumed_capacity = if let Some(v) = v { + Some( + crate::aws_sdk::serialize::value_to_consumed_capacity(v) + .map_err(de::Error::custom)?, + ) + } else { + None + }; + } + Field::Items => { + if items.is_some() { + return Err(de::Error::duplicate_field("items")); + } + + items = Some(map.next_value()?); + } + Field::Count => { + if count.is_some() { + return Err(de::Error::duplicate_field("count")); + } + + count = map.next_value()?; + } + Field::LastEvaluatedKey => { + if last_evaluated_key.is_some() { + return Err(de::Error::duplicate_field("last_evaluated_key")); + } + + let v: Option> = map.next_value()?; + + last_evaluated_key = if let Some(v) = v { + let mut map: HashMap = + HashMap::new(); + + for (k, v) in v { + map.insert( + k, + value_to_attribute_value(v) + .map_err(|err| { + de::Error::custom(format!( + "Invalid value was detected as AttributeValue: {err}", + )) + })?, + ); + } + + Some(map) + } else { + None + }; + } + Field::ScannedCount => { + if scanned_count.is_some() { + return Err(de::Error::duplicate_field("scanned_count")); + } + + scanned_count = map.next_value()?; + } + } + } + + let items = items.ok_or_else(|| de::Error::missing_field("items"))?; + + Ok(ScanOutput { + consumed_capacity, + items, + count, + last_evaluated_key, + scanned_count, + }) + } + } + + deserializer.deserialize_struct( + "ScanOutput", + FIELDS, + ScanOutputVisitor { + marker: std::marker::PhantomData::>, + lifetime: std::marker::PhantomData, + }, + ) + } +} diff --git a/raiden/src/aws_sdk/ops/transact_write.rs b/raiden/src/aws_sdk/ops/transact_write.rs new file mode 100644 index 00000000..ea541177 --- /dev/null +++ b/raiden/src/aws_sdk/ops/transact_write.rs @@ -0,0 +1,105 @@ +// DynamoDb, DynamoDbClient, TransactWriteItem, TransactWriteItemsInput +use crate::{ + aws_sdk::{ + config::Region, operation::transact_write_items::builders::TransactWriteItemsFluentBuilder, + types::TransactWriteItem, + }, + Client, Config, RaidenError, RetryCondition, RetryStrategy, TransactWriteConditionCheckBuilder, + TransactWriteDeleteBuilder, TransactWritePutBuilder, TransactWriteUpdateBuilder, +}; + +pub struct WriteTx { + items: Vec, + client: Client, + retry_condition: RetryCondition, +} + +impl WriteTx { + pub fn new(region: Region) -> Self { + let config = Config::builder().region(region).build(); + + Self { + items: vec![], + client: Client::from_conf(config), + // NOTE: + // Since the AWS SDK provides a retry option, + // configure it to not retry by default. + retry_condition: RetryCondition::never(), + } + } + + pub fn new_with_client(client: Client) -> Self { + Self { + items: vec![], + client, + // NOTE: + // Since the AWS SDK provides a retry option, + // configure it to not retry by default. + retry_condition: RetryCondition::never(), + } + } + + pub fn with_retries(mut self, s: Box) -> Self { + self.retry_condition.strategy = s; + self + } + + pub fn put(mut self, builder: impl TransactWritePutBuilder) -> Self { + let builder = TransactWriteItem::builder().put(builder.build()); + + self.items.push(builder.build()); + self + } + + pub fn update(mut self, builder: impl TransactWriteUpdateBuilder) -> Self { + let builder = TransactWriteItem::builder().update(builder.build()); + + self.items.push(builder.build()); + self + } + + pub fn delete(mut self, builder: impl TransactWriteDeleteBuilder) -> Self { + let builder = TransactWriteItem::builder().delete(builder.build()); + + self.items.push(builder.build()); + self + } + + pub fn condition_check(mut self, builder: impl TransactWriteConditionCheckBuilder) -> Self { + let builder = TransactWriteItem::builder().condition_check(builder.build()); + + self.items.push(builder.build()); + self + } + + pub async fn run(self) -> Result<(), RaidenError> { + let policy: crate::RetryPolicy = self.retry_condition.strategy.policy().into(); + let req = self + .client + .transact_write_items() + .set_transact_items(Some(self.items)); + + policy + .retry_if( + move || { + let req = req.clone(); + async { WriteTx::inner_run(req).await } + }, + &self.retry_condition, + ) + .await + } + + #[cfg_attr(feature = "tracing", tracing::instrument( + level = tracing::Level::DEBUG, + name = "dynamodb::action", + skip_all, + fields(api = "transact_write_items") + ))] + async fn inner_run(req: TransactWriteItemsFluentBuilder) -> Result<(), RaidenError> { + let _res = req.send().await?; + + // TODO: ADD Resp + Ok(()) + } +} diff --git a/raiden/src/aws_sdk/ops/update.rs b/raiden/src/aws_sdk/ops/update.rs new file mode 100644 index 00000000..f5db5a2a --- /dev/null +++ b/raiden/src/aws_sdk/ops/update.rs @@ -0,0 +1,139 @@ +use serde::de::{self, Deserialize, Deserializer, MapAccess, Visitor}; +use serde::ser::{Serialize, SerializeStruct, Serializer}; + +use crate::ops::update::UpdateOutput; + +impl Serialize for UpdateOutput +where + T: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut state = serializer.serialize_struct("UpdateOutput", 3)?; + state.serialize_field( + "consumed_capacity", + &self + .consumed_capacity + .as_ref() + .map(crate::aws_sdk::serialize::consumed_capacity_to_value), + )?; + state.serialize_field("item", &self.item)?; + state.serialize_field( + "item_collection_metrics", + &self + .item_collection_metrics + .as_ref() + .map(crate::aws_sdk::serialize::item_collection_metrics_to_value), + )?; + state.end() + } +} + +impl<'de, T> Deserialize<'de> for UpdateOutput +where + T: Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + #[derive(serde::Deserialize)] + #[serde(field_identifier, rename_all = "snake_case")] + enum Field { + ConsumedCapacity, + Item, + ItemCollectionMetrics, + } + + const FIELDS: &[&str] = &["consumed_capacity", "item", "item_collection_metrics"]; + + struct UpdateOutputVisitor<'de, T> + where + T: Deserialize<'de>, + { + marker: std::marker::PhantomData>, + lifetime: std::marker::PhantomData<&'de ()>, + } + + impl<'de, T> Visitor<'de> for UpdateOutputVisitor<'de, T> + where + T: Deserialize<'de>, + { + type Value = UpdateOutput; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("struct UpdateOutput") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut consumed_capacity = None; + let mut item = None; + let mut item_collection_metrics = None; + + while let Some(key) = map.next_key()? { + match key { + Field::ConsumedCapacity => { + if consumed_capacity.is_some() { + return Err(de::Error::duplicate_field("consumed_capacity")); + } + + let v: Option = map.next_value()?; + + consumed_capacity = if let Some(v) = v { + Some( + crate::aws_sdk::serialize::value_to_consumed_capacity(v) + .map_err(de::Error::custom)?, + ) + } else { + None + }; + } + Field::Item => { + if item.is_some() { + return Err(de::Error::duplicate_field("item")); + } + + item = map.next_value()?; + } + Field::ItemCollectionMetrics => { + if item_collection_metrics.is_some() { + return Err(de::Error::duplicate_field("item_collection_metrics")); + } + + let v: Option = map.next_value()?; + + item_collection_metrics = if let Some(v) = v { + Some( + crate::aws_sdk::serialize::value_to_item_collection_metrics(v) + .map_err(de::Error::custom)?, + ) + } else { + None + }; + } + } + } + + Ok(UpdateOutput { + consumed_capacity, + item, + item_collection_metrics, + }) + } + } + + deserializer.deserialize_struct( + "UpdateOutput", + FIELDS, + UpdateOutputVisitor { + marker: std::marker::PhantomData::>, + lifetime: std::marker::PhantomData, + }, + ) + } +} diff --git a/raiden/src/aws_sdk/serialize/attribute_value.rs b/raiden/src/aws_sdk/serialize/attribute_value.rs new file mode 100644 index 00000000..8740d9d3 --- /dev/null +++ b/raiden/src/aws_sdk/serialize/attribute_value.rs @@ -0,0 +1,149 @@ +use std::collections::HashMap; + +use base64::{engine::general_purpose::STANDARD, Engine}; +use serde::de::{self, Error as _}; +use serde_json::{json, Error, Value}; + +use crate::aws_sdk::{primitives::Blob, types::AttributeValue}; + +pub fn attribute_value_to_value(value: &AttributeValue) -> Value { + match value { + AttributeValue::B(v) => json!({ "B": STANDARD.encode(v) }), + AttributeValue::Bool(v) => json!({ "BOOL": v }), + AttributeValue::Bs(vs) => json!({ "BS": [ + vs.iter().map(|v| json!({ "B": STANDARD.encode(v) })).collect::>(), + ]}), + AttributeValue::L(vs) => json!({ "L": [ + vs.iter().map(attribute_value_to_value).collect::>(), + ]}), + AttributeValue::M(vs) => json!({ "M": vs.iter().map(|(k, v)| { + (k.clone(), attribute_value_to_value(v)) + }).collect::>() }), + AttributeValue::N(v) => json!({ "N": v }), + AttributeValue::Ns(vs) => json!({ "NS": [ + vs.iter().map(|v| json!({ "N": v })).collect::>(), + ]}), + AttributeValue::Null(v) => json!({ "NULL": v }), + AttributeValue::S(v) => json!({ "S": v }), + AttributeValue::Ss(vs) => json!({ "SS": [ + vs.iter().map(|v| json!({ "S": v })).collect::>(), + ]}), + _ => { + panic!("Unknown data type. Consider upgrading your SDK to the latest version.") + } + } +} + +pub fn value_to_attribute_value(value: Value) -> Result { + let Value::Object(value) = value else { + return Err(de::Error::custom("value is not type of AttributeValue")); + }; + + if value.len() != 1 { + return Err(de::Error::custom( + "AttributeValue must include only 1 field", + )); + } + + let (ty, value) = value.into_iter().next().unwrap(); + let v = match (ty.as_str(), value) { + ("B", Value::String(s)) => { + let b = STANDARD.decode(s).map_err(Error::custom)?; + + AttributeValue::B(Blob::new(b)) + } + ("BOOL", Value::Bool(b)) => AttributeValue::Bool(b), + ("BS", Value::Array(vs)) => { + let mut values = vec![]; + + for (i, v) in vs.into_iter().enumerate() { + match v { + Value::String(s) => { + let b = STANDARD.decode(s).map_err(Error::custom)?; + + values.push(Blob::new(b)) + } + _ => { + return Err(Error::custom(format!( + "Unexpected value was detected in BS field at index of {i}", + ))) + } + }; + } + + AttributeValue::Bs(values) + } + ("L", Value::Array(vs)) => { + let mut values = vec![]; + + for (i, v) in vs.into_iter().enumerate() { + let v = value_to_attribute_value(v).map_err(|err| { + Error::custom(format!( + "Unexpected value was detected in L field at index of {i}: {err}", + )) + })?; + + values.push(v); + } + + AttributeValue::L(values) + } + ("M", Value::Object(m)) => { + let mut values: HashMap = HashMap::new(); + + for (k, v) in m { + let v = value_to_attribute_value(v).map_err(|err| { + Error::custom(format!( + "Unexpected value was detected in M field at key of {k}: {err}", + )) + })?; + + values.insert(k, v); + } + + AttributeValue::M(values) + } + ("N", Value::String(s)) => AttributeValue::N(s), + ("NS", Value::Array(vs)) => { + let mut values = vec![]; + + for (i, v) in vs.into_iter().enumerate() { + match v { + Value::String(s) => { + values.push(s); + } + _ => { + return Err(Error::custom(format!( + "Unexpected value was detected in NS field at index of {i}", + ))) + } + }; + } + + AttributeValue::Ns(values) + } + ("NULL", Value::Null) => AttributeValue::Null(true), + ("S", Value::String(s)) => AttributeValue::S(s), + ("SS", Value::Array(vs)) => { + let mut values = vec![]; + + for (i, v) in vs.into_iter().enumerate() { + match v { + Value::String(s) => { + values.push(s); + } + _ => { + return Err(Error::custom(format!( + "Unexpected value was detected in SS field at index of {i}", + ))) + } + }; + } + + AttributeValue::Ss(values) + } + _ => return Err(Error::custom("Unexpected data type was detected")), + }; + + Ok(v) +} diff --git a/raiden/src/aws_sdk/serialize/consumed_capacity.rs b/raiden/src/aws_sdk/serialize/consumed_capacity.rs new file mode 100644 index 00000000..03b2d225 --- /dev/null +++ b/raiden/src/aws_sdk/serialize/consumed_capacity.rs @@ -0,0 +1,106 @@ +use std::collections::HashMap; + +use serde::de; +use serde_json::{json, Error, Map, Value}; + +use crate::aws_sdk::{ + serialize::set_optional_value, + types::{Capacity, ConsumedCapacity}, +}; + +pub fn consumed_capacity_to_value(v: &ConsumedCapacity) -> Value { + json!({ + "table_name": v.table_name, + "capacity_units": v.capacity_units, + "read_capacity_units": v.read_capacity_units, + "write_capacity_units": v.write_capacity_units, + "table": v.table.as_ref().map(capacity_to_value), + "local_secondary_indexes": v.local_secondary_indexes.as_ref() + .map(|v| { + v + .iter() + .map(|(k, v)| (k.clone(), capacity_to_value(v))) + .collect::>() + }), + "global_secondary_indexes": v.global_secondary_indexes.as_ref() + .map(|v| { + v + .iter() + .map(|(k, v)| (k.clone(), capacity_to_value(v))) + .collect::>() + }), + }) +} + +pub fn value_to_consumed_capacity(value: Value) -> Result { + let Value::Object(m) = value else { + return Err(de::Error::custom("value is not type of ConsumedCapacity")); + }; + + let mut builder = ConsumedCapacity::builder(); + + set_optional_value!(builder, m, table_name, String); + set_optional_value!(builder, m, capacity_units, f64); + set_optional_value!(builder, m, read_capacity_units, f64); + set_optional_value!(builder, m, write_capacity_units, f64); + + if let Some(v) = m.get("table") { + builder = builder.table(value_to_capacity(v.clone())?); + } + + set_optional_value!(builder, m, local_secondary_indexes, object, |m: &Map< + _, + _, + >| + -> Result< + _, + _, + > { + let mut map = HashMap::new(); + for (k, v) in m.iter() { + map.insert(k.clone(), value_to_capacity(v.clone())?); + } + + Ok(Some(map)) + }); + + set_optional_value!(builder, m, global_secondary_indexes, object, |m: &Map< + _, + _, + >| + -> Result< + _, + _, + > { + let mut map = HashMap::new(); + for (k, v) in m.iter() { + map.insert(k.clone(), value_to_capacity(v.clone())?); + } + + Ok(Some(map)) + }); + + Ok(builder.build()) +} + +pub fn capacity_to_value(v: &Capacity) -> Value { + json!({ + "read_capacity_units": v.read_capacity_units, + "write_capacity_units": v.write_capacity_units, + "capacity_units": v.capacity_units, + }) +} + +pub fn value_to_capacity(value: Value) -> Result { + let Value::Object(m) = value else { + return Err(de::Error::custom("value is not type of Capacity")); + }; + + let mut builder = Capacity::builder(); + + set_optional_value!(builder, m, read_capacity_units, f64); + set_optional_value!(builder, m, write_capacity_units, f64); + set_optional_value!(builder, m, capacity_units, f64); + + Ok(builder.build()) +} diff --git a/raiden/src/aws_sdk/serialize/delete_request.rs b/raiden/src/aws_sdk/serialize/delete_request.rs new file mode 100644 index 00000000..4bb691bf --- /dev/null +++ b/raiden/src/aws_sdk/serialize/delete_request.rs @@ -0,0 +1,42 @@ +use std::collections::HashMap; + +use serde::de::{self, Error as _}; +use serde_json::{json, Error, Map, Value}; + +use crate::aws_sdk::{ + serialize::{attribute_value_to_value, set_optional_value, value_to_attribute_value}, + types::DeleteRequest, +}; + +pub fn delete_request_to_value(v: &DeleteRequest) -> Value { + json!({ + "key": v.key.iter().map(|(k, v)| { + (k.clone(), attribute_value_to_value(v)) + }).collect::>(), + }) +} + +pub fn value_to_delete_request(value: Value) -> Result { + if let Value::Object(m) = value { + let mut builder = DeleteRequest::builder(); + + set_optional_value!(builder, m, key, object, |m: &Map<_, _>| -> Result<_, _> { + let mut map = HashMap::new(); + + for (k, v) in m.iter() { + let v = value_to_attribute_value(v.clone()) + .map_err(|err| de::Error::custom(format!("{k} set in key: {err}")))?; + + map.insert(k.clone(), v); + } + + Ok(Some(map)) + }); + + builder + .build() + .map_err(|err| Error::custom(err.to_string())) + } else { + Err(de::Error::custom("value is not type of DeleteRequest")) + } +} diff --git a/raiden/src/aws_sdk/serialize/item_collection_metrics.rs b/raiden/src/aws_sdk/serialize/item_collection_metrics.rs new file mode 100644 index 00000000..070e07ae --- /dev/null +++ b/raiden/src/aws_sdk/serialize/item_collection_metrics.rs @@ -0,0 +1,71 @@ +use std::collections::HashMap; + +use serde::de; +use serde_json::{json, Error, Map, Value}; + +use crate::aws_sdk::{ + serialize::{ + attribute_value_to_value, parse_value, set_optional_value, value_to_attribute_value, + }, + types::ItemCollectionMetrics, +}; + +pub fn item_collection_metrics_to_value(v: &ItemCollectionMetrics) -> Value { + json!({ + "item_collection_key": v.item_collection_key.as_ref().map(|v| { + v + .iter() + .map(|(k, v)| (k.clone(), attribute_value_to_value(v))) + .collect::>() + }), + "size_estimate_range_gb": v.size_estimate_range_gb, + }) +} + +pub fn value_to_item_collection_metrics(value: Value) -> Result { + if let Value::Object(m) = value { + let mut builder = ItemCollectionMetrics::builder(); + + set_optional_value!( + builder, + m, + item_collection_key, + object, + |m: &Map<_, _>| -> Result<_, _> { + let mut map = HashMap::new(); + + for (k, v) in m.iter() { + let v = value_to_attribute_value(v.clone()).map_err(|err| { + de::Error::custom(format!("{k} set in item_collection_key: {err}")) + })?; + + map.insert(k.clone(), v); + } + + Ok(Some(map)) + } + ); + + set_optional_value!( + builder, + m, + size_estimate_range_gb, + array, + |vs: &Vec<_>| -> Result<_, _> { + let mut values = vec![]; + + for v in vs { + values.push(parse_value!(v, f64)?); + } + + Ok(Some(values)) + } + ); + + Ok(builder.build()) + } else { + Err(de::Error::custom( + "value is not type of ItemCollectionMetrics", + )) + } +} diff --git a/raiden/src/aws_sdk/serialize/keys_and_attributes.rs b/raiden/src/aws_sdk/serialize/keys_and_attributes.rs new file mode 100644 index 00000000..07a580a0 --- /dev/null +++ b/raiden/src/aws_sdk/serialize/keys_and_attributes.rs @@ -0,0 +1,94 @@ +use std::collections::HashMap; + +use serde::de::{self, Error as _}; +use serde_json::{json, Error, Map, Value}; + +use crate::{ + aws_sdk::{ + serialize::{attribute_value_to_value, parse_value, set_optional_value}, + types::KeysAndAttributes, + }, + serialize::value_to_attribute_value, +}; + +pub fn keys_and_attributes_to_value(v: &KeysAndAttributes) -> Value { + json!({ + "keys": v.keys.iter().map(|v| v.iter().map(|(k, v)| { + (k.clone(), attribute_value_to_value(v)) + }).collect::>()).collect::>(), + "attributes_to_get": v.attributes_to_get, + "consistent_read": v.consistent_read, + "projection_expression": v.projection_expression, + "expression_attribute_names": v.expression_attribute_names, + }) +} + +pub fn value_to_keys_and_attributes(value: Value) -> Result { + if let Value::Object(m) = value { + let mut builder = KeysAndAttributes::builder(); + + set_optional_value!(builder, m, keys, array, |vs: &Vec<_>| -> Result<_, _> { + let mut values = vec![]; + + for v in vs { + let v = parse_value!(v, object, |m: &Map<_, _>| -> Result<_, _> { + let mut map = HashMap::new(); + + for (k, v) in m.iter() { + let v = value_to_attribute_value(v.clone()) + .map_err(|err| de::Error::custom(format!("{k} set in keys: {err}")))?; + + map.insert(k.clone(), v); + } + + Ok(map) + })?; + + values.push(v); + } + + Ok(Some(values)) + }); + + set_optional_value!( + builder, + m, + attributes_to_get, + array, + |vs: &Vec<_>| -> Result<_, _> { + let mut values = vec![]; + + for v in vs { + values.push(parse_value!(v, String)?.clone()); + } + + Ok(Some(values)) + } + ); + + set_optional_value!(builder, m, consistent_read, bool); + set_optional_value!(builder, m, projection_expression, String); + + set_optional_value!( + builder, + m, + expression_attribute_names, + object, + |m: &Map<_, _>| -> Result<_, _> { + let mut map = HashMap::new(); + + for (k, v) in m.iter() { + map.insert(k.clone(), parse_value!(v, String)?.clone()); + } + + Ok(Some(map)) + } + ); + + builder + .build() + .map_err(|err| Error::custom(err.to_string())) + } else { + Err(de::Error::custom("value is not type of KeysAndAttributes")) + } +} diff --git a/raiden/src/aws_sdk/serialize/mod.rs b/raiden/src/aws_sdk/serialize/mod.rs new file mode 100644 index 00000000..20fa1398 --- /dev/null +++ b/raiden/src/aws_sdk/serialize/mod.rs @@ -0,0 +1,113 @@ +mod attribute_value; +mod consumed_capacity; +mod delete_request; +mod item_collection_metrics; +mod keys_and_attributes; + +pub use self::{ + attribute_value::*, consumed_capacity::*, delete_request::*, item_collection_metrics::*, + keys_and_attributes::*, +}; + +macro_rules! set_optional_value { + ($builder: expr, $object: expr, $key: ident, bool) => { + if let Some(v) = $object.get(stringify!($key)) { + let v = crate::aws_sdk::serialize::parse_value!(v, bool).map_err( + |err: serde_json::Error| { + serde::de::Error::custom(format!("{}: {err}", stringify!($key))) + }, + )?; + + $builder = $builder.$key(*v); + } + }; + ($builder: expr, $object: expr, $key: ident, String) => { + if let Some(v) = $object.get(stringify!($key)) { + let v = crate::aws_sdk::serialize::parse_value!(v, String).map_err( + |err: serde_json::Error| { + serde::de::Error::custom(format!("{}: {err}", stringify!($key))) + }, + )?; + + $builder = $builder.$key(v); + } + }; + ($builder: expr, $object: expr, $key: ident, f64) => { + if let Some(v) = $object.get(stringify!($key)) { + let v = crate::aws_sdk::serialize::parse_value!(v, f64).map_err( + |err: serde_json::Error| { + serde::de::Error::custom(format!("{}: {err}", stringify!($key))) + }, + )?; + + $builder = $builder.$key(v); + } + }; + ($builder: expr, $object: expr, $key: ident, object, $closure: expr) => { + if let Some(v) = $object.get(stringify!($key)) { + let v = crate::aws_sdk::serialize::parse_value!(v, object, $closure).map_err( + |err: serde_json::Error| { + serde::de::Error::custom(format!("{}: {err}", stringify!($key))) + }, + )?; + + paste::paste! { + $builder = $builder.[](v); + } + } + }; + ($builder: expr, $object: expr, $key: ident, array, $closure: expr) => { + if let Some(v) = $object.get(stringify!($key)) { + let v = crate::aws_sdk::serialize::parse_value!(v, array, $closure).map_err( + |err: serde_json::Error| { + serde::de::Error::custom(format!("{}: {err}", stringify!($key))) + }, + )?; + + paste::paste! { + $builder = $builder.[](v); + } + } + }; +} + +macro_rules! parse_value { + ($value: expr, bool) => {{ + if let serde_json::Value::Bool(v) = $value { + Ok(v) + } else { + Err(serde::de::Error::custom("value must be type of bool")) + } + }}; + ($value: expr, String) => {{ + if let serde_json::Value::String(v) = $value { + Ok(v) + } else { + Err(serde::de::Error::custom("value must be type of String")) + } + }}; + ($value: expr, f64) => {{ + if let serde_json::Value::Number(v) = $value { + v.as_f64() + .ok_or_else(|| de::Error::custom("value must be type of f64")) + } else { + Err(de::Error::custom("value must be type of Number")) + } + }}; + ($value: expr, object, $closure: expr) => {{ + if let serde_json::Value::Object(__m) = $value { + Ok($closure(__m)?) + } else { + Err(serde::de::Error::custom("value must be type of Object")) + } + }}; + ($value: expr, array, $closure: expr) => {{ + if let serde_json::Value::Array(__vs) = $value { + Ok($closure(__vs)?) + } else { + Err(serde::de::Error::custom("value must be type of Array")) + } + }}; +} + +use {parse_value, set_optional_value}; diff --git a/raiden/src/condition/mod.rs b/raiden/src/condition/mod.rs index f639b0ad..15690ea9 100644 --- a/raiden/src/condition/mod.rs +++ b/raiden/src/condition/mod.rs @@ -150,6 +150,8 @@ impl super::IntoAttrValues for ConditionFunctionExpression { use crypto::digest::Digest; use crypto::md5::Md5; let mut m: super::AttributeValues = std::collections::HashMap::new(); + + #[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] match self { Self::AttributeType(_path, t) => { m.insert( @@ -184,6 +186,30 @@ impl super::IntoAttrValues for ConditionFunctionExpression { } _ => {} } + + #[cfg(feature = "aws-sdk")] + match self { + Self::AttributeType(_path, t) => { + m.insert(format!(":type{t}"), super::AttributeValue::S(t.to_string())); + } + Self::BeginsWith(_path, s) => { + let mut md5 = Md5::new(); + md5.input(s.as_bytes()); + m.insert( + format!(":begins_with_{}", md5.result_str()), + super::AttributeValue::S(s), + ); + } + Self::Contains(_path, s) => { + let mut md5 = Md5::new(); + md5.input(s.as_bytes()); + m.insert( + format!(":contains_{}", md5.result_str()), + super::AttributeValue::S(s), + ); + } + _ => {} + } m } } diff --git a/raiden/src/errors/mod.rs b/raiden/src/errors/mod.rs index 146b4adb..cebb0e02 100644 --- a/raiden/src/errors/mod.rs +++ b/raiden/src/errors/mod.rs @@ -1,297 +1,89 @@ mod transaction; -use crate::*; -use thiserror::Error; - pub use transaction::*; -#[derive(Error, Debug, PartialEq)] +#[derive(thiserror::Error, Debug)] pub enum RaidenError { - #[error("`{0}`")] - DataExistsError(String), + #[error("attribute {attr_name:?} convert error")] + AttributeConvertError { attr_name: String }, #[error("`{0}`")] ConditionalCheckFailed(String), #[error("`{0}`")] - ItemCollectionSizeLimitExceeded(String), + IdempotentParameterMismatch(String), #[error("`{0}`")] - TransactionConflict(String), + InternalServerError(String), #[error("`{0}`")] - ResourceNotFound(String), + ItemCollectionSizeLimitExceeded(String), + #[error("next_token decode error")] + NextTokenDecodeError, + #[error("`{0}`")] + ProvisionedThroughputExceeded(String), #[error("`{0}`")] RequestLimitExceeded(String), #[error("`{0}`")] - TransactionConflictError(String), + ResourceNotFound(String), #[error("`{0}`")] SizeLimitExceeded(String), #[error("`{0}`")] - InternalServerError(String), + TransactionConflict(String), #[error("`{0}`")] - ProvisionedThroughputExceeded(String), + TransactionInProgress(String), #[error("`{0}`")] - HttpDispatch(crate::request::HttpDispatchError), + Validation(String), + // + // Following errors are returned only using rusoto. + // + #[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] + #[error("blocking error")] + Blocking, + #[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] #[error("`{0}`")] Credentials(crate::CredentialsError), + #[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] #[error("`{0}`")] - Validation(String), + HttpDispatch(crate::HttpDispatchError), + #[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] #[error("`{0}`")] ParseError(String), + #[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] + #[error("`transaction canceled error {reasons}`")] + TransactionCanceled { + reasons: RaidenTransactionCancellationReasons, + }, + #[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] #[error("unknown error")] Unknown(crate::request::BufferedHttpResponse), - #[error("`transaction canceled error {reasons}`")] + // + // Following errors are returned only using aws-sdk. + // + #[cfg(feature = "aws-sdk")] + #[error("`{0:?}`")] + Construction(aws_smithy_runtime_api::client::result::ConstructionFailure), + #[cfg(feature = "aws-sdk")] + #[error("`{0:?}`")] + HttpDispatch(aws_smithy_runtime_api::client::result::DispatchFailure), + #[cfg(feature = "aws-sdk")] + #[error("`{0:?}`")] + Timeout(aws_smithy_runtime_api::client::result::TimeoutError), + #[cfg(feature = "aws-sdk")] + #[error("`transaction canceled error {reasons}: {raw_reasons:?}`")] TransactionCanceled { reasons: RaidenTransactionCancellationReasons, + raw_reasons: Vec, }, - #[error("`{0}`")] - TransactionInProgress(String), - #[error("`{0}`")] - IdempotentParameterMismatch(String), - #[error("blocking error")] - Blocking, - #[error("next_token decode error")] - NextTokenDecodeError, - #[error("attribute {attr_name:?} convert error")] - AttributeConvertError { attr_name: String }, + #[cfg(feature = "aws-sdk")] + #[error("unknown error")] + Unknown(aws_smithy_runtime_api::client::orchestrator::HttpResponse), + // + // Following errors are never returned. + // + #[deprecated = "unused. this variant never returns."] #[error("attribute {attr_name:?} value not found")] AttributeValueNotFoundError { attr_name: String }, -} - -impl From> for RaidenError { - fn from(error: RusotoError) -> Self { - match error { - RusotoError::Service(error) => match error { - BatchGetItemError::InternalServerError(msg) => { - RaidenError::InternalServerError(msg) - } - BatchGetItemError::ProvisionedThroughputExceeded(msg) => { - RaidenError::ProvisionedThroughputExceeded(msg) - } - BatchGetItemError::RequestLimitExceeded(msg) => { - RaidenError::RequestLimitExceeded(msg) - } - BatchGetItemError::ResourceNotFound(msg) => RaidenError::ResourceNotFound(msg), - }, - RusotoError::HttpDispatch(e) => RaidenError::HttpDispatch(e), - RusotoError::Credentials(e) => RaidenError::Credentials(e), - RusotoError::Validation(msg) => RaidenError::Validation(msg), - RusotoError::ParseError(msg) => RaidenError::ParseError(msg), - RusotoError::Unknown(res) => RaidenError::Unknown(res), - RusotoError::Blocking => RaidenError::Blocking, - } - } -} - -impl From> for RaidenError { - fn from(error: RusotoError) -> Self { - match error { - RusotoError::Service(error) => match error { - BatchWriteItemError::InternalServerError(msg) => { - RaidenError::InternalServerError(msg) - } - BatchWriteItemError::ItemCollectionSizeLimitExceeded(msg) => { - RaidenError::ItemCollectionSizeLimitExceeded(msg) - } - BatchWriteItemError::ProvisionedThroughputExceeded(msg) => { - RaidenError::ProvisionedThroughputExceeded(msg) - } - BatchWriteItemError::RequestLimitExceeded(msg) => { - RaidenError::RequestLimitExceeded(msg) - } - BatchWriteItemError::ResourceNotFound(msg) => RaidenError::ResourceNotFound(msg), - }, - RusotoError::HttpDispatch(e) => RaidenError::HttpDispatch(e), - RusotoError::Credentials(e) => RaidenError::Credentials(e), - RusotoError::Validation(msg) => RaidenError::Validation(msg), - RusotoError::ParseError(msg) => RaidenError::ParseError(msg), - RusotoError::Unknown(res) => RaidenError::Unknown(res), - RusotoError::Blocking => RaidenError::Blocking, - } - } -} - -impl From> for RaidenError { - fn from(error: RusotoError) -> Self { - match error { - RusotoError::Service(error) => match error { - GetItemError::InternalServerError(msg) => RaidenError::InternalServerError(msg), - GetItemError::ProvisionedThroughputExceeded(msg) => { - RaidenError::ProvisionedThroughputExceeded(msg) - } - GetItemError::RequestLimitExceeded(msg) => RaidenError::RequestLimitExceeded(msg), - GetItemError::ResourceNotFound(msg) => RaidenError::ResourceNotFound(msg), - }, - RusotoError::HttpDispatch(e) => RaidenError::HttpDispatch(e), - RusotoError::Credentials(e) => RaidenError::Credentials(e), - RusotoError::Validation(msg) => RaidenError::Validation(msg), - RusotoError::ParseError(msg) => RaidenError::ParseError(msg), - RusotoError::Unknown(res) => RaidenError::Unknown(res), - RusotoError::Blocking => RaidenError::Blocking, - } - } -} - -impl From> for RaidenError { - fn from(error: RusotoError) -> Self { - match error { - RusotoError::Service(error) => match error { - QueryError::InternalServerError(msg) => RaidenError::InternalServerError(msg), - QueryError::ProvisionedThroughputExceeded(msg) => { - RaidenError::ProvisionedThroughputExceeded(msg) - } - QueryError::RequestLimitExceeded(msg) => RaidenError::RequestLimitExceeded(msg), - QueryError::ResourceNotFound(msg) => RaidenError::ResourceNotFound(msg), - }, - RusotoError::HttpDispatch(e) => RaidenError::HttpDispatch(e), - RusotoError::Credentials(e) => RaidenError::Credentials(e), - RusotoError::Validation(msg) => RaidenError::Validation(msg), - RusotoError::ParseError(msg) => RaidenError::ParseError(msg), - RusotoError::Unknown(res) => RaidenError::Unknown(res), - RusotoError::Blocking => RaidenError::Blocking, - } - } -} - -impl From> for RaidenError { - fn from(error: RusotoError) -> Self { - match error { - RusotoError::Service(error) => match error { - ScanError::InternalServerError(msg) => RaidenError::InternalServerError(msg), - ScanError::ProvisionedThroughputExceeded(msg) => { - RaidenError::ProvisionedThroughputExceeded(msg) - } - ScanError::RequestLimitExceeded(msg) => RaidenError::RequestLimitExceeded(msg), - ScanError::ResourceNotFound(msg) => RaidenError::ResourceNotFound(msg), - }, - RusotoError::HttpDispatch(e) => RaidenError::HttpDispatch(e), - RusotoError::Credentials(e) => RaidenError::Credentials(e), - RusotoError::Validation(msg) => RaidenError::Validation(msg), - RusotoError::ParseError(msg) => RaidenError::ParseError(msg), - RusotoError::Unknown(res) => RaidenError::Unknown(res), - RusotoError::Blocking => RaidenError::Blocking, - } - } -} - -impl From> for RaidenError { - fn from(error: RusotoError) -> Self { - match error { - RusotoError::Service(error) => match error { - PutItemError::InternalServerError(msg) => RaidenError::InternalServerError(msg), - PutItemError::ProvisionedThroughputExceeded(msg) => { - RaidenError::ProvisionedThroughputExceeded(msg) - } - PutItemError::RequestLimitExceeded(msg) => RaidenError::RequestLimitExceeded(msg), - PutItemError::ResourceNotFound(msg) => RaidenError::ResourceNotFound(msg), - PutItemError::ConditionalCheckFailed(msg) => { - RaidenError::ConditionalCheckFailed(msg) - } - PutItemError::ItemCollectionSizeLimitExceeded(msg) => { - RaidenError::ItemCollectionSizeLimitExceeded(msg) - } - PutItemError::TransactionConflict(msg) => RaidenError::TransactionConflict(msg), - }, - RusotoError::HttpDispatch(e) => RaidenError::HttpDispatch(e), - RusotoError::Credentials(e) => RaidenError::Credentials(e), - RusotoError::Validation(msg) => RaidenError::Validation(msg), - RusotoError::ParseError(msg) => RaidenError::ParseError(msg), - RusotoError::Unknown(res) => RaidenError::Unknown(res), - RusotoError::Blocking => RaidenError::Blocking, - } - } -} - -impl From> for RaidenError { - fn from(error: RusotoError) -> Self { - match error { - RusotoError::Service(error) => match error { - UpdateItemError::InternalServerError(msg) => RaidenError::InternalServerError(msg), - UpdateItemError::ProvisionedThroughputExceeded(msg) => { - RaidenError::ProvisionedThroughputExceeded(msg) - } - UpdateItemError::RequestLimitExceeded(msg) => { - RaidenError::RequestLimitExceeded(msg) - } - UpdateItemError::ResourceNotFound(msg) => RaidenError::ResourceNotFound(msg), - UpdateItemError::ConditionalCheckFailed(msg) => { - RaidenError::ConditionalCheckFailed(msg) - } - UpdateItemError::ItemCollectionSizeLimitExceeded(msg) => { - RaidenError::ItemCollectionSizeLimitExceeded(msg) - } - UpdateItemError::TransactionConflict(msg) => RaidenError::TransactionConflict(msg), - }, - RusotoError::HttpDispatch(e) => RaidenError::HttpDispatch(e), - RusotoError::Credentials(e) => RaidenError::Credentials(e), - RusotoError::Validation(msg) => RaidenError::Validation(msg), - RusotoError::ParseError(msg) => RaidenError::ParseError(msg), - RusotoError::Unknown(res) => RaidenError::Unknown(res), - RusotoError::Blocking => RaidenError::Blocking, - } - } -} - -impl From> for RaidenError { - fn from(error: RusotoError) -> Self { - match error { - RusotoError::Service(error) => match error { - DeleteItemError::InternalServerError(msg) => RaidenError::InternalServerError(msg), - DeleteItemError::ProvisionedThroughputExceeded(msg) => { - RaidenError::ProvisionedThroughputExceeded(msg) - } - DeleteItemError::RequestLimitExceeded(msg) => { - RaidenError::RequestLimitExceeded(msg) - } - DeleteItemError::ResourceNotFound(msg) => RaidenError::ResourceNotFound(msg), - DeleteItemError::ConditionalCheckFailed(msg) => { - RaidenError::ConditionalCheckFailed(msg) - } - DeleteItemError::ItemCollectionSizeLimitExceeded(msg) => { - RaidenError::ItemCollectionSizeLimitExceeded(msg) - } - DeleteItemError::TransactionConflict(msg) => RaidenError::TransactionConflict(msg), - }, - RusotoError::HttpDispatch(e) => RaidenError::HttpDispatch(e), - RusotoError::Credentials(e) => RaidenError::Credentials(e), - RusotoError::Validation(msg) => RaidenError::Validation(msg), - RusotoError::ParseError(msg) => RaidenError::ParseError(msg), - RusotoError::Unknown(res) => RaidenError::Unknown(res), - RusotoError::Blocking => RaidenError::Blocking, - } - } -} - -impl From> for RaidenError { - fn from(error: RusotoError) -> Self { - match error { - RusotoError::Service(error) => match error { - TransactWriteItemsError::IdempotentParameterMismatch(msg) => { - RaidenError::IdempotentParameterMismatch(msg) - } - TransactWriteItemsError::InternalServerError(msg) => { - RaidenError::InternalServerError(msg) - } - TransactWriteItemsError::ProvisionedThroughputExceeded(msg) => { - RaidenError::ProvisionedThroughputExceeded(msg) - } - TransactWriteItemsError::RequestLimitExceeded(msg) => { - RaidenError::RequestLimitExceeded(msg) - } - TransactWriteItemsError::ResourceNotFound(msg) => { - RaidenError::ResourceNotFound(msg) - } - TransactWriteItemsError::TransactionCanceled(msg) => { - let reasons = RaidenTransactionCancellationReasons::from_str(&msg); - RaidenError::TransactionCanceled { reasons } - } - TransactWriteItemsError::TransactionInProgress(msg) => { - RaidenError::TransactionInProgress(msg) - } - }, - RusotoError::HttpDispatch(e) => RaidenError::HttpDispatch(e), - RusotoError::Credentials(e) => RaidenError::Credentials(e), - RusotoError::Validation(msg) => RaidenError::Validation(msg), - RusotoError::ParseError(msg) => RaidenError::ParseError(msg), - RusotoError::Unknown(res) => RaidenError::Unknown(res), - RusotoError::Blocking => RaidenError::Blocking, - } - } + #[deprecated = "unused. this variant never returns."] + #[error("`{0}`")] + DataExistsError(String), + #[deprecated = "unused. this variant never returns."] + #[error("`{0}`")] + TransactionConflictError(String), } diff --git a/raiden/src/errors/transaction.rs b/raiden/src/errors/transaction.rs index 2bd36e8d..37491b31 100644 --- a/raiden/src/errors/transaction.rs +++ b/raiden/src/errors/transaction.rs @@ -1,7 +1,5 @@ use std::fmt; -use thiserror::Error; - const TRANSACTION_CANCELLED_MESSAGE_PREFIX: &str = "Transaction cancelled, please refer cancellation reasons for specific reasons"; @@ -11,7 +9,7 @@ pub struct RaidenTransactionCancellationReasons( ); impl RaidenTransactionCancellationReasons { - // If `message` is unexcepted format, [RaidenTransactionCancellationReason::Unknown] is returned instead of Err(_) + // If `message` is unexpected format, [RaidenTransactionCancellationReason::Unknown] is returned instead of Err(_) // TODO: Fix it later. #[allow(clippy::should_implement_trait)] pub fn from_str(message: &str) -> Self { @@ -80,7 +78,7 @@ impl fmt::Display for RaidenTransactionCancellationReasons { } } -#[derive(Error, Clone, Debug, PartialEq)] +#[derive(thiserror::Error, Clone, Debug, PartialEq)] pub enum RaidenTransactionCancellationReason { #[error("Unknown")] Unknown, @@ -109,7 +107,7 @@ impl RaidenTransactionCancellationReason { "ProvisionedThroughputExceeded" => Self::ProvisionedThroughputExceeded, "ThrottlingError" => Self::ThrottlingError, "ValidationError" => Self::ValidationError, - // If `reason` is unexcepted, Self::Unknown is returned instead of Err(_) + // If `reason` is unexpected, Self::Unknown is returned instead of Err(_) _ => Self::Unknown, } } diff --git a/raiden/src/lib.rs b/raiden/src/lib.rs index 0af27a63..1364caa8 100644 --- a/raiden/src/lib.rs +++ b/raiden/src/lib.rs @@ -1,3 +1,14 @@ +#[cfg(all(feature = "rusoto", feature = "rusoto_rustls"))] +compile_error!("feature \"rusoto\" and \"rusoto_rustls\" cannot be enabled at the same time."); + +#[cfg(any( + all(feature = "aws-sdk", feature = "rusoto"), + all(feature = "aws-sdk", feature = "rusoto_rustls") +))] +compile_error!( + "feature \"aws-sdk\" and \"rusoto\" or \"rusoto_rustls\" cannot be enabled at the same time." +); + #[macro_use] extern crate serde_derive; @@ -13,6 +24,18 @@ pub mod types; pub mod update_expression; pub mod value_id; +#[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] +mod rusoto; + +#[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] +pub use self::rusoto::*; + +#[cfg(feature = "aws-sdk")] +pub mod aws_sdk; + +#[cfg(feature = "aws-sdk")] +pub use self::aws_sdk::{types::AttributeValue, *}; + pub use condition::*; pub use errors::*; pub use filter_expression::*; @@ -23,23 +46,9 @@ pub use retry::*; pub use id_generator::*; pub use raiden_derive::*; -pub use rusoto_credential::*; +pub use types::*; pub use value_id::*; -#[cfg(feature = "default")] -pub use rusoto_dynamodb_default::*; - -#[cfg(feature = "default")] -pub use rusoto_core_default::*; - -#[cfg(feature = "rustls")] -pub use rusoto_dynamodb_rustls::*; - -#[cfg(feature = "rustls")] -pub use rusoto_core_rustls::*; - -pub type Placeholder = String; - pub use safe_builder::Builder; #[derive(Debug, Clone, PartialEq)] @@ -57,15 +66,6 @@ pub enum AttributeType { M, // Map } -impl IntoAttribute for AttributeType { - fn into_attr(self) -> AttributeValue { - AttributeValue { - s: Some(self.to_string()), - ..AttributeValue::default() - } - } -} - impl std::fmt::Display for AttributeType { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{self:?}") @@ -73,7 +73,6 @@ impl std::fmt::Display for AttributeType { } pub type AttributeNames = std::collections::HashMap; - pub type AttributeValues = std::collections::HashMap; pub struct Attributes(AttributeValues); @@ -129,109 +128,6 @@ pub trait FromStringSetItem: Sized { fn from_ss_item(value: String) -> Result; } -impl IntoAttribute for String { - fn into_attr(self) -> AttributeValue { - // Empty String is allowed since 2020/5 - // https://aws.amazon.com/jp/about-aws/whats-new/2020/05/amazon-dynamodb-now-supports-empty-values-for-non-key-string-and-binary-attributes-in-dynamodb-tables/ - AttributeValue { - s: Some(self), - ..AttributeValue::default() - } - } -} - -impl FromAttribute for String { - fn from_attr(value: Option) -> Result { - if value.is_none() { - return Err(ConversionError::ValueIsNone); - } - let value = value.unwrap(); - if let Some(true) = value.null { - // See. https://github.com/raiden-rs/raiden/issues/58 - return Ok("".to_owned()); - } - value.s.ok_or(ConversionError::ValueIsNone) - } -} - -impl IntoAttribute for &'_ str { - fn into_attr(self) -> AttributeValue { - if self.is_empty() { - // See. https://github.com/raiden-rs/raiden-dynamo/issues/58 - return AttributeValue { - null: Some(true), - ..Default::default() - }; - } - AttributeValue { - s: Some(self.to_owned()), - ..AttributeValue::default() - } - } -} - -impl<'a> IntoAttribute for std::borrow::Cow<'a, str> { - fn into_attr(self) -> AttributeValue { - let s = match self { - std::borrow::Cow::Owned(o) => o, - std::borrow::Cow::Borrowed(b) => b.to_owned(), - }; - if s.is_empty() { - // See. https://github.com/raiden-rs/raiden-dynamo/issues/58 - return AttributeValue { - null: Some(true), - ..Default::default() - }; - } - AttributeValue { - s: Some(s), - ..AttributeValue::default() - } - } -} - -impl<'a> FromAttribute for std::borrow::Cow<'a, str> { - fn from_attr(value: Option) -> Result { - if value.is_none() { - return Err(ConversionError::ValueIsNone); - } - let value = value.unwrap(); - if let Some(true) = value.null { - // See. https://github.com/raiden-rs/raiden/issues/58 - return Ok(std::borrow::Cow::Owned("".to_owned())); - } - value - .s - .map(std::borrow::Cow::Owned) - .ok_or(ConversionError::ValueIsNone) - } -} - -macro_rules! default_attr_for_num { - ($to: ty) => { - impl IntoAttribute for $to { - fn into_attr(self) -> AttributeValue { - AttributeValue { - n: Some(format!("{}", self)), - ..AttributeValue::default() - } - } - } - impl FromAttribute for $to { - fn from_attr(value: Option) -> Result { - if value.is_none() { - return Err(ConversionError::ValueIsNone); - } - value - .unwrap() - .n - .map(|v| v.parse().unwrap()) - .ok_or(ConversionError::ValueIsNone) - } - } - }; -} - default_attr_for_num!(usize); default_attr_for_num!(u64); default_attr_for_num!(u32); @@ -247,49 +143,6 @@ default_attr_for_num!(i8); default_attr_for_num!(f32); default_attr_for_num!(f64); -impl IntoAttribute for Option { - fn into_attr(self) -> AttributeValue { - match self { - Some(value) => value.into_attr(), - _ => AttributeValue { - null: Some(true), - ..Default::default() - }, - } - } -} - -impl FromAttribute for Option { - fn from_attr(value: Option) -> Result { - if value.is_none() { - return Ok(None); - } - let value = value.unwrap(); - match value.null { - Some(true) => Ok(None), - _ => Ok(Some(FromAttribute::from_attr(Some(value))?)), - } - } -} - -impl IntoAttribute for bool { - fn into_attr(self) -> AttributeValue { - AttributeValue { - bool: Some(self), - ..AttributeValue::default() - } - } -} - -impl FromAttribute for bool { - fn from_attr(value: Option) -> Result { - if value.is_none() { - return Err(ConversionError::ValueIsNone); - } - value.unwrap().bool.ok_or(ConversionError::ValueIsNone) - } -} - impl IntoStringSetItem for String { fn into_ss_item(self) -> String { self @@ -302,78 +155,6 @@ impl FromStringSetItem for String { } } -impl IntoAttribute for Vec { - fn into_attr(mut self) -> AttributeValue { - if self.is_empty() { - // See. https://github.com/raiden-rs/raiden/issues/57 - return AttributeValue { - null: Some(true), - ..Default::default() - }; - } - AttributeValue { - l: Some(self.drain(..).map(|s| s.into_attr()).collect()), - ..AttributeValue::default() - } - } -} - -impl FromAttribute for Vec { - fn from_attr(value: Option) -> Result { - if value.is_none() { - return Ok(vec![]); - } - let value = value.unwrap(); - if let Some(true) = value.null { - // See. https://github.com/raiden-rs/raiden/issues/57 - return Ok(vec![]); - } - value - .l - .ok_or(ConversionError::ValueIsNone)? - .into_iter() - .map(|item| A::from_attr(Some(item))) - .collect() - } -} - -macro_rules! default_number_hash_set_convertor { - ($to: ty) => { - impl IntoAttribute for std::collections::HashSet<$to> { - fn into_attr(self) -> AttributeValue { - if self.is_empty() { - // See. https://github.com/raiden-rs/raiden/issues/57 - // https://github.com/raiden-rs/raiden-dynamo/issues/64 - return AttributeValue::default(); - } - AttributeValue { - ns: Some(self.into_iter().map(|s| s.to_string()).collect()), - ..AttributeValue::default() - } - } - } - - impl FromAttribute for std::collections::HashSet<$to> { - fn from_attr(value: Option) -> Result { - if value.is_none() { - return Ok(std::collections::HashSet::new()); - } - let value = value.unwrap(); - if let Some(true) = value.null { - // See. https://github.com/raiden-rs/raiden/issues/57 - return Ok(std::collections::HashSet::new()); - } - let mut nums = value.ns.ok_or(ConversionError::ValueIsNone)?; - let mut results: Vec> = nums - .drain(..) - .map(|ns| ns.parse().map_err(|_| ConversionError::ParseInt)) - .collect(); - results.drain(..).collect() - } - } - }; -} - default_number_hash_set_convertor!(usize); default_number_hash_set_convertor!(u64); default_number_hash_set_convertor!(u32); @@ -386,43 +167,6 @@ default_number_hash_set_convertor!(i32); default_number_hash_set_convertor!(i16); default_number_hash_set_convertor!(i8); -macro_rules! default_number_btree_set_convertor { - ($to: ty) => { - impl IntoAttribute for std::collections::BTreeSet<$to> { - fn into_attr(self) -> AttributeValue { - if self.is_empty() { - // See. https://github.com/raiden-rs/raiden/issues/57 - // https://github.com/raiden-rs/raiden-dynamo/issues/64 - return AttributeValue::default(); - } - AttributeValue { - ns: Some(self.into_iter().map(|s| s.to_string()).collect()), - ..AttributeValue::default() - } - } - } - - impl FromAttribute for std::collections::BTreeSet<$to> { - fn from_attr(value: Option) -> Result { - if value.is_none() { - return Ok(std::collections::BTreeSet::new()); - } - let value = value.unwrap(); - if let Some(true) = value.null { - // See. https://github.com/raiden-rs/raiden/issues/57 - return Ok(std::collections::BTreeSet::new()); - } - let mut nums = value.ns.ok_or(ConversionError::ValueIsNone)?; - let mut results: Vec> = nums - .drain(..) - .map(|ns| ns.parse().map_err(|_| ConversionError::ParseInt)) - .collect(); - results.drain(..).collect() - } - } - }; -} - default_number_btree_set_convertor!(usize); default_number_btree_set_convertor!(u64); default_number_btree_set_convertor!(u32); @@ -435,66 +179,6 @@ default_number_btree_set_convertor!(i32); default_number_btree_set_convertor!(i16); default_number_btree_set_convertor!(i8); -impl IntoAttribute for std::collections::HashSet { - fn into_attr(self) -> AttributeValue { - if self.is_empty() { - // See. https://github.com/raiden-rs/raiden/issues/57 - // https://github.com/raiden-rs/raiden-dynamo/issues/64 - return AttributeValue::default(); - } - AttributeValue { - ss: Some(self.into_iter().map(|s| s.into_ss_item()).collect()), - ..AttributeValue::default() - } - } -} - -impl FromAttribute - for std::collections::HashSet -{ - fn from_attr(value: Option) -> Result { - if value.is_none() { - return Ok(std::collections::HashSet::new()); - } - let value = value.unwrap(); - if let Some(true) = value.null { - // See. https://github.com/raiden-rs/raiden/issues/57 - return Ok(std::collections::HashSet::new()); - } - let mut ss = value.ss.ok_or(ConversionError::ValueIsNone)?; - ss.drain(..).map(A::from_ss_item).collect() - } -} - -impl IntoAttribute for std::collections::BTreeSet { - fn into_attr(self) -> AttributeValue { - if self.is_empty() { - // See. https://github.com/raiden-rs/raiden/issues/57 - // https://github.com/raiden-rs/raiden-dynamo/issues/64 - return AttributeValue::default(); - } - AttributeValue { - ss: Some(self.into_iter().map(|s| s.into_ss_item()).collect()), - ..AttributeValue::default() - } - } -} - -impl FromAttribute for std::collections::BTreeSet { - fn from_attr(value: Option) -> Result { - if value.is_none() { - return Ok(std::collections::BTreeSet::new()); - } - let value = value.unwrap(); - if let Some(true) = value.null { - // See. https://github.com/raiden-rs/raiden/issues/57 - return Ok(std::collections::BTreeSet::new()); - } - let mut ss = value.ss.ok_or(ConversionError::ValueIsNone)?; - ss.drain(..).map(A::from_ss_item).collect() - } -} - pub trait ResolveAttribute: Sized + FromAttribute { fn resolve_attr( key: &str, @@ -504,18 +188,9 @@ pub trait ResolveAttribute: Sized + FromAttribute { } } -pub struct GetItemController<'a> { - pub client: &'a DynamoDbClient, - pub item: GetItemInput, -} - pub fn merge_map( map1: std::collections::HashMap, map2: std::collections::HashMap, ) -> std::collections::HashMap { map1.into_iter().chain(map2).collect() } - -pub fn is_attr_value_empty(a: &AttributeValue) -> bool { - a == &AttributeValue::default() -} diff --git a/raiden/src/next_token/mod.rs b/raiden/src/next_token/mod.rs index ad273700..570c4aed 100644 --- a/raiden/src/next_token/mod.rs +++ b/raiden/src/next_token/mod.rs @@ -19,16 +19,11 @@ impl NextToken { Err(_) => return Err(super::RaidenError::NextTokenDecodeError), }; - let deserialized: std::collections::HashMap = - match serde_json::from_str(s) { - Ok(deserialized) => deserialized, - Err(_) => return Err(super::RaidenError::NextTokenDecodeError), - }; - Ok(deserialized) + crate::deserialize_attr_value(s) } pub fn from_attr(key: &super::AttributeValues) -> Self { - let serialized = serde_json::to_string(key).expect("should serialize"); + let serialized = crate::serialize_attr_values(key); Self(STANDARD.encode(serialized)) } } diff --git a/raiden/src/ops/batch_delete.rs b/raiden/src/ops/batch_delete.rs index d740592f..b82a3c8f 100644 --- a/raiden/src/ops/batch_delete.rs +++ b/raiden/src/ops/batch_delete.rs @@ -1,8 +1,16 @@ -use serde::{Deserialize, Serialize}; +#[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] +use crate::{ConsumedCapacity, DeleteRequest}; + +#[cfg(feature = "aws-sdk")] +use crate::aws_sdk::types::{ConsumedCapacity, DeleteRequest}; // See. https://github.com/rusoto/rusoto/blob/69e7c9150d98916ef8fc814f5cd17eb0e4dee3d3/rusoto/services/dynamodb/src/generated.rs#L395 -#[derive(Default, Debug, Clone, PartialEq, Deserialize, Serialize)] +#[derive(Default, Debug, Clone, PartialEq)] +#[cfg_attr( + any(feature = "rusoto", feature = "rusoto_rustls"), + derive(serde::Deserialize, serde::Serialize) +)] pub struct BatchDeleteOutput { - pub consumed_capacity: Option>, - pub unprocessed_items: Vec, + pub consumed_capacity: Option>, + pub unprocessed_items: Vec, } diff --git a/raiden/src/ops/batch_get.rs b/raiden/src/ops/batch_get.rs index 432144a1..7d8a9461 100644 --- a/raiden/src/ops/batch_get.rs +++ b/raiden/src/ops/batch_get.rs @@ -1,9 +1,17 @@ -use serde::{Deserialize, Serialize}; +#[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] +use crate::{ConsumedCapacity, KeysAndAttributes}; + +#[cfg(feature = "aws-sdk")] +use crate::aws_sdk::types::{ConsumedCapacity, KeysAndAttributes}; // See. https://github.com/rusoto/rusoto/blob/69e7c9150d98916ef8fc814f5cd17eb0e4dee3d3/rusoto/services/dynamodb/src/generated.rs#L356 -#[derive(Default, Debug, Clone, PartialEq, Deserialize, Serialize)] +#[derive(Default, Debug, Clone, PartialEq)] +#[cfg_attr( + any(feature = "rusoto", feature = "rusoto_rustls"), + derive(serde::Deserialize, serde::Serialize) +)] pub struct BatchGetOutput { - pub consumed_capacity: Option>, + pub consumed_capacity: Option>, pub items: Vec, - pub unprocessed_keys: Option, + pub unprocessed_keys: Option, } diff --git a/raiden/src/ops/get.rs b/raiden/src/ops/get.rs index 3a68e68f..52be363a 100644 --- a/raiden/src/ops/get.rs +++ b/raiden/src/ops/get.rs @@ -1,8 +1,16 @@ -use serde::{Deserialize, Serialize}; +#[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] +use crate::ConsumedCapacity; + +#[cfg(feature = "aws-sdk")] +use crate::aws_sdk::types::ConsumedCapacity; // See. https://github.com/rusoto/rusoto/blob/cf22a4348ae717a20760bb9934cfd118ddb4437e/rusoto/services/dynamodb/src/generated.rs#L1168 -#[derive(Default, Debug, Clone, PartialEq, Deserialize, Serialize)] +#[derive(Default, Debug, Clone, PartialEq)] +#[cfg_attr( + any(feature = "rusoto", feature = "rusoto_rustls"), + derive(serde::Deserialize, serde::Serialize) +)] pub struct GetOutput { - pub consumed_capacity: Option, + pub consumed_capacity: Option, pub item: T, } diff --git a/raiden/src/ops/mod.rs b/raiden/src/ops/mod.rs index 6319914c..1be3fc2b 100644 --- a/raiden/src/ops/mod.rs +++ b/raiden/src/ops/mod.rs @@ -4,8 +4,7 @@ pub mod get; pub mod put; pub mod query; pub mod scan; -pub mod update; - pub mod transact_write; +pub mod update; pub use transact_write::*; diff --git a/raiden/src/ops/put.rs b/raiden/src/ops/put.rs index 16927619..7309b3ed 100644 --- a/raiden/src/ops/put.rs +++ b/raiden/src/ops/put.rs @@ -1,8 +1,16 @@ -use serde::{Deserialize, Serialize}; +#[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] +use crate::ConsumedCapacity; + +#[cfg(feature = "aws-sdk")] +use crate::aws_sdk::types::ConsumedCapacity; // See. https://github.com/rusoto/rusoto/blob/cf22a4348ae717a20760bb9934cfd118ddb4437e/rusoto/services/dynamodb/src/generated.rs#L1168 -#[derive(Default, Debug, Clone, PartialEq, Deserialize, Serialize)] +#[derive(Default, Debug, Clone, PartialEq)] +#[cfg_attr( + any(feature = "rusoto", feature = "rusoto_rustls"), + derive(serde::Deserialize, serde::Serialize) +)] pub struct PutOutput { - pub consumed_capacity: Option, + pub consumed_capacity: Option, pub item: T, } diff --git a/raiden/src/ops/query.rs b/raiden/src/ops/query.rs index 091af0fa..8b07ec19 100644 --- a/raiden/src/ops/query.rs +++ b/raiden/src/ops/query.rs @@ -1,9 +1,17 @@ -use serde::{Deserialize, Serialize}; +#[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] +use crate::ConsumedCapacity; + +#[cfg(feature = "aws-sdk")] +use crate::aws_sdk::types::ConsumedCapacity; // See. https://github.com/rusoto/rusoto/blob/cf22a4348ae717a20760bb9934cfd118ddb4437e/rusoto/services/dynamodb/src/generated.rs#L1168 -#[derive(Default, Debug, Clone, PartialEq, Deserialize, Serialize)] +#[derive(Default, Debug, Clone, PartialEq)] +#[cfg_attr( + any(feature = "rusoto", feature = "rusoto_rustls"), + derive(serde::Deserialize, serde::Serialize) +)] pub struct QueryOutput { - pub consumed_capacity: Option, + pub consumed_capacity: Option, pub items: Vec, pub count: Option, pub next_token: Option, diff --git a/raiden/src/ops/scan.rs b/raiden/src/ops/scan.rs index 6038e984..cfe097cb 100644 --- a/raiden/src/ops/scan.rs +++ b/raiden/src/ops/scan.rs @@ -1,11 +1,19 @@ -use serde::{Deserialize, Serialize}; +#[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] +use crate::{AttributeValue, ConsumedCapacity}; + +#[cfg(feature = "aws-sdk")] +use crate::aws_sdk::types::{AttributeValue, ConsumedCapacity}; // See. https://github.com/rusoto/rusoto/blob/cf22a4348ae717a20760bb9934cfd118ddb4437e/rusoto/services/dynamodb/src/generated.rs#L2406 -#[derive(Default, Debug, Clone, PartialEq, Deserialize, Serialize)] +#[derive(Default, Debug, Clone, PartialEq)] +#[cfg_attr( + any(feature = "rusoto", feature = "rusoto_rustls"), + derive(serde::Deserialize, serde::Serialize) +)] pub struct ScanOutput { - pub consumed_capacity: Option, + pub consumed_capacity: Option, pub items: Vec, pub count: Option, - pub last_evaluated_key: Option<::std::collections::HashMap>, + pub last_evaluated_key: Option<::std::collections::HashMap>, pub scanned_count: Option, } diff --git a/raiden/src/ops/transact_write.rs b/raiden/src/ops/transact_write.rs index e53c31b6..65183127 100644 --- a/raiden/src/ops/transact_write.rs +++ b/raiden/src/ops/transact_write.rs @@ -1,122 +1,21 @@ -use crate::{DynamoDb, TransactWriteItem}; +#[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] +use crate::{ConditionCheck, Delete, Put, Update}; -pub struct WriteTx { - items: Vec, - client: crate::DynamoDbClient, - retry_condition: crate::RetryCondition, -} -impl WriteTx { - pub fn new(region: crate::Region) -> Self { - let client = crate::DynamoDbClient::new(region); - Self { - items: vec![], - client, - retry_condition: crate::RetryCondition::new(), - } - } - pub fn new_with_client(client: crate::Client, region: crate::Region) -> Self { - let client = crate::DynamoDbClient::new_with_client(client, region); - Self { - items: vec![], - client, - retry_condition: crate::RetryCondition::new(), - } - } - - pub fn with_retries(mut self, s: Box) -> Self { - self.retry_condition.strategy = s; - self - } - - pub fn put(mut self, builder: impl TransactWritePutBuilder) -> Self { - self.items.push(TransactWriteItem { - condition_check: None, - delete: None, - update: None, - put: Some(builder.build()), - }); - self - } - - pub fn update(mut self, builder: impl TransactWriteUpdateBuilder) -> Self { - self.items.push(TransactWriteItem { - condition_check: None, - delete: None, - update: Some(builder.build()), - put: None, - }); - self - } - - pub fn delete(mut self, builder: impl TransactWriteDeleteBuilder) -> Self { - self.items.push(TransactWriteItem { - condition_check: None, - delete: Some(builder.build()), - update: None, - put: None, - }); - self - } - - pub fn condition_check(mut self, builder: impl TransactWriteConditionCheckBuilder) -> Self { - self.items.push(TransactWriteItem { - condition_check: Some(builder.build()), - delete: None, - update: None, - put: None, - }); - self - } - - pub async fn run(self) -> Result<(), crate::RaidenError> { - let policy: crate::RetryPolicy = self.retry_condition.strategy.policy().into(); - let client = self.client; - let input = crate::TransactWriteItemsInput { - client_request_token: None, - return_consumed_capacity: None, - return_item_collection_metrics: None, - transact_items: self.items, - }; - policy - .retry_if( - move || { - let client = client.clone(); - let input = input.clone(); - async { WriteTx::inner_run(client, input).await } - }, - &self.retry_condition, - ) - .await - } - - #[cfg_attr(feature = "tracing", tracing::instrument( - level = tracing::Level::DEBUG, - name = "dynamodb::action", - skip_all, - fields(api = "transact_write_items") - ))] - async fn inner_run( - client: crate::DynamoDbClient, - input: crate::TransactWriteItemsInput, - ) -> Result<(), crate::RaidenError> { - let _res = client.transact_write_items(input).await?; - // TODO: ADD Resp - Ok(()) - } -} +#[cfg(feature = "aws-sdk")] +use crate::aws_sdk::types::{ConditionCheck, Delete, Put, Update}; pub trait TransactWritePutBuilder { - fn build(self) -> crate::Put; + fn build(self) -> Put; } pub trait TransactWriteUpdateBuilder { - fn build(self) -> crate::Update; + fn build(self) -> Update; } pub trait TransactWriteDeleteBuilder { - fn build(self) -> crate::Delete; + fn build(self) -> Delete; } pub trait TransactWriteConditionCheckBuilder { - fn build(self) -> crate::ConditionCheck; + fn build(self) -> ConditionCheck; } diff --git a/raiden/src/ops/update.rs b/raiden/src/ops/update.rs index 6e0ca91b..8ce01ab5 100644 --- a/raiden/src/ops/update.rs +++ b/raiden/src/ops/update.rs @@ -1,9 +1,17 @@ -use serde::{Deserialize, Serialize}; +#[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] +use crate::{ConsumedCapacity, ItemCollectionMetrics}; + +#[cfg(feature = "aws-sdk")] +use crate::aws_sdk::types::{ConsumedCapacity, ItemCollectionMetrics}; // See. https://github.com/rusoto/rusoto/blob/cf22a4348ae717a20760bb9934cfd118ddb4437e/rusoto/services/dynamodb/src/generated.rs#L2971 -#[derive(Default, Debug, Clone, PartialEq, Deserialize, Serialize)] +#[derive(Default, Debug, Clone, PartialEq)] +#[cfg_attr( + any(feature = "rusoto", feature = "rusoto_rustls"), + derive(serde::Deserialize, serde::Serialize) +)] pub struct UpdateOutput { - pub consumed_capacity: Option, + pub consumed_capacity: Option, pub item: Option, - pub item_collection_metrics: Option, + pub item_collection_metrics: Option, } diff --git a/raiden/src/retry/mod.rs b/raiden/src/retry/mod.rs index 6b0f1bb9..3644dd5a 100644 --- a/raiden/src/retry/mod.rs +++ b/raiden/src/retry/mod.rs @@ -1,4 +1,6 @@ -pub use again::{Condition, RetryPolicy}; +pub use again::RetryPolicy; + +use again::Condition; use std::time::Duration; use super::RaidenError; @@ -43,6 +45,12 @@ impl RetryCondition { pub fn new() -> Self { Default::default() } + + pub fn never() -> Self { + Self { + strategy: Box::new(NopRetryStrategy), + } + } } impl Default for RetryCondition { @@ -69,7 +77,8 @@ pub struct DefaultRetryStrategy; impl RetryStrategy for DefaultRetryStrategy { fn should_retry(&self, error: &RaidenError) -> bool { - matches!( + #[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] + return matches!( error, RaidenError::InternalServerError(_) | RaidenError::ProvisionedThroughputExceeded(_) @@ -82,10 +91,37 @@ impl RetryStrategy for DefaultRetryStrategy { // This is because, sometimes throttlingException is included in unknown error. // please make more rigorous classification of errors. | RaidenError::Unknown(_) - ) + ); + + #[cfg(feature = "aws-sdk")] + return matches!( + error, + RaidenError::InternalServerError(_) + | RaidenError::ProvisionedThroughputExceeded(_) + | RaidenError::RequestLimitExceeded(_) + | RaidenError::HttpDispatch(_) + | RaidenError::Timeout(_) + // INFO: For now, return true, when unknown error detected. + // This is because, sometimes throttlingException is included in unknown error. + // please make more rigorous classification of errors. + | RaidenError::Unknown(_) + ); } fn policy(&self) -> Policy { Policy::default() } } + +#[derive(Clone, Copy, PartialEq, Debug)] +pub struct NopRetryStrategy; + +impl RetryStrategy for NopRetryStrategy { + fn should_retry(&self, _: &RaidenError) -> bool { + false + } + + fn policy(&self) -> Policy { + Policy::None + } +} diff --git a/raiden/src/rusoto/errors.rs b/raiden/src/rusoto/errors.rs new file mode 100644 index 00000000..525ec866 --- /dev/null +++ b/raiden/src/rusoto/errors.rs @@ -0,0 +1,209 @@ +use crate::*; + +fn into_raiden_error(err: RusotoError) -> RaidenError { + match err { + RusotoError::HttpDispatch(err) => RaidenError::HttpDispatch(err), + RusotoError::Credentials(err) => RaidenError::Credentials(err), + RusotoError::Validation(msg) => RaidenError::Validation(msg), + RusotoError::ParseError(msg) => RaidenError::ParseError(msg), + RusotoError::Unknown(res) => RaidenError::Unknown(res), + RusotoError::Blocking => RaidenError::Blocking, + RusotoError::Service(_) => unimplemented!("RusotoError::Service should be handled."), + } +} + +impl From> for RaidenError { + fn from(error: RusotoError) -> Self { + match error { + RusotoError::Service(error) => match error { + BatchGetItemError::InternalServerError(msg) => { + RaidenError::InternalServerError(msg) + } + BatchGetItemError::ProvisionedThroughputExceeded(msg) => { + RaidenError::ProvisionedThroughputExceeded(msg) + } + BatchGetItemError::RequestLimitExceeded(msg) => { + RaidenError::RequestLimitExceeded(msg) + } + BatchGetItemError::ResourceNotFound(msg) => RaidenError::ResourceNotFound(msg), + }, + _ => into_raiden_error(error), + } + } +} + +impl From> for RaidenError { + fn from(error: RusotoError) -> Self { + match error { + RusotoError::Service(error) => match error { + BatchWriteItemError::InternalServerError(msg) => { + RaidenError::InternalServerError(msg) + } + BatchWriteItemError::ItemCollectionSizeLimitExceeded(msg) => { + RaidenError::ItemCollectionSizeLimitExceeded(msg) + } + BatchWriteItemError::ProvisionedThroughputExceeded(msg) => { + RaidenError::ProvisionedThroughputExceeded(msg) + } + BatchWriteItemError::RequestLimitExceeded(msg) => { + RaidenError::RequestLimitExceeded(msg) + } + BatchWriteItemError::ResourceNotFound(msg) => RaidenError::ResourceNotFound(msg), + }, + _ => into_raiden_error(error), + } + } +} + +impl From> for RaidenError { + fn from(error: RusotoError) -> Self { + match error { + RusotoError::Service(error) => match error { + GetItemError::InternalServerError(msg) => RaidenError::InternalServerError(msg), + GetItemError::ProvisionedThroughputExceeded(msg) => { + RaidenError::ProvisionedThroughputExceeded(msg) + } + GetItemError::RequestLimitExceeded(msg) => RaidenError::RequestLimitExceeded(msg), + GetItemError::ResourceNotFound(msg) => RaidenError::ResourceNotFound(msg), + }, + _ => into_raiden_error(error), + } + } +} + +impl From> for RaidenError { + fn from(error: RusotoError) -> Self { + match error { + RusotoError::Service(error) => match error { + QueryError::InternalServerError(msg) => RaidenError::InternalServerError(msg), + QueryError::ProvisionedThroughputExceeded(msg) => { + RaidenError::ProvisionedThroughputExceeded(msg) + } + QueryError::RequestLimitExceeded(msg) => RaidenError::RequestLimitExceeded(msg), + QueryError::ResourceNotFound(msg) => RaidenError::ResourceNotFound(msg), + }, + _ => into_raiden_error(error), + } + } +} + +impl From> for RaidenError { + fn from(error: RusotoError) -> Self { + match error { + RusotoError::Service(error) => match error { + ScanError::InternalServerError(msg) => RaidenError::InternalServerError(msg), + ScanError::ProvisionedThroughputExceeded(msg) => { + RaidenError::ProvisionedThroughputExceeded(msg) + } + ScanError::RequestLimitExceeded(msg) => RaidenError::RequestLimitExceeded(msg), + ScanError::ResourceNotFound(msg) => RaidenError::ResourceNotFound(msg), + }, + _ => into_raiden_error(error), + } + } +} + +impl From> for RaidenError { + fn from(error: RusotoError) -> Self { + match error { + RusotoError::Service(error) => match error { + PutItemError::InternalServerError(msg) => RaidenError::InternalServerError(msg), + PutItemError::ProvisionedThroughputExceeded(msg) => { + RaidenError::ProvisionedThroughputExceeded(msg) + } + PutItemError::RequestLimitExceeded(msg) => RaidenError::RequestLimitExceeded(msg), + PutItemError::ResourceNotFound(msg) => RaidenError::ResourceNotFound(msg), + PutItemError::ConditionalCheckFailed(msg) => { + RaidenError::ConditionalCheckFailed(msg) + } + PutItemError::ItemCollectionSizeLimitExceeded(msg) => { + RaidenError::ItemCollectionSizeLimitExceeded(msg) + } + PutItemError::TransactionConflict(msg) => RaidenError::TransactionConflict(msg), + }, + _ => into_raiden_error(error), + } + } +} + +impl From> for RaidenError { + fn from(error: RusotoError) -> Self { + match error { + RusotoError::Service(error) => match error { + UpdateItemError::InternalServerError(msg) => RaidenError::InternalServerError(msg), + UpdateItemError::ProvisionedThroughputExceeded(msg) => { + RaidenError::ProvisionedThroughputExceeded(msg) + } + UpdateItemError::RequestLimitExceeded(msg) => { + RaidenError::RequestLimitExceeded(msg) + } + UpdateItemError::ResourceNotFound(msg) => RaidenError::ResourceNotFound(msg), + UpdateItemError::ConditionalCheckFailed(msg) => { + RaidenError::ConditionalCheckFailed(msg) + } + UpdateItemError::ItemCollectionSizeLimitExceeded(msg) => { + RaidenError::ItemCollectionSizeLimitExceeded(msg) + } + UpdateItemError::TransactionConflict(msg) => RaidenError::TransactionConflict(msg), + }, + _ => into_raiden_error(error), + } + } +} + +impl From> for RaidenError { + fn from(error: RusotoError) -> Self { + match error { + RusotoError::Service(error) => match error { + DeleteItemError::InternalServerError(msg) => RaidenError::InternalServerError(msg), + DeleteItemError::ProvisionedThroughputExceeded(msg) => { + RaidenError::ProvisionedThroughputExceeded(msg) + } + DeleteItemError::RequestLimitExceeded(msg) => { + RaidenError::RequestLimitExceeded(msg) + } + DeleteItemError::ResourceNotFound(msg) => RaidenError::ResourceNotFound(msg), + DeleteItemError::ConditionalCheckFailed(msg) => { + RaidenError::ConditionalCheckFailed(msg) + } + DeleteItemError::ItemCollectionSizeLimitExceeded(msg) => { + RaidenError::ItemCollectionSizeLimitExceeded(msg) + } + DeleteItemError::TransactionConflict(msg) => RaidenError::TransactionConflict(msg), + }, + _ => into_raiden_error(error), + } + } +} + +impl From> for RaidenError { + fn from(error: RusotoError) -> Self { + match error { + RusotoError::Service(error) => match error { + TransactWriteItemsError::IdempotentParameterMismatch(msg) => { + RaidenError::IdempotentParameterMismatch(msg) + } + TransactWriteItemsError::InternalServerError(msg) => { + RaidenError::InternalServerError(msg) + } + TransactWriteItemsError::ProvisionedThroughputExceeded(msg) => { + RaidenError::ProvisionedThroughputExceeded(msg) + } + TransactWriteItemsError::RequestLimitExceeded(msg) => { + RaidenError::RequestLimitExceeded(msg) + } + TransactWriteItemsError::ResourceNotFound(msg) => { + RaidenError::ResourceNotFound(msg) + } + TransactWriteItemsError::TransactionCanceled(msg) => { + let reasons = RaidenTransactionCancellationReasons::from_str(&msg); + RaidenError::TransactionCanceled { reasons } + } + TransactWriteItemsError::TransactionInProgress(msg) => { + RaidenError::TransactionInProgress(msg) + } + }, + _ => into_raiden_error(error), + } + } +} diff --git a/raiden/src/rusoto/mod.rs b/raiden/src/rusoto/mod.rs new file mode 100644 index 00000000..e80fad74 --- /dev/null +++ b/raiden/src/rusoto/mod.rs @@ -0,0 +1,366 @@ +mod errors; +mod ops; + +#[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] +pub use rusoto_dynamodb::*; + +#[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] +pub use rusoto_core::*; + +pub use self::ops::*; +pub use rusoto_credential::*; + +use std::collections::{BTreeSet, HashSet}; + +use crate::{ + AttributeType, AttributeValues, ConversionError, FromAttribute, FromStringSetItem, + IntoAttribute, IntoStringSetItem, RaidenError, +}; + +impl IntoAttribute for AttributeType { + fn into_attr(self) -> AttributeValue { + AttributeValue { + s: Some(self.to_string()), + ..AttributeValue::default() + } + } +} + +impl IntoAttribute for String { + fn into_attr(self) -> AttributeValue { + // Empty String is allowed since 2020/5 + // https://aws.amazon.com/jp/about-aws/whats-new/2020/05/amazon-dynamodb-now-supports-empty-values-for-non-key-string-and-binary-attributes-in-dynamodb-tables/ + AttributeValue { + s: Some(self), + ..AttributeValue::default() + } + } +} + +impl FromAttribute for String { + fn from_attr(value: Option) -> Result { + match value { + // See. https://github.com/raiden-rs/raiden/issues/58 + Some(AttributeValue { null: Some(v), .. }) if v => Ok("".to_owned()), + Some(AttributeValue { s: Some(v), .. }) => Ok(v), + _ => Err(ConversionError::ValueIsNone), + } + } +} + +impl IntoAttribute for &'_ str { + fn into_attr(self) -> AttributeValue { + if self.is_empty() { + // See. https://github.com/raiden-rs/raiden-dynamo/issues/58 + AttributeValue { + null: Some(true), + ..Default::default() + } + } else { + AttributeValue { + s: Some(self.to_owned()), + ..AttributeValue::default() + } + } + } +} + +impl<'a> IntoAttribute for std::borrow::Cow<'a, str> { + fn into_attr(self) -> AttributeValue { + let s = match self { + std::borrow::Cow::Owned(o) => o, + std::borrow::Cow::Borrowed(b) => b.to_owned(), + }; + + if s.is_empty() { + // See. https://github.com/raiden-rs/raiden-dynamo/issues/58 + AttributeValue { + null: Some(true), + ..Default::default() + } + } else { + AttributeValue { + s: Some(s), + ..AttributeValue::default() + } + } + } +} + +impl<'a> FromAttribute for std::borrow::Cow<'a, str> { + fn from_attr(value: Option) -> Result { + match value { + // See. https://github.com/raiden-rs/raiden/issues/58 + Some(AttributeValue { null: Some(v), .. }) if v => { + Ok(std::borrow::Cow::Owned("".to_owned())) + } + Some(AttributeValue { s: Some(v), .. }) => Ok(std::borrow::Cow::Owned(v)), + _ => Err(ConversionError::ValueIsNone), + } + } +} + +macro_rules! default_attr_for_num { + ($to: ty) => { + impl IntoAttribute for $to { + fn into_attr(self) -> AttributeValue { + AttributeValue { + n: Some(format!("{self}")), + ..AttributeValue::default() + } + } + } + + impl FromAttribute for $to { + fn from_attr(value: Option) -> Result { + if let Some(AttributeValue { n: Some(v), .. }) = value { + Ok(v.parse().unwrap()) + } else { + Err(ConversionError::ValueIsNone) + } + } + } + }; +} + +pub(crate) use default_attr_for_num; + +impl IntoAttribute for Option { + fn into_attr(self) -> AttributeValue { + if let Some(value) = self { + value.into_attr() + } else { + AttributeValue { + null: Some(true), + ..Default::default() + } + } + } +} + +impl FromAttribute for Option { + fn from_attr(value: Option) -> Result { + match value { + None => Ok(None), + Some(v) if Some(true) == v.null => Ok(None), + _ => Ok(Some(FromAttribute::from_attr(value)?)), + } + } +} + +impl IntoAttribute for bool { + fn into_attr(self) -> AttributeValue { + AttributeValue { + bool: Some(self), + ..AttributeValue::default() + } + } +} + +impl FromAttribute for bool { + fn from_attr(value: Option) -> Result { + if let Some(AttributeValue { bool: Some(v), .. }) = value { + Ok(v) + } else { + Err(ConversionError::ValueIsNone) + } + } +} + +impl IntoAttribute for Vec { + fn into_attr(mut self) -> AttributeValue { + if self.is_empty() { + // See. https://github.com/raiden-rs/raiden/issues/57 + AttributeValue { + null: Some(true), + ..Default::default() + } + } else { + AttributeValue { + l: Some(self.drain(..).map(|s| s.into_attr()).collect()), + ..AttributeValue::default() + } + } + } +} + +impl FromAttribute for Vec { + fn from_attr(value: Option) -> Result { + match value { + Some(AttributeValue { l: Some(v), .. }) => { + v.into_iter().map(|item| A::from_attr(Some(item))).collect() + } + // See. https://github.com/raiden-rs/raiden/issues/57 + Some(AttributeValue { + null: Some(true), .. + }) + | None => Ok(vec![]), + _ => Err(ConversionError::ValueIsNone), + } + } +} + +macro_rules! default_number_hash_set_convertor { + ($to: ty) => { + impl IntoAttribute for std::collections::HashSet<$to> { + fn into_attr(self) -> AttributeValue { + if self.is_empty() { + // See. https://github.com/raiden-rs/raiden/issues/57 + // https://github.com/raiden-rs/raiden-dynamo/issues/64 + AttributeValue::default() + } else { + AttributeValue { + ns: Some(self.into_iter().map(|s| s.to_string()).collect()), + ..AttributeValue::default() + } + } + } + } + + impl FromAttribute for std::collections::HashSet<$to> { + fn from_attr(value: Option) -> Result { + match value { + Some(AttributeValue { + ns: Some(mut nums), .. + }) => { + let mut results: Vec> = nums + .drain(..) + .map(|ns| ns.parse().map_err(|_| ConversionError::ParseInt)) + .collect(); + + results.drain(..).collect() + } + // See. https://github.com/raiden-rs/raiden/issues/57 + Some(AttributeValue { + null: Some(true), .. + }) + | None => Ok(std::collections::HashSet::new()), + _ => Err(ConversionError::ValueIsNone), + } + } + } + }; +} + +pub(crate) use default_number_hash_set_convertor; + +macro_rules! default_number_btree_set_convertor { + ($to: ty) => { + impl IntoAttribute for std::collections::BTreeSet<$to> { + fn into_attr(self) -> AttributeValue { + if self.is_empty() { + // See. https://github.com/raiden-rs/raiden/issues/57 + // https://github.com/raiden-rs/raiden-dynamo/issues/64 + AttributeValue::default() + } else { + AttributeValue { + ns: Some(self.into_iter().map(|s| s.to_string()).collect()), + ..AttributeValue::default() + } + } + } + } + + impl FromAttribute for std::collections::BTreeSet<$to> { + fn from_attr(value: Option) -> Result { + match value { + Some(AttributeValue { + ns: Some(mut nums), .. + }) => { + let mut results: Vec> = nums + .drain(..) + .map(|ns| ns.parse().map_err(|_| ConversionError::ParseInt)) + .collect(); + + results.drain(..).collect() + } + // See. https://github.com/raiden-rs/raiden/issues/57 + Some(AttributeValue { + null: Some(true), .. + }) + | None => Ok(std::collections::BTreeSet::new()), + _ => Err(ConversionError::ValueIsNone), + } + } + } + }; +} + +pub(crate) use default_number_btree_set_convertor; + +impl IntoAttribute for HashSet { + fn into_attr(self) -> AttributeValue { + if self.is_empty() { + // See. https://github.com/raiden-rs/raiden/issues/57 + // https://github.com/raiden-rs/raiden-dynamo/issues/64 + AttributeValue::default() + } else { + AttributeValue { + ss: Some(self.into_iter().map(|s| s.into_ss_item()).collect()), + ..AttributeValue::default() + } + } + } +} + +impl FromAttribute for HashSet { + fn from_attr(value: Option) -> Result { + match value { + Some(AttributeValue { + ss: Some(mut ss), .. + }) => ss.drain(..).map(A::from_ss_item).collect(), + // See. https://github.com/raiden-rs/raiden/issues/57 + Some(AttributeValue { + null: Some(true), .. + }) + | None => Ok(HashSet::new()), + _ => Err(ConversionError::ValueIsNone), + } + } +} + +impl IntoAttribute for BTreeSet { + fn into_attr(self) -> AttributeValue { + if self.is_empty() { + // See. https://github.com/raiden-rs/raiden/issues/57 + // https://github.com/raiden-rs/raiden-dynamo/issues/64 + AttributeValue::default() + } else { + AttributeValue { + ss: Some(self.into_iter().map(|s| s.into_ss_item()).collect()), + ..AttributeValue::default() + } + } + } +} + +impl FromAttribute for BTreeSet { + fn from_attr(value: Option) -> Result { + match value { + Some(AttributeValue { + ss: Some(mut ss), .. + }) => ss.drain(..).map(A::from_ss_item).collect(), + // See. https://github.com/raiden-rs/raiden/issues/57 + Some(AttributeValue { + null: Some(true), .. + }) + | None => Ok(BTreeSet::new()), + _ => Err(ConversionError::ValueIsNone), + } + } +} + +pub fn is_attr_value_empty(a: &AttributeValue) -> bool { + a == &AttributeValue::default() +} + +pub(crate) fn deserialize_attr_value(s: &str) -> Result { + match serde_json::from_str(s) { + Ok(deserialized) => Ok(deserialized), + Err(_) => Err(super::RaidenError::NextTokenDecodeError), + } +} + +pub(crate) fn serialize_attr_values(value: &AttributeValues) -> String { + serde_json::to_string(value).expect("should serialize") +} diff --git a/raiden/src/rusoto/ops.rs b/raiden/src/rusoto/ops.rs new file mode 100644 index 00000000..1482dc01 --- /dev/null +++ b/raiden/src/rusoto/ops.rs @@ -0,0 +1,117 @@ +pub use transact_write::*; + +mod transact_write { + use crate::{ + DynamoDb, TransactWriteConditionCheckBuilder, TransactWriteDeleteBuilder, + TransactWriteItem, TransactWritePutBuilder, TransactWriteUpdateBuilder, + }; + + pub struct WriteTx { + items: Vec, + client: crate::DynamoDbClient, + retry_condition: crate::RetryCondition, + } + + impl WriteTx { + pub fn new(region: crate::Region) -> Self { + Self { + items: vec![], + client: crate::DynamoDbClient::new(region), + retry_condition: crate::RetryCondition::new(), + } + } + + pub fn new_with_client(client: crate::Client, region: crate::Region) -> Self { + Self { + items: vec![], + client: crate::DynamoDbClient::new_with_client(client, region), + retry_condition: crate::RetryCondition::new(), + } + } + + pub fn with_retries( + mut self, + s: Box, + ) -> Self { + self.retry_condition.strategy = s; + self + } + + pub fn put(mut self, builder: impl TransactWritePutBuilder) -> Self { + self.items.push(TransactWriteItem { + condition_check: None, + delete: None, + update: None, + put: Some(builder.build()), + }); + self + } + + pub fn update(mut self, builder: impl TransactWriteUpdateBuilder) -> Self { + self.items.push(TransactWriteItem { + condition_check: None, + delete: None, + update: Some(builder.build()), + put: None, + }); + self + } + + pub fn delete(mut self, builder: impl TransactWriteDeleteBuilder) -> Self { + self.items.push(TransactWriteItem { + condition_check: None, + delete: Some(builder.build()), + update: None, + put: None, + }); + self + } + + pub fn condition_check(mut self, builder: impl TransactWriteConditionCheckBuilder) -> Self { + self.items.push(TransactWriteItem { + condition_check: Some(builder.build()), + delete: None, + update: None, + put: None, + }); + self + } + + pub async fn run(self) -> Result<(), crate::RaidenError> { + let policy: crate::RetryPolicy = self.retry_condition.strategy.policy().into(); + let client = self.client; + let input = crate::TransactWriteItemsInput { + client_request_token: None, + return_consumed_capacity: None, + return_item_collection_metrics: None, + transact_items: self.items, + }; + + policy + .retry_if( + move || { + let client = client.clone(); + let input = input.clone(); + async { WriteTx::inner_run(client, input).await } + }, + &self.retry_condition, + ) + .await + } + + #[cfg_attr(feature = "tracing", tracing::instrument( + level = tracing::Level::DEBUG, + name = "dynamodb::action", + skip_all, + fields(api = "transact_write_items") + ))] + async fn inner_run( + client: crate::DynamoDbClient, + input: crate::TransactWriteItemsInput, + ) -> Result<(), crate::RaidenError> { + let _res = client.transact_write_items(input).await?; + // TODO: ADD Resp + Ok(()) + } + } +} diff --git a/raiden/src/update_expression/add.rs b/raiden/src/update_expression/add.rs index a2320ca5..21c4dd14 100644 --- a/raiden/src/update_expression/add.rs +++ b/raiden/src/update_expression/add.rs @@ -33,7 +33,12 @@ impl UpdateAddExpressionBuilder for AddExpressionFilled< // See. https://github.com/raiden-rs/raiden/issues/57 // https://github.com/raiden-rs/raiden/issues/58 - if value.null.is_some() || value == AttributeValue::default() { + #[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] + let is_null = value.null.is_some(); + #[cfg(feature = "aws-sdk")] + let is_null = value.is_null(); + + if is_null || crate::is_attr_value_empty(&value) { return ("".to_owned(), names, values); } @@ -74,7 +79,7 @@ mod tests { let mut expected_values = std::collections::HashMap::new(); expected_names.insert("#age".to_owned(), "age".to_owned()); expected_values.insert(":value0".to_owned(), 42.into_attr()); - assert_eq!(expression, "#age :value0".to_owned(),); + assert_eq!(expression, "#age :value0".to_owned()); assert_eq!(names, expected_names); assert_eq!(values, expected_values); } diff --git a/raiden/src/update_expression/delete.rs b/raiden/src/update_expression/delete.rs index 3531f8df..4ce14018 100644 --- a/raiden/src/update_expression/delete.rs +++ b/raiden/src/update_expression/delete.rs @@ -33,7 +33,12 @@ impl UpdateDeleteExpressionBuilder for DeleteExpressionF // See. https://github.com/raiden-rs/raiden/issues/57 // https://github.com/raiden-rs/raiden/issues/58 - if value.null.is_some() || value == AttributeValue::default() { + #[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] + let is_null = value.null.is_some(); + #[cfg(feature = "aws-sdk")] + let is_null = value.is_null(); + + if is_null || crate::is_attr_value_empty(&value) { return ("".to_owned(), names, values); } diff --git a/raiden/src/update_expression/set.rs b/raiden/src/update_expression/set.rs index 3fbda230..89f8d606 100644 --- a/raiden/src/update_expression/set.rs +++ b/raiden/src/update_expression/set.rs @@ -137,7 +137,7 @@ impl UpdateSetExpressionBuilder for SetExpressionFilledW SetValue::Value(placeholder, value) => { // See. https://github.com/raiden-rs/raiden/issues/57 // https://github.com/raiden-rs/raiden/issues/58 - if value == AttributeValue::default() { + if crate::is_attr_value_empty(&value) { // Use remove instead of set return SetOrRemove::Remove(attr_name, names); } @@ -229,7 +229,7 @@ mod tests { let mut expected_values = std::collections::HashMap::new(); expected_names.insert("#name".to_owned(), "name".to_owned()); expected_values.insert(":value0".to_owned(), "updated!!".into_attr()); - assert_eq!(expression, "#name = :value0".to_owned(),); + assert_eq!(expression, "#name = :value0".to_owned()); assert_eq!(names, expected_names); assert_eq!(values, expected_values); return; @@ -272,7 +272,7 @@ mod tests { let mut expected_values = std::collections::HashMap::new(); expected_names.insert("#age".to_owned(), "age".to_owned()); expected_values.insert(":value0".to_owned(), 10.into_attr()); - assert_eq!(expression, "#age = #age + :value0".to_owned(),); + assert_eq!(expression, "#age = #age + :value0".to_owned()); assert_eq!(names, expected_names); assert_eq!(values, expected_values); return; diff --git a/raiden/tests/all/batch_delete.rs b/raiden/tests/all/batch_delete.rs index d6b06892..8293cced 100644 --- a/raiden/tests/all/batch_delete.rs +++ b/raiden/tests/all/batch_delete.rs @@ -12,80 +12,52 @@ mod partition_key_tests { name: String, } - #[test] - fn test_batch_delete_item() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = BatchDeleteTest0::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - - let res: batch_delete::BatchDeleteOutput = client - .batch_delete(vec!["id0", "id1", "id2"]) - .run() - .await - .unwrap(); - assert_eq!( - res, - batch_delete::BatchDeleteOutput { - consumed_capacity: None, - unprocessed_items: vec![], - } - ); - } - rt.block_on(example()); + #[tokio::test] + async fn test_batch_delete_item() { + let client = crate::all::create_client_from_struct!(BatchDeleteTest0); + let res: batch_delete::BatchDeleteOutput = client + .batch_delete(vec!["id0", "id1", "id2"]) + .run() + .await + .unwrap(); + + assert_eq!( + res, + batch_delete::BatchDeleteOutput { + consumed_capacity: None, + unprocessed_items: vec![], + } + ); } - #[test] - fn test_batch_delete_item_for_stored_and_unstored_keys() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = BatchDeleteTest0::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - - let res = client.batch_delete(vec!["id3", "unstored"]).run().await; - assert!(res.is_ok()); - } - rt.block_on(example()); + #[tokio::test] + async fn test_batch_delete_item_for_stored_and_unstored_keys() { + let client = crate::all::create_client_from_struct!(BatchDeleteTest0); + let res = client.batch_delete(vec!["id3", "unstored"]).run().await; + + assert!(res.is_ok()); } - #[test] - fn test_batch_delete_item_for_unstored_keys() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = BatchDeleteTest0::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - - let res = client - .batch_delete(vec!["unstored0", "unstored1", "unstored2"]) - .run() - .await; - assert!(res.is_ok()); - } - rt.block_on(example()); + #[tokio::test] + async fn test_batch_delete_item_for_unstored_keys() { + let client = crate::all::create_client_from_struct!(BatchDeleteTest0); + let res = client + .batch_delete(vec!["unstored0", "unstored1", "unstored2"]) + .run() + .await; + + assert!(res.is_ok()); } - #[test] - fn test_batch_delete_over_25_items() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = BatchDeleteTest0::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - - let res = client - .batch_delete((4..=100).map(|i| format!("id{i}")).collect()) - .run() - .await; - assert!(res.is_ok()); - } - rt.block_on(example()); + #[tokio::test] + async fn test_batch_delete_over_25_items() { + let client = crate::all::create_client_from_struct!(BatchDeleteTest0); + let res = client + .batch_delete((4..=100).map(|i| format!("id{i}")).collect()) + .run() + .await; + + assert!(res.is_ok()); } } @@ -103,87 +75,59 @@ mod partition_key_and_sort_key_tests { year: usize, } - #[test] - fn test_batch_delete_item_with_sort_key() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = BatchDeleteTest1::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - - let res = client - .batch_delete(vec![ - ("id0", 1999_usize), - ("id1", 2000_usize), - ("id2", 2001_usize), - ]) - .run() - .await; - assert!(res.is_ok()); - } - rt.block_on(example()); + #[tokio::test] + async fn test_batch_delete_item_with_sort_key() { + let client = crate::all::create_client_from_struct!(BatchDeleteTest1); + let res = client + .batch_delete(vec![ + ("id0", 1999_usize), + ("id1", 2000_usize), + ("id2", 2001_usize), + ]) + .run() + .await; + + assert!(res.is_ok()); } - #[test] - fn test_batch_delete_item_with_sort_key_for_stored_and_unstored_keys() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = BatchDeleteTest1::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - - let res = client - .batch_delete(vec![("id3", 2002_usize), ("unstored", 2000_usize)]) - .run() - .await; - assert!(res.is_ok()); - } - rt.block_on(example()); + #[tokio::test] + async fn test_batch_delete_item_with_sort_key_for_stored_and_unstored_keys() { + let client = crate::all::create_client_from_struct!(BatchDeleteTest1); + let res = client + .batch_delete(vec![("id3", 2002_usize), ("unstored", 2000_usize)]) + .run() + .await; + + assert!(res.is_ok()); } - #[test] - fn test_batch_delete_item_with_sort_key_for_unstored_keys() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = BatchDeleteTest1::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - - let res = client - .batch_delete(vec![ - ("unstored0", 1999_usize), - ("unstore1", 2000_usize), - ("unstored2", 2001_usize), - ]) - .run() - .await; - assert!(res.is_ok()); - } - rt.block_on(example()); + #[tokio::test] + async fn test_batch_delete_item_with_sort_key_for_unstored_keys() { + let client = crate::all::create_client_from_struct!(BatchDeleteTest1); + let res = client + .batch_delete(vec![ + ("unstored0", 1999_usize), + ("unstore1", 2000_usize), + ("unstored2", 2001_usize), + ]) + .run() + .await; + + assert!(res.is_ok()); } - #[test] - fn test_batch_delete_with_sort_key_over_25_items() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = BatchDeleteTest1::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - - let res = client - .batch_delete( - (4..=100) - .map(|i| (format!("id{i}"), 1999_usize + i)) - .collect(), - ) - .run() - .await; - assert!(res.is_ok()); - } - rt.block_on(example()); + #[tokio::test] + async fn test_batch_delete_with_sort_key_over_25_items() { + let client = crate::all::create_client_from_struct!(BatchDeleteTest1); + let res = client + .batch_delete( + (4..=100) + .map(|i| (format!("id{i}"), 1999_usize + i)) + .collect(), + ) + .run() + .await; + + assert!(res.is_ok()); } } diff --git a/raiden/tests/all/batch_get.rs b/raiden/tests/all/batch_get.rs index 0a18952c..7fb005ec 100644 --- a/raiden/tests/all/batch_get.rs +++ b/raiden/tests/all/batch_get.rs @@ -23,122 +23,83 @@ mod tests { batch_get::BatchGetOutput { ..output } } - #[test] - fn test_batch_get_item() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = BatchTest0::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - - let res: batch_get::BatchGetOutput = client - .batch_get(vec!["id0", "id1", "id2"]) - .run() - .await - .unwrap(); - assert_eq!( - sort_by_id_0(res), - batch_get::BatchGetOutput { - items: vec![ - BatchTest0 { - id: "id0".to_owned(), - name: "bob".to_owned(), - }, - BatchTest0 { - id: "id1".to_owned(), - name: "bob".to_owned(), - }, - BatchTest0 { - id: "id2".to_owned(), - name: "bob".to_owned(), - }, - ], - consumed_capacity: None, - unprocessed_keys: Some(KeysAndAttributes { - attributes_to_get: None, - consistent_read: None, - expression_attribute_names: None, - keys: vec![], - projection_expression: None, - }), - } - ); - } - rt.block_on(example()); + #[tokio::test] + async fn test_batch_get_item() { + let client = crate::all::create_client_from_struct!(BatchTest0); + let res: batch_get::BatchGetOutput = client + .batch_get(vec!["id0", "id1", "id2"]) + .run() + .await + .unwrap(); + + assert_eq!( + sort_by_id_0(res), + batch_get::BatchGetOutput { + items: vec![ + BatchTest0 { + id: "id0".to_owned(), + name: "bob".to_owned(), + }, + BatchTest0 { + id: "id1".to_owned(), + name: "bob".to_owned(), + }, + BatchTest0 { + id: "id2".to_owned(), + name: "bob".to_owned(), + }, + ], + consumed_capacity: None, + unprocessed_keys: Some(crate::all::default_key_and_attributes()), + } + ); } - #[test] - fn test_batch_get_item_extended() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = BatchTest0::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - - let keys: Vec = (0..101).into_iter().map(|n| format!("id{n}")).collect(); - let expected_items = (0..101) - .map(|n| BatchTest0 { - id: format!("id{n}"), - name: "bob".to_owned(), - }) - .collect(); - let res: batch_get::BatchGetOutput = - client.batch_get(keys).run().await.unwrap(); - assert_eq!( - sort_by_id_0(res), - batch_get::BatchGetOutput { - items: expected_items, - consumed_capacity: None, - unprocessed_keys: Some(KeysAndAttributes { - attributes_to_get: None, - consistent_read: None, - expression_attribute_names: None, - keys: vec![], - projection_expression: None, - }), - } - ); - } - rt.block_on(example()); + #[tokio::test] + async fn test_batch_get_item_extended() { + let client = crate::all::create_client_from_struct!(BatchTest0); + let keys: Vec = (0..101).map(|n| format!("id{n}")).collect(); + let expected_items = (0..101) + .map(|n| BatchTest0 { + id: format!("id{n}"), + name: "bob".to_owned(), + }) + .collect(); + let res: batch_get::BatchGetOutput = + client.batch_get(keys).run().await.unwrap(); + + assert_eq!( + sort_by_id_0(res), + batch_get::BatchGetOutput { + items: expected_items, + consumed_capacity: None, + unprocessed_keys: Some(crate::all::default_key_and_attributes()), + } + ); } // NOTE: Same behavior with original SDK, but we're planning to improve this. // ref. https://github.com/raiden-rs/raiden/issues/44 - #[test] - fn test_batch_get_item_partial_missing() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = BatchTest0::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - - let res: batch_get::BatchGetOutput = client - .batch_get(vec!["id100", "id101", "id102"]) - .run() - .await - .unwrap(); - assert_eq!( - sort_by_id_0(res), - batch_get::BatchGetOutput { - items: vec![BatchTest0 { - id: "id100".to_owned(), - name: "bob".to_owned(), - },], - consumed_capacity: None, - unprocessed_keys: Some(KeysAndAttributes { - attributes_to_get: None, - consistent_read: None, - expression_attribute_names: None, - keys: vec![], - projection_expression: None, - }), - } - ); - } - rt.block_on(example()); + #[tokio::test] + async fn test_batch_get_item_partial_missing() { + let client = crate::all::create_client_from_struct!(BatchTest0); + let res: batch_get::BatchGetOutput = client + .batch_get(vec!["id100", "id101", "id102"]) + .run() + .await + .unwrap(); + + assert_eq!( + sort_by_id_0(res), + batch_get::BatchGetOutput { + items: vec![BatchTest0 { + id: "id100".to_owned(), + name: "bob".to_owned(), + },], + consumed_capacity: None, + unprocessed_keys: Some(crate::all::default_key_and_attributes()), + } + ); } #[derive(Raiden, Debug, PartialEq)] @@ -162,45 +123,31 @@ mod tests { batch_get::BatchGetOutput { ..output } } - #[test] - fn test_batch_get_item_sort_key() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = BatchTest1::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - - let keys: Vec<(String, usize)> = (0..250) - .into_iter() - .map(|n| (format!("id{n}"), (2000 + n) as usize)) - .collect(); - let expected_items = (0..250) - .map(|n| BatchTest1 { - id: format!("id{n}"), - name: "bob".to_owned(), - year: (2000 + n), - num: n, - }) - .collect(); - let res: batch_get::BatchGetOutput = - client.batch_get(keys).run().await.unwrap(); - assert_eq!( - sort_by_id_1(res), - batch_get::BatchGetOutput { - items: expected_items, - consumed_capacity: None, - unprocessed_keys: Some(KeysAndAttributes { - attributes_to_get: None, - consistent_read: None, - expression_attribute_names: None, - keys: vec![], - projection_expression: None, - }), - } - ); - } - rt.block_on(example()); + #[tokio::test] + async fn test_batch_get_item_sort_key() { + let client = crate::all::create_client_from_struct!(BatchTest1); + let keys: Vec<(String, usize)> = (0..250) + .map(|n| (format!("id{n}"), (2000 + n) as usize)) + .collect(); + let expected_items = (0..250) + .map(|n| BatchTest1 { + id: format!("id{n}"), + name: "bob".to_owned(), + year: (2000 + n), + num: n, + }) + .collect(); + let res: batch_get::BatchGetOutput = + client.batch_get(keys).run().await.unwrap(); + + assert_eq!( + sort_by_id_1(res), + batch_get::BatchGetOutput { + items: expected_items, + consumed_capacity: None, + unprocessed_keys: Some(crate::all::default_key_and_attributes()), + } + ); } #[derive(Raiden, Debug, PartialEq)] @@ -213,87 +160,58 @@ mod tests { year: usize, } - #[test] - fn test_batch_get_item_for_projection_expression() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = BatchTest1a::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - - let keys: Vec<(String, usize)> = (0..250) - .into_iter() - .map(|n| (format!("id{n}"), (2000 + n) as usize)) - .collect(); - let expected_items = (0..250) - .map(|n| BatchTest1a { - id: format!("id{n}"), - name: "bob".to_owned(), - year: 2000 + n as usize, - }) - .collect(); - - let mut res: batch_get::BatchGetOutput = - client.batch_get(keys).run().await.unwrap(); - res.items.sort_by_key(|i| { - let mut id = i.id.to_string(); - id.replace_range(0..2, ""); - id.parse::().unwrap() - }); + #[tokio::test] + async fn test_batch_get_item_for_projection_expression() { + let client = crate::all::create_client_from_struct!(BatchTest1a); + let keys: Vec<(String, usize)> = (0..250) + .map(|n| (format!("id{n}"), (2000 + n) as usize)) + .collect(); + let expected_items = (0..250) + .map(|n| BatchTest1a { + id: format!("id{n}"), + name: "bob".to_owned(), + year: 2000 + n as usize, + }) + .collect(); + let mut res: batch_get::BatchGetOutput = + client.batch_get(keys).run().await.unwrap(); + res.items.sort_by_key(|i| { + let mut id = i.id.to_string(); + id.replace_range(0..2, ""); + id.parse::().unwrap() + }); - assert_eq!( - res, - batch_get::BatchGetOutput { - items: expected_items, - consumed_capacity: None, - unprocessed_keys: Some(KeysAndAttributes { - attributes_to_get: None, - consistent_read: None, - expression_attribute_names: None, - keys: vec![], - projection_expression: None, - }), - } - ); - } - rt.block_on(example()); + assert_eq!( + res, + batch_get::BatchGetOutput { + items: expected_items, + consumed_capacity: None, + unprocessed_keys: Some(crate::all::default_key_and_attributes()), + } + ); } - #[test] - fn test_batch_get_item_missing_all() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = BatchTest1::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - - let res: batch_get::BatchGetOutput = client - .batch_get(vec![ - ("id300", 2300_usize), - ("id301", 2301_usize), - ("id302", 2302_usize), - ]) - .run() - .await - .unwrap(); - assert_eq!( - sort_by_id_1(res), - batch_get::BatchGetOutput { - items: vec![], - consumed_capacity: None, - unprocessed_keys: Some(KeysAndAttributes { - attributes_to_get: None, - consistent_read: None, - expression_attribute_names: None, - keys: vec![], - projection_expression: None, - }), - } - ); - } - rt.block_on(example()); + #[tokio::test] + async fn test_batch_get_item_missing_all() { + let client = crate::all::create_client_from_struct!(BatchTest1); + let res: batch_get::BatchGetOutput = client + .batch_get(vec![ + ("id300", 2300_usize), + ("id301", 2301_usize), + ("id302", 2302_usize), + ]) + .run() + .await + .unwrap(); + + assert_eq!( + sort_by_id_1(res), + batch_get::BatchGetOutput { + items: vec![], + consumed_capacity: None, + unprocessed_keys: Some(crate::all::default_key_and_attributes()), + } + ); } #[derive(Raiden, Debug, Clone, PartialEq)] @@ -302,28 +220,21 @@ mod tests { id: String, name: String, } - #[test] - fn test_batch_get_item_with_16mb_limitation() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = BatchTest2::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let res: batch_get::BatchGetOutput = client - .batch_get(vec![ - "id0", "id1", "id2", "id3", "id4", "id5", "id6", "id7", "id8", "id9", "id10", - "id11", "id12", "id13", "id14", "id15", "id16", "id17", "id18", "id19", "id20", - "id21", "id22", "id23", "id24", "id25", "id26", "id27", "id28", "id29", "id30", - "id31", "id32", "id33", "id34", "id35", "id36", "id37", "id38", "id39", "id40", - "id41", "id42", "id43", "id44", "id45", "id46", "id47", "id48", "id49", "id50", - ]) - .run() - .await - .unwrap(); - assert_eq!(res.items.len(), 51); - } - rt.block_on(example()); + #[tokio::test] + async fn test_batch_get_item_with_16mb_limitation() { + let client = crate::all::create_client_from_struct!(BatchTest2); + let res: batch_get::BatchGetOutput = client + .batch_get(vec![ + "id0", "id1", "id2", "id3", "id4", "id5", "id6", "id7", "id8", "id9", "id10", + "id11", "id12", "id13", "id14", "id15", "id16", "id17", "id18", "id19", "id20", + "id21", "id22", "id23", "id24", "id25", "id26", "id27", "id28", "id29", "id30", + "id31", "id32", "id33", "id34", "id35", "id36", "id37", "id38", "id39", "id40", + "id41", "id42", "id43", "id44", "id45", "id46", "id47", "id48", "id49", "id50", + ]) + .run() + .await + .unwrap(); + assert_eq!(res.items.len(), 51); } } diff --git a/raiden/tests/all/condition.rs b/raiden/tests/all/condition.rs index d945b24d..caef5c42 100644 --- a/raiden/tests/all/condition.rs +++ b/raiden/tests/all/condition.rs @@ -23,7 +23,7 @@ mod tests { let mut expected_names: std::collections::HashMap = std::collections::HashMap::new(); expected_names.insert("#name".to_owned(), "name".to_owned()); - assert_eq!(condition_expression, "attribute_exists(#name)".to_owned(),); + assert_eq!(condition_expression, "attribute_exists(#name)".to_owned()); assert_eq!(attribute_names, expected_names); } @@ -80,13 +80,8 @@ mod tests { std::collections::HashMap::new(); expected_names.insert("#id".to_owned(), "id".to_owned()); let mut expected_values: raiden::AttributeValues = std::collections::HashMap::new(); - expected_values.insert( - ":typeS".to_owned(), - raiden::AttributeValue { - s: Some("S".to_string()), - ..raiden::AttributeValue::default() - }, - ); + let attr_value: raiden::AttributeValue = "S".into_attr(); + expected_values.insert(":typeS".to_owned(), attr_value); assert_eq!( condition_expression, @@ -103,12 +98,10 @@ mod tests { let mut expected_names: raiden::AttributeNames = std::collections::HashMap::new(); expected_names.insert("#name".to_owned(), "name".to_owned()); let mut expected_values: raiden::AttributeValues = std::collections::HashMap::new(); + let attr_value: raiden::AttributeValue = "boku".into_attr(); expected_values.insert( ":begins_with_17d8e2e8233d9a6ae428061cb2cdf226".to_owned(), - raiden::AttributeValue { - s: Some("boku".to_string()), - ..raiden::AttributeValue::default() - }, + attr_value, ); assert_eq!( @@ -202,7 +195,7 @@ mod tests { let mut expected_names: std::collections::HashMap = std::collections::HashMap::new(); expected_names.insert("#name".to_owned(), "name".to_owned()); - assert_eq!(condition_expression, "#name = #name".to_owned(),); + assert_eq!(condition_expression, "#name = #name".to_owned()); assert_eq!(attribute_names, expected_names); } @@ -215,14 +208,10 @@ mod tests { std::collections::HashMap::new(); expected_names.insert("#name".to_owned(), "name".to_owned()); let mut expected_values: raiden::AttributeValues = std::collections::HashMap::new(); - expected_values.insert( - ":value0".to_owned(), - raiden::AttributeValue { - s: Some("bokuweb".to_string()), - ..raiden::AttributeValue::default() - }, - ); - assert_eq!(condition_expression, ":value0 = #name".to_owned(),); + let attr_value: raiden::AttributeValue = "bokuweb".into_attr(); + expected_values.insert(":value0".to_owned(), attr_value); + + assert_eq!(condition_expression, ":value0 = #name".to_owned()); assert_eq!(attribute_names, expected_names); assert_eq!(attribute_values, expected_values); } diff --git a/raiden/tests/all/delete.rs b/raiden/tests/all/delete.rs index bc3c46e7..9054adcc 100644 --- a/raiden/tests/all/delete.rs +++ b/raiden/tests/all/delete.rs @@ -14,53 +14,32 @@ mod tests { removable: bool, } - #[test] - fn test_delete_item() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = DeleteTest0::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); + #[tokio::test] + async fn test_delete_item() { + let client = crate::all::create_client_from_struct!(DeleteTest0); + let res = client.delete("id0").run().await; - let res = client.delete("id0").run().await; - assert_eq!(res.is_ok(), true); - } - rt.block_on(example()); + assert_eq!(res.is_ok(), true); } - #[test] - fn test_delete_item_with_unstored_key() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = DeleteTest0::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); + #[tokio::test] + async fn test_delete_item_with_unstored_key() { + let client = crate::all::create_client_from_struct!(DeleteTest0); + let res = client.delete("unstored").run().await; - let res = client.delete("unstored").run().await; - assert_eq!(res.is_ok(), true); - } - rt.block_on(example()); + assert_eq!(res.is_ok(), true); } - #[test] - fn test_delete_item_with_condition() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = DeleteTest0::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let cond = DeleteTest0::condition() - .attr(DeleteTest0::removable()) - .eq_value(true); - let res = client.delete("id0").condition(cond.clone()).run().await; - assert_eq!(res.is_ok(), false); - let res = client.delete("id1").condition(cond).run().await; - assert_eq!(res.is_ok(), true); - } - rt.block_on(example()); + #[tokio::test] + async fn test_delete_item_with_condition() { + let client = crate::all::create_client_from_struct!(DeleteTest0); + let cond = DeleteTest0::condition() + .attr(DeleteTest0::removable()) + .eq_value(true); + let res = client.delete("id0").condition(cond.clone()).run().await; + assert_eq!(res.is_ok(), false); + let res = client.delete("id1").condition(cond).run().await; + assert_eq!(res.is_ok(), true); } #[allow(dead_code)] @@ -73,18 +52,11 @@ mod tests { year: usize, } - #[test] - fn test_delete_item_with_sort_key() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = DeleteTest1::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); + #[tokio::test] + async fn test_delete_item_with_sort_key() { + let client = crate::all::create_client_from_struct!(DeleteTest1); + let res = client.delete("id0", 1999_usize).run().await; - let res = client.delete("id0", 1999_usize).run().await; - assert_eq!(res.is_ok(), true); - } - rt.block_on(example()); + assert_eq!(res.is_ok(), true); } } diff --git a/raiden/tests/all/filter_expression.rs b/raiden/tests/all/filter_expression.rs index 3f11a290..3cb1dc75 100644 --- a/raiden/tests/all/filter_expression.rs +++ b/raiden/tests/all/filter_expression.rs @@ -30,7 +30,7 @@ mod tests { let mut expected_values: std::collections::HashMap = std::collections::HashMap::new(); expected_values.insert(":value0".to_owned(), "bokuweb".into_attr()); - assert_eq!(filter_expression, "#name = :value0".to_owned(),); + assert_eq!(filter_expression, "#name = :value0".to_owned()); assert_eq!(attribute_names, expected_names); assert_eq!(attribute_values, expected_values); } @@ -46,7 +46,7 @@ mod tests { let mut expected_values: std::collections::HashMap = std::collections::HashMap::new(); expected_values.insert(":value0".to_owned(), 7.into_attr()); - assert_eq!(filter_expression, "size(#name) = :value0".to_owned(),); + assert_eq!(filter_expression, "size(#name) = :value0".to_owned()); assert_eq!(attribute_names, expected_names); assert_eq!(attribute_values, expected_values); } @@ -63,7 +63,7 @@ mod tests { std::collections::HashMap::new(); expected_values.insert(":value0".to_owned(), "raiden".into_attr()); - assert_eq!(filter_expression, "#name <> :value0".to_owned(),); + assert_eq!(filter_expression, "#name <> :value0".to_owned()); assert_eq!(attribute_names, expected_names); assert_eq!(attribute_values, expected_values); } @@ -158,7 +158,7 @@ mod tests { let mut expected_values: std::collections::HashMap = std::collections::HashMap::new(); expected_values.insert(":value0".to_owned(), "bokuweb".into_attr()); - assert_eq!(filter_expression, "begins_with(#name, :value0)".to_owned(),); + assert_eq!(filter_expression, "begins_with(#name, :value0)".to_owned()); assert_eq!(attribute_names, expected_names); assert_eq!(attribute_values, expected_values); } @@ -188,7 +188,7 @@ mod tests { expected_names.insert("#name".to_owned(), "name".to_owned()); let expected_values: std::collections::HashMap = std::collections::HashMap::new(); - assert_eq!(filter_expression, "attribute_exists(#name)".to_owned(),); + assert_eq!(filter_expression, "attribute_exists(#name)".to_owned()); assert_eq!(attribute_names, expected_names); assert_eq!(attribute_values, expected_values); } @@ -204,7 +204,7 @@ mod tests { expected_names.insert("#name".to_owned(), "name".to_owned()); let expected_values: std::collections::HashMap = std::collections::HashMap::new(); - assert_eq!(filter_expression, "attribute_not_exists(#name)".to_owned(),); + assert_eq!(filter_expression, "attribute_not_exists(#name)".to_owned()); assert_eq!(attribute_names, expected_names); assert_eq!(attribute_values, expected_values); } @@ -241,7 +241,7 @@ mod tests { let mut expected_values: std::collections::HashMap = std::collections::HashMap::new(); expected_values.insert(":value0".to_owned(), "boku".into_attr()); - assert_eq!(filter_expression, "contains(#name, :value0)".to_owned(),); + assert_eq!(filter_expression, "contains(#name, :value0)".to_owned()); assert_eq!(attribute_names, expected_names); assert_eq!(attribute_values, expected_values); } diff --git a/raiden/tests/all/get.rs b/raiden/tests/all/get.rs index 10e6ec34..54b2210c 100644 --- a/raiden/tests/all/get.rs +++ b/raiden/tests/all/get.rs @@ -19,80 +19,62 @@ mod tests { option_i16: Option, } - #[test] - fn test_user_get_item() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = User::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - - let res = client.get("user_primary_key").run().await; - assert_eq!( - res.unwrap(), - get::GetOutput { - item: User { - id: "user_primary_key".to_owned(), - name: "bokuweb".to_owned(), - num_usize: 42, - num_u8: 255, - num_i8: -127, - option_u16: None, - option_i16: Some(-1), - }, - consumed_capacity: None, - } - ); - } - rt.block_on(example()); - } - - #[test] - fn test_user_get_item_with_consistent_read() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = UserClient::new(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let res = client.get("user_primary_key").consistent().run().await; - assert_eq!( - res.unwrap(), - get::GetOutput { - item: User { - id: "user_primary_key".to_owned(), - name: "bokuweb".to_owned(), - num_usize: 42, - num_u8: 255, - num_i8: -127, - option_u16: None, - option_i16: Some(-1), - }, - consumed_capacity: None, - } - ); - } - rt.block_on(example()); - } - - #[test] - fn test_user_get_item_with_not_found_error() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = UserClient::new(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let res = client.get("not_exist_key").consistent().run().await; - assert_eq!( - res, - Err(RaidenError::ResourceNotFound( - "resource not found".to_owned() - )), - ); + #[tokio::test] + async fn test_user_get_item() { + let client = crate::all::create_client_from_struct!(User); + let res = client.get("user_primary_key").run().await; + + assert_eq!( + res.unwrap(), + get::GetOutput { + item: User { + id: "user_primary_key".to_owned(), + name: "bokuweb".to_owned(), + num_usize: 42, + num_u8: 255, + num_i8: -127, + option_u16: None, + option_i16: Some(-1), + }, + consumed_capacity: None, + } + ); + } + + #[tokio::test] + async fn test_user_get_item_with_consistent_read() { + let client = crate::all::create_client!(UserClient); + let res = client.get("user_primary_key").consistent().run().await; + + assert_eq!( + res.unwrap(), + get::GetOutput { + item: User { + id: "user_primary_key".to_owned(), + name: "bokuweb".to_owned(), + num_usize: 42, + num_u8: 255, + num_i8: -127, + option_u16: None, + option_i16: Some(-1), + }, + consumed_capacity: None, + } + ); + } + + #[tokio::test] + async fn test_user_get_item_with_not_found_error() { + let client = crate::all::create_client!(UserClient); + let res = client.get("not_exist_key").consistent().run().await; + + assert!(res.is_err()); + + if let RaidenError::ResourceNotFound(msg) = res.unwrap_err() { + assert_eq!("resource not found", msg); + } else { + panic!("err should be RaidenError::ResourceNotFound"); } - rt.block_on(example()); } #[derive(Raiden)] @@ -105,26 +87,18 @@ mod tests { unstored: usize, } - #[test] - fn test_get_unstored_value() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = UserWithUnStored::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let res = client.get("user_primary_key").consistent().run().await; - assert_eq!( - res, - // Err(RaidenError::AttributeValueNotFoundError { - // attr_name: "unstored".to_owned(), - // }), - Err(RaidenError::AttributeConvertError { - attr_name: "unstored".to_owned(), - }), - ); + #[tokio::test] + async fn test_get_unstored_value() { + let client = crate::all::create_client_from_struct!(UserWithUnStored); + let res = client.get("user_primary_key").consistent().run().await; + + assert!(res.is_err()); + + if let RaidenError::AttributeConvertError { attr_name } = res.unwrap_err() { + assert_eq!("unstored", attr_name); + } else { + panic!("err should be RaidenError::AttributeConvertError"); } - rt.block_on(example()); } #[derive(Raiden)] @@ -137,28 +111,22 @@ mod tests { empty_set: std::collections::HashSet, } - #[test] - fn test_get_empty_hashset() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = UserWithEmptyHashSet::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let res = client.get("user_primary_key").consistent().run().await; - assert_eq!( - res.unwrap(), - get::GetOutput { - item: UserWithEmptyHashSet { - id: "user_primary_key".to_owned(), - name: "bokuweb".to_owned(), - empty_set: std::collections::HashSet::new(), - }, - consumed_capacity: None, - } - ); - } - rt.block_on(example()); + #[tokio::test] + async fn test_get_empty_hashset() { + let client = crate::all::create_client_from_struct!(UserWithEmptyHashSet); + let res = client.get("user_primary_key").consistent().run().await; + + assert_eq!( + res.unwrap(), + get::GetOutput { + item: UserWithEmptyHashSet { + id: "user_primary_key".to_owned(), + name: "bokuweb".to_owned(), + empty_set: std::collections::HashSet::new(), + }, + consumed_capacity: None, + } + ); } #[derive(Raiden)] @@ -171,28 +139,22 @@ mod tests { empty_vec: Vec, } - #[test] - fn test_get_empty_vec() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = UserWithEmptyVec::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let res = client.get("user_primary_key").consistent().run().await; - assert_eq!( - res.unwrap(), - get::GetOutput { - item: UserWithEmptyVec { - id: "user_primary_key".to_owned(), - name: "bokuweb".to_owned(), - empty_vec: vec![], - }, - consumed_capacity: None, - } - ); - } - rt.block_on(example()); + #[tokio::test] + async fn test_get_empty_vec() { + let client = crate::all::create_client_from_struct!(UserWithEmptyVec); + let res = client.get("user_primary_key").consistent().run().await; + + assert_eq!( + res.unwrap(), + get::GetOutput { + item: UserWithEmptyVec { + id: "user_primary_key".to_owned(), + name: "bokuweb".to_owned(), + empty_vec: vec![], + }, + consumed_capacity: None, + } + ); } #[derive(Raiden)] @@ -205,30 +167,24 @@ mod tests { string_set: std::collections::HashSet, } - #[test] - fn test_get_stringset() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = UserWithStringSet::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let res = client.get("user_primary_key").consistent().run().await; - let mut set = std::collections::HashSet::new(); - set.insert("Hello".to_owned()); - assert_eq!( - res.unwrap(), - get::GetOutput { - item: UserWithStringSet { - id: "user_primary_key".to_owned(), - name: "bokuweb".to_owned(), - string_set: set, - }, - consumed_capacity: None, - } - ); - } - rt.block_on(example()); + #[tokio::test] + async fn test_get_stringset() { + let client = crate::all::create_client_from_struct!(UserWithStringSet); + let res = client.get("user_primary_key").consistent().run().await; + let mut set = std::collections::HashSet::new(); + set.insert("Hello".to_owned()); + + assert_eq!( + res.unwrap(), + get::GetOutput { + item: UserWithStringSet { + id: "user_primary_key".to_owned(), + name: "bokuweb".to_owned(), + string_set: set, + }, + consumed_capacity: None, + } + ); } #[derive(Raiden)] @@ -241,30 +197,24 @@ mod tests { string_set: std::collections::BTreeSet, } - #[test] - fn test_get_btree_stringset() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = UserWithStringBTreeSet::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let res = client.get("user_primary_key").consistent().run().await; - let mut set = std::collections::BTreeSet::new(); - set.insert("Hello".to_owned()); - assert_eq!( - res.unwrap(), - get::GetOutput { - item: UserWithStringBTreeSet { - id: "user_primary_key".to_owned(), - name: "bokuweb".to_owned(), - string_set: set, - }, - consumed_capacity: None, - } - ); - } - rt.block_on(example()); + #[tokio::test] + async fn test_get_btree_stringset() { + let client = crate::all::create_client_from_struct!(UserWithStringBTreeSet); + let res = client.get("user_primary_key").consistent().run().await; + let mut set = std::collections::BTreeSet::new(); + set.insert("Hello".to_owned()); + + assert_eq!( + res.unwrap(), + get::GetOutput { + item: UserWithStringBTreeSet { + id: "user_primary_key".to_owned(), + name: "bokuweb".to_owned(), + string_set: set, + }, + consumed_capacity: None, + } + ); } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -292,30 +242,24 @@ mod tests { pub string_set: std::collections::HashSet, } - #[test] - fn test_get_custom_stringset() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = UserWithCustomStringSet::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let res = client.get("user_primary_key").consistent().run().await; - let mut set = std::collections::HashSet::new(); - set.insert(CustomSSItem("Hello".to_owned())); - assert_eq!( - res.unwrap(), - get::GetOutput { - item: UserWithCustomStringSet { - id: "user_primary_key".to_owned(), - name: "bokuweb".to_owned(), - string_set: set, - }, - consumed_capacity: None, - } - ); - } - rt.block_on(example()); + #[tokio::test] + async fn test_get_custom_stringset() { + let client = crate::all::create_client_from_struct!(UserWithCustomStringSet); + let res = client.get("user_primary_key").consistent().run().await; + let mut set = std::collections::HashSet::new(); + set.insert(CustomSSItem("Hello".to_owned())); + + assert_eq!( + res.unwrap(), + get::GetOutput { + item: UserWithCustomStringSet { + id: "user_primary_key".to_owned(), + name: "bokuweb".to_owned(), + string_set: set, + }, + consumed_capacity: None, + } + ); } #[derive(Raiden, Debug, PartialEq)] @@ -329,30 +273,23 @@ mod tests { num: usize, } - #[test] - fn test_user_get_item_with_sort_key() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = UserWithSortKey::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - - let res = client.get("id1", 2003_usize).run().await; - assert_eq!( - res.unwrap(), - get::GetOutput { - item: UserWithSortKey { - id: "id1".to_owned(), - name: "bob".to_owned(), - year: 2003, - num: 300, - }, - consumed_capacity: None, - } - ); - } - rt.block_on(example()); + #[tokio::test] + async fn test_user_get_item_with_sort_key() { + let client = crate::all::create_client_from_struct!(UserWithSortKey); + let res = client.get("id1", 2003_usize).run().await; + + assert_eq!( + res.unwrap(), + get::GetOutput { + item: UserWithSortKey { + id: "id1".to_owned(), + name: "bob".to_owned(), + year: 2003, + num: 300, + }, + consumed_capacity: None, + } + ); } #[derive(Raiden, Debug, Clone, PartialEq)] @@ -363,28 +300,21 @@ mod tests { name: String, } - #[test] - fn test_get_empty_string() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = EmptyStringTestData0::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - - let res = client.get("id0").run().await; - assert_eq!( - res.unwrap(), - get::GetOutput { - item: EmptyStringTestData0 { - id: "id0".to_owned(), - name: "".to_owned(), - }, - consumed_capacity: None, - } - ); - } - rt.block_on(example()); + #[tokio::test] + async fn test_get_empty_string() { + let client = crate::all::create_client_from_struct!(EmptyStringTestData0); + let res = client.get("id0").run().await; + + assert_eq!( + res.unwrap(), + get::GetOutput { + item: EmptyStringTestData0 { + id: "id0".to_owned(), + name: "".to_owned(), + }, + consumed_capacity: None, + } + ); } #[derive(Raiden, Debug, Clone, PartialEq)] @@ -400,12 +330,9 @@ mod tests { #[tokio::test] async fn test_use_default_for_null() { - let client = UseDefaultForNull::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - + let client = crate::all::create_client_from_struct!(UseDefaultForNull); let res = client.get("id0").run().await; + assert_eq!( res.unwrap(), get::GetOutput { @@ -435,37 +362,24 @@ mod tests { } } - #[test] - fn test_retry() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = User::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let _ = client - .with_retries(Box::new(MyRetryStrategy)) - .get("anonymous") - .run() - .await; - } - rt.block_on(example()); + #[tokio::test] + async fn test_retry() { + let client = crate::all::create_client_from_struct!(User); + let _ = client + .with_retries(Box::new(MyRetryStrategy)) + .get("anonymous") + .run() + .await; + assert_eq!(RETRY_COUNT.load(Ordering::Relaxed), 4) } - #[test] - fn test_should_build_with_twice_retry() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = User::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }) - .with_retries(Box::new(MyRetryStrategy)); - let _ = client.get("anonymous").run().await; - let _ = client.get("anonymous").run().await; - } - rt.block_on(example()); + #[tokio::test] + async fn test_should_build_with_twice_retry() { + let client = + crate::all::create_client_from_struct!(User).with_retries(Box::new(MyRetryStrategy)); + let _ = client.get("anonymous").run().await; + let _ = client.get("anonymous").run().await; } #[derive(Raiden)] @@ -478,29 +392,22 @@ mod tests { num_usize: usize, } - #[test] - fn test_user_get_item_for_projection_expression() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = PartialUser::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - - let res = client.get("user_primary_key").run().await; - assert_eq!( - res.unwrap(), - get::GetOutput { - item: PartialUser { - id: "user_primary_key".to_owned(), - name: "bokuweb".to_owned(), - num_usize: 42, - }, - consumed_capacity: None, - } - ); - } - rt.block_on(example()); + #[tokio::test] + async fn test_user_get_item_for_projection_expression() { + let client = crate::all::create_client_from_struct!(PartialUser); + let res = client.get("user_primary_key").run().await; + + assert_eq!( + res.unwrap(), + get::GetOutput { + item: PartialUser { + id: "user_primary_key".to_owned(), + name: "bokuweb".to_owned(), + num_usize: 42, + }, + consumed_capacity: None, + } + ); } #[derive(Raiden)] @@ -513,27 +420,21 @@ mod tests { r#type: String, } - #[test] - fn test_reserved_keyword() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = Reserved::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let res = client.get("id0").run().await; - assert_eq!( - res.unwrap(), - get::GetOutput { - item: Reserved { - id: "id0".to_owned(), - r#type: "reserved".to_owned(), - }, - consumed_capacity: None, - } - ); - } - rt.block_on(example()); + #[tokio::test] + async fn test_reserved_keyword() { + let client = crate::all::create_client_from_struct!(Reserved); + let res = client.get("id0").run().await; + + assert_eq!( + res.unwrap(), + get::GetOutput { + item: Reserved { + id: "id0".to_owned(), + r#type: "reserved".to_owned(), + }, + consumed_capacity: None, + } + ); } #[derive(Raiden)] @@ -546,27 +447,21 @@ mod tests { some_type: String, } - #[test] - fn test_rename_with_reserved() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = ReservedWithRename::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let res = client.get("id0").run().await; - assert_eq!( - res.unwrap(), - get::GetOutput { - item: ReservedWithRename { - id: "id0".to_owned(), - some_type: "reserved".to_owned(), - }, - consumed_capacity: None, - } - ); - } - rt.block_on(example()); + #[tokio::test] + async fn test_rename_with_reserved() { + let client = crate::all::create_client_from_struct!(ReservedWithRename); + let res = client.get("id0").run().await; + + assert_eq!( + res.unwrap(), + get::GetOutput { + item: ReservedWithRename { + id: "id0".to_owned(), + some_type: "reserved".to_owned(), + }, + consumed_capacity: None, + } + ); } #[derive(Raiden)] @@ -579,27 +474,21 @@ mod tests { is_ok: bool, } - #[test] - fn test_use_default() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = UseDefault::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let res = client.get("id0").run().await; - assert_eq!( - res.unwrap(), - get::GetOutput { - item: UseDefault { - id: "id0".to_owned(), - is_ok: false, - }, - consumed_capacity: None, - } - ); - } - rt.block_on(example()); + #[tokio::test] + async fn test_use_default() { + let client = crate::all::create_client_from_struct!(UseDefault); + let res = client.get("id0").run().await; + + assert_eq!( + res.unwrap(), + get::GetOutput { + item: UseDefault { + id: "id0".to_owned(), + is_ok: false, + }, + consumed_capacity: None, + } + ); } #[derive(Raiden)] @@ -612,27 +501,21 @@ mod tests { float64: f64, } - #[test] - fn test_float() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = FloatTest::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let res = client.get("primary_key").run().await; - assert_eq!( - res.unwrap(), - get::GetOutput { - item: FloatTest { - id: "primary_key".to_owned(), - float32: 1.23, - float64: 2.34, - }, - consumed_capacity: None, - } - ); - } - rt.block_on(example()); + #[tokio::test] + async fn test_float() { + let client = crate::all::create_client_from_struct!(FloatTest); + let res = client.get("primary_key").run().await; + + assert_eq!( + res.unwrap(), + get::GetOutput { + item: FloatTest { + id: "primary_key".to_owned(), + float32: 1.23, + float64: 2.34, + }, + consumed_capacity: None, + } + ); } } diff --git a/raiden/tests/all/key_condition.rs b/raiden/tests/all/key_condition.rs index c86a119f..d3a0e054 100644 --- a/raiden/tests/all/key_condition.rs +++ b/raiden/tests/all/key_condition.rs @@ -30,7 +30,7 @@ mod tests { let mut expected_values: std::collections::HashMap = std::collections::HashMap::new(); expected_values.insert(":value0".to_owned(), "bokuweb".into_attr()); - assert_eq!(key_condition, "#name = :value0".to_owned(),); + assert_eq!(key_condition, "#name = :value0".to_owned()); assert_eq!(attribute_names, expected_names); assert_eq!(attribute_values, expected_values); } @@ -77,7 +77,7 @@ mod tests { let mut expected_values: std::collections::HashMap = std::collections::HashMap::new(); expected_values.insert(":value0".to_owned(), "bokuweb".into_attr()); - assert_eq!(key_condition, "begins_with(#name, :value0)".to_owned(),); + assert_eq!(key_condition, "begins_with(#name, :value0)".to_owned()); assert_eq!(attribute_names, expected_names); assert_eq!(attribute_values, expected_values); } diff --git a/raiden/tests/all/mod.rs b/raiden/tests/all/mod.rs index ebc4839f..1d66fa1f 100644 --- a/raiden/tests/all/mod.rs +++ b/raiden/tests/all/mod.rs @@ -12,3 +12,79 @@ mod rename_all; mod scan; mod transact_write; mod update; + +#[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] +macro_rules! create_client { + ($ty: ty) => { + <$ty>::new(raiden::Region::Custom { + endpoint: "http://localhost:8000".into(), + name: "ap-northeast-1".into(), + }) + }; +} + +#[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] +macro_rules! create_client_from_struct { + ($ty: ty) => { + <$ty>::client(raiden::Region::Custom { + endpoint: "http://localhost:8000".into(), + name: "ap-northeast-1".into(), + }) + }; +} + +#[cfg(feature = "aws-sdk")] +macro_rules! create_client { + ($ty: ty) => {{ + let sdk_config = ::raiden::aws_sdk::aws_config::defaults( + ::raiden::aws_sdk::config::BehaviorVersion::latest(), + ) + .endpoint_url("http://localhost:8000") + .region(::raiden::config::Region::from_static("ap-northeast-1")) + .load() + .await; + let sdk_client = ::raiden::Client::new(&sdk_config); + + <$ty>::new_with_client(sdk_client) + }}; +} + +#[cfg(feature = "aws-sdk")] +macro_rules! create_client_from_struct { + ($ty: ty) => {{ + let sdk_config = ::raiden::aws_sdk::aws_config::defaults( + ::raiden::aws_sdk::config::BehaviorVersion::latest(), + ) + .endpoint_url("http://localhost:8000") + .region(::raiden::config::Region::from_static("ap-northeast-1")) + .load() + .await; + let sdk_client = ::raiden::Client::new(&sdk_config); + + <$ty>::client_with(sdk_client) + }}; +} + +use {create_client, create_client_from_struct}; + +#[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] +fn default_key_and_attributes() -> raiden::KeysAndAttributes { + raiden::KeysAndAttributes { + attributes_to_get: None, + consistent_read: None, + expression_attribute_names: None, + keys: vec![], + projection_expression: None, + } +} + +#[cfg(feature = "aws-sdk")] +fn default_key_and_attributes() -> raiden::aws_sdk::types::KeysAndAttributes { + let mut v = raiden::aws_sdk::types::KeysAndAttributes::builder() + .keys(std::collections::HashMap::new()) + .build() + .expect("should be built"); + + v.keys = vec![]; + v +} diff --git a/raiden/tests/all/put.rs b/raiden/tests/all/put.rs index 89711371..60a96c55 100644 --- a/raiden/tests/all/put.rs +++ b/raiden/tests/all/put.rs @@ -26,84 +26,73 @@ mod tests { name: String, } - #[test] - fn test_put_user_with_attribute_not_exists_condition_input() { - let client = User::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); + #[tokio::test] + async fn test_put_user_with_attribute_not_exists_condition_input() { + let client = crate::all::create_client_from_struct!(User); let user = UserPutItemInput { id: "mock_id".to_owned(), name: "bokuweb".to_owned(), }; let cond = User::condition().attr_not_exists(User::name()); + + #[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] let input = client.put(user).condition(cond).input; + #[cfg(feature = "aws-sdk")] + let input = client.put(user).condition(cond).builder.build().unwrap(); + let mut attribute_names: std::collections::HashMap = std::collections::HashMap::new(); let mut item: std::collections::HashMap = std::collections::HashMap::new(); - item.insert( - "name".to_owned(), - raiden::AttributeValue { - s: Some("bokuweb".to_owned()), - ..raiden::AttributeValue::default() - }, - ); - item.insert( - "id".to_owned(), - raiden::AttributeValue { - s: Some("mock_id".to_owned()), - ..raiden::AttributeValue::default() - }, - ); + let attr_value: raiden::AttributeValue = "bokuweb".into_attr(); + item.insert("name".to_owned(), attr_value); + let attr_value: raiden::AttributeValue = "mock_id".into_attr(); + item.insert("id".to_owned(), attr_value); + attribute_names.insert("#name".to_owned(), "name".to_owned()); - assert_eq!( - input, - ::raiden::PutItemInput { - condition_expression: Some("attribute_not_exists(#name)".to_owned()), - expression_attribute_names: Some(attribute_names), - expression_attribute_values: None, - item, - table_name: "user".to_owned(), - ..::raiden::PutItemInput::default() - }, - ); + + #[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] + let expected = ::raiden::PutItemInput { + condition_expression: Some("attribute_not_exists(#name)".to_owned()), + expression_attribute_names: Some(attribute_names), + expression_attribute_values: None, + item, + table_name: "user".to_owned(), + ..::raiden::PutItemInput::default() + }; + + #[cfg(feature = "aws-sdk")] + let expected = ::raiden::operation::put_item::PutItemInput::builder() + .condition_expression("attribute_not_exists(#name)") + .set_expression_attribute_names(Some(attribute_names)) + .set_item(Some(item)) + .table_name("user") + .build() + .unwrap(); + + assert_eq!(input, expected); } - #[test] - fn test_put_user() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = User::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let user = UserPutItemInput { - id: "mock_id".to_owned(), - name: "bokuweb".to_owned(), - }; - let _res = client.put(user).run().await; - } - rt.block_on(example()); + #[tokio::test] + async fn test_put_user() { + let client = crate::all::create_client_from_struct!(User); + let user = UserPutItemInput { + id: "mock_id".to_owned(), + name: "bokuweb".to_owned(), + }; + let _res = client.put(user).run().await; } - #[test] - fn test_put_user_with_builder() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = User::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let user = User::put_item_builder() - .id("mock_id".to_owned()) - .name("bokuweb".to_owned()) - .build(); - let _res = client.put(user).run().await; - } - rt.block_on(example()); + #[tokio::test] + async fn test_put_user_with_builder() { + let client = crate::all::create_client_from_struct!(User); + let user = User::put_item_builder() + .id("mock_id".to_owned()) + .name("bokuweb".to_owned()) + .build(); + let _res = client.put(user).run().await; } #[derive(Raiden, Debug, Clone)] @@ -114,111 +103,78 @@ mod tests { name: String, } - #[test] - fn test_put_user_eq_op_condition_expression() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = User::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let user = UserPutItemInput { - id: "id0".to_owned(), - name: "bokuweb".to_owned(), - }; - let cond = User::condition().value("bokuweb").eq_attr(User::name()); - let res = client.put(user).condition(cond).run().await; - assert_eq!(res.is_ok(), true); - } - rt.block_on(example()); + #[tokio::test] + async fn test_put_user_eq_op_condition_expression() { + let client = crate::all::create_client_from_struct!(User); + let user = UserPutItemInput { + id: "id0".to_owned(), + name: "bokuweb".to_owned(), + }; + let cond = User::condition().value("bokuweb").eq_attr(User::name()); + let res = client.put(user).condition(cond).run().await; + assert_eq!(res.is_ok(), true); } - #[test] #[allow(dead_code)] - fn test_put_user_eq_op_condition_expression_with_not_exist_name() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = User::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let user = UserPutItemInput { - id: "id0".to_owned(), - name: "bokuweb".to_owned(), - }; - let cond = User::condition().value("bokuweb_").eq_attr(User::name()); - let res = client.put(user).condition(cond).run().await; - assert_eq!( - Err(::raiden::RaidenError::ConditionalCheckFailed( - "The conditional request failed".to_owned() - )), - res - ); + #[tokio::test] + async fn test_put_user_eq_op_condition_expression_with_not_exist_name() { + let client = crate::all::create_client_from_struct!(User); + let user = UserPutItemInput { + id: "id0".to_owned(), + name: "bokuweb".to_owned(), + }; + let cond = User::condition().value("bokuweb_").eq_attr(User::name()); + let res = client.put(user).condition(cond).run().await; + assert!(res.is_err()); + + if let RaidenError::ConditionalCheckFailed(msg) = res.unwrap_err() { + assert!(msg.contains("The conditional request failed")); + } else { + panic!("err should be RaidenError::ConditionalCheckFailed"); } - rt.block_on(example()); } - #[test] #[allow(dead_code)] - fn test_put_user_id_not_exists_expression() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = User::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let user = UserPutItemInput { - id: "id0".to_owned(), - name: "bokuweb".to_owned(), - }; - let cond = User::condition().attr_not_exists(User::id()); - let res = client.put(user).condition(cond).run().await; - assert_eq!( - Err(::raiden::RaidenError::ConditionalCheckFailed( - "The conditional request failed".to_owned() - )), - res - ); + #[tokio::test] + async fn test_put_user_id_not_exists_expression() { + let client = crate::all::create_client_from_struct!(User); + let user = UserPutItemInput { + id: "id0".to_owned(), + name: "bokuweb".to_owned(), + }; + let cond = User::condition().attr_not_exists(User::id()); + let res = client.put(user).condition(cond).run().await; + assert!(res.is_err()); + + if let RaidenError::ConditionalCheckFailed(msg) = res.unwrap_err() { + assert!(msg.contains("The conditional request failed")); + } else { + panic!("err should be RaidenError::ConditionalCheckFailed"); } - rt.block_on(example()); } - #[test] #[allow(dead_code)] - fn test_put_user_id_exists_expression() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = User::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let user = UserPutItemInput { - id: "id0".to_owned(), - name: "bokuweb".to_owned(), - }; - let cond = User::condition().attr_exists(User::id()); - let res = client.put(user).condition(cond).run().await; - assert_eq!(res.is_ok(), true); - } - rt.block_on(example()); + #[tokio::test] + async fn test_put_user_id_exists_expression() { + let client = crate::all::create_client_from_struct!(User); + let user = UserPutItemInput { + id: "id0".to_owned(), + name: "bokuweb".to_owned(), + }; + let cond = User::condition().attr_exists(User::id()); + let res = client.put(user).condition(cond).run().await; + assert_eq!(res.is_ok(), true); } - #[test] #[allow(dead_code)] - fn test_put_user_with_uuid() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = UserWithUuid::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let item = UserWithUuid::put_item_builder() - .name("bokuweb".to_owned()) - .build(); - let res = client.put(item).run().await; - assert_eq!(res.is_ok(), true); - } - rt.block_on(example()); + #[tokio::test] + async fn test_put_user_with_uuid() { + let client = crate::all::create_client_from_struct!(UserWithUuid); + let item = UserWithUuid::put_item_builder() + .name("bokuweb".to_owned()) + .build(); + let res = client.put(item).run().await; + assert_eq!(res.is_ok(), true); } #[derive(Raiden)] @@ -233,22 +189,15 @@ mod tests { nums: Vec, } - #[test] - fn test_put_user_with_number_vec() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = UserVecTest::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let item = UserVecTest::put_item_builder() - .name("bokuweb".to_owned()) - .nums(vec![0, 1, 2]) - .build(); - let res = client.put(item).run().await; - assert_eq!(res.is_ok(), true); - } - rt.block_on(example()); + #[tokio::test] + async fn test_put_user_with_number_vec() { + let client = crate::all::create_client_from_struct!(UserVecTest); + let item = UserVecTest::put_item_builder() + .name("bokuweb".to_owned()) + .nums(vec![0, 1, 2]) + .build(); + let res = client.put(item).run().await; + assert_eq!(res.is_ok(), true); } #[derive(Raiden)] @@ -263,25 +212,18 @@ mod tests { nums: std::collections::HashSet, } - #[test] - fn test_put_user_with_number_set() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = UserSetTest::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let mut nums: std::collections::HashSet = std::collections::HashSet::new(); - nums.insert(1); - - let item = UserSetTest::put_item_builder() - .name("bokuweb".to_owned()) - .nums(nums) - .build(); - let res = client.put(item).run().await; - assert_eq!(res.is_ok(), true); - } - rt.block_on(example()); + #[tokio::test] + async fn test_put_user_with_number_set() { + let client = crate::all::create_client_from_struct!(UserSetTest); + let mut nums: std::collections::HashSet = std::collections::HashSet::new(); + nums.insert(1); + + let item = UserSetTest::put_item_builder() + .name("bokuweb".to_owned()) + .nums(nums) + .build(); + let res = client.put(item).run().await; + assert_eq!(res.is_ok(), true); } #[derive(Debug, Clone, Hash, PartialEq, Eq)] @@ -311,25 +253,18 @@ mod tests { nums: std::collections::HashSet, } - #[test] - fn test_put_user_with_user_defined_set() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = UserSetTest::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let mut nums: std::collections::HashSet = std::collections::HashSet::new(); - nums.insert(1); - - let item = UserSetTest::put_item_builder() - .name("bokuweb".to_owned()) - .nums(nums) - .build(); - let res = client.put(item).run().await; - assert_eq!(res.is_ok(), true); - } - rt.block_on(example()); + #[tokio::test] + async fn test_put_user_with_user_defined_set() { + let client = crate::all::create_client_from_struct!(UserSetTest); + let mut nums: std::collections::HashSet = std::collections::HashSet::new(); + nums.insert(1); + + let item = UserSetTest::put_item_builder() + .name("bokuweb".to_owned()) + .nums(nums) + .build(); + let res = client.put(item).run().await; + assert_eq!(res.is_ok(), true); } #[derive(Raiden)] @@ -343,21 +278,14 @@ mod tests { set: std::collections::HashSet, } - #[test] - fn test_put_user_with_empty_set() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = UserEmptySetTest::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let set: std::collections::HashSet = std::collections::HashSet::new(); - - let item = UserEmptySetTest::put_item_builder().set(set).build(); - let res = client.put(item).run().await; - assert_eq!(res.is_ok(), true); - } - rt.block_on(example()); + #[tokio::test] + async fn test_put_user_with_empty_set() { + let client = crate::all::create_client_from_struct!(UserEmptySetTest); + let set: std::collections::HashSet = std::collections::HashSet::new(); + let item = UserEmptySetTest::put_item_builder().set(set).build(); + let res = client.put(item).run().await; + + assert_eq!(res.is_ok(), true); } #[derive(Raiden, Debug, Clone)] @@ -369,21 +297,14 @@ mod tests { name: String, } - #[test] - fn test_put_with_empty_string() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = EmptyStringTestData0::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let item = EmptyStringTestData0::put_item_builder() - .name("".to_owned()) - .build(); - let res = client.put(item).run().await; - assert_eq!(res.is_ok(), true); - } - rt.block_on(example()); + #[tokio::test] + async fn test_put_with_empty_string() { + let client = crate::all::create_client_from_struct!(EmptyStringTestData0); + let item = EmptyStringTestData0::put_item_builder() + .name("".to_owned()) + .build(); + let res = client.put(item).run().await; + assert_eq!(res.is_ok(), true); } #[derive(Raiden, Debug, Clone, PartialEq)] @@ -394,31 +315,24 @@ mod tests { sset: std::collections::HashSet, } - #[test] - fn test_put_with_empty_sset() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = EmptyPutTestData0::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let set: std::collections::HashSet = std::collections::HashSet::new(); - let expected_set: std::collections::HashSet = std::collections::HashSet::new(); - let item = EmptyPutTestData0::put_item_builder() - .id("testid".to_owned()) - .sset(set) - .build(); - let res = client.put(item).run().await; - assert_eq!(res.is_ok(), true); - let res = client.get(res.unwrap().item.id).run().await; - assert_eq!( - res.unwrap().item, - EmptyPutTestData0 { - id: "testid".to_owned(), - sset: expected_set - } - ); - } - rt.block_on(example()); + #[tokio::test] + async fn test_put_with_empty_sset() { + let client = crate::all::create_client_from_struct!(EmptyPutTestData0); + let set: std::collections::HashSet = std::collections::HashSet::new(); + let expected_set: std::collections::HashSet = std::collections::HashSet::new(); + let item = EmptyPutTestData0::put_item_builder() + .id("testid".to_owned()) + .sset(set) + .build(); + let res = client.put(item).run().await; + assert_eq!(res.is_ok(), true); + let res = client.get(res.unwrap().item.id).run().await; + assert_eq!( + res.unwrap().item, + EmptyPutTestData0 { + id: "testid".to_owned(), + sset: expected_set + } + ); } } diff --git a/raiden/tests/all/query.rs b/raiden/tests/all/query.rs index f81aea72..3f16e8fd 100644 --- a/raiden/tests/all/query.rs +++ b/raiden/tests/all/query.rs @@ -15,85 +15,68 @@ mod tests { option: Option, } - #[test] - fn test_query() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = QueryTestData0::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let cond = QueryTestData0::key_condition(QueryTestData0::id()).eq("id0"); - let res = client.query().key_condition(cond).run().await; - - assert_eq!( - res.unwrap(), - query::QueryOutput { - consumed_capacity: None, - count: Some(2), - items: vec![ - QueryTestData0 { - id: "id0".to_owned(), - name: "john".to_owned(), - year: 1999, - num: 1000, - option: None, - }, - QueryTestData0 { - id: "id0".to_owned(), - name: "john".to_owned(), - year: 2000, - num: 2000, - option: None, - }, - ], - next_token: None, - scanned_count: Some(2), - } - ) - } - rt.block_on(example()); - } - - #[test] - fn test_query_with_and_key_condition() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = QueryTestData0::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let cond = QueryTestData0::key_condition(QueryTestData0::id()) - .eq("id0") - .and(QueryTestData0::key_condition(QueryTestData0::year()).eq(1999)); - let res = client.query().key_condition(cond).run().await; - - assert_eq!( - res.unwrap(), - query::QueryOutput { - consumed_capacity: None, - count: Some(1), - items: vec![QueryTestData0 { + #[tokio::test] + async fn test_query() { + let client = crate::all::create_client_from_struct!(QueryTestData0); + let cond = QueryTestData0::key_condition(QueryTestData0::id()).eq("id0"); + let res = client.query().key_condition(cond).run().await; + + assert_eq!( + res.unwrap(), + query::QueryOutput { + consumed_capacity: None, + count: Some(2), + items: vec![ + QueryTestData0 { id: "id0".to_owned(), name: "john".to_owned(), year: 1999, num: 1000, option: None, - },], - next_token: None, - scanned_count: Some(1), - } - ) - } - rt.block_on(example()); + }, + QueryTestData0 { + id: "id0".to_owned(), + name: "john".to_owned(), + year: 2000, + num: 2000, + option: None, + }, + ], + next_token: None, + scanned_count: Some(2), + } + ); + } + + #[tokio::test] + async fn test_query_with_and_key_condition() { + let client = crate::all::create_client_from_struct!(QueryTestData0); + let cond = QueryTestData0::key_condition(QueryTestData0::id()) + .eq("id0") + .and(QueryTestData0::key_condition(QueryTestData0::year()).eq(1999)); + let res = client.query().key_condition(cond).run().await; + + assert_eq!( + res.unwrap(), + query::QueryOutput { + consumed_capacity: None, + count: Some(1), + items: vec![QueryTestData0 { + id: "id0".to_owned(), + name: "john".to_owned(), + year: 1999, + num: 1000, + option: None, + },], + next_token: None, + scanned_count: Some(1), + } + ); } #[tokio::test] async fn test_query_with_simple_filter() { - let client = QueryTestData0::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); + let client = crate::all::create_client_from_struct!(QueryTestData0); let cond = QueryTestData0::key_condition(QueryTestData0::id()).eq("id3"); let filter = QueryTestData0::filter_expression(QueryTestData0::num()).eq(4000); let res = client @@ -108,10 +91,7 @@ mod tests { #[tokio::test] async fn test_query_with_size_filter() { - let client = QueryTestData0::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); + let client = crate::all::create_client_from_struct!(QueryTestData0); let cond = QueryTestData0::key_condition(QueryTestData0::id()).eq("id5"); let filter = QueryTestData0::filter_expression(QueryTestData0::name()) .size() @@ -128,10 +108,7 @@ mod tests { #[tokio::test] async fn test_query_with_or_filter() { - let client = QueryTestData0::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); + let client = crate::all::create_client_from_struct!(QueryTestData0); let cond = QueryTestData0::key_condition(QueryTestData0::id()).eq("id3"); let filter = QueryTestData0::filter_expression(QueryTestData0::name()) .eq("bar0") @@ -148,10 +125,7 @@ mod tests { #[tokio::test] async fn test_query_with_attribute_exists_filter() { - let client = QueryTestData0::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); + let client = crate::all::create_client_from_struct!(QueryTestData0); let cond = QueryTestData0::key_condition(QueryTestData0::id()).eq("id4"); let filter = QueryTestData0::filter_expression(QueryTestData0::option()).attribute_exists(); let res = client @@ -166,10 +140,7 @@ mod tests { #[tokio::test] async fn test_query_with_attribute_not_exists_filter() { - let client = QueryTestData0::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); + let client = crate::all::create_client_from_struct!(QueryTestData0); let cond = QueryTestData0::key_condition(QueryTestData0::id()).eq("id4"); let filter = QueryTestData0::filter_expression(QueryTestData0::option()).attribute_not_exists(); @@ -185,10 +156,7 @@ mod tests { #[tokio::test] async fn test_query_with_attribute_type_filter() { - let client = QueryTestData0::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); + let client = crate::all::create_client_from_struct!(QueryTestData0); let cond = QueryTestData0::key_condition(QueryTestData0::id()).eq("id4"); let filter = QueryTestData0::filter_expression(QueryTestData0::option()) .attribute_type(raiden::AttributeType::S); @@ -204,10 +172,7 @@ mod tests { #[tokio::test] async fn test_query_with_contains_filter() { - let client = QueryTestData0::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); + let client = crate::all::create_client_from_struct!(QueryTestData0); let cond = QueryTestData0::key_condition(QueryTestData0::id()).eq("id4"); let filter = QueryTestData0::filter_expression(QueryTestData0::name()).contains("bar"); let res = client @@ -222,10 +187,7 @@ mod tests { #[tokio::test] async fn test_query_in_filter() { - let client = QueryTestData0::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); + let client = crate::all::create_client_from_struct!(QueryTestData0); let cond = QueryTestData0::key_condition(QueryTestData0::id()).eq("id4"); let filter = QueryTestData0::filter_expression(QueryTestData0::name()).r#in(vec!["bar0", "bar1"]); @@ -249,121 +211,86 @@ mod tests { long_text: String, } - #[test] - fn test_query_limit_1() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = Test::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let cond = Test::key_condition(Test::ref_id()).eq("id0"); - let res = client - .query() - .index("testGSI") - .limit(1) - .key_condition(cond) - .run() - .await; - assert_eq!(res.unwrap().items.len(), 1); - } - rt.block_on(example()); + #[tokio::test] + async fn test_query_limit_1() { + let client = crate::all::create_client_from_struct!(Test); + let cond = Test::key_condition(Test::ref_id()).eq("id0"); + let res = client + .query() + .index("testGSI") + .limit(1) + .key_condition(cond) + .run() + .await; + assert_eq!(res.unwrap().items.len(), 1); } - #[test] - fn test_query_limit_5() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = Test::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let cond = Test::key_condition(Test::ref_id()).eq("id0"); - let res = client - .query() - .index("testGSI") - .limit(5) - .key_condition(cond) - .run() - .await; - assert_eq!(res.unwrap().items.len(), 5); - } - rt.block_on(example()); + #[tokio::test] + async fn test_query_limit_5() { + let client = crate::all::create_client_from_struct!(Test); + let cond = Test::key_condition(Test::ref_id()).eq("id0"); + let res = client + .query() + .index("testGSI") + .limit(5) + .key_condition(cond) + .run() + .await; + assert_eq!(res.unwrap().items.len(), 5); } - #[test] - fn test_query_no_limit() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = Test::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let cond = Test::key_condition(Test::ref_id()).eq("id0"); - let res = client - .query() - .index("testGSI") - .key_condition(cond) - .run() - .await; - assert_eq!(res.unwrap().items.len(), 10); - } - rt.block_on(example()); + #[tokio::test] + async fn test_query_no_limit() { + let client = crate::all::create_client_from_struct!(Test); + let cond = Test::key_condition(Test::ref_id()).eq("id0"); + let res = client + .query() + .index("testGSI") + .key_condition(cond) + .run() + .await; + assert_eq!(res.unwrap().items.len(), 10); } - #[test] - fn test_query_over_limit() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = Test::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let cond = Test::key_condition(Test::ref_id()).eq("id0"); - let res = client - .query() - .index("testGSI") - .limit(11) - .key_condition(cond) - .run() - .await; - assert_eq!(res.unwrap().items.len(), 10); - } - rt.block_on(example()); + #[tokio::test] + async fn test_query_over_limit() { + let client = crate::all::create_client_from_struct!(Test); + let cond = Test::key_condition(Test::ref_id()).eq("id0"); + let res = client + .query() + .index("testGSI") + .limit(11) + .key_condition(cond) + .run() + .await; + assert_eq!(res.unwrap().items.len(), 10); } - #[test] - fn test_query_over_limit_with_next_token() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = Test::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let cond = Test::key_condition(Test::ref_id()).eq("id0"); - let res = client - .query() - .index("testGSI") - .limit(9) - .key_condition(cond) - .run() - .await - .unwrap(); - assert_eq!(res.items.len(), 9); - assert!(res.next_token.is_some()); - let cond = Test::key_condition(Test::ref_id()).eq("id0"); - let res = client - .query() - .index("testGSI") - .limit(10) - .next_token(res.next_token.unwrap()) - .key_condition(cond) - .run() - .await - .unwrap(); - assert_eq!(res.items.len(), 1); - } - rt.block_on(example()); + #[tokio::test] + async fn test_query_over_limit_with_next_token() { + let client = crate::all::create_client_from_struct!(Test); + let cond = Test::key_condition(Test::ref_id()).eq("id0"); + let res = client + .query() + .index("testGSI") + .limit(9) + .key_condition(cond) + .run() + .await + .unwrap(); + assert_eq!(res.items.len(), 9); + assert!(res.next_token.is_some()); + let cond = Test::key_condition(Test::ref_id()).eq("id0"); + let res = client + .query() + .index("testGSI") + .limit(10) + .next_token(res.next_token.unwrap()) + .key_condition(cond) + .run() + .await + .unwrap(); + assert_eq!(res.items.len(), 1); } #[derive(Raiden)] @@ -377,25 +304,18 @@ mod tests { updated_at: String, } - #[test] - fn test_query_with_renamed() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = Project::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let cond = Project::key_condition(Project::org_id()).eq("myOrg"); - let res = client - .query() - .index("orgIndex") - .limit(11) - .key_condition(cond) - .run() - .await; - assert_eq!(res.unwrap().items.len(), 10); - } - rt.block_on(example()); + #[tokio::test] + async fn test_query_with_renamed() { + let client = crate::all::create_client_from_struct!(Project); + let cond = Project::key_condition(Project::org_id()).eq("myOrg"); + let res = client + .query() + .index("orgIndex") + .limit(11) + .key_condition(cond) + .run() + .await; + assert_eq!(res.unwrap().items.len(), 10); } #[derive(Raiden, Debug, PartialEq)] @@ -406,40 +326,34 @@ mod tests { name: String, year: usize, } - #[test] - fn test_query_for_projection_expression() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = QueryTestData0a::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let cond = QueryTestData0a::key_condition(QueryTestData0a::id()).eq("id0"); - let res = client.query().key_condition(cond).run().await; - - assert_eq!( - res.unwrap(), - query::QueryOutput { - consumed_capacity: None, - count: Some(2), - items: vec![ - QueryTestData0a { - id: "id0".to_owned(), - name: "john".to_owned(), - year: 1999, - }, - QueryTestData0a { - id: "id0".to_owned(), - name: "john".to_owned(), - year: 2000, - }, - ], - next_token: None, - scanned_count: Some(2), - } - ) - } - rt.block_on(example()); + + #[tokio::test] + async fn test_query_for_projection_expression() { + let client = crate::all::create_client_from_struct!(QueryTestData0a); + let cond = QueryTestData0a::key_condition(QueryTestData0a::id()).eq("id0"); + let res = client.query().key_condition(cond).run().await; + + assert_eq!( + res.unwrap(), + query::QueryOutput { + consumed_capacity: None, + count: Some(2), + items: vec![ + QueryTestData0a { + id: "id0".to_owned(), + name: "john".to_owned(), + year: 1999, + }, + QueryTestData0a { + id: "id0".to_owned(), + name: "john".to_owned(), + year: 2000, + }, + ], + next_token: None, + scanned_count: Some(2), + } + ); } #[derive(Raiden, Debug, PartialEq)] @@ -451,40 +365,33 @@ mod tests { name: String, } - #[test] - fn test_query_with_begins_with_key_condition() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = QueryTestData1::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let cond = QueryTestData1::key_condition(QueryTestData1::id()) - .eq("id0") - .and(QueryTestData1::key_condition(QueryTestData1::name()).begins_with("j")); - let res = client.query().key_condition(cond).run().await; - - assert_eq!( - res.unwrap(), - query::QueryOutput { - consumed_capacity: None, - count: Some(2), - items: vec![ - QueryTestData1 { - id: "id0".to_owned(), - name: "jack".to_owned(), - }, - QueryTestData1 { - id: "id0".to_owned(), - name: "john".to_owned(), - } - ], - next_token: None, - scanned_count: Some(2), - } - ) - } - rt.block_on(example()); + #[tokio::test] + async fn test_query_with_begins_with_key_condition() { + let client = crate::all::create_client_from_struct!(QueryTestData1); + let cond = QueryTestData1::key_condition(QueryTestData1::id()) + .eq("id0") + .and(QueryTestData1::key_condition(QueryTestData1::name()).begins_with("j")); + let res = client.query().key_condition(cond).run().await; + + assert_eq!( + res.unwrap(), + query::QueryOutput { + consumed_capacity: None, + count: Some(2), + items: vec![ + QueryTestData1 { + id: "id0".to_owned(), + name: "jack".to_owned(), + }, + QueryTestData1 { + id: "id0".to_owned(), + name: "john".to_owned(), + } + ], + next_token: None, + scanned_count: Some(2), + } + ); } #[derive(Raiden, Debug, PartialEq)] @@ -496,61 +403,47 @@ mod tests { name: String, } - #[test] - fn should_be_obtainable_when_the_size_is_1mb_or_larger() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = QueryLargeDataTest::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let cond = QueryLargeDataTest::key_condition(QueryLargeDataTest::ref_id()).eq("ref"); - let res = client - .query() - .index("testGSI") - .key_condition(cond) - .run() - .await; - - assert_eq!(res.unwrap().items.len(), 100) - } - rt.block_on(example()); + #[tokio::test] + async fn should_be_obtainable_when_the_size_is_1mb_or_larger() { + let client = crate::all::create_client_from_struct!(QueryLargeDataTest); + let cond = QueryLargeDataTest::key_condition(QueryLargeDataTest::ref_id()).eq("ref"); + let res = client + .query() + .index("testGSI") + .key_condition(cond) + .run() + .await; + + assert_eq!(res.unwrap().items.len(), 100); } - #[test] - fn should_be_obtainable_specified_limit_items_when_the_size_is_1mb_or_larger() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = QueryLargeDataTest::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let cond = QueryLargeDataTest::key_condition(QueryLargeDataTest::ref_id()).eq("ref"); - let res = client - .query() - .index("testGSI") - .key_condition(cond) - .limit(40) - .run() - .await - .unwrap(); - - assert_eq!(res.items.len(), 40); - - let token = res.next_token; - - let cond = QueryLargeDataTest::key_condition(QueryLargeDataTest::ref_id()).eq("ref"); - let res = client - .query() - .index("testGSI") - .key_condition(cond) - .next_token(token.unwrap()) - .run() - .await - .unwrap(); - - assert_eq!(res.items.len(), 60); - } - rt.block_on(example()); + #[tokio::test] + async fn should_be_obtainable_specified_limit_items_when_the_size_is_1mb_or_larger() { + let client = crate::all::create_client_from_struct!(QueryLargeDataTest); + let cond = QueryLargeDataTest::key_condition(QueryLargeDataTest::ref_id()).eq("ref"); + let res = client + .query() + .index("testGSI") + .key_condition(cond) + .limit(40) + .run() + .await + .unwrap(); + + assert_eq!(res.items.len(), 40); + + let token = res.next_token; + + let cond = QueryLargeDataTest::key_condition(QueryLargeDataTest::ref_id()).eq("ref"); + let res = client + .query() + .index("testGSI") + .key_condition(cond) + .next_token(token.unwrap()) + .run() + .await + .unwrap(); + + assert_eq!(res.items.len(), 60); } } diff --git a/raiden/tests/all/rename.rs b/raiden/tests/all/rename.rs index 20b6ae8a..031761c3 100644 --- a/raiden/tests/all/rename.rs +++ b/raiden/tests/all/rename.rs @@ -28,87 +28,65 @@ mod tests { before_rename: usize, } - #[test] - fn test_rename_get_item() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = RenameTest::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); + #[tokio::test] + async fn test_rename_get_item() { + let client = crate::all::create_client_from_struct!(RenameTest); + let res = client.get("id0").run().await; - let res = client.get("id0").run().await; - assert_eq!( - res.unwrap(), - get::GetOutput { - item: RenameTest { - id: "id0".to_owned(), - name: "john".to_owned(), - before_rename: 1999, - }, - consumed_capacity: None, - } - ); - assert_eq!( - RenameTestAttrNames::Renamed.into_attr_name(), - "renamed".to_owned() - ); - } - rt.block_on(example()); + assert_eq!( + res.unwrap(), + get::GetOutput { + item: RenameTest { + id: "id0".to_owned(), + name: "john".to_owned(), + before_rename: 1999, + }, + consumed_capacity: None, + } + ); + assert_eq!( + RenameTestAttrNames::Renamed.into_attr_name(), + "renamed".to_owned() + ); } - #[test] - fn test_rename_key_get_item() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = RenameKeyTest::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); + #[tokio::test] + async fn test_rename_key_get_item() { + let client = crate::all::create_client_from_struct!(RenameKeyTest); + let res = client.get("id0").run().await; - let res = client.get("id0").run().await; - assert_eq!( - res.unwrap(), - get::GetOutput { - item: RenameKeyTest { - before_renamed_id: "id0".to_owned(), - name: "john".to_owned(), - before_rename: 1999, - }, - consumed_capacity: None, - } - ); - } - rt.block_on(example()); + assert_eq!( + res.unwrap(), + get::GetOutput { + item: RenameKeyTest { + before_renamed_id: "id0".to_owned(), + name: "john".to_owned(), + before_rename: 1999, + }, + consumed_capacity: None, + } + ); } - #[test] - fn test_rename_query() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = RenameTest::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); + #[tokio::test] + async fn test_rename_query() { + let client = crate::all::create_client_from_struct!(RenameTest); + let cond = RenameTest::key_condition(RenameTest::id()).eq("id0"); + let res = client.query().key_condition(cond).run().await; - let cond = RenameTest::key_condition(RenameTest::id()).eq("id0"); - let res = client.query().key_condition(cond).run().await; - - assert_eq!( - res.unwrap(), - query::QueryOutput { - consumed_capacity: None, - count: Some(1), - items: vec![RenameTest { - id: "id0".to_owned(), - name: "john".to_owned(), - before_rename: 1999, - },], - next_token: None, - scanned_count: Some(1), - } - ) - } - rt.block_on(example()); + assert_eq!( + res.unwrap(), + query::QueryOutput { + consumed_capacity: None, + count: Some(1), + items: vec![RenameTest { + id: "id0".to_owned(), + name: "john".to_owned(), + before_rename: 1999, + },], + next_token: None, + scanned_count: Some(1), + } + ); } } diff --git a/raiden/tests/all/rename_all.rs b/raiden/tests/all/rename_all.rs index 5b2b034d..f67db3dd 100644 --- a/raiden/tests/all/rename_all.rs +++ b/raiden/tests/all/rename_all.rs @@ -16,29 +16,22 @@ mod tests { project_id: usize, } - #[test] - fn test_rename_all_camelcase_get() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = RenameAllCamelCaseTest::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); + #[tokio::test] + async fn test_rename_all_camelcase_get() { + let client = crate::all::create_client_from_struct!(RenameAllCamelCaseTest); + let res = client.get("id0").run().await; - let res = client.get("id0").run().await; - assert_eq!( - res.unwrap(), - get::GetOutput { - item: RenameAllCamelCaseTest { - partition_key: "id0".to_owned(), - foo_bar: "john".to_owned(), - project_id: 1, - }, - consumed_capacity: None, - } - ); - } - rt.block_on(example()); + assert_eq!( + res.unwrap(), + get::GetOutput { + item: RenameAllCamelCaseTest { + partition_key: "id0".to_owned(), + foo_bar: "john".to_owned(), + project_id: 1, + }, + consumed_capacity: None, + } + ); } #[derive(Raiden)] @@ -52,28 +45,21 @@ mod tests { project_id: usize, } - #[test] - fn test_rename_all_pascalcase_get() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = RenameAllPascalCaseTest::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); + #[tokio::test] + async fn test_rename_all_pascalcase_get() { + let client = crate::all::create_client_from_struct!(RenameAllPascalCaseTest); + let res = client.get("id0").run().await; - let res = client.get("id0").run().await; - assert_eq!( - res.unwrap(), - get::GetOutput { - item: RenameAllPascalCaseTest { - partition_key: "id0".to_owned(), - foo_bar: "john".to_owned(), - project_id: 1, - }, - consumed_capacity: None, - } - ); - } - rt.block_on(example()); + assert_eq!( + res.unwrap(), + get::GetOutput { + item: RenameAllPascalCaseTest { + partition_key: "id0".to_owned(), + foo_bar: "john".to_owned(), + project_id: 1, + }, + consumed_capacity: None, + } + ); } } diff --git a/raiden/tests/all/scan.rs b/raiden/tests/all/scan.rs index 6582aed6..8d643b5c 100644 --- a/raiden/tests/all/scan.rs +++ b/raiden/tests/all/scan.rs @@ -14,33 +14,26 @@ mod tests { num: usize, } - #[test] - fn test_scan() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = ScanTestData0::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let res = client.scan().run().await; - - assert_eq!( - res.unwrap(), - scan::ScanOutput { - consumed_capacity: None, - count: Some(1), - items: vec![ScanTestData0 { - id: "scanId0".to_owned(), - name: "scanAlice".to_owned(), - year: 2001, - num: 2000 - }], - last_evaluated_key: None, - scanned_count: Some(1), - } - ) - } - rt.block_on(example()); + #[tokio::test] + async fn test_scan() { + let client = crate::all::create_client_from_struct!(ScanTestData0); + let res = client.scan().run().await; + + assert_eq!( + res.unwrap(), + scan::ScanOutput { + consumed_capacity: None, + count: Some(1), + items: vec![ScanTestData0 { + id: "scanId0".to_owned(), + name: "scanAlice".to_owned(), + year: 2001, + num: 2000 + }], + last_evaluated_key: None, + scanned_count: Some(1), + } + ); } #[derive(Raiden)] @@ -53,60 +46,36 @@ mod tests { long_text: String, } - #[test] - fn test_scan_limit_1() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = Test::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let res = client.scan().limit(1).run().await; - assert_eq!(res.unwrap().items.len(), 1); - } - rt.block_on(example()); + #[tokio::test] + async fn test_scan_limit_1() { + let client = crate::all::create_client_from_struct!(Test); + let res = client.scan().limit(1).run().await; + + assert_eq!(res.unwrap().items.len(), 1); } - #[test] - fn test_scan_limit_5() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = Test::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let res = client.scan().limit(5).run().await; - assert_eq!(res.unwrap().items.len(), 5); - } - rt.block_on(example()); + #[tokio::test] + async fn test_scan_limit_5() { + let client = crate::all::create_client_from_struct!(Test); + let res = client.scan().limit(5).run().await; + + assert_eq!(res.unwrap().items.len(), 5); } - #[test] - fn test_scan_no_limit() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = Test::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let res = client.scan().run().await; - assert_eq!(res.unwrap().items.len(), 10); - } - rt.block_on(example()); + #[tokio::test] + async fn test_scan_no_limit() { + let client = crate::all::create_client_from_struct!(Test); + let res = client.scan().run().await; + + assert_eq!(res.unwrap().items.len(), 10); } - #[test] - fn test_scan_over_limit() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = Test::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let res = client.scan().limit(11).run().await; - assert_eq!(res.unwrap().items.len(), 10); - } - rt.block_on(example()); + #[tokio::test] + async fn test_scan_over_limit() { + let client = crate::all::create_client_from_struct!(Test); + let res = client.scan().limit(11).run().await; + + assert_eq!(res.unwrap().items.len(), 10); } #[derive(Raiden)] @@ -120,18 +89,12 @@ mod tests { updated_at: String, } - #[test] - fn test_scan_with_renamed() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = Project::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let res = client.scan().limit(11).run().await; - assert_eq!(res.unwrap().items.len(), 10); - } - rt.block_on(example()); + #[tokio::test] + async fn test_scan_with_renamed() { + let client = crate::all::create_client_from_struct!(Project); + let res = client.scan().limit(11).run().await; + + assert_eq!(res.unwrap().items.len(), 10); } #[derive(Raiden, Debug, PartialEq)] @@ -142,31 +105,24 @@ mod tests { name: String, } - #[test] - fn test_scan_for_projection_expression() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = ScanTestData0a::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let res = client.scan().run().await; - - assert_eq!( - res.unwrap(), - scan::ScanOutput { - consumed_capacity: None, - count: Some(1), - items: vec![ScanTestData0a { - id: "scanId0".to_owned(), - name: "scanAlice".to_owned(), - }], - last_evaluated_key: None, - scanned_count: Some(1), - } - ) - } - rt.block_on(example()); + #[tokio::test] + async fn test_scan_for_projection_expression() { + let client = crate::all::create_client_from_struct!(ScanTestData0a); + let res = client.scan().run().await; + + assert_eq!( + res.unwrap(), + scan::ScanOutput { + consumed_capacity: None, + count: Some(1), + items: vec![ScanTestData0a { + id: "scanId0".to_owned(), + name: "scanAlice".to_owned(), + }], + last_evaluated_key: None, + scanned_count: Some(1), + } + ); } #[derive(Raiden, Debug, PartialEq)] @@ -178,18 +134,12 @@ mod tests { name: String, } - #[test] - fn should_be_scan_when_the_size_is_1mb_or_larger() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = ScanLargeDataTest::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let res = client.scan().run().await; - assert_eq!(res.unwrap().items.len(), 100) - } - rt.block_on(example()); + #[tokio::test] + async fn should_be_scan_when_the_size_is_1mb_or_larger() { + let client = crate::all::create_client_from_struct!(ScanLargeDataTest); + let res = client.scan().run().await; + + assert_eq!(res.unwrap().items.len(), 100); } #[derive(Raiden, Debug)] @@ -206,70 +156,58 @@ mod tests { #[tokio::test] async fn test_simple_filter() { - let client = Scan::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); + let client = crate::all::create_client_from_struct!(Scan); let filter = Scan::filter_expression(Scan::num()).eq(1000); let res = client.scan().filter(filter).run().await.unwrap(); + assert_eq!(res.items.len(), 50); } #[tokio::test] async fn test_size_filter() { - let client = Scan::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); + let client = crate::all::create_client_from_struct!(Scan); let filter = Scan::filter_expression(Scan::name()).size().eq(10); let res = client.scan().filter(filter).run().await.unwrap(); + assert_eq!(res.items.len(), 10); } #[tokio::test] async fn test_or_with_contain_filter() { - let client = Scan::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); + let client = crate::all::create_client_from_struct!(Scan); let filter = Scan::filter_expression(Scan::num()) .eq(1000) .or(Scan::filter_expression(Scan::id()).contains("scanId50")); let res = client.scan().filter(filter).run().await.unwrap(); + assert_eq!(res.items.len(), 51); } #[tokio::test] async fn test_attribute_exists_filter() { - let client = Scan::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); + let client = crate::all::create_client_from_struct!(Scan); let filter = Scan::filter_expression(Scan::option()).attribute_exists(); let res = client.scan().filter(filter).run().await.unwrap(); + assert_eq!(res.items.len(), 50); } #[tokio::test] async fn test_attribute_not_exists_filter() { - let client = Scan::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); + let client = crate::all::create_client_from_struct!(Scan); let filter = Scan::filter_expression(Scan::option()).attribute_not_exists(); let res = client.scan().filter(filter).run().await.unwrap(); + assert_eq!(res.items.len(), 50); } #[tokio::test] async fn test_attribute_type_filter() { - let client = Scan::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); + let client = crate::all::create_client_from_struct!(Scan); let filter = Scan::filter_expression(Scan::option()).attribute_type(raiden::AttributeType::S); let res = client.scan().filter(filter).run().await.unwrap(); + assert_eq!(res.items.len(), 50); } } diff --git a/raiden/tests/all/transact_write.rs b/raiden/tests/all/transact_write.rs index 8f02c90d..85abef49 100644 --- a/raiden/tests/all/transact_write.rs +++ b/raiden/tests/all/transact_write.rs @@ -1,5 +1,26 @@ #[cfg(test)] mod tests { + #[cfg(any(feature = "rusoto", feature = "rusoto_rustls"))] + async fn create_client() -> ::raiden::WriteTx { + ::raiden::WriteTx::new(Region::Custom { + endpoint: "http://localhost:8000".into(), + name: "ap-northeast-1".into(), + }) + } + + #[cfg(feature = "aws-sdk")] + async fn create_client() -> ::raiden::WriteTx { + let sdk_config = ::raiden::aws_sdk::aws_config::defaults( + ::raiden::aws_sdk::config::BehaviorVersion::latest(), + ) + .endpoint_url("http://localhost:8000") + .region(::raiden::config::Region::from_static("ap-northeast-1")) + .load() + .await; + let sdk_client = ::raiden::Client::new(&sdk_config); + + ::raiden::WriteTx::new_with_client(sdk_client) + } #[cfg(test)] use pretty_assertions::assert_eq; @@ -15,86 +36,67 @@ mod tests { name: String, } - #[test] - fn test_minimum_transact_write() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let tx = ::raiden::WriteTx::new(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let cond = User::condition().attr_not_exists(User::id()); - let input = User::put_item_builder() - .id("testId".to_owned()) - .name("bokuweb".to_owned()) - .build(); - let input2 = User::put_item_builder() - .id("testId2".to_owned()) - .name("bokuweb".to_owned()) - .build(); - assert_eq!( - tx.put(User::put(input).condition(cond)) - .put(User::put(input2)) - .run() - .await - .is_ok(), - true, - ) - } - rt.block_on(example()); + #[tokio::test] + async fn test_minimum_transact_write() { + let tx = create_client().await; + let cond = User::condition().attr_not_exists(User::id()); + let input = User::put_item_builder() + .id("testId".to_owned()) + .name("bokuweb".to_owned()) + .build(); + let input2 = User::put_item_builder() + .id("testId2".to_owned()) + .name("bokuweb".to_owned()) + .build(); + + assert_eq!( + tx.put(User::put(input).condition(cond)) + .put(User::put(input2)) + .run() + .await + .is_ok(), + true, + ); } - #[test] - fn test_transact_write_put_and_update() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let tx = ::raiden::WriteTx::new(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let input = User::put_item_builder() - .id("testId".to_owned()) - .name("bokuweb".to_owned()) - .build(); - let set_expression = User::update_expression() - .set(User::name()) - .value("updated!!"); + #[tokio::test] + async fn test_transact_write_put_and_update() { + let tx = create_client().await; + let input = User::put_item_builder() + .id("testId".to_owned()) + .name("bokuweb".to_owned()) + .build(); + let set_expression = User::update_expression() + .set(User::name()) + .value("updated!!"); + let res = tx + .put(User::put(input)) + .update(User::update("testId2").set(set_expression)) + .run() + .await; - let res = tx - .put(User::put(input)) - .update(User::update("testId2").set(set_expression)) - .run() - .await; - assert_eq!(res.is_ok(), true); - } - rt.block_on(example()); + assert_eq!(res.is_ok(), true); } - #[test] - fn test_transact_write_with_prefix_suffix() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let tx = ::raiden::WriteTx::new(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let input = User::put_item_builder() - .id("testId".to_owned()) - .name("bokuweb".to_owned()) - .build(); - assert_eq!( - tx.put( - User::put(input) - .table_prefix("test-") - .table_suffix("-staging"), - ) - .run() - .await - .is_ok(), - true, + #[tokio::test] + async fn test_transact_write_with_prefix_suffix() { + let tx = create_client().await; + let input = User::put_item_builder() + .id("testId".to_owned()) + .name("bokuweb".to_owned()) + .build(); + + assert_eq!( + tx.put( + User::put(input) + .table_prefix("test-") + .table_suffix("-staging"), ) - } - rt.block_on(example()); + .run() + .await + .is_ok(), + true, + ); } use std::sync::atomic::{AtomicUsize, Ordering}; @@ -113,28 +115,23 @@ mod tests { } } - #[test] - fn test_retry() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let tx = ::raiden::WriteTx::new(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let input = User::put_item_builder() - .id("testId".to_owned()) - .name("bokuweb".to_owned()) - .build(); - assert_eq!( - tx.with_retries(Box::new(MyRetryStrategy)) - .put(User::put(input).table_prefix("unknown")) - .run() - .await - .is_err(), - true, - ) - } - rt.block_on(example()); + #[tokio::test] + async fn test_retry() { + let tx = create_client().await; + let input = User::put_item_builder() + .id("testId".to_owned()) + .name("bokuweb".to_owned()) + .build(); + + assert_eq!( + tx.with_retries(Box::new(MyRetryStrategy)) + .put(User::put(input).table_prefix("unknown")) + .run() + .await + .is_err(), + true, + ); + assert_eq!(RETRY_COUNT.load(Ordering::Relaxed), 4) } @@ -145,46 +142,41 @@ mod tests { name: String, } - #[test] - fn test_transact_delete_and_put() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let tx = ::raiden::WriteTx::new(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let input = TxDeleteTestData0::put_item_builder() - .id("testId".to_owned()) - .name("bokuweb".to_owned()) - .build(); - assert_eq!( - tx.put(TxDeleteTestData0::put(input)) - .delete(TxDeleteTestData0::delete("id0")) - .run() - .await - .is_ok(), - true, - ); + #[tokio::test] + async fn test_transact_delete_and_put() { + let tx = create_client().await; + let input = TxDeleteTestData0::put_item_builder() + .id("testId".to_owned()) + .name("bokuweb".to_owned()) + .build(); - let client = TxDeleteTestData0::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let res = client.get("id0").run().await; - assert_eq!( - res.unwrap_err(), - RaidenError::ResourceNotFound("resource not found".to_owned()) - ); - let res = client.get("testId").run().await; - assert_eq!( - res.unwrap().item, - TxDeleteTestData0 { - id: "testId".to_owned(), - name: "bokuweb".to_owned() - } - ); + assert_eq!( + tx.put(TxDeleteTestData0::put(input)) + .delete(TxDeleteTestData0::delete("id0")) + .run() + .await + .is_ok(), + true, + ); + + let client = crate::all::create_client_from_struct!(TxDeleteTestData0); + let res = client.get("id0").run().await; + assert!(res.is_err()); + + if let RaidenError::ResourceNotFound(msg) = res.unwrap_err() { + assert_eq!("resource not found", msg); + } else { + panic!("err should be RaidenError::ResourceNotFound"); } - rt.block_on(example()); + + let res = client.get("testId").run().await; + assert_eq!( + res.unwrap().item, + TxDeleteTestData0 { + id: "testId".to_owned(), + name: "bokuweb".to_owned() + } + ); } #[derive(Raiden, Debug, Clone, PartialEq)] @@ -201,80 +193,64 @@ mod tests { name: String, } - #[test] - fn should_succeed_to_put_when_condition_check_ok() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let tx = ::raiden::WriteTx::new(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let input = TxConditionalCheckTestData0::put_item_builder() - .id("testId0".to_owned()) - .name("bokuweb".to_owned()) - .build(); - let cond = TxConditionalCheckTestData1::condition() - .attr_exists(TxConditionalCheckTestData1::id()); - assert_eq!( - tx.put(TxConditionalCheckTestData0::put(input)) - .condition_check( - TxConditionalCheckTestData1::condition_check("id1").condition(cond) - ) - .run() - .await - .is_ok(), - true, - ); + #[tokio::test] + async fn should_succeed_to_put_when_condition_check_ok() { + let tx = create_client().await; + let input = TxConditionalCheckTestData0::put_item_builder() + .id("testId0".to_owned()) + .name("bokuweb".to_owned()) + .build(); + let cond = + TxConditionalCheckTestData1::condition().attr_exists(TxConditionalCheckTestData1::id()); + assert_eq!( + tx.put(TxConditionalCheckTestData0::put(input)) + .condition_check( + TxConditionalCheckTestData1::condition_check("id1").condition(cond) + ) + .run() + .await + .is_ok(), + true, + ); - let client = TxConditionalCheckTestData0::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let res = client.get("testId0").run().await; - assert_eq!( - res.unwrap().item, - TxConditionalCheckTestData0 { - id: "testId0".to_owned(), - name: "bokuweb".to_owned() - } - ); - } - rt.block_on(example()); + let client = crate::all::create_client_from_struct!(TxConditionalCheckTestData0); + let res = client.get("testId0").run().await; + assert_eq!( + res.unwrap().item, + TxConditionalCheckTestData0 { + id: "testId0".to_owned(), + name: "bokuweb".to_owned() + } + ); } - #[test] - fn should_fail_to_put_when_condition_check_ng() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let tx = ::raiden::WriteTx::new(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let input = TxConditionalCheckTestData0::put_item_builder() - .id("testId1".to_owned()) - .name("bokuweb".to_owned()) - .build(); - let cond = TxConditionalCheckTestData1::condition() - .attr_not_exists(TxConditionalCheckTestData1::id()); + #[tokio::test] + async fn should_fail_to_put_when_condition_check_ng() { + let tx = create_client().await; + let input = TxConditionalCheckTestData0::put_item_builder() + .id("testId1".to_owned()) + .name("bokuweb".to_owned()) + .build(); + let cond = TxConditionalCheckTestData1::condition() + .attr_not_exists(TxConditionalCheckTestData1::id()); - let res = tx - .put(TxConditionalCheckTestData0::put(input)) - .condition_check( - TxConditionalCheckTestData1::condition_check("id1").condition(cond), - ) - .run() - .await; - assert_eq!(res.is_err(), true,); + let res = tx + .put(TxConditionalCheckTestData0::put(input)) + .condition_check(TxConditionalCheckTestData1::condition_check("id1").condition(cond)) + .run() + .await; + assert!(res.is_err()); + + if let RaidenError::TransactionCanceled { reasons, .. } = res.unwrap_err() { assert_eq!( - res.unwrap_err(), - RaidenError::TransactionCanceled { - reasons: RaidenTransactionCancellationReasons(vec![ - None, - Some(RaidenTransactionCancellationReason::ConditionalCheckFailed), - ]), - } + RaidenTransactionCancellationReasons(vec![ + None, + Some(RaidenTransactionCancellationReason::ConditionalCheckFailed), + ]), + reasons ); + } else { + panic!("err should be RaidenError::TransactionCanceled"); } - rt.block_on(example()); } } diff --git a/raiden/tests/all/update.rs b/raiden/tests/all/update.rs index 9b0f27b2..caf8a16d 100644 --- a/raiden/tests/all/update.rs +++ b/raiden/tests/all/update.rs @@ -15,60 +15,48 @@ mod tests { age: usize, } - #[test] - fn test_update() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = User::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let set_name_expression = User::update_expression() - .set(User::name()) - .value("updated!!"); - let set_age_expression = User::update_expression().set(User::age()).value(12); - let res = client - .update("id0") - .set(set_name_expression) - .set(set_age_expression) - .return_all_new() - .run() - .await - .unwrap(); - assert_eq!( - res.item, - Some(User { - id: "id0".to_owned(), - name: "updated!!".to_owned(), - age: 12, - }) - ); - } - rt.block_on(example()); + #[tokio::test] + async fn test_update() { + let client = crate::all::create_client_from_struct!(User); + let set_name_expression = User::update_expression() + .set(User::name()) + .value("updated!!"); + let set_age_expression = User::update_expression().set(User::age()).value(12); + let res = client + .update("id0") + .set(set_name_expression) + .set(set_age_expression) + .return_all_new() + .run() + .await + .unwrap(); + + assert_eq!( + res.item, + Some(User { + id: "id0".to_owned(), + name: "updated!!".to_owned(), + age: 12, + }) + ); } - #[test] - fn test_update_with_invalid_key_with_condition() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = User::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let cond = User::condition().attr_exists(User::id()); - let set_expression = User::update_expression() - .set(User::name()) - .value("updated!!"); - let res = client - .update("invalid_key!!!!!!") - .return_all_new() - .condition(cond) - .set(set_expression) - .run() - .await; - assert_eq!(res.is_err(), true); - } - rt.block_on(example()); + #[tokio::test] + async fn test_update_with_invalid_key_with_condition() { + let client = crate::all::create_client_from_struct!(User); + let cond = User::condition().attr_exists(User::id()); + let set_expression = User::update_expression() + .set(User::name()) + .value("updated!!"); + let res = client + .update("invalid_key!!!!!!") + .return_all_new() + .condition(cond) + .set(set_expression) + .run() + .await; + + assert_eq!(res.is_err(), true); } #[derive(Raiden)] @@ -81,26 +69,20 @@ mod tests { unstored: usize, } - #[test] - fn test_update_with_unstored() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = UserWithUnStored::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let set_expression = UserWithUnStored::update_expression() - .set(UserWithUnStored::name()) - .value("updated!!"); - let res = client - .update("id0") - .set(set_expression) - .run() - .await - .unwrap(); - assert_eq!(res.item, None,); - } - rt.block_on(example()); + #[tokio::test] + async fn test_update_with_unstored() { + let client = crate::all::create_client_from_struct!(UserWithUnStored); + let set_expression = UserWithUnStored::update_expression() + .set(UserWithUnStored::name()) + .value("updated!!"); + let res = client + .update("id0") + .set(set_expression) + .run() + .await + .unwrap(); + + assert_eq!(res.item, None); } #[derive(Raiden, Debug, Clone, PartialEq)] @@ -112,34 +94,28 @@ mod tests { age: usize, } - #[test] - fn test_update_with_sort_key() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = UpdateTestData1::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let set_expression = UpdateTestData1::update_expression() - .set(UpdateTestData1::name()) - .value("bob"); - let res = client - .update("id0", 36_usize) - .set(set_expression) - .return_all_new() - .run() - .await - .unwrap(); - assert_eq!( - res.item, - Some(UpdateTestData1 { - id: "id0".to_owned(), - name: "bob".to_owned(), - age: 36 - }) - ); - } - rt.block_on(example()); + #[tokio::test] + async fn test_update_with_sort_key() { + let client = crate::all::create_client_from_struct!(UpdateTestData1); + let set_expression = UpdateTestData1::update_expression() + .set(UpdateTestData1::name()) + .value("bob"); + let res = client + .update("id0", 36_usize) + .set(set_expression) + .return_all_new() + .run() + .await + .unwrap(); + + assert_eq!( + res.item, + Some(UpdateTestData1 { + id: "id0".to_owned(), + name: "bob".to_owned(), + age: 36 + }) + ); } #[derive(Raiden, Debug, Clone, PartialEq)] @@ -149,78 +125,59 @@ mod tests { sset: std::collections::HashSet, } - #[test] - fn test_update_with_only_attr() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = EmptySetTestData0::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let set_expression = EmptySetTestData0::update_expression() - .set(EmptySetTestData0::sset()) - .attr(EmptySetTestData0::sset()); - let res = client - .update("id1") - .set(set_expression) - .return_all_new() - .run() - .await; - assert_eq!(res.is_ok(), true); - } - rt.block_on(example()); + #[tokio::test] + async fn test_update_with_only_attr() { + let client = crate::all::create_client_from_struct!(EmptySetTestData0); + let set_expression = EmptySetTestData0::update_expression() + .set(EmptySetTestData0::sset()) + .attr(EmptySetTestData0::sset()); + let res = client + .update("id1") + .set(set_expression) + .return_all_new() + .run() + .await; + + assert_eq!(res.is_ok(), true); } - #[test] - fn test_update_empty_set_sort_key() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = EmptySetTestData0::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let sset: std::collections::HashSet = std::collections::HashSet::new(); - let expected_sset: std::collections::HashSet = std::collections::HashSet::new(); - let set_expression = EmptySetTestData0::update_expression() - .set(EmptySetTestData0::sset()) - .value(sset); - let res = client - .update("id0") - .set(set_expression) - .return_all_new() - .run() - .await; - assert!(res.is_ok()); - assert_eq!(res.unwrap().item.unwrap().sset, expected_sset); - } - rt.block_on(example()); + #[tokio::test] + async fn test_update_empty_set_sort_key() { + let client = crate::all::create_client_from_struct!(EmptySetTestData0); + let sset: std::collections::HashSet = std::collections::HashSet::new(); + let expected_sset: std::collections::HashSet = std::collections::HashSet::new(); + let set_expression = EmptySetTestData0::update_expression() + .set(EmptySetTestData0::sset()) + .value(sset); + let res = client + .update("id0") + .set(set_expression) + .return_all_new() + .run() + .await; + + assert!(res.is_ok()); + assert_eq!(res.unwrap().item.unwrap().sset, expected_sset); } - #[test] - fn test_add_with_empty_hash_set() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = EmptySetTestData0::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let sset: std::collections::HashSet = std::collections::HashSet::new(); - let mut expected_sset: std::collections::HashSet = - std::collections::HashSet::new(); - expected_sset.insert("Hello".to_owned()); - let expression = EmptySetTestData0::update_expression() - .add(EmptySetTestData0::sset()) - .value(sset); - let res = client - .update("id0") - .add(expression) - .return_all_new() - .run() - .await; - assert_eq!(res.is_ok(), true); - assert_eq!(res.unwrap().item.unwrap().sset, expected_sset); - } - rt.block_on(example()); + #[tokio::test] + async fn test_add_with_empty_hash_set() { + let client = crate::all::create_client_from_struct!(EmptySetTestData0); + let sset: std::collections::HashSet = std::collections::HashSet::new(); + let mut expected_sset: std::collections::HashSet = std::collections::HashSet::new(); + expected_sset.insert("Hello".to_owned()); + let expression = EmptySetTestData0::update_expression() + .add(EmptySetTestData0::sset()) + .value(sset); + let res = client + .update("id0") + .add(expression) + .return_all_new() + .run() + .await; + + assert_eq!(res.is_ok(), true); + assert_eq!(res.unwrap().item.unwrap().sset, expected_sset); } #[derive(Raiden, Debug, Clone, PartialEq)] @@ -230,33 +187,25 @@ mod tests { sset: std::collections::HashSet, } - #[test] - fn test_update_delete_sset() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = UpdateDeleteTestData0::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let mut delete_sset: std::collections::HashSet = - std::collections::HashSet::new(); - delete_sset.insert("foo".to_owned()); - let delete_expression = UpdateDeleteTestData0::update_expression() - .delete(UpdateDeleteTestData0::sset()) - .value(delete_sset); - let mut expected_sset: std::collections::HashSet = - std::collections::HashSet::new(); - expected_sset.insert("bar".to_owned()); - - let res = client - .update("id0") - .delete(delete_expression) - .return_all_new() - .run() - .await; - assert_eq!(res.unwrap().item.unwrap().sset, expected_sset); - } - rt.block_on(example()); + #[tokio::test] + async fn test_update_delete_sset() { + let client = crate::all::create_client_from_struct!(UpdateDeleteTestData0); + let mut delete_sset: std::collections::HashSet = std::collections::HashSet::new(); + delete_sset.insert("foo".to_owned()); + let delete_expression = UpdateDeleteTestData0::update_expression() + .delete(UpdateDeleteTestData0::sset()) + .value(delete_sset); + let mut expected_sset: std::collections::HashSet = std::collections::HashSet::new(); + expected_sset.insert("bar".to_owned()); + + let res = client + .update("id0") + .delete(delete_expression) + .return_all_new() + .run() + .await; + + assert_eq!(res.unwrap().item.unwrap().sset, expected_sset); } #[derive(Raiden, Debug, Clone, PartialEq)] @@ -266,83 +215,63 @@ mod tests { sset: std::collections::HashSet, } - #[test] - fn test_update_add_sset() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = UpdateAddTestData0::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let mut add_sset: std::collections::HashSet = std::collections::HashSet::new(); - add_sset.insert("added".to_owned()); - let add_expression = UpdateAddTestData0::update_expression() - .add(UpdateAddTestData0::sset()) - .value(add_sset); - let mut expected_sset: std::collections::HashSet = - std::collections::HashSet::new(); - expected_sset.insert("foo".to_owned()); - expected_sset.insert("bar".to_owned()); - expected_sset.insert("added".to_owned()); - - let res = client - .update("id0") - .add(add_expression) - .return_all_new() - .run() - .await; - assert_eq!(res.unwrap().item.unwrap().sset, expected_sset); - } - rt.block_on(example()); + #[tokio::test] + async fn test_update_add_sset() { + let client = crate::all::create_client_from_struct!(UpdateAddTestData0); + let mut add_sset: std::collections::HashSet = std::collections::HashSet::new(); + add_sset.insert("added".to_owned()); + let add_expression = UpdateAddTestData0::update_expression() + .add(UpdateAddTestData0::sset()) + .value(add_sset); + let mut expected_sset: std::collections::HashSet = std::collections::HashSet::new(); + expected_sset.insert("foo".to_owned()); + expected_sset.insert("bar".to_owned()); + expected_sset.insert("added".to_owned()); + + let res = client + .update("id0") + .add(add_expression) + .return_all_new() + .run() + .await; + + assert_eq!(res.unwrap().item.unwrap().sset, expected_sset); } - #[test] - fn test_update_add_sset_to_empty() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = UpdateAddTestData0::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let mut add_sset: std::collections::HashSet = std::collections::HashSet::new(); - add_sset.insert("added".to_owned()); - let add_expression = UpdateAddTestData0::update_expression() - .add(UpdateAddTestData0::sset()) - .value(add_sset); - let mut expected_sset: std::collections::HashSet = - std::collections::HashSet::new(); - expected_sset.insert("added".to_owned()); - - let res = client - .update("id2") - .add(add_expression) - .return_all_new() - .run() - .await; - assert_eq!(res.unwrap().item.unwrap().sset, expected_sset); - } - rt.block_on(example()); + #[tokio::test] + async fn test_update_add_sset_to_empty() { + let client = crate::all::create_client_from_struct!(UpdateAddTestData0); + let mut add_sset: std::collections::HashSet = std::collections::HashSet::new(); + add_sset.insert("added".to_owned()); + let add_expression = UpdateAddTestData0::update_expression() + .add(UpdateAddTestData0::sset()) + .value(add_sset); + let mut expected_sset: std::collections::HashSet = std::collections::HashSet::new(); + expected_sset.insert("added".to_owned()); + + let res = client + .update("id2") + .add(add_expression) + .return_all_new() + .run() + .await; + + assert_eq!(res.unwrap().item.unwrap().sset, expected_sset); } - #[test] - fn test_update_to_empty_string() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = User::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let set_name_expression = User::update_expression().set(User::name()).value(""); - let res = client - .update("id0") - .set(set_name_expression) - .return_all_new() - .run() - .await - .unwrap(); - assert_eq!(res.item.unwrap().name, "".to_owned(),); - } - rt.block_on(example()); + #[tokio::test] + async fn test_update_to_empty_string() { + let client = crate::all::create_client_from_struct!(User); + let set_name_expression = User::update_expression().set(User::name()).value(""); + let res = client + .update("id0") + .set(set_name_expression) + .return_all_new() + .run() + .await + .unwrap(); + + assert_eq!(res.item.unwrap().name, "".to_owned()); } #[derive(Raiden, Debug, Clone, PartialEq)] @@ -352,43 +281,38 @@ mod tests { name: Option, } - #[test] - fn test_update_remove_sset() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = UpdateRemoveTestData0::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - let res = client - .update("id1") - .remove(UpdateRemoveTestData0::name()) - .return_all_new() - .run() - .await; - assert_eq!( - res.unwrap().item.unwrap(), - UpdateRemoveTestData0 { - id: "id1".to_owned(), - name: None - } - ); - - let res = client - .update("id2") - .remove(UpdateRemoveTestData0::name()) - .return_all_new() - .run() - .await; - assert_eq!( - res.unwrap().item.unwrap(), - UpdateRemoveTestData0 { - id: "id2".to_owned(), - name: None - } - ); - } - rt.block_on(example()); + #[tokio::test] + async fn test_update_remove_sset() { + let client = crate::all::create_client_from_struct!(UpdateRemoveTestData0); + let res = client + .update("id1") + .remove(UpdateRemoveTestData0::name()) + .return_all_new() + .run() + .await; + + assert_eq!( + res.unwrap().item.unwrap(), + UpdateRemoveTestData0 { + id: "id1".to_owned(), + name: None + } + ); + + let res = client + .update("id2") + .remove(UpdateRemoveTestData0::name()) + .return_all_new() + .run() + .await; + + assert_eq!( + res.unwrap().item.unwrap(), + UpdateRemoveTestData0 { + id: "id2".to_owned(), + name: None + } + ); } #[derive(Raiden, Debug, Clone, PartialEq)] @@ -399,143 +323,107 @@ mod tests { name: String, } - #[test] - fn should_update_with_contains_condition_in_sset() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = UpdateWithContainsInSetCondition::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); + #[tokio::test] + async fn should_update_with_contains_condition_in_sset() { + let client = crate::all::create_client_from_struct!(UpdateWithContainsInSetCondition); + let set_expression = UpdateWithContainsInSetCondition::update_expression() + .set(UpdateWithContainsInSetCondition::name()) + .value("Changed"); + let cond = UpdateWithContainsInSetCondition::condition().contains( + UpdateWithContainsInSetCondition::sset(), + "Hello".to_string(), + ); - let set_expression = UpdateWithContainsInSetCondition::update_expression() - .set(UpdateWithContainsInSetCondition::name()) - .value("Changed"); + let res = client + .update("id0") + .set(set_expression) + .condition(cond) + .return_all_new() + .run() + .await; - let cond = UpdateWithContainsInSetCondition::condition().contains( - UpdateWithContainsInSetCondition::sset(), - "Hello".to_string(), - ); - - let res = client - .update("id0") - .set(set_expression) - .condition(cond) - .return_all_new() - .run() - .await; - assert_eq!(res.is_ok(), true); - } - rt.block_on(example()); + assert_eq!(res.is_ok(), true); } - #[test] - fn should_not_update_with_contains_condition_in_sset() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = UpdateWithContainsInSetCondition::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); + #[tokio::test] + async fn should_not_update_with_contains_condition_in_sset() { + let client = crate::all::create_client_from_struct!(UpdateWithContainsInSetCondition); + let set_expression = UpdateWithContainsInSetCondition::update_expression() + .set(UpdateWithContainsInSetCondition::name()) + .value("Changed"); + let cond = UpdateWithContainsInSetCondition::condition().contains( + UpdateWithContainsInSetCondition::sset(), + "World".to_string(), + ); + + let res = client + .update("id0") + .set(set_expression) + .condition(cond) + .return_all_new() + .run() + .await; - let set_expression = UpdateWithContainsInSetCondition::update_expression() - .set(UpdateWithContainsInSetCondition::name()) - .value("Changed"); + assert_eq!(res.is_err(), true); + } - let cond = UpdateWithContainsInSetCondition::condition().contains( + #[tokio::test] + async fn should_not_update_with_or_condition_failed() { + let client = crate::all::create_client_from_struct!(UpdateWithContainsInSetCondition); + let set_expression = UpdateWithContainsInSetCondition::update_expression() + .set(UpdateWithContainsInSetCondition::name()) + .value("Changed"); + let cond = UpdateWithContainsInSetCondition::condition() + .contains( UpdateWithContainsInSetCondition::sset(), - "World".to_string(), - ); - - let res = client - .update("id0") - .set(set_expression) - .condition(cond) - .return_all_new() - .run() - .await; - assert_eq!(res.is_err(), true); - } - rt.block_on(example()); - } + "Merhaba".to_string(), + ) + .or(UpdateWithContainsInSetCondition::condition().contains( + UpdateWithContainsInSetCondition::sset(), + "Bonjour".to_string(), + )); - #[test] - fn should_not_update_with_or_condition_failed() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = UpdateWithContainsInSetCondition::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - - let set_expression = UpdateWithContainsInSetCondition::update_expression() - .set(UpdateWithContainsInSetCondition::name()) - .value("Changed"); - - let cond = UpdateWithContainsInSetCondition::condition() - .contains( - UpdateWithContainsInSetCondition::sset(), - "Merhaba".to_string(), - ) - .or(UpdateWithContainsInSetCondition::condition().contains( - UpdateWithContainsInSetCondition::sset(), - "Bonjour".to_string(), - )); - - let res = client - .update("id0") - .set(set_expression) - .condition(cond) - .return_all_new() - .run() - .await; - assert_eq!(res.is_err(), true); - } - rt.block_on(example()); + let res = client + .update("id0") + .set(set_expression) + .condition(cond) + .return_all_new() + .run() + .await; + + assert_eq!(res.is_err(), true); } - #[test] - fn should_update_with_or_condition() { - let rt = tokio::runtime::Runtime::new().unwrap(); - async fn example() { - let client = UpdateWithContainsInSetCondition::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - - let set_expression = UpdateWithContainsInSetCondition::update_expression() - .set(UpdateWithContainsInSetCondition::name()) - .value("Changed"); - - let cond = UpdateWithContainsInSetCondition::condition() - .contains( - UpdateWithContainsInSetCondition::sset(), - "Hello".to_string(), - ) - .or(UpdateWithContainsInSetCondition::condition().contains( - UpdateWithContainsInSetCondition::sset(), - "Bonjour".to_string(), - )); - - let res = client - .update("id0") - .set(set_expression) - .condition(cond) - .return_all_new() - .run() - .await; - assert_eq!(res.is_ok(), true); - } - rt.block_on(example()); + #[tokio::test] + async fn should_update_with_or_condition() { + let client = crate::all::create_client_from_struct!(UpdateWithContainsInSetCondition); + let set_expression = UpdateWithContainsInSetCondition::update_expression() + .set(UpdateWithContainsInSetCondition::name()) + .value("Changed"); + let cond = UpdateWithContainsInSetCondition::condition() + .contains( + UpdateWithContainsInSetCondition::sset(), + "Hello".to_string(), + ) + .or(UpdateWithContainsInSetCondition::condition().contains( + UpdateWithContainsInSetCondition::sset(), + "Bonjour".to_string(), + )); + + let res = client + .update("id0") + .set(set_expression) + .condition(cond) + .return_all_new() + .run() + .await; + + assert_eq!(res.is_ok(), true); } #[tokio::test] async fn should_set_if_attr_not_exists() { - let client = User::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); - + let client = crate::all::create_client_from_struct!(User); let set_name_expression = User::update_expression() .set(User::name()) .value("updated") @@ -550,6 +438,7 @@ mod tests { .run() .await .unwrap(); + assert_eq!( res.item, Some(User { @@ -562,10 +451,7 @@ mod tests { #[tokio::test] async fn should_not_set_if_attr_exists() { - let client = User::client(Region::Custom { - endpoint: "http://localhost:8000".into(), - name: "ap-northeast-1".into(), - }); + let client = crate::all::create_client_from_struct!(User); let set_name_expression = User::update_expression().set(User::name()).value("created"); let set_age_expression = User::update_expression().set(User::age()).value(12); diff --git a/rust-toolchain b/rust-toolchain index 65ee0959..32a6ce3c 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -1.67.0 +1.76.0 diff --git a/setup/dynamo_util.ts b/setup/dynamo_util.ts index 6141c754..49ce3d36 100644 --- a/setup/dynamo_util.ts +++ b/setup/dynamo_util.ts @@ -18,7 +18,10 @@ export async function createTableAndPutItems( client: DynamoDBClient, { table, items }: CreateAndPut, ) { + console.group(); + await createTable(client, table); + console.log("table created"); // NOTE: Running `put` operations concurrently with `Promise.all` would lead to running out of write buffer. for (const item of items) { @@ -27,6 +30,9 @@ export async function createTableAndPutItems( Item: item, }); } + + console.log("items are put"); + console.groupEnd(); } export function getCredFromEnv(): { diff --git a/setup/fixtures/hello.ts b/setup/fixtures/hello.ts new file mode 100644 index 00000000..e70d1ada --- /dev/null +++ b/setup/fixtures/hello.ts @@ -0,0 +1,26 @@ +import type { CreateAndPut } from "../dynamo_util.ts"; + +export const hello: CreateAndPut = { + table: { + TableName: "hello", + KeySchema: [ + { AttributeName: "id", KeyType: "HASH" }, + { AttributeName: "year", KeyType: "RANGE" }, + ], + AttributeDefinitions: [ + { AttributeName: "id", AttributeType: "S" }, + { AttributeName: "year", AttributeType: "N" }, + ], + ProvisionedThroughput: { ReadCapacityUnits: 5, WriteCapacityUnits: 5 }, + }, + items: [ + { + id: { S: "bokuweb" }, + year: { N: "2019" }, + }, + { + id: { S: "raiden" }, + year: { N: "2020" }, + }, + ], +}; diff --git a/setup/setup.ts b/setup/setup.ts index eb632aaf..09c77bd7 100644 --- a/setup/setup.ts +++ b/setup/setup.ts @@ -1,42 +1,43 @@ import { DynamoDBClient } from "./deps.ts"; -import { createTableAndPutItems, getCredFromEnv } from "./dynamo_util.ts"; -import { user } from "./fixtures/user.ts"; -import { floatTest } from "./fixtures/float_test.ts"; -import { queryTestData0 } from "./fixtures/query_test_data_0.ts"; -import { queryTestData1 } from "./fixtures/query_test_data_1.ts"; -import { renameTestData0 } from "./fixtures/rename_test_data_0.ts"; -import { renameAllCamelCaseTestData0 } from "./fixtures/rename_all_camel_case_test_data_0.ts"; -import { renameAllPascalCaseTestData0 } from "./fixtures/rename_all_pascal_case_test_data_0.ts"; -import { updateTestData0 } from "./fixtures/update_test_data_0.ts"; -import { updateTestData1 } from "./fixtures/update_test_data_1.ts"; -import { putItemConditionData0 } from "./fixtures/put_item_condition_data_0.ts"; -import { lastEvaluateKeyData } from "./fixtures/last_evaluate_key_data.ts"; -import { project } from "./fixtures/project.ts"; +import { batchDeleteTest0 } from "./fixtures/batch_delete_test_0.ts"; +import { batchDeleteTest1 } from "./fixtures/batch_delete_test_1.ts"; import { batchTest0 } from "./fixtures/batch_test_0.ts"; import { batchTest1 } from "./fixtures/batch_test_1.ts"; import { batchTest2 } from "./fixtures/batch_test_2.ts"; -import { batchDeleteTest0 } from "./fixtures/batch_delete_test_0.ts"; -import { batchDeleteTest1 } from "./fixtures/batch_delete_test_1.ts"; -import { testUserStaging } from "./fixtures/test_user_staging.ts"; +import { createTableAndPutItems, getCredFromEnv } from "./dynamo_util.ts"; import { deleteTest0 } from "./fixtures/delete_test_0.ts"; import { deleteTest1 } from "./fixtures/delete_test_1.ts"; -import { scanTestData0 } from "./fixtures/scan_test_data_0.ts"; -import { scanWithFilterTestData0 } from "./fixtures/scan_with_filter_test_data_0.ts"; +import { emptyPutTestData0 } from "./fixtures/empty_put_test_data_0.ts"; import { emptySetTestData0 } from "./fixtures/empty_set_test_data_0.ts"; import { emptyStringTestData0 } from "./fixtures/empty_string_test_data_0.ts"; -import { useDefaultForNull } from "./fixtures/use_default_for_null_data.ts"; -import { updateDeleteTestData0 } from "./fixtures/update_delete_test_data_0.ts"; -import { updateAddTestData0 } from "./fixtures/update_add_test_data_0.ts"; -import { emptyPutTestData0 } from "./fixtures/empty_put_test_data_0.ts"; +import { floatTest } from "./fixtures/float_test.ts"; +import { hello } from "./fixtures/hello.ts"; +import { lastEvaluateKeyData } from "./fixtures/last_evaluate_key_data.ts"; +import { project } from "./fixtures/project.ts"; +import { putItemConditionData0 } from "./fixtures/put_item_condition_data_0.ts"; +import { queryLargeDataTest } from "./fixtures/query_large_data_test.ts"; +import { queryTestData0 } from "./fixtures/query_test_data_0.ts"; +import { queryTestData1 } from "./fixtures/query_test_data_1.ts"; +import { renameAllCamelCaseTestData0 } from "./fixtures/rename_all_camel_case_test_data_0.ts"; +import { renameAllPascalCaseTestData0 } from "./fixtures/rename_all_pascal_case_test_data_0.ts"; +import { renameTestData0 } from "./fixtures/rename_test_data_0.ts"; import { reservedTestData0 } from "./fixtures/reserved_test_data_0.ts"; -import { useDefaultTestData0 } from "./fixtures/use_default_test_data_0.ts"; -import { txDeleteTestData0 } from "./fixtures/tx_delete_test_data_0.ts"; +import { scanLargeDataTest } from "./fixtures/scan_large_data_test.ts"; +import { scanTestData0 } from "./fixtures/scan_test_data_0.ts"; +import { scanWithFilterTestData0 } from "./fixtures/scan_with_filter_test_data_0.ts"; +import { testUserStaging } from "./fixtures/test_user_staging.ts"; import { txConditionalCheckTestData0 } from "./fixtures/tx_conditional_check_test_data_0.ts"; import { txConditionalCheckTestData1 } from "./fixtures/tx_conditional_check_test_data_1.ts"; +import { txDeleteTestData0 } from "./fixtures/tx_delete_test_data_0.ts"; +import { updateAddTestData0 } from "./fixtures/update_add_test_data_0.ts"; +import { updateDeleteTestData0 } from "./fixtures/update_delete_test_data_0.ts"; import { updateRemoveTestData0 } from "./fixtures/update_remove_test_data_0.ts"; +import { updateTestData0 } from "./fixtures/update_test_data_0.ts"; +import { updateTestData1 } from "./fixtures/update_test_data_1.ts"; import { updateWithContainsInSetCondition } from "./fixtures/update_with_contains_in_set_condition.ts"; -import { queryLargeDataTest } from "./fixtures/query_large_data_test.ts"; -import { scanLargeDataTest } from "./fixtures/scan_large_data_test.ts"; +import { useDefaultForNull } from "./fixtures/use_default_for_null_data.ts"; +import { useDefaultTestData0 } from "./fixtures/use_default_test_data_0.ts"; +import { user } from "./fixtures/user.ts"; const client = new DynamoDBClient({ region: "ap-northeast-1", @@ -45,43 +46,44 @@ const client = new DynamoDBClient({ }); const data = [ - user, - floatTest, - queryTestData0, - queryTestData1, - renameTestData0, - renameAllCamelCaseTestData0, - renameAllPascalCaseTestData0, - updateTestData0, - updateTestData1, - putItemConditionData0, - lastEvaluateKeyData, - project, + batchDeleteTest0, + batchDeleteTest1, batchTest0, batchTest1, batchTest2, - batchDeleteTest0, - batchDeleteTest1, - testUserStaging, deleteTest0, deleteTest1, - scanTestData0, - scanWithFilterTestData0, + emptyPutTestData0, emptySetTestData0, emptyStringTestData0, - updateDeleteTestData0, - updateAddTestData0, - emptyPutTestData0, + floatTest, + hello, + lastEvaluateKeyData, + project, + putItemConditionData0, + queryLargeDataTest, + queryTestData0, + queryTestData1, + renameAllCamelCaseTestData0, + renameAllPascalCaseTestData0, + renameTestData0, reservedTestData0, - useDefaultTestData0, - txDeleteTestData0, + scanLargeDataTest, + scanTestData0, + scanWithFilterTestData0, + testUserStaging, txConditionalCheckTestData0, txConditionalCheckTestData1, + txDeleteTestData0, + updateAddTestData0, + updateDeleteTestData0, updateRemoveTestData0, + updateTestData0, + updateTestData1, updateWithContainsInSetCondition, - queryLargeDataTest, - scanLargeDataTest, useDefaultForNull, + useDefaultTestData0, + user, ]; // NOTE: Running these operations concurrently with `Promise.all` would lead to running out of write buffer.