max / makenotwork
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
- Claude-Session
- https://claude.ai/code/session_01DwpiantpUgohzML4xr6KeQ
3 files changed,
+639 insertions,
-9 deletions
| @@ -8,6 +8,16 @@ | |||
| 8 | 8 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 9 | 9 | checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" | |
| 10 | 10 | ||
| 11 | + | [[package]] | |
| 12 | + | name = "assert-json-diff" | |
| 13 | + | version = "2.0.2" | |
| 14 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 15 | + | checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" | |
| 16 | + | dependencies = [ | |
| 17 | + | "serde", | |
| 18 | + | "serde_json", | |
| 19 | + | ] | |
| 20 | + | ||
| 11 | 21 | [[package]] | |
| 12 | 22 | name = "atomic-waker" | |
| 13 | 23 | version = "1.1.2" | |
| @@ -245,17 +255,23 @@ | |||
| 245 | 255 | checksum = "6a2f165a7feee6f263028b899d0a181987f4fa7179a6411a32a439fba7c5f769" | |
| 246 | 256 | dependencies = [ | |
| 247 | 257 | "aws-smithy-async", | |
| 258 | + | "aws-smithy-protocol-test", | |
| 248 | 259 | "aws-smithy-runtime-api", | |
| 249 | 260 | "aws-smithy-types", | |
| 261 | + | "bytes", | |
| 250 | 262 | "h2", | |
| 251 | 263 | "http 1.4.0", | |
| 264 | + | "http-body 1.0.1", | |
| 252 | 265 | "hyper", | |
| 253 | 266 | "hyper-rustls", | |
| 254 | 267 | "hyper-util", | |
| 268 | + | "indexmap", | |
| 255 | 269 | "pin-project-lite", | |
| 256 | 270 | "rustls", | |
| 257 | 271 | "rustls-native-certs", | |
| 258 | 272 | "rustls-pki-types", | |
| 273 | + | "serde", | |
| 274 | + | "serde_json", | |
| 259 | 275 | "tokio", | |
| 260 | 276 | "tokio-rustls", | |
| 261 | 277 | "tower", | |
| @@ -280,6 +296,25 @@ | |||
| 280 | 296 | "aws-smithy-runtime-api", | |
| 281 | 297 | ] | |
| 282 | 298 | ||
| 299 | + | [[package]] | |
| 300 | + | name = "aws-smithy-protocol-test" | |
| 301 | + | version = "0.63.14" | |
| 302 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 303 | + | checksum = "5b227aa94af99a8e5ee52551cc7e3ee30a217019ef99207b6f0b7a1527685941" | |
| 304 | + | dependencies = [ | |
| 305 | + | "assert-json-diff", | |
| 306 | + | "aws-smithy-runtime-api", | |
| 307 | + | "base64-simd", | |
| 308 | + | "cbor-diag", | |
| 309 | + | "ciborium", | |
| 310 | + | "http 0.2.12", | |
| 311 | + | "pretty_assertions", | |
| 312 | + | "regex-lite", | |
| 313 | + | "roxmltree", | |
| 314 | + | "serde_json", | |
| 315 | + | "thiserror", | |
| 316 | + | ] | |
| 317 | + | ||
| 283 | 318 | [[package]] | |
| 284 | 319 | name = "aws-smithy-query" | |
| 285 | 320 | version = "0.60.15" | |
| @@ -340,7 +375,7 @@ | |||
| 340 | 375 | dependencies = [ | |
| 341 | 376 | "proc-macro2", | |
| 342 | 377 | "quote", | |
| 343 | - | "syn", | |
| 378 | + | "syn 2.0.117", | |
| 344 | 379 | ] | |
| 345 | 380 | ||
| 346 | 381 | [[package]] | |
| @@ -444,6 +479,15 @@ | |||
| 444 | 479 | "hybrid-array", | |
| 445 | 480 | ] | |
| 446 | 481 | ||
| 482 | + | [[package]] | |
| 483 | + | name = "bs58" | |
| 484 | + | version = "0.5.1" | |
| 485 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 486 | + | checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" | |
| 487 | + | dependencies = [ | |
| 488 | + | "tinyvec", | |
| 489 | + | ] | |
| 490 | + | ||
| 447 | 491 | [[package]] | |
| 448 | 492 | name = "bumpalo" | |
| 449 | 493 | version = "3.20.2" | |
| @@ -466,6 +510,25 @@ | |||
| 466 | 510 | "either", | |
| 467 | 511 | ] | |
| 468 | 512 | ||
| 513 | + | [[package]] | |
| 514 | + | name = "cbor-diag" | |
| 515 | + | version = "0.1.12" | |
| 516 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 517 | + | checksum = "dc245b6ecd09b23901a4fbad1ad975701fd5061ceaef6afa93a2d70605a64429" | |
| 518 | + | dependencies = [ | |
| 519 | + | "bs58", | |
| 520 | + | "chrono", | |
| 521 | + | "data-encoding", | |
| 522 | + | "half", | |
| 523 | + | "nom", | |
| 524 | + | "num-bigint", | |
| 525 | + | "num-rational", | |
| 526 | + | "num-traits", | |
| 527 | + | "separator", | |
| 528 | + | "url", | |
| 529 | + | "uuid", | |
| 530 | + | ] | |
| 531 | + | ||
| 469 | 532 | [[package]] | |
| 470 | 533 | name = "cc" | |
| 471 | 534 | version = "1.2.59" | |
| @@ -482,6 +545,42 @@ | |||
| 482 | 545 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 483 | 546 | checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" | |
| 484 | 547 | ||
| 548 | + | [[package]] | |
| 549 | + | name = "chrono" | |
| 550 | + | version = "0.4.45" | |
| 551 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 552 | + | checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" | |
| 553 | + | dependencies = [ | |
| 554 | + | "num-traits", | |
| 555 | + | ] | |
| 556 | + | ||
| 557 | + | [[package]] | |
| 558 | + | name = "ciborium" | |
| 559 | + | version = "0.2.2" | |
| 560 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 561 | + | checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" | |
| 562 | + | dependencies = [ | |
| 563 | + | "ciborium-io", | |
| 564 | + | "ciborium-ll", | |
| 565 | + | "serde", | |
| 566 | + | ] | |
| 567 | + | ||
| 568 | + | [[package]] | |
| 569 | + | name = "ciborium-io" | |
| 570 | + | version = "0.2.2" | |
| 571 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 572 | + | checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" | |
| 573 | + | ||
| 574 | + | [[package]] | |
| 575 | + | name = "ciborium-ll" | |
| 576 | + | version = "0.2.2" | |
| 577 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 578 | + | checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" | |
| 579 | + | dependencies = [ | |
| 580 | + | "ciborium-io", | |
| 581 | + | "half", | |
| 582 | + | ] | |
| 583 | + | ||
| 485 | 584 | [[package]] | |
| 486 | 585 | name = "cmov" | |
| 487 | 586 | version = "0.5.3" | |
| @@ -570,6 +669,12 @@ | |||
| 570 | 669 | "cfg-if", | |
| 571 | 670 | ] | |
| 572 | 671 | ||
| 672 | + | [[package]] | |
| 673 | + | name = "crunchy" | |
| 674 | + | version = "0.2.4" | |
| 675 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 676 | + | checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" | |
| 677 | + | ||
| 573 | 678 | [[package]] | |
| 574 | 679 | name = "crypto-bigint" | |
| 575 | 680 | version = "0.4.9" | |
| @@ -620,6 +725,12 @@ | |||
| 620 | 725 | "cmov", | |
| 621 | 726 | ] | |
| 622 | 727 | ||
| 728 | + | [[package]] | |
| 729 | + | name = "data-encoding" | |
| 730 | + | version = "2.11.1" | |
| 731 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 732 | + | checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" | |
| 733 | + | ||
| 623 | 734 | [[package]] | |
| 624 | 735 | name = "der" | |
| 625 | 736 | version = "0.6.1" | |
| @@ -639,6 +750,12 @@ | |||
| 639 | 750 | "powerfmt", | |
| 640 | 751 | ] | |
| 641 | 752 | ||
| 753 | + | [[package]] | |
| 754 | + | name = "diff" | |
| 755 | + | version = "0.1.13" | |
| 756 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 757 | + | checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" | |
| 758 | + | ||
| 642 | 759 | [[package]] | |
| 643 | 760 | name = "digest" | |
| 644 | 761 | version = "0.10.7" | |
| @@ -670,7 +787,7 @@ | |||
| 670 | 787 | dependencies = [ | |
| 671 | 788 | "proc-macro2", | |
| 672 | 789 | "quote", | |
| 673 | - | "syn", | |
| 790 | + | "syn 2.0.117", | |
| 674 | 791 | ] | |
| 675 | 792 | ||
| 676 | 793 | [[package]] | |
| @@ -849,6 +966,17 @@ | |||
| 849 | 966 | "tracing", | |
| 850 | 967 | ] | |
| 851 | 968 | ||
| 969 | + | [[package]] | |
| 970 | + | name = "half" | |
| 971 | + | version = "2.7.1" | |
| 972 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 973 | + | checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" | |
| 974 | + | dependencies = [ | |
| 975 | + | "cfg-if", | |
| 976 | + | "crunchy", | |
| 977 | + | "zerocopy", | |
| 978 | + | ] | |
| 979 | + | ||
| 852 | 980 | [[package]] | |
| 853 | 981 | name = "hashbrown" | |
| 854 | 982 | version = "0.16.1" | |
| @@ -1126,6 +1254,8 @@ | |||
| 1126 | 1254 | dependencies = [ | |
| 1127 | 1255 | "equivalent", | |
| 1128 | 1256 | "hashbrown", | |
| 1257 | + | "serde", | |
| 1258 | + | "serde_core", | |
| 1129 | 1259 | ] | |
| 1130 | 1260 | ||
| 1131 | 1261 | [[package]] | |
| @@ -1181,6 +1311,18 @@ | |||
| 1181 | 1311 | "digest 0.11.3", | |
| 1182 | 1312 | ] | |
| 1183 | 1313 | ||
| 1314 | + | [[package]] | |
| 1315 | + | name = "memchr" | |
| 1316 | + | version = "2.8.3" | |
| 1317 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1318 | + | checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" | |
| 1319 | + | ||
| 1320 | + | [[package]] | |
| 1321 | + | name = "minimal-lexical" | |
| 1322 | + | version = "0.2.1" | |
| 1323 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1324 | + | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" | |
| 1325 | + | ||
| 1184 | 1326 | [[package]] | |
| 1185 | 1327 | name = "mio" | |
| 1186 | 1328 | version = "1.2.0" | |
| @@ -1192,6 +1334,26 @@ | |||
| 1192 | 1334 | "windows-sys 0.61.2", | |
| 1193 | 1335 | ] | |
| 1194 | 1336 | ||
| 1337 | + | [[package]] | |
| 1338 | + | name = "nom" | |
| 1339 | + | version = "7.1.3" | |
| 1340 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1341 | + | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" | |
| 1342 | + | dependencies = [ | |
| 1343 | + | "memchr", | |
| 1344 | + | "minimal-lexical", | |
| 1345 | + | ] | |
| 1346 | + | ||
| 1347 | + | [[package]] | |
| 1348 | + | name = "num-bigint" | |
| 1349 | + | version = "0.4.8" | |
| 1350 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1351 | + | checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" | |
| 1352 | + | dependencies = [ | |
| 1353 | + | "num-integer", | |
| 1354 | + | "num-traits", | |
| 1355 | + | ] | |
| 1356 | + | ||
| 1195 | 1357 | [[package]] | |
| 1196 | 1358 | name = "num-conv" | |
| 1197 | 1359 | version = "0.2.1" | |
| @@ -1207,6 +1369,17 @@ | |||
| 1207 | 1369 | "num-traits", | |
| 1208 | 1370 | ] | |
| 1209 | 1371 | ||
| 1372 | + | [[package]] | |
| 1373 | + | name = "num-rational" | |
| 1374 | + | version = "0.4.2" | |
| 1375 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1376 | + | checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" | |
| 1377 | + | dependencies = [ | |
| 1378 | + | "num-bigint", | |
| 1379 | + | "num-integer", | |
| 1380 | + | "num-traits", | |
| 1381 | + | ] | |
| 1382 | + | ||
| 1210 | 1383 | [[package]] | |
| 1211 | 1384 | name = "num-traits" | |
| 1212 | 1385 | version = "0.2.19" | |
| @@ -1288,6 +1461,16 @@ | |||
| 1288 | 1461 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1289 | 1462 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" | |
| 1290 | 1463 | ||
| 1464 | + | [[package]] | |
| 1465 | + | name = "pretty_assertions" | |
| 1466 | + | version = "1.4.1" | |
| 1467 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1468 | + | checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" | |
| 1469 | + | dependencies = [ | |
| 1470 | + | "diff", | |
| 1471 | + | "yansi", | |
| 1472 | + | ] | |
| 1473 | + | ||
| 1291 | 1474 | [[package]] | |
| 1292 | 1475 | name = "proc-macro2" | |
| 1293 | 1476 | version = "1.0.106" | |
| @@ -1346,6 +1529,15 @@ | |||
| 1346 | 1529 | "windows-sys 0.52.0", | |
| 1347 | 1530 | ] | |
| 1348 | 1531 | ||
| 1532 | + | [[package]] | |
| 1533 | + | name = "roxmltree" | |
| 1534 | + | version = "0.14.1" | |
| 1535 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1536 | + | checksum = "921904a62e410e37e215c40381b7117f830d9d89ba60ab5236170541dd25646b" | |
| 1537 | + | dependencies = [ | |
| 1538 | + | "xmlparser", | |
| 1539 | + | ] | |
| 1540 | + | ||
| 1349 | 1541 | [[package]] | |
| 1350 | 1542 | name = "rustc_version" | |
| 1351 | 1543 | version = "0.4.1" | |
| @@ -1420,7 +1612,9 @@ | |||
| 1420 | 1612 | "aws-config", | |
| 1421 | 1613 | "aws-sdk-s3", | |
| 1422 | 1614 | "aws-smithy-http-client", | |
| 1615 | + | "aws-smithy-types", | |
| 1423 | 1616 | "bytes", | |
| 1617 | + | "http 1.4.0", | |
| 1424 | 1618 | "tokio", | |
| 1425 | 1619 | "tracing", | |
| 1426 | 1620 | ] | |
| @@ -1477,6 +1671,12 @@ | |||
| 1477 | 1671 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1478 | 1672 | checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" | |
| 1479 | 1673 | ||
| 1674 | + | [[package]] | |
| 1675 | + | name = "separator" | |
| 1676 | + | version = "0.4.1" | |
| 1677 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1678 | + | checksum = "f97841a747eef040fcd2e7b3b9a220a7205926e60488e673d9e4926d27772ce5" | |
| 1679 | + | ||
| 1480 | 1680 | [[package]] | |
| 1481 | 1681 | name = "serde" | |
| 1482 | 1682 | version = "1.0.228" | |
| @@ -1504,7 +1704,21 @@ | |||
| 1504 | 1704 | dependencies = [ | |
| 1505 | 1705 | "proc-macro2", | |
| 1506 | 1706 | "quote", | |
| 1507 | - | "syn", | |
| 1707 | + | "syn 2.0.117", | |
| 1708 | + | ] | |
| 1709 | + | ||
| 1710 | + | [[package]] | |
| 1711 | + | name = "serde_json" | |
| 1712 | + | version = "1.0.151" | |
| 1713 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1714 | + | checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" | |
| 1715 | + | dependencies = [ | |
| 1716 | + | "indexmap", | |
| 1717 | + | "itoa", | |
| 1718 | + | "memchr", | |
| 1719 | + | "serde", | |
| 1720 | + | "serde_core", | |
| 1721 | + | "zmij", | |
| 1508 | 1722 | ] | |
| 1509 | 1723 | ||
| 1510 | 1724 | [[package]] | |
| @@ -1617,6 +1831,17 @@ | |||
| 1617 | 1831 | "unicode-ident", | |
| 1618 | 1832 | ] | |
| 1619 | 1833 | ||
| 1834 | + | [[package]] | |
| 1835 | + | name = "syn" | |
| 1836 | + | version = "3.0.4" | |
| 1837 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1838 | + | checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" | |
| 1839 | + | dependencies = [ | |
| 1840 | + | "proc-macro2", | |
| 1841 | + | "quote", | |
| 1842 | + | "unicode-ident", | |
| 1843 | + | ] | |
| 1844 | + | ||
| 1620 | 1845 | [[package]] | |
| 1621 | 1846 | name = "synstructure" | |
| 1622 | 1847 | version = "0.13.2" | |
| @@ -1625,7 +1850,27 @@ | |||
| 1625 | 1850 | dependencies = [ | |
| 1626 | 1851 | "proc-macro2", | |
| 1627 | 1852 | "quote", | |
| 1628 | - | "syn", | |
| 1853 | + | "syn 2.0.117", | |
| 1854 | + | ] | |
| 1855 | + | ||
| 1856 | + | [[package]] | |
| 1857 | + | name = "thiserror" | |
| 1858 | + | version = "2.0.20" | |
| 1859 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1860 | + | checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" | |
| 1861 | + | dependencies = [ | |
| 1862 | + | "thiserror-impl", | |
| 1863 | + | ] | |
| 1864 | + | ||
| 1865 | + | [[package]] | |
| 1866 | + | name = "thiserror-impl" | |
| 1867 | + | version = "2.0.20" | |
| 1868 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1869 | + | checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" | |
| 1870 | + | dependencies = [ | |
| 1871 | + | "proc-macro2", | |
| 1872 | + | "quote", | |
| 1873 | + | "syn 3.0.4", | |
| 1629 | 1874 | ] | |
| 1630 | 1875 | ||
| 1631 | 1876 | [[package]] | |
| @@ -1668,6 +1913,21 @@ | |||
| 1668 | 1913 | "zerovec", | |
| 1669 | 1914 | ] | |
| 1670 | 1915 | ||
| 1916 | + | [[package]] | |
| 1917 | + | name = "tinyvec" | |
| 1918 | + | version = "1.12.0" | |
| 1919 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1920 | + | checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" | |
| 1921 | + | dependencies = [ | |
| 1922 | + | "tinyvec_macros", | |
| 1923 | + | ] | |
| 1924 | + | ||
| 1925 | + | [[package]] | |
| 1926 | + | name = "tinyvec_macros" | |
| 1927 | + | version = "0.1.1" | |
| 1928 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 1929 | + | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" | |
| 1930 | + | ||
| 1671 | 1931 | [[package]] | |
| 1672 | 1932 | name = "tokio" | |
| 1673 | 1933 | version = "1.51.0" | |
| @@ -1691,7 +1951,7 @@ | |||
| 1691 | 1951 | dependencies = [ | |
| 1692 | 1952 | "proc-macro2", | |
| 1693 | 1953 | "quote", | |
| 1694 | - | "syn", | |
| 1954 | + | "syn 2.0.117", | |
| 1695 | 1955 | ] | |
| 1696 | 1956 | ||
| 1697 | 1957 | [[package]] | |
| @@ -1758,7 +2018,7 @@ | |||
| 1758 | 2018 | dependencies = [ | |
| 1759 | 2019 | "proc-macro2", | |
| 1760 | 2020 | "quote", | |
| 1761 | - | "syn", | |
| 2021 | + | "syn 2.0.117", | |
| 1762 | 2022 | ] | |
| 1763 | 2023 | ||
| 1764 | 2024 | [[package]] | |
| @@ -1887,7 +2147,7 @@ | |||
| 1887 | 2147 | "bumpalo", | |
| 1888 | 2148 | "proc-macro2", | |
| 1889 | 2149 | "quote", | |
| 1890 | - | "syn", | |
| 2150 | + | "syn 2.0.117", | |
| 1891 | 2151 | "wasm-bindgen-shared", | |
| 1892 | 2152 | ] | |
| 1893 | 2153 | ||
| @@ -2000,6 +2260,12 @@ | |||
| 2000 | 2260 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2001 | 2261 | checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" | |
| 2002 | 2262 | ||
| 2263 | + | [[package]] | |
| 2264 | + | name = "yansi" | |
| 2265 | + | version = "1.0.1" | |
| 2266 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2267 | + | checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" | |
| 2268 | + | ||
| 2003 | 2269 | [[package]] | |
| 2004 | 2270 | name = "yoke" | |
| 2005 | 2271 | version = "0.8.2" | |
| @@ -2019,10 +2285,30 @@ | |||
| 2019 | 2285 | dependencies = [ | |
| 2020 | 2286 | "proc-macro2", | |
| 2021 | 2287 | "quote", | |
| 2022 | - | "syn", | |
| 2288 | + | "syn 2.0.117", | |
| 2023 | 2289 | "synstructure", | |
| 2024 | 2290 | ] | |
| 2025 | 2291 | ||
| 2292 | + | [[package]] | |
| 2293 | + | name = "zerocopy" | |
| 2294 | + | version = "0.8.56" | |
| 2295 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2296 | + | checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" | |
| 2297 | + | dependencies = [ | |
| 2298 | + | "zerocopy-derive", | |
| 2299 | + | ] | |
| 2300 | + | ||
| 2301 | + | [[package]] | |
| 2302 | + | name = "zerocopy-derive" | |
| 2303 | + | version = "0.8.56" | |
| 2304 | + | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 2305 | + | checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" | |
| 2306 | + | dependencies = [ | |
| 2307 | + | "proc-macro2", | |
| 2308 | + | "quote", | |
| 2309 | + | "syn 2.0.117", | |
| 2310 | + | ] | |
| 2311 | + | ||
| 2026 | 2312 | [[package]] | |
| 2027 | 2313 | name = "zerofrom" | |
| 2028 | 2314 | version = "0.1.7" | |
| @@ -2040,7 +2326,7 @@ | |||
| 2040 | 2326 | dependencies = [ | |
| 2041 | 2327 | "proc-macro2", | |
| 2042 | 2328 | "quote", | |
| 2043 | - | "syn", | |
| 2329 | + | "syn 2.0.117", | |
| 2044 | 2330 | "synstructure", | |
| 2045 | 2331 | ] | |
| 2046 | 2332 | ||
| @@ -2080,32 +2366,42 @@ | |||
| 2080 | 2366 | dependencies = [ |
Lines truncated
| @@ -26,6 +26,14 @@ | |||
| 26 | 26 | ||
| 27 | 27 | [dev-dependencies] | |
| 28 | 28 | tokio = { version = "1", features = ["macros", "rt", "time"] } | |
| 29 | + | # The replay client, for the two paths a live MinIO cannot reach: a response | |
| 30 | + | # shape S3 does not currently produce (the truncated-without-markers guard), and | |
| 31 | + | # a setting the object store offers no way to read back under the test bucket's | |
| 32 | + | # credentials. Same crate as the runtime dependency above; cargo unifies the | |
| 33 | + | # features, so this only adds `test-util` to the test build. | |
| 34 | + | aws-smithy-http-client = { version = "1.1", features = ["test-util"] } | |
| 35 | + | aws-smithy-types = "1.4" | |
| 36 | + | http = "1" | |
| 29 | 37 | ||
| 30 | 38 | [lints.rust] | |
| 31 | 39 | unused = "warn" |
| @@ -12,6 +12,7 @@ | |||
| 12 | 12 | use aws_config::BehaviorVersion; | |
| 13 | 13 | use aws_sdk_s3::Client; | |
| 14 | 14 | use aws_sdk_s3::config::{Credentials, Region}; | |
| 15 | + | use aws_sdk_s3::error::ProvideErrorMetadata; | |
| 15 | 16 | use aws_sdk_s3::presigning::PresigningConfig; | |
| 16 | 17 | use aws_sdk_s3::types::{ | |
| 17 | 18 | CompletedMultipartUpload, CompletedPart, CorsConfiguration, CorsRule, Delete, ObjectIdentifier, | |
| @@ -49,6 +50,27 @@ | |||
| 49 | 50 | pub region: String, | |
| 50 | 51 | } | |
| 51 | 52 | ||
| 53 | + | /// One CORS rule as the bucket reports it, in this crate's own types. | |
| 54 | + | /// | |
| 55 | + | /// A caller that wants to know what CORS the bucket is set to has nowhere to | |
| 56 | + | /// ask otherwise: [`S3Client::configure_cors`] returns `()` and the SDK's | |
| 57 | + | /// `CorsRule` is not re-exported, so the setting was write-only from outside | |
| 58 | + | /// this crate. Owning the shape here also keeps the SDK out of the callers' | |
| 59 | + | /// signatures, which is the reason the wrapper exists at all. | |
| 60 | + | #[derive(Debug, Clone, PartialEq, Eq, Default)] | |
| 61 | + | pub struct CorsRuleView { | |
| 62 | + | /// Origins the rule allows, verbatim as stored. | |
| 63 | + | pub allowed_origins: Vec<String>, | |
| 64 | + | /// Methods the rule allows. | |
| 65 | + | pub allowed_methods: Vec<String>, | |
| 66 | + | /// Request headers the rule allows. | |
| 67 | + | pub allowed_headers: Vec<String>, | |
| 68 | + | /// Response headers the rule exposes to the browser. | |
| 69 | + | pub expose_headers: Vec<String>, | |
| 70 | + | /// Preflight cache lifetime in seconds, if the rule sets one. | |
| 71 | + | pub max_age_seconds: Option<i32>, | |
| 72 | + | } | |
| 73 | + | ||
| 52 | 74 | /// S3 client wrapper. | |
| 53 | 75 | #[derive(Clone)] | |
| 54 | 76 | pub struct S3Client { | |
| @@ -1351,6 +1373,42 @@ | |||
| 1351 | 1373 | } | |
| 1352 | 1374 | } | |
| 1353 | 1375 | ||
| 1376 | + | /// Read the bucket's CORS configuration back. | |
| 1377 | + | /// | |
| 1378 | + | /// A bucket with no CORS configuration is `Ok(vec![])`, not an error: S3 | |
| 1379 | + | /// answers `GetBucketCors` with `NoSuchCORSConfiguration` in that case, and | |
| 1380 | + | /// "no rules" is the honest reading of it rather than a failure. Anything | |
| 1381 | + | /// else is an error. | |
| 1382 | + | pub async fn bucket_cors(&self) -> Result<Vec<CorsRuleView>, String> { | |
| 1383 | + | match self | |
| 1384 | + | .client | |
| 1385 | + | .get_bucket_cors() | |
| 1386 | + | .bucket(&self.bucket) | |
| 1387 | + | .send() | |
| 1388 | + | .await | |
| 1389 | + | { | |
| 1390 | + | Ok(resp) => Ok(resp | |
| 1391 | + | .cors_rules() | |
| 1392 | + | .iter() | |
| 1393 | + | .map(|r| CorsRuleView { | |
| 1394 | + | allowed_origins: r.allowed_origins().to_vec(), | |
| 1395 | + | allowed_methods: r.allowed_methods().to_vec(), | |
| 1396 | + | allowed_headers: r.allowed_headers().to_vec(), | |
| 1397 | + | expose_headers: r.expose_headers().to_vec(), | |
| 1398 | + | max_age_seconds: r.max_age_seconds(), | |
| 1399 | + | }) | |
| 1400 | + | .collect()), | |
| 1401 | + | Err(e) => { | |
| 1402 | + | let service_error = e.into_service_error(); | |
| 1403 | + | if service_error.code() == Some("NoSuchCORSConfiguration") { | |
| 1404 | + | Ok(Vec::new()) | |
| 1405 | + | } else { | |
| 1406 | + | Err(format!("S3 get_bucket_cors failed: {service_error}")) | |
| 1407 | + | } | |
| 1408 | + | } | |
| 1409 | + | } | |
| 1410 | + | } | |
| 1411 | + | ||
| 1354 | 1412 | /// Lightweight connectivity check — `list_objects_v2` with `max_keys(0)`. | |
| 1355 | 1413 | pub async fn check_connectivity(&self) -> Result<(), String> { | |
| 1356 | 1414 | self.client | |
| @@ -1368,6 +1426,10 @@ | |||
| 1368 | 1426 | mod tests { | |
| 1369 | 1427 | use super::*; | |
| 1370 | 1428 | ||
| 1429 | + | use aws_sdk_s3::config::retry::RetryConfig; | |
| 1430 | + | use aws_smithy_http_client::test_util::{ReplayEvent, StaticReplayClient}; | |
| 1431 | + | use aws_smithy_types::body::SdkBody; | |
| 1432 | + | ||
| 1371 | 1433 | fn test_client() -> S3Client { | |
| 1372 | 1434 | // `from_conf` is local — no network until a request is sent — so this | |
| 1373 | 1435 | // builds a usable client without reaching any endpoint. | |
| @@ -1385,6 +1447,280 @@ | |||
| 1385 | 1447 | } | |
| 1386 | 1448 | } | |
| 1387 | 1449 | ||
| 1450 | + | /// An `S3Client` whose HTTP layer replays canned responses. | |
| 1451 | + | /// | |
| 1452 | + | /// WHY THIS EXISTS. Two paths in this crate cannot be reached from a live | |
| 1453 | + | /// object store, and both were mutation survivors (infra `f7f13914`, | |
| 1454 | + | /// `a536db81`). One is defensive code against a response shape S3 does not | |
| 1455 | + | /// currently produce; the other writes a bucket setting the test bucket's | |
| 1456 | + | /// credentials cannot read back. A canned response reaches the first and a | |
| 1457 | + | /// recorded request reaches the second, with no live infrastructure and no | |
| 1458 | + | /// bucket-owner permission. | |
| 1459 | + | /// | |
| 1460 | + | /// Retries are off so an exhausted replay list fails immediately: a mutant | |
| 1461 | + | /// that makes the crate send one request too many should show up as a | |
| 1462 | + | /// failed assertion rather than as seconds of SDK backoff. | |
| 1463 | + | fn replay_client(events: Vec<ReplayEvent>) -> (S3Client, StaticReplayClient) { | |
| 1464 | + | let replay = StaticReplayClient::new(events); | |
| 1465 | + | let s3_config = aws_sdk_s3::Config::builder() | |
| 1466 | + | .behavior_version(BehaviorVersion::latest()) | |
| 1467 | + | .http_client(replay.clone()) | |
| 1468 | + | .retry_config(RetryConfig::disabled()) | |
| 1469 | + | .region(Region::new("test")) | |
| 1470 | + | .endpoint_url("http://127.0.0.1:1") | |
| 1471 | + | .credentials_provider(Credentials::new("ak", "sk", None, None, "test")) | |
| 1472 | + | .force_path_style(true) | |
| 1473 | + | .build(); | |
| 1474 | + | ( | |
| 1475 | + | S3Client { | |
| 1476 | + | client: Client::from_conf(s3_config), | |
| 1477 | + | bucket: "test-bucket".to_string(), | |
| 1478 | + | }, | |
| 1479 | + | replay, | |
| 1480 | + | ) | |
| 1481 | + | } | |
| 1482 | + | ||
| 1483 | + | /// A canned 200 with an XML body, which is what every S3 read returns. | |
| 1484 | + | fn xml_ok(body: &str) -> ReplayEvent { | |
| 1485 | + | ReplayEvent::new( | |
| 1486 | + | http::Request::builder() | |
| 1487 | + | .uri("http://test-bucket.localhost/") | |
| 1488 | + | .body(SdkBody::empty()) | |
| 1489 | + | .unwrap(), | |
| 1490 | + | http::Response::builder() | |
| 1491 | + | .status(200) | |
| 1492 | + | .header("content-type", "application/xml") | |
| 1493 | + | .body(SdkBody::from(body.to_string())) | |
| 1494 | + | .unwrap(), | |
| 1495 | + | ) | |
| 1496 | + | } | |
| 1497 | + | ||
| 1498 | + | /// A canned S3 error document, which is how S3 says "no CORS here". | |
| 1499 | + | fn xml_err(status: u16, code: &str) -> ReplayEvent { | |
| 1500 | + | let body = format!( | |
| 1501 | + | "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\ | |
| 1502 | + | <Error><Code>{code}</Code><Message>canned</Message>\ | |
| 1503 | + | <RequestId>r</RequestId><HostId>h</HostId></Error>" | |
| 1504 | + | ); | |
| 1505 | + | ReplayEvent::new( | |
| 1506 | + | http::Request::builder() | |
| 1507 | + | .uri("http://test-bucket.localhost/") | |
| 1508 | + | .body(SdkBody::empty()) | |
| 1509 | + | .unwrap(), | |
| 1510 | + | http::Response::builder() | |
| 1511 | + | .status(status) | |
| 1512 | + | .header("content-type", "application/xml") | |
| 1513 | + | .body(SdkBody::from(body)) | |
| 1514 | + | .unwrap(), | |
| 1515 | + | ) | |
| 1516 | + | } | |
| 1517 | + | ||
| 1518 | + | /// infra `f7f13914`. The "truncated but no continuation markers" guard in | |
| 1519 | + | /// `list_multipart_uploads_for_key` survived mutation because reaching it | |
| 1520 | + | /// needs a response S3 does not produce: truncated, yet naming nothing to | |
| 1521 | + | /// continue from. Live MinIO will not build it, and >1000 pending uploads on | |
| 1522 | + | /// one key is not a fixture anyone should own. A canned body is. | |
| 1523 | + | /// | |
| 1524 | + | /// The guard is kept rather than deleted because without it that response | |
| 1525 | + | /// is an infinite loop against a real endpoint, and the crate is the last | |
| 1526 | + | /// thing standing between the orphan reaper and a spin. | |
| 1527 | + | /// | |
| 1528 | + | /// The single-request assertion is the one that matters. Removing the | |
| 1529 | + | /// `break` makes the loop ask again with the same (absent) markers, and | |
| 1530 | + | /// there is no second canned response, so it also fails the `Ok` — either | |
| 1531 | + | /// way the mutant dies. | |
| 1532 | + | #[tokio::test] | |
| 1533 | + | async fn a_truncated_listing_with_no_markers_stops_instead_of_looping() { | |
| 1534 | + | let (client, replay) = replay_client(vec![xml_ok( | |
| 1535 | + | r#"<?xml version="1.0" encoding="UTF-8"?> | |
| 1536 | + | <ListMultipartUploadsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> | |
| 1537 | + | <Bucket>test-bucket</Bucket> | |
| 1538 | + | <Prefix>staging/abc</Prefix> | |
| 1539 | + | <MaxUploads>1000</MaxUploads> | |
| 1540 | + | <IsTruncated>true</IsTruncated> | |
| 1541 | + | <Upload> | |
| 1542 | + | <Key>staging/abc</Key> | |
| 1543 | + | <UploadId>upload-one</UploadId> | |
| 1544 | + | <Initiated>2026-08-31T00:00:00.000Z</Initiated> | |
| 1545 | + | </Upload> | |
| 1546 | + | <Upload> | |
| 1547 | + | <Key>staging/abcdef</Key> | |
| 1548 | + | <UploadId>not-ours</UploadId> | |
| 1549 | + | <Initiated>2026-08-31T00:00:00.000Z</Initiated> | |
| 1550 | + | </Upload> | |
| 1551 | + | </ListMultipartUploadsResult>"#, | |
| 1552 | + | )]); | |
| 1553 | + | ||
| 1554 | + | let ids = client | |
| 1555 | + | .list_multipart_uploads_for_key("staging/abc") | |
| 1556 | + | .await | |
| 1557 | + | .expect("a truncated page with nothing to continue from is a complete answer"); | |
| 1558 | + | ||
| 1559 | + | // The exact-key filter travels with it: `ListMultipartUploads` matches a | |
| 1560 | + | // prefix, so `staging/abcdef` is a live session the reaper must not abort. | |
| 1561 | + | assert_eq!(ids, vec!["upload-one".to_string()]); | |
| 1562 | + | assert_eq!( | |
| 1563 | + | replay.actual_requests().count(), | |
| 1564 | + | 1, | |
| 1565 | + | "the guard exists to stop a second identical request" | |
| 1566 | + | ); | |
| 1567 | + | } | |
| 1568 | + | ||
| 1569 | + | /// The other half of the guard: a truncated page that DOES name a marker is | |
| 1570 | + | /// followed. Without this the test above is satisfied by a loop that never | |
| 1571 | + | /// iterates at all, which is a different bug wearing the same result. | |
| 1572 | + | #[tokio::test] | |
| 1573 | + | async fn a_truncated_listing_with_a_marker_asks_for_the_next_page() { | |
| 1574 | + | let (client, replay) = replay_client(vec![ | |
| 1575 | + | xml_ok( | |
| 1576 | + | r#"<?xml version="1.0" encoding="UTF-8"?> | |
| 1577 | + | <ListMultipartUploadsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> | |
| 1578 | + | <Bucket>test-bucket</Bucket> | |
| 1579 | + | <IsTruncated>true</IsTruncated> | |
| 1580 | + | <NextKeyMarker>staging/abc</NextKeyMarker> | |
| 1581 | + | <NextUploadIdMarker>upload-one</NextUploadIdMarker> | |
| 1582 | + | <Upload> | |
| 1583 | + | <Key>staging/abc</Key> | |
| 1584 | + | <UploadId>upload-one</UploadId> | |
| 1585 | + | <Initiated>2026-08-31T00:00:00.000Z</Initiated> | |
| 1586 | + | </Upload> | |
| 1587 | + | </ListMultipartUploadsResult>"#, | |
| 1588 | + | ), | |
| 1589 | + | xml_ok( | |
| 1590 | + | r#"<?xml version="1.0" encoding="UTF-8"?> | |
| 1591 | + | <ListMultipartUploadsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> | |
| 1592 | + | <Bucket>test-bucket</Bucket> | |
| 1593 | + | <IsTruncated>false</IsTruncated> | |
| 1594 | + | <Upload> | |
| 1595 | + | <Key>staging/abc</Key> | |
| 1596 | + | <UploadId>upload-two</UploadId> | |
| 1597 | + | <Initiated>2026-08-31T00:00:00.000Z</Initiated> | |
| 1598 | + | </Upload> | |
| 1599 | + | </ListMultipartUploadsResult>"#, | |
| 1600 | + | ), | |
| 1601 | + | ]); | |
| 1602 | + | ||
| 1603 | + | let ids = client | |
| 1604 | + | .list_multipart_uploads_for_key("staging/abc") | |
| 1605 | + | .await | |
| 1606 | + | .expect("two pages is an ordinary listing"); | |
| 1607 | + | ||
| 1608 | + | assert_eq!( | |
| 1609 | + | ids, | |
| 1610 | + | vec!["upload-one".to_string(), "upload-two".to_string()] | |
| 1611 | + | ); | |
| 1612 | + | let second = replay | |
| 1613 | + | .actual_requests() | |
| 1614 | + | .nth(1) | |
| 1615 | + | .expect("the second page was requested") | |
| 1616 | + | .uri() | |
| 1617 | + | .to_string(); | |
| 1618 | + | assert!( | |
| 1619 | + | second.contains("upload-id-marker=upload-one"), | |
| 1620 | + | "the marker from page one carries into page two: {second}" | |
| 1621 | + | ); | |
| 1622 | + | } | |
| 1623 | + | ||
| 1624 | + | /// infra `a536db81`. `configure_cors` returns `()` and the crate exposed no | |
| 1625 | + | /// readback, so replacing its whole body with `()` was invisible to every | |
| 1626 | + | /// test that could be written against it. The request it sends is the | |
| 1627 | + | /// observable, and it is the right observable: what the object store ends up | |
| 1628 | + | /// configured with is decided entirely by that one PUT. | |
| 1629 | + | #[tokio::test] | |
| 1630 | + | async fn configuring_cors_sends_the_rule_the_browser_upload_needs() { | |
| 1631 | + | let (client, replay) = replay_client(vec![xml_ok("")]); | |
| 1632 | + | ||
| 1633 | + | client.configure_cors("https://example.test/").await; | |
| 1634 | + | ||
| 1635 | + | let requests: Vec<_> = replay.actual_requests().collect(); | |
| 1636 | + | assert_eq!(requests.len(), 1, "configure_cors must send a PUT"); | |
| 1637 | + | let uri = requests[0].uri().to_string(); | |
| 1638 | + | assert!(uri.contains("cors"), "put_bucket_cors, not some other PUT"); | |
| 1639 | + | ||
| 1640 | + | let body = String::from_utf8( | |
| 1641 | + | requests[0] | |
| 1642 | + | .body() | |
| 1643 | + | .bytes() | |
| 1644 | + | .expect("an in-memory XML body") | |
| 1645 | + | .to_vec(), | |
| 1646 | + | ) | |
| 1647 | + | .expect("the CORS document is UTF-8"); | |
| 1648 | + | ||
| 1649 | + | // The trailing slash is trimmed: an origin is a scheme-host-port and a | |
| 1650 | + | // browser never sends the slash, so a rule carrying one matches nothing. | |
| 1651 | + | assert!(body.contains("<AllowedOrigin>https://example.test</AllowedOrigin>")); | |
| 1652 | + | for method in ["PUT", "GET", "HEAD"] { | |
| 1653 | + | assert!( | |
| 1654 | + | body.contains(&format!("<AllowedMethod>{method}</AllowedMethod>")), | |
| 1655 | + | "{method} is missing from {body}" | |
| 1656 | + | ); | |
| 1657 | + | } | |
| 1658 | + | for header in ["Content-Type", "Cache-Control", "Content-Disposition"] { | |
| 1659 | + | assert!( | |
| 1660 | + | body.contains(&format!("<AllowedHeader>{header}</AllowedHeader>")), | |
| 1661 | + | "{header} is missing from {body}" | |
| 1662 | + | ); | |
| 1663 | + | } | |
| 1664 | + | // ETag is what a direct-to-S3 part upload reads back to complete the | |
| 1665 | + | // upload; without exposing it the browser cannot finish a multipart. | |
| 1666 | + | assert!(body.contains("<ExposeHeader>ETag</ExposeHeader>")); | |
| 1667 | + | assert!(body.contains("<MaxAgeSeconds>3600</MaxAgeSeconds>")); | |
| 1668 | + | } | |
| 1669 | + | ||
| 1670 | + | /// The readback the wrapper was missing, over a canned response. | |
| 1671 | + | #[tokio::test] | |
| 1672 | + | async fn cors_rules_come_back_in_the_crates_own_shape() { | |
| 1673 | + | let (client, _replay) = replay_client(vec![xml_ok( | |
| 1674 | + | r#"<?xml version="1.0" encoding="UTF-8"?> | |
| 1675 | + | <CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> | |
| 1676 | + | <CORSRule> | |
| 1677 | + | <AllowedOrigin>https://example.test</AllowedOrigin> | |
| 1678 | + | <AllowedMethod>PUT</AllowedMethod> | |
| 1679 | + | <AllowedMethod>GET</AllowedMethod> | |
| 1680 | + | <AllowedHeader>Content-Type</AllowedHeader> | |
| 1681 | + | <ExposeHeader>ETag</ExposeHeader> | |
| 1682 | + | <MaxAgeSeconds>3600</MaxAgeSeconds> | |
| 1683 | + | </CORSRule> | |
| 1684 | + | </CORSConfiguration>"#, | |
| 1685 | + | )]); | |
| 1686 | + | ||
| 1687 | + | let rules = client.bucket_cors().await.expect("a configured bucket"); | |
| 1688 | + | assert_eq!( | |
| 1689 | + | rules, | |
| 1690 | + | vec![CorsRuleView { | |
| 1691 | + | allowed_origins: vec!["https://example.test".to_string()], | |
| 1692 | + | allowed_methods: vec!["PUT".to_string(), "GET".to_string()], | |
| 1693 | + | allowed_headers: vec!["Content-Type".to_string()], | |
| 1694 | + | expose_headers: vec!["ETag".to_string()], | |
| 1695 | + | max_age_seconds: Some(3600), | |
| 1696 | + | }] | |
| 1697 | + | ); | |
| 1698 | + | } | |
| 1699 | + | ||
| 1700 | + | /// A bucket with no CORS is not a failure. S3 answers `GetBucketCors` with | |
| 1701 | + | /// `NoSuchCORSConfiguration`, and a caller asking "what is set" is entitled | |
| 1702 | + | /// to hear "nothing" rather than an error it has to pattern-match itself. | |
| 1703 | + | #[tokio::test] | |
| 1704 | + | async fn a_bucket_with_no_cors_reads_back_as_no_rules() { | |
| 1705 | + | let (client, _replay) = replay_client(vec![xml_err(404, "NoSuchCORSConfiguration")]); | |
| 1706 | + | ||
| 1707 | + | let rules = client.bucket_cors().await.expect("absence is not an error"); | |
| 1708 | + | assert!(rules.is_empty()); | |
| 1709 | + | } | |
| 1710 | + | ||
| 1711 | + | /// Any other error still is one, or the method above would report a broken | |
| 1712 | + | /// endpoint as an unconfigured bucket. | |
| 1713 | + | #[tokio::test] | |
| 1714 | + | async fn a_cors_read_that_fails_for_another_reason_is_an_error() { | |
| 1715 | + | let (client, _replay) = replay_client(vec![xml_err(403, "AccessDenied")]); | |
| 1716 | + | ||
| 1717 | + | let err = client | |
| 1718 | + | .bucket_cors() | |
| 1719 | + | .await | |
| 1720 | + | .expect_err("AccessDenied is not an empty CORS configuration"); | |
| 1721 | + | assert!(err.contains("get_bucket_cors"), "{err}"); | |
| 1722 | + | } | |
| 1723 | + | ||
| 1388 | 1724 | const MIB: u64 = 1024 * 1024; | |
| 1389 | 1725 | ||
| 1390 | 1726 | #[test] |