From 3b12cc2404f90125b258f02402581e27e557ba14 Mon Sep 17 00:00:00 2001 From: tu6ge Date: Wed, 29 May 2024 10:31:57 +0800 Subject: [PATCH] Next version (#29) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add OssUrl * move files * doc * change rfc * change README * chore(version): version number update * refactor: 抽象授权逻辑 * feat: 获取文件列表支持快捷翻页 * feat: add bucket in client struct * feat: add const in ObjectQuery * feat: remove unused method * feat: enhance insert_next_token * feat: export data with custom type * doc: 自定义类型的文档说明 * refactor * chore: CI/CD * chore: CI/CD add publish * chore: add package info --- .github/actions-rs/grcov.yml | 4 - .github/publish.yml | 8 - .github/workflows/publish.yml | 79 +- CONTRIBUTION.md | 39 - Cargo.toml | 111 +- Changelog.md | 586 -------- README.md | 75 +- build.rs | 9 - examples/bg2015071010.png | Bin 7217 -> 0 bytes examples/bucket.rs | 21 - examples/buckets.rs | 29 - examples/custom.rs | 79 -- examples/delete_file.rs | 18 - examples/file.rs | 65 - examples/files.rs | 70 - examples/head_object.rs | 26 - examples/io_write.rs | 25 - examples/object_custom.rs | 46 - examples/object_stream.rs | 31 - examples/objects.rs | 19 - examples/parse_xml.rs | 85 -- examples/put_demo1.txt | 1 - examples/put_file.rs | 23 - examples/sts.rs | 16 - oss_derive/Cargo.toml | 34 - oss_derive/README.md | 3 - oss_derive/src/derive.rs | 10 - oss_derive/src/file.rs | 272 ---- oss_derive/src/gen_rc.rs | 69 - oss_derive/src/lib.rs | 72 - oss_derive/src/path_where.rs | 41 - oss_derive/tests/file.rs | 42 - oss_derive/tests/file_async.rs | 49 - oss_derive/tests/gen_rc_client.rs | 63 - oss_derive/tests/gen_rc_clone.rs | 81 -- oss_derive/tests/gen_rc_eq.rs | 46 - oss_derive/tests/run.rs | 16 - rfc.md | 51 + rustfmt.toml | 1 - src/auth.rs | 851 ------------ src/auth/query.rs | 252 ---- src/auth/test.rs | 797 ----------- src/blocking/builder.rs | 112 -- src/blocking/mod.rs | 163 --- src/bucket.rs | 1068 +++++---------- src/bucket/test.rs | 713 ---------- src/builder.rs | 294 ---- src/builder/test.rs | 108 -- src/client.rs | 638 ++++----- src/config.rs | 855 ------------ src/consts.rs | 4 - src/decode.rs | 681 ---------- src/decode/test.rs | 863 ------------ src/error.rs | 44 + src/errors.rs | 467 ------- src/file.rs | 974 -------------- src/file/test.rs | 467 ------- src/lib.rs | 334 +---- src/object.rs | 1706 +++--------------------- src/object/content.rs | 1068 --------------- src/object/content/blocking.rs | 494 ------- src/object/content/mock.rs | 167 --- src/object/content/test_suite.rs | 227 ---- src/object/content/test_suite_block.rs | 221 --- src/object/test.rs | 627 --------- src/sts.rs | 118 -- src/tests/client.rs | 259 ---- src/tests/env.rs | 165 --- src/tests/errors.rs | 317 ----- src/tests/mod.rs | 25 - src/tests/object.rs | 1065 --------------- src/types.rs | 1205 +++-------------- src/types/core.rs | 905 ------------- src/types/object.rs | 583 -------- src/types/object/base.rs | 316 ----- src/types/object/test.rs | 227 ---- src/types/test.rs | 361 ----- tests/integration_test.rs | 236 ---- tests/query_auth.rs | 49 - 79 files changed, 1119 insertions(+), 21222 deletions(-) delete mode 100644 .github/actions-rs/grcov.yml delete mode 100644 .github/publish.yml delete mode 100644 CONTRIBUTION.md delete mode 100644 Changelog.md delete mode 100644 build.rs delete mode 100644 examples/bg2015071010.png delete mode 100644 examples/bucket.rs delete mode 100644 examples/buckets.rs delete mode 100644 examples/custom.rs delete mode 100644 examples/delete_file.rs delete mode 100644 examples/file.rs delete mode 100644 examples/files.rs delete mode 100644 examples/head_object.rs delete mode 100644 examples/io_write.rs delete mode 100644 examples/object_custom.rs delete mode 100644 examples/object_stream.rs delete mode 100644 examples/objects.rs delete mode 100644 examples/parse_xml.rs delete mode 100644 examples/put_demo1.txt delete mode 100644 examples/put_file.rs delete mode 100644 examples/sts.rs delete mode 100644 oss_derive/Cargo.toml delete mode 100644 oss_derive/README.md delete mode 100644 oss_derive/src/derive.rs delete mode 100644 oss_derive/src/file.rs delete mode 100644 oss_derive/src/gen_rc.rs delete mode 100644 oss_derive/src/lib.rs delete mode 100644 oss_derive/src/path_where.rs delete mode 100644 oss_derive/tests/file.rs delete mode 100644 oss_derive/tests/file_async.rs delete mode 100644 oss_derive/tests/gen_rc_client.rs delete mode 100644 oss_derive/tests/gen_rc_clone.rs delete mode 100644 oss_derive/tests/gen_rc_eq.rs delete mode 100644 oss_derive/tests/run.rs create mode 100644 rfc.md delete mode 100644 rustfmt.toml delete mode 100644 src/auth.rs delete mode 100644 src/auth/query.rs delete mode 100644 src/auth/test.rs delete mode 100644 src/blocking/builder.rs delete mode 100644 src/blocking/mod.rs delete mode 100644 src/bucket/test.rs delete mode 100644 src/builder.rs delete mode 100644 src/builder/test.rs delete mode 100644 src/config.rs delete mode 100644 src/consts.rs delete mode 100644 src/decode.rs delete mode 100644 src/decode/test.rs create mode 100644 src/error.rs delete mode 100644 src/errors.rs delete mode 100644 src/file.rs delete mode 100644 src/file/test.rs delete mode 100644 src/object/content.rs delete mode 100644 src/object/content/blocking.rs delete mode 100644 src/object/content/mock.rs delete mode 100644 src/object/content/test_suite.rs delete mode 100644 src/object/content/test_suite_block.rs delete mode 100644 src/object/test.rs delete mode 100644 src/sts.rs delete mode 100644 src/tests/client.rs delete mode 100644 src/tests/env.rs delete mode 100644 src/tests/errors.rs delete mode 100644 src/tests/mod.rs delete mode 100644 src/tests/object.rs delete mode 100644 src/types/core.rs delete mode 100644 src/types/object.rs delete mode 100644 src/types/object/base.rs delete mode 100644 src/types/object/test.rs delete mode 100644 src/types/test.rs delete mode 100644 tests/integration_test.rs delete mode 100644 tests/query_auth.rs diff --git a/.github/actions-rs/grcov.yml b/.github/actions-rs/grcov.yml deleted file mode 100644 index 16341fc..0000000 --- a/.github/actions-rs/grcov.yml +++ /dev/null @@ -1,4 +0,0 @@ -ignore: - - "oss_derive/*" - - "/*" - - "../*" diff --git a/.github/publish.yml b/.github/publish.yml deleted file mode 100644 index aedbb0a..0000000 --- a/.github/publish.yml +++ /dev/null @@ -1,8 +0,0 @@ -projects: - - name: project1 - dir: "/" - tag_prefix: "v" - - - name: oss_derive - dir: "/oss_derive/" - tag_prefix: "derive_" \ No newline at end of file diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index c796665..0a4ed5a 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -25,7 +25,6 @@ jobs: - name: Check fmt run: cargo fmt --check - # v0.1.15 目前版本 - name: Cache uses: Swatinem/rust-cache@v1 with: @@ -38,85 +37,13 @@ jobs: if: steps.cache-publish-action.outputs.cache-hit != 'true' run: | cargo install publish-action --version=0.3.5 - + - name: Run Test run: | - cargo test --lib -- tests - cargo test --lib --no-default-features -- tests - # --all-features, exclude env_test features - cargo test --lib --features="blocking,sts,put_file,core,auth,decode" -- tests - cargo test --lib --features blocking --no-default-features tests - cargo test --lib --features blocking tests - cargo test --lib --features sts --no-default-features tests - cargo test --lib --features put_file --no-default-features tests - cargo test --lib --features core --no-default-features tests - cargo test --lib --features auth --no-default-features tests - cargo test --lib --features decode --no-default-features tests - cargo test --lib --jobs 1 --features env_test tests::env::client_from_env - cargo test --lib --jobs 1 --features env_test tests::env::config_from_env - cargo test --lib --jobs 1 --features env_test tests::env::bucket_base_from_env - cargo test --lib --jobs 1 --features env_test tests::env::end_point_from_env - # --all-features, exclude bench features - cargo test --doc --all-features - cd oss_derive - cargo test - cargo test --features blocking + cargo test --doc - - name: Run publish-action of derive - run: publish-action --dir="/oss_derive/" --tag-prefix="derive_" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} - - name: Run publish-action of oss run: publish-action --dir="/" --tag-prefix="v" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} - - - - grcov: - name: Coverage and Grcov - runs-on: ubuntu-latest - - steps: - - name: Checkout sources - uses: actions/checkout@v2 - - - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly-2023-03-26 - override: true - - - name: Cache - uses: Swatinem/rust-cache@v1 - with: - key: ${{ runner.os }}-coverage-v0.2.0 - - - uses: actions-rs/cargo@v1 - with: - command: test - args: --all-features --lib - env: - CARGO_INCREMENTAL: '0' - RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests' - RUSTDOCFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests' - - - uses: actions-rs/cargo@v1 - with: - command: test - args: --all-features --doc - env: - CARGO_INCREMENTAL: '0' - RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests' - RUSTDOCFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Cpanic=abort -Zpanic_abort_tests' - - name: Gather coverage data - id: coverage - uses: actions-rs/grcov@v0.1 - - - name: Coveralls upload - uses: coverallsapp/github-action@master - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - path-to-lcov: ${{ steps.coverage.outputs.report }} + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} \ No newline at end of file diff --git a/CONTRIBUTION.md b/CONTRIBUTION.md deleted file mode 100644 index cf11eaa..0000000 --- a/CONTRIBUTION.md +++ /dev/null @@ -1,39 +0,0 @@ -# 贡献者指南 - -## 下载代码 -``` -git clone git@github.com:tu6ge/oss-rs.git -cd oss-rs -``` - -## 运行单元测试 - -``` -cargo test --lib --all-features tests -``` - -## 运行单元测试 + 集成测试 - -1. 需要连接aliyun OSS 账号 - -复制根目录下的 `.env-example` 文件为 `.env` 并在里面填充 aliyun 的配置信息 - -2. 运行测试 - -``` -cargo t -q --all-features -``` - -## 注意事项 - -- 请保持 commit 信息的整洁,每次 commit 只做一件事 -- commit 信息,请遵守 Angular 规范 -- 一次 Pull Request 可以包含多个 commit -- 新增特性时,提供必要的单元测试,也欢迎为现有的代码补充单测 -- 新增特性时,请补充必要的文档,预览文档命令: - -``` -cargo doc --open --no-deps -``` - -## 感谢您的贡献 diff --git a/Cargo.toml b/Cargo.toml index 61f84fd..7a7f169 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,119 +1,30 @@ [package] name = "aliyun-oss-client" -version.workspace = true -edition.workspace = true +version = "0.13.0-alpha" +edition = "2021" resolver = "2" -license.workspace = true -documentation.workspace = true description = "an aliyun oss client" keywords = ["aliyun", "oss", "sdk"] -categories = ["api-bindings"] -repository.workspace = true -rust-version = "1.62" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[package.metadata.docs.rs] -all-features = true - -[workspace] -members = ["oss_derive"] - -[workspace.package] -version = "0.12.10" -edition = "2021" license = "MIT" repository = "https://github.com/tu6ge/oss-rs" documentation = "https://docs.rs/aliyun-oss-client" +categories = ["api-bindings"] -[dependencies.oss_derive] -version = "0.10.0" -path = "./oss_derive" -optional = true - -[[example]] -name = "bucket" -required-features = ["blocking"] - -[[example]] -name = "buckets" -required-features = ["blocking"] - -[[example]] -name = "delete_file" -required-features = ["blocking"] - -[[example]] -name = "objects" -required-features = ["blocking"] - -[[example]] -name = "put_file" -required-features = ["blocking", "put_file"] - -[[example]] -name = "io_write" - -[[sts]] -name = "sts" -required-features = ["sts"] - -[features] -default = [ - #"blocking", - "core", - "sts", - "put_file", -] -core = [ - "reqwest", - "async-trait", - "futures", - "futures-core", - "async-stream", - "thiserror", - "oss_derive", - "auth", - "decode", -] -blocking = ["core", "reqwest/blocking"] -sts = ["core", "auth"] -put_file = ["core", "infer"] -auth = ["reqwest", "percent-encoding"] -decode = ["quick-xml", "oss_derive"] -env_test = [] -# bench = [] +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -async-stream = {version = "^0.3", optional = true} -async-trait = {version = "^0.1", optional = true} -base64 = {version = "^0.21"} -chrono = {version = "^0.4"} -futures = {version = "^0.3", optional = true} -futures-core = {version = "^0.3", optional = true} hmac = {version = "^0.12"} -http= {version = "^0.2"} -infer = {version = "^0.14", optional = true} -percent-encoding = {version = "2.2.0", optional = true} -quick-xml = {version = "^0.29", optional = true} -reqwest = {version ="^0.11", optional = true} +base64 = {version = "^0.22"} +chrono = {version = "^0.4"} sha1 = {version = "^0.10"} -thiserror = {version = "^1", optional = true} url= {version = "^2"} +reqwest = {version ="^0.12"} +thiserror = {version = "^1"} +serde = {version = "1.0", features = ["derive"] } +serde-xml-rs = "0.6.0" [dev-dependencies] -assert_matches = "1.5.0" -chrono = {version = "^0.4"} dotenv = "0.15.0" -futures = {version = "^0.3"} -http= {version = "^0.2"} -mockall = "0.11.2" -mockall_double= "0.3.0" -thiserror = {version = "^1"} tokio = { version = "1.19.2", features = ["rt","macros","rt-multi-thread"] } -url = { version = "^2" } -serde = { version = "1.0", features = ["derive"] } -reqwest = { version ="^0.11", features = ["json"] } -# [build-dependencies] -# rustc_version = "0.4.0" + diff --git a/Changelog.md b/Changelog.md deleted file mode 100644 index 9331c64..0000000 --- a/Changelog.md +++ /dev/null @@ -1,586 +0,0 @@ -## (2023-06-09) - -### [0.12.1](https://github.com/tu6ge/oss-rs/compare/0.12.0...0.12.1) (2023-06-08) - - -### Features - -* **auth:** 在 Url 中包含签名 ([#21](https://github.com/tu6ge/oss-rs/issues/21)) ([a15e644](https://github.com/tu6ge/oss-rs/commit/a15e64486769004f42bedbb2729e6cf5e530e14a)), closes [#20](https://github.com/tu6ge/oss-rs/issues/20) - -## (2023-06-01) - -## [0.12.0](https://github.com/tu6ge/oss-rs/compare/0.11.2...0.12.0) (2023-06-01) - - -### ⚠ BREAKING CHANGES - -* **object:** object 构建器调整 -* **object:** object 构建器调整 -* **decode:** remove set_next_continuation_token -* **error:** set field to private -* **types:** change QueryKey to struct -* **types:** change EndPoint to struct -* **decode:** remove ItemError trait -* **types:** change Query insert method -* **decode:** next_token remove Option warp - -### Features - -* **auth:** 为 Request 附加 with_oss 方法 ([114c1e0](https://github.com/tu6ge/oss-rs/commit/114c1e0f2e90cfe4e458631ab0a63765c5c9fe8c)) -* **auth:** change AuthError to struct ([a4b7962](https://github.com/tu6ge/oss-rs/commit/a4b79626d400cc7b650676b11a6d4e148ce2a4fc)) -* **auth:** change with_oss args type ([21820c7](https://github.com/tu6ge/oss-rs/commit/21820c7439c2ca1755681a346fc6e0b9735f6e87)) -* **auth:** set OssHost trait to private ([2befdb5](https://github.com/tu6ge/oss-rs/commit/2befdb538f0de5920a795ace2296146d3c0650a0)) -* **auth:** set_sensitive with secret ([7a02e84](https://github.com/tu6ge/oss-rs/commit/7a02e840597b6a5e7eddfa7ca1af7da795b08f41)) -* **bucket:** add to_vec method in blocking ([2d63089](https://github.com/tu6ge/oss-rs/commit/2d63089f6d5bae6dd5ee8d369d9ff26ac04a536f)) -* **bucket:** change BucketError to struct ([70dfe5b](https://github.com/tu6ge/oss-rs/commit/70dfe5bfd0d8cd2ef30affaf94a21df39661083f)) -* **bucket:** change ExtractItemError to struct ([ff317e9](https://github.com/tu6ge/oss-rs/commit/ff317e915893456d4a78ecb6db5f52203ea08123)) -* **builder:** add Box in Error enum ([5117b52](https://github.com/tu6ge/oss-rs/commit/5117b5272a346629f5ae528d65403317fee280ae)) -* **builder:** change BuilderError to struct ([cd7f123](https://github.com/tu6ge/oss-rs/commit/cd7f123e7f8aec46c97f871e6e88899c0ee1d581)) -* **config:** add set_internal in BucketBase ([576de60](https://github.com/tu6ge/oss-rs/commit/576de60b980612583f025f6369f086fe2aa3f067)) -* **config:** change InvalidConfig to struct ([c80355d](https://github.com/tu6ge/oss-rs/commit/c80355dce74e73e0ec20bbd7078c34ff308f67ea)) -* **config:** fixed InvalidConfig display info ([7f31892](https://github.com/tu6ge/oss-rs/commit/7f31892fa22da97d9968f80a07f682fc04ce133e)) -* **core:** decode xml return more info ([0c3170f](https://github.com/tu6ge/oss-rs/commit/0c3170f6a6598ee3857ef28176cffcbff8532240)) -* **core:** decoupling in Errors ([9610ad6](https://github.com/tu6ge/oss-rs/commit/9610ad6d9e5aa616800c9f84084e92e59cdba0fc)) -* **core:** default datetime value update ([a8c2bed](https://github.com/tu6ge/oss-rs/commit/a8c2bed9b7c2b7f00caaaaef21aa24bc31440c6a)) -* **core:** renamed ObjectList, ListBuckets ([8c17aa9](https://github.com/tu6ge/oss-rs/commit/8c17aa96b62148460ebc46c5943cfcd5f7efd3db)) -* **decode:** 解析 xml 错误时,返回更多信息 ([c1dad3d](https://github.com/tu6ge/oss-rs/commit/c1dad3d2e93a93a61e1aadaa6d6c2289063d437f)) -* **decode:** add Box in Error enum ([985cb45](https://github.com/tu6ge/oss-rs/commit/985cb453b020a3937d050b027b8066add9bec856)) -* **decode:** Add non_exhaustive in Error type ([f39f76b](https://github.com/tu6ge/oss-rs/commit/f39f76bfc73d6a6f0ad4fe643b8a9a6c22ccf556)) -* **decode:** Add non_exhaustive in InnerListError ([72f33c9](https://github.com/tu6ge/oss-rs/commit/72f33c904a0ebb6ccf69282027fc3b055d699b76)) -* **decode:** change enum to struct InnerListError ([5ea1a06](https://github.com/tu6ge/oss-rs/commit/5ea1a06b4eda9c6378c6d09052355216db1fc59c)) -* **decode:** change Error in trait ([0dd2c1b](https://github.com/tu6ge/oss-rs/commit/0dd2c1bf916b3e7fd1e1981be87910764808f7ce)) -* **decode:** next_token remove Option warp ([d91f473](https://github.com/tu6ge/oss-rs/commit/d91f473eba2c2ea1d7e2285da06d8ac73483b5c3)) -* **decode:** remove ItemError trait ([e0c8424](https://github.com/tu6ge/oss-rs/commit/e0c8424d5367deeef5297986f3ba1588b224416c)) -* **decode:** remove set_next_continuation_token ([dd005f8](https://github.com/tu6ge/oss-rs/commit/dd005f88dcb9f84319418809be9c7c41a17ac527)) -* **decode:** set ListErrorKind to private ([5adfb2a](https://github.com/tu6ge/oss-rs/commit/5adfb2a732f5462866ebea96c3d265cd902e0adc)) -* **derive:** remove #[derive(CustomItemError)] ([db6cca3](https://github.com/tu6ge/oss-rs/commit/db6cca3243ec5c1caed2b6931af16c825d91c09a)) -* **error:** change OssError to struct ([e089790](https://github.com/tu6ge/oss-rs/commit/e0897909f0b747caefc828f284a5e91bc11000d7)) -* **error:** remove Input in Error enum ([e8c6d4e](https://github.com/tu6ge/oss-rs/commit/e8c6d4e10fc170cb77b3aef9840e2499e06e0a83)) -* **error:** set field to private ([e0bbc43](https://github.com/tu6ge/oss-rs/commit/e0bbc4398c7bd247db2360e67b04b5ba6d09081a)) -* **file:** change FileError to struct ([7fb1a0a](https://github.com/tu6ge/oss-rs/commit/7fb1a0aa0b34033b75f7370174a6421d94dcb6de)) -* **file:** change OssError enum item ([a489952](https://github.com/tu6ge/oss-rs/commit/a489952831c7111ea7ad7470f73c0086356520d5)) -* **file:** get_object support more num type ([c8c111b](https://github.com/tu6ge/oss-rs/commit/c8c111bd5323979cb738ced9c4f087aa10dccf27)) -* **file:** rename BlockingFiles ([25bb539](https://github.com/tu6ge/oss-rs/commit/25bb5395d60acbf00d857f02d74989b037c9a1a0)) -* **lint:** remove warning lint ([9915737](https://github.com/tu6ge/oss-rs/commit/9915737e7199e99d2226ac5a92bc0746966d6497)) -* **object:** add ListBucketsSync type alias ([8f567ae](https://github.com/tu6ge/oss-rs/commit/8f567aec82e1b3c872e66bd2ca3fc0a5df862957)) -* **object:** add new method of StorageClass ([11ebb58](https://github.com/tu6ge/oss-rs/commit/11ebb589cc42a2cb22348092eee5d59a6422708d)) -* **object:** add ObjectListSync type ([e8012be](https://github.com/tu6ge/oss-rs/commit/e8012be630e1fc34c007cae60c8ec572fa350f9f)) -* **object:** change BuildInItemError to struct ([5a8b4b5](https://github.com/tu6ge/oss-rs/commit/5a8b4b5b33dec4e8de96230f6eb0c5d228f48fd3)) -* **object:** change ExtractListError to struct ([8af9d72](https://github.com/tu6ge/oss-rs/commit/8af9d7286d1756b98425a72961e4262158ab9030)) -* **object:** change ObjectListError to struct ([d3cfe3c](https://github.com/tu6ge/oss-rs/commit/d3cfe3c14406e82155d694b6b7d85ab8335c0d63)) -* **object:** object 构建器调整 ([d693e50](https://github.com/tu6ge/oss-rs/commit/d693e50fe575fba3bcbeac3ca4155900d3f299bc)) -* **object:** object 构建器调整 ([5a094b3](https://github.com/tu6ge/oss-rs/commit/5a094b3ea8f6891d628c0c050731ac03bff166dd)) -* **object:** set BuildInItemErrorKind private ([6f6b198](https://github.com/tu6ge/oss-rs/commit/6f6b19833b92c88a013d6aa67a229d0baef05b51)) -* **object:** set object_list to private in struct ([ba66764](https://github.com/tu6ge/oss-rs/commit/ba667644ff0b57a8eeae8ba9ff2e0803c04a0ace)) -* **sts:** set sensitive security-token ([b9dad10](https://github.com/tu6ge/oss-rs/commit/b9dad10af1246891693bb1d1cc9a361532a385ec)) -* **type:** change InvalidObjectBase inner ([0e37837](https://github.com/tu6ge/oss-rs/commit/0e37837b3404b89d50ace749d9761fa6e798067f)) -* **types:** 从 env 转化配置的时候,处理 endpoint 的情况 ([8e95b37](https://github.com/tu6ge/oss-rs/commit/8e95b3758f63605e2c6a25d9088a2a315ba86da2)) -* **types:** 升级 KeySecret 类型的安全性 ([29c44fc](https://github.com/tu6ge/oss-rs/commit/29c44fc4d07aaac38016dadc0f255503c7d12cf7)) -* **types:** add non_exhaustive in QueryKey ([2601b03](https://github.com/tu6ge/oss-rs/commit/2601b037efc5332793b9232845605aaa9155058a)) -* **types:** Add priv in inner type ([671cb12](https://github.com/tu6ge/oss-rs/commit/671cb12ea54b132897e12767993880e9308e6b23)) -* **types:** change EndPoint to struct ([32628ee](https://github.com/tu6ge/oss-rs/commit/32628eec1224f7f8ad9cfa938e05f0e6b185b5c6)) -* **types:** change InvalidBucketName display info ([71df709](https://github.com/tu6ge/oss-rs/commit/71df7093407a61818d4c5621ed1f6f5b4cfb6308)) -* **types:** change InvalidObjectDir display info ([8da7edf](https://github.com/tu6ge/oss-rs/commit/8da7edf3a30613759e439f1c8991b7b0a26caece)) -* **types:** change Query insert method ([8e041c8](https://github.com/tu6ge/oss-rs/commit/8e041c807e66d8dae31fadf91a86e6a3288b9f31)) -* **types:** change QueryKey to struct ([12fe959](https://github.com/tu6ge/oss-rs/commit/12fe959ec5a4373e0e86fad05d2fea3de2de8e16)) -* **types:** Date only from DateTime ([bdfed8c](https://github.com/tu6ge/oss-rs/commit/bdfed8c3b09f5788b0881c50b719ce96c7288ae4)) -* **types:** rename Trait ([4fc1b87](https://github.com/tu6ge/oss-rs/commit/4fc1b87767abf34cc6875767c63f61ab95360301)) -* **types:** set ObjectBase::new to pub(crate) ([7746c73](https://github.com/tu6ge/oss-rs/commit/7746c73fe63f24f7170f88bbb471066b29e70424)) - - -### Bug Fixes - -* **auth:** 解决模块之间的依赖问题 ([3965c77](https://github.com/tu6ge/oss-rs/commit/3965c77cfe0b6d29e2489601d940e5c89ead9d6b)) -* no default features ([98220d4](https://github.com/tu6ge/oss-rs/commit/98220d46d4f0ddf43c39e1100c5e92b492b6cca7)) -* **object:** fixed macro error ([c7d4b4e](https://github.com/tu6ge/oss-rs/commit/c7d4b4efc9d12f74cddc74b76ec810dd962077c9)) - - -### Reverts - -* Revert "feat(file)!: GetStd GetStdWithPath remove method" ([799cbff](https://github.com/tu6ge/oss-rs/commit/799cbff462b2bec0afbe72a11907ca73dd8971f3)) -* Revert "fix(test)" ([9a3a7c5](https://github.com/tu6ge/oss-rs/commit/9a3a7c5356a60cef505b398caacf1e30d1fc9d80)) - -# (2023-03-24) - - - -## [0.11.2](https://github.com/tu6ge/oss-rs/compare/0.11.1...0.11.2) (2023-03-24) - - -### Bug Fixes - -* **types:** 解决 Endpoint 匹配错误 ([0a65ad2](https://github.com/tu6ge/oss-rs/commit/0a65ad2d581efef14d9b9b697f7e79d6c4e34247)), closes [#15](https://github.com/tu6ge/oss-rs/issues/15) - - -### Features - -* **config:** Add AsMut of ObjectDir ([499de79](https://github.com/tu6ge/oss-rs/commit/499de79393324e161486b642030618529614f5e5)) -* **core:** Add AsRef in some type ([2b72ba0](https://github.com/tu6ge/oss-rs/commit/2b72ba07ea8ac18feaa88396b7dddd241dcb2c31)) -* re-export Error Result ([f1cdae3](https://github.com/tu6ge/oss-rs/commit/f1cdae3668909cb4c4a50e1c402d4ba2ee4c523a)) - - - -# (2023-03-10) - - - -## [0.11.1](https://github.com/tu6ge/oss-rs/compare/0.11.0...0.11.1) (2023-03-10) - - -### Features - -* **types:** Custom endpoint deny `oss` prefix ([4aa7bbc](https://github.com/tu6ge/oss-rs/commit/4aa7bbc6d3e4b18a59d87d87a8096b9905cb66ad)) -* **types:** From<&'static str> change to From<&'a str> ([848f149](https://github.com/tu6ge/oss-rs/commit/848f1499b00daa4bfaf66bf5e453d56fd9f2e028)) - - - -# (2023-03-09) - - - -# [0.11.0](https://github.com/tu6ge/oss-rs/compare/0.10.1...0.11.0) (2023-03-09) - - -### Features - -* **auth:** AuthBuilder method 参数签名更改 ([b0c5182](https://github.com/tu6ge/oss-rs/commit/b0c5182ef99e231f29cbea092f2535edef15a6cd)) -* **bucket:** 读取列表和详情内部的信息 ([c006da4](https://github.com/tu6ge/oss-rs/commit/c006da4afe08bb63c2132160b089a72c6d9f6693)) -* **bucket:** 更改 BucketList 内部字段类型 ([03edbd3](https://github.com/tu6ge/oss-rs/commit/03edbd37f7cd7ebf6ca652776be0d9b9a5153566)) -* **bucket:** BucketList Add Item generic ([0e85c82](https://github.com/tu6ge/oss-rs/commit/0e85c82bc9105afbaf1550db3752dbaa73df501a)) -* **bucket:** remove Option wrapper in ListBuckets ([f9e2a3d](https://github.com/tu6ge/oss-rs/commit/f9e2a3d9f9e78e00035068a2694604332948ae15)) -* **builder:** 更改方法的可见性 ([5fe7326](https://github.com/tu6ge/oss-rs/commit/5fe7326fa02a35409423172a7dfa63c0470f9cb0)) -* **config:** 增加内部类型 ObjectPathInner ([de94aee](https://github.com/tu6ge/oss-rs/commit/de94aee58c313b0b020683cd53285f7ab381d182)) -* **config:** Add ObjectDir type ([84a89cd](https://github.com/tu6ge/oss-rs/commit/84a89cdc265be74116af4c6d2479ceb31c3ea4ea)), closes [#12](https://github.com/tu6ge/oss-rs/issues/12) -* **config:** ObjectDir Support + operator ([b95faed](https://github.com/tu6ge/oss-rs/commit/b95faedc977671bcfc3d9a71165d7debdd8c6be9)) -* **config:** remove repeat method ([f740008](https://github.com/tu6ge/oss-rs/commit/f7400086ff0dde35c82d7452751e69c99a2f9cf1)) -* **config:** update ObjectDir new method ([2d8f842](https://github.com/tu6ge/oss-rs/commit/2d8f842afad4171957ef7d829a1ec1ddec14243b)) -* **decode:** 对导出的 trait 改名 ([dc9c20c](https://github.com/tu6ge/oss-rs/commit/dc9c20ca55f8e8e0db3fdf93cf5dd7f8fe7ae639)) -* **decode:** 减少对自定义类型的限制条件 ([2c6e445](https://github.com/tu6ge/oss-rs/commit/2c6e44596d9508dba58ad8c6cb9d0b5eadc0f984)) -* **decode:** 减少对自定义类型的限制条件 ([8d8a639](https://github.com/tu6ge/oss-rs/commit/8d8a6396af8e215ad74e036e92efc80ad35e4309)) -* **decode:** 减少对自定义类型的限制条件 ([4fe2441](https://github.com/tu6ge/oss-rs/commit/4fe2441a634e3f0c745f30d497240cccb032412c)), closes [#12](https://github.com/tu6ge/oss-rs/issues/12) -* **decode:** 内部 trait 增加默认实现 ([7acaec3](https://github.com/tu6ge/oss-rs/commit/7acaec37396a28dce53c88c39e3b7bd737056806)) -* **decode:** traits change to decode ([a9b3a8d](https://github.com/tu6ge/oss-rs/commit/a9b3a8d61237a01387c5692a928cb7d1291e10f1)) -* **file:** 对文件操作改为更加灵活的方式 ([f8cf9ea](https://github.com/tu6ge/oss-rs/commit/f8cf9ea739e210354b402bc452447750b37c0225)) -* **file:** 将 blocking 的 File trait 改名为 Files ([977db3f](https://github.com/tu6ge/oss-rs/commit/977db3f7ea0638154bf0f556722cdf44bc33ab5a)) -* **file:** 将 File trait 改名为 Files,另外新增 File trait ([023c320](https://github.com/tu6ge/oss-rs/commit/023c320626b495320eddeb81119dea494c85dd4e)) -* **file:** remove put_file and more method ([5a95a8f](https://github.com/tu6ge/oss-rs/commit/5a95a8f1692c72e547acc04396a3ec7ccdf37cd6)) -* **lib:** remove traits mod name ([35c1773](https://github.com/tu6ge/oss-rs/commit/35c177348b6db696da7ed339e28a9eefb28832bc)) -* **macro:** add derive with decode ([f51865d](https://github.com/tu6ge/oss-rs/commit/f51865d69de76743e0b1df1fac8109da3e0bde55)) -* **objcet:** change ObjectList prefix type ([9f07f34](https://github.com/tu6ge/oss-rs/commit/9f07f34bb800312dbe058553c9a28fdc1b08cefa)) -* **object:** add get_next_base method ([c77b379](https://github.com/tu6ge/oss-rs/commit/c77b379a456908ad9428fea1b5b182c0debcd9c2)) -* **object:** ObjectList Add Item generic ([63d85e2](https://github.com/tu6ge/oss-rs/commit/63d85e295bdae7f07ca13a04a5cef707426564a2)), closes [#12](https://github.com/tu6ge/oss-rs/issues/12) -* **object:** Support CommonPrefix ([c3e54c1](https://github.com/tu6ge/oss-rs/commit/c3e54c1aa665617b9a2ef7fec083b4ba4da14bf3)), closes [#9](https://github.com/tu6ge/oss-rs/issues/9) -* **sts:** STS 秘钥支持更多类型 ([f2e1531](https://github.com/tu6ge/oss-rs/commit/f2e153120c4601c62864d07261babb8c212bda63)) -* **type:** 支持更多的可用区 ([8e65f01](https://github.com/tu6ge/oss-rs/commit/8e65f01bf67e0241ebc7b306f4819d2630d15bb0)) -* **types:** 提升 BucketName EndPoint 等类型的安全性 ([895e373](https://github.com/tu6ge/oss-rs/commit/895e373f71f78eb4dc2ec25289c46cbc2bc24398)) -* **types:** Support FromStr for more buildin type ([e56afe8](https://github.com/tu6ge/oss-rs/commit/e56afe83d700d037092b27fe8b8a5f5cfb4f9bd0)) -* **types:** unwrap changed to expect ([066813b](https://github.com/tu6ge/oss-rs/commit/066813b4dfd095d4cbbd3babde578e0385b36318)) - - -# [0.10.0](https://github.com/tu6ge/oss-rs/compare/0.9.7...0.10.0) (2022-12-10) - - -### Features - -* **auth:** remove VERB ,use http::Method ([06ed16b](https://github.com/tu6ge/oss-rs/commit/06ed16b08db653435149270314a810a889138a84)) -* **bucket:** deprecated intranet_endpoint field ([68f1fc0](https://github.com/tu6ge/oss-rs/commit/68f1fc02971049829f52b15bdff0994f8e4152c7)) -* **builder:** Support Response without xml error ([cd49a01](https://github.com/tu6ge/oss-rs/commit/cd49a017fdfbc50c3a4bb8a021ae2edf2ec27877)), closes [#7](https://github.com/tu6ge/oss-rs/issues/7) -* **client:** 添加获取 object 元信息的方法 ([4c9d8e3](https://github.com/tu6ge/oss-rs/commit/4c9d8e341d68b96fce68161d442075e59e8623a1)) -* **client:** deprecated set_bucket_name method ([acc281a](https://github.com/tu6ge/oss-rs/commit/acc281ab103516123903aeae2296533058531f52)) -* **core:** builder_with_header 签名更改 ([2104d5c](https://github.com/tu6ge/oss-rs/commit/2104d5cc87d962a4193c64a28dc263c6f62ec54f)) -* **error:** changed OssService ([7bc42ac](https://github.com/tu6ge/oss-rs/commit/7bc42acde7c383b2e48071a551206e41cc445244)) -* **error:** Enhance OssService ([e74deec](https://github.com/tu6ge/oss-rs/commit/e74deec568c7f39d5ca6996b0d5d7365d61c70d8)) -* **file:** 为 ObjectBase 增加了几个方法 ([0e48c21](https://github.com/tu6ge/oss-rs/commit/0e48c2113e81283e121c07e25f0ae08e7ff6f285)) -* **object:** 读取和设置 object 信息更改 ([57796d3](https://github.com/tu6ge/oss-rs/commit/57796d370b96279b5ac3913c009bed0c4bbf421c)) -* **object:** head_object example ([b8669b2](https://github.com/tu6ge/oss-rs/commit/b8669b23b379a36ec9c43a096767db6ddec199b6)) -* **types:** 支持 &str 转 Query ([597b530](https://github.com/tu6ge/oss-rs/commit/597b5309ae6417ddc93e594153af89dd3aa716c2)) -* **types:** BucketBase 添加 get_url_resource 方法 ([2197750](https://github.com/tu6ge/oss-rs/commit/2197750745bd0a66b18909b171677b41f76ad7b0)) -* **types:** Query 添加 IntoIterator ([910f565](https://github.com/tu6ge/oss-rs/commit/910f565282b9300d370e5a56e0c0e65308133b9b)) -* **types:** Query 支持更多的生成方式 ([5634669](https://github.com/tu6ge/oss-rs/commit/56346692a93ed388823cdf6cd4e14d897fac1bc9)) - - - -## [0.9.7](https://github.com/tu6ge/oss-rs/compare/0.9.6...0.9.7) (2022-12-05) - - - -## [0.9.6](https://github.com/tu6ge/oss-rs/compare/0.9.5...0.9.6) (2022-12-05) - - - -## [0.9.5](https://github.com/tu6ge/oss-rs/compare/0.9.4...0.9.5) (2022-12-05) - - -### Bug Fixes - -* **file:** Fixed tip message ([20582dd](https://github.com/tu6ge/oss-rs/commit/20582dd417ce8ad7aabd23433a339aab06aac415)), closes [#5](https://github.com/tu6ge/oss-rs/issues/5) - - -### Features - -* **client:** 可导出 bucket 列表到自定义类型 ([246042c](https://github.com/tu6ge/oss-rs/commit/246042c05c041d5514ca43c72b8813cf62c27c4e)) -* **client:** 可导出 bucket 信息到自定义类型 ([225c7e5](https://github.com/tu6ge/oss-rs/commit/225c7e5db43e82a9e41f5f7856275798e46b29dd)) -* **client:** 可导出自定义object列表 ([d1e95be](https://github.com/tu6ge/oss-rs/commit/d1e95be8f15c8afd583dade73757f174a50de680)) -* **client:** 可导出自定义object列表 ([b254d5c](https://github.com/tu6ge/oss-rs/commit/b254d5ce27bd94268608b69992e1f69ef21e2367)) -* **client:** 可导出自定义object列表 ([e2c701f](https://github.com/tu6ge/oss-rs/commit/e2c701fccabec0f34b1af6f545b8d2c8428d23fd)) -* **init:** 支持更便捷的初始化方式 ([c9bb523](https://github.com/tu6ge/oss-rs/commit/c9bb52394bf89b761326d640dad3d2a461334f31)) -* **object:** base_object_list 可传递自定义bucket名称 ([8b7ef6e](https://github.com/tu6ge/oss-rs/commit/8b7ef6e906315653b51b7d507746d95e6ff56094)) -* **query:** Enhance Query type ([db1675f](https://github.com/tu6ge/oss-rs/commit/db1675f79318890ff4713061438278fdbe68eea8)) -* **traits:** 减少特征要求 ([b28015f](https://github.com/tu6ge/oss-rs/commit/b28015fc1f98f272c2c9cb9af24e8355de5199bf)) -* **traits:** 解析xml方法改为更加通用的方式 ([99fabb0](https://github.com/tu6ge/oss-rs/commit/99fabb0373ba04e87c94ffbec95d3a0e9ddc3d8c)) -* **traits:** 解析xml方法改为更加通用的方式 ([7ef563f](https://github.com/tu6ge/oss-rs/commit/7ef563ffebf534ee221477b9ee2dbb859879cb91)) -* **traits:** trait 名称中 OssInto 更改为 Refine ([37c5e13](https://github.com/tu6ge/oss-rs/commit/37c5e13caf5cdfca4f975922acb4107c47933dff)) -* **types:** Support vec! into Query ([be6fb16](https://github.com/tu6ge/oss-rs/commit/be6fb16babeac7f8e77f3d7e5146d7d149812873)) -* **xml:** Parse xml Reduced copy ([bea5f89](https://github.com/tu6ge/oss-rs/commit/bea5f89caa7fd1016545a12c14115f0190c9ee24)) - - - -## [0.9.4](https://github.com/tu6ge/oss-rs/compare/0.9.3...0.9.4) (2022-11-27) - - -### Bug Fixes - -* **auth:** remove default in accident ([f11122d](https://github.com/tu6ge/oss-rs/commit/f11122d5ac019c5e6d2fb16148f7acc3ccb08699)) -* **config:** InvalidBucketBase changed to enum ([a91e8f1](https://github.com/tu6ge/oss-rs/commit/a91e8f131d928a02b08d36abbd725631a4d64a5e)) -* **error:** remove println code ([15a9d5d](https://github.com/tu6ge/oss-rs/commit/15a9d5df7ada1ac52693f0f76ce2bfcd190a8b39)) -* **macro:** syntex fixed ([ad5f89f](https://github.com/tu6ge/oss-rs/commit/ad5f89f45642a62af96c5be279282c9979bea4e4)) -* **styles:** fix PartialEq in ObjectBase ([c243ea4](https://github.com/tu6ge/oss-rs/commit/c243ea49865113d3b7cc982f61775a7d94f0fb86)) - - -### Features - -* **deprecated:** intranet_endpoint in bucket ([a200da9](https://github.com/tu6ge/oss-rs/commit/a200da935e2af72cb3c0ffb64b3bc7ac1ff05269)) -* **file:** 路径参数统一 ObjectPath 类型 ([00676a5](https://github.com/tu6ge/oss-rs/commit/00676a51697a0687968ec84d21de32f4fd439e94)) -* **file:** 文件相关功能增强 ([c4bdd62](https://github.com/tu6ge/oss-rs/commit/c4bdd622ef06ce279c1fcb13d411e471c2408f1a)) -* **file:** 文件相关功能增强 ([7bf7566](https://github.com/tu6ge/oss-rs/commit/7bf756600263a41ec61a4a33d7ba97b5979b44f5)) -* **macro:** 区分 async 和同步 ([06bfbf4](https://github.com/tu6ge/oss-rs/commit/06bfbf4971c60bd6d4906c61dc9988a8a9a92c8d)) -* **macro:** Add oss_gen_rc Macro ([795add1](https://github.com/tu6ge/oss-rs/commit/795add16ed35a6ed901ad18bd3e3d24d1eeea860)) -* **macro:** blocking finished ([271c8eb](https://github.com/tu6ge/oss-rs/commit/271c8eb74eae16b922d0ac63323fb5d2ae03d390)) -* **macro:** support async ([0c82d47](https://github.com/tu6ge/oss-rs/commit/0c82d47240eb39b53eb899f3d3d757890f062c93)) -* **macro:** support attribute, eg. cfg ([3a72dda](https://github.com/tu6ge/oss-rs/commit/3a72dda55399aa9deb08fb3410c309bc03b49603)) -* **macro:** support ClientRc,Rc::clone ([35b29a6](https://github.com/tu6ge/oss-rs/commit/35b29a63683975e04565c6d6d727d920e40e9827)) -* **macro:** support inline ([1c35164](https://github.com/tu6ge/oss-rs/commit/1c35164642e22ca1119930d26a403f93cb518bb6)) -* **marco:** init 可以导入代码并原样导出 ([d373793](https://github.com/tu6ge/oss-rs/commit/d373793903440606edb2b6ca2da5f062e7d1c99c)) -* **object:** 移除多余的字段 ([a79cc1b](https://github.com/tu6ge/oss-rs/commit/a79cc1b59e90474cdf655719f7913c190dbe1ff3)) -* **object:** add blocking get_object method ([ce93a7a](https://github.com/tu6ge/oss-rs/commit/ce93a7a4be8ca07226285d3bc263c2ec83df7be0)) -* **object:** object 结构体支持文件操作 ([d6f5d22](https://github.com/tu6ge/oss-rs/commit/d6f5d22713901f944d497687d98e62dbef6bc020)) -* **object:** object 结构体支持文件操作 ([efb7c17](https://github.com/tu6ge/oss-rs/commit/efb7c1778affdf5f93078b87222b440ce064646f)) -* **object:** ObjectList Support Steam ([4dfaa30](https://github.com/tu6ge/oss-rs/commit/4dfaa30caac7096077b966fc4d78e0026bf0fa17)) -* **object:** Objects Support Iterator ([b1dda87](https://github.com/tu6ge/oss-rs/commit/b1dda87864179535369a336403d531d6247c6c47)) -* **object:** try steam get next ([ead7f5c](https://github.com/tu6ge/oss-rs/commit/ead7f5cdd8b2f768acf54120249d350d7e461747)) -* **types:** buildin types support PartialEq ([cb88290](https://github.com/tu6ge/oss-rs/commit/cb88290ff53130b98e14a253ba99217313f96501)) -* support internal ([05c8f85](https://github.com/tu6ge/oss-rs/commit/05c8f85d896b804ae46257689575dc7bdb2547bb)) -* **types:** CR::from_object Arg update ([5bd6344](https://github.com/tu6ge/oss-rs/commit/5bd6344adbe02b3487cc71a8158d4c96c31d2653)) -* **types:** Query support u16 value ([249725f](https://github.com/tu6ge/oss-rs/commit/249725f9b4e0c0039811bea6fe7ecf3edfe4cb88)) -* **types:** Query support u8 value ([4ba8eac](https://github.com/tu6ge/oss-rs/commit/4ba8eac3eb3eaaafaa253da54f1cca9dedebeb50)) - - - -## [0.9.3](https://github.com/tu6ge/oss-rs/compare/0.9.2...0.9.3) (2022-11-16) - - -### Features - -* **object:** read object_list field ([0bb48b2](https://github.com/tu6ge/oss-rs/commit/0bb48b259362edca32508338e0589c02d6d6a3bd)), closes [#4](https://github.com/tu6ge/oss-rs/issues/4) - - - -## [0.9.2](https://github.com/tu6ge/oss-rs/compare/0.9.1...0.9.2) (2022-11-15) - - -### Bug Fixes - -* **object:** fix obejct field read or written ([cd5a3e9](https://github.com/tu6ge/oss-rs/commit/cd5a3e9e4d8debc326e95af5d0197897244e019d)), closes [#2](https://github.com/tu6ge/oss-rs/issues/2) - - - -## [0.9.1](https://github.com/tu6ge/oss-rs/compare/0.9.0...0.9.1) (2022-11-15) - - -### Bug Fixes - -* **object:** fix obejct field read or written ([f4b0bf5](https://github.com/tu6ge/oss-rs/commit/f4b0bf50668414858432a2cf49a28bf90b15c669)), closes [#2](https://github.com/tu6ge/oss-rs/issues/2) - - - -# [0.9.0](https://github.com/tu6ge/oss-rs/compare/0.8.0...0.9.0) (2022-11-08) - - -### Bug Fixes - -* **auth:** Prevent secret exposure ([e4553f7](https://github.com/tu6ge/oss-rs/commit/e4553f7d74fce682d802f8fb073943387796df29)) -* **client:** ignore put_content example ([f3ebb70](https://github.com/tu6ge/oss-rs/commit/f3ebb70748ee0ce6234f42b51ae3a1415148cbd8)) -* **types:** ContentRange i32 update to u32 ([d7f32bf](https://github.com/tu6ge/oss-rs/commit/d7f32bf0ea8dc468b4f5c12627cba115fdf13021)) - - -### Features - -* **auth:** set all field to private ([61da9b2](https://github.com/tu6ge/oss-rs/commit/61da9b2e29a9500f571ea5a1ceed6cf997b295b6)) -* **client:** remove infer field ([04b1cff](https://github.com/tu6ge/oss-rs/commit/04b1cff27c2dcf2a4fd233858ef6b18ab813f352)) -* **client:** set middleware pub(crate) ([6073ad8](https://github.com/tu6ge/oss-rs/commit/6073ad83654d048c842a59fb62b4f91e8c169469)) -* **object:** Add get_object method ([be828e1](https://github.com/tu6ge/oss-rs/commit/be828e136a90facf71a695270cc5e67a57262917)) - - - -# [0.8.0](https://github.com/tu6ge/oss-rs/compare/0.7.6...0.8.0) (2022-11-05) - - -### Bug Fixes - -* **object:** 解决 object struct 默认值导致的问题 ([a5b5b86](https://github.com/tu6ge/oss-rs/commit/a5b5b86110e5903ea8f69f84921cee473a21a5e6)) -* **plugin:** 修复几个过时代码的错误 ([4429681](https://github.com/tu6ge/oss-rs/commit/442968154f0412ba6a9c90b18728b81c18346d4f)) -* **test:** 测试用例更改 ([98433a4](https://github.com/tu6ge/oss-rs/commit/98433a473cf100e09a1ef5faca2721de6625b68e)) - - -### Features - -* support STS ([676935f](https://github.com/tu6ge/oss-rs/commit/676935fad116faed65e70d501c19b0a95006be72)), closes [#1](https://github.com/tu6ge/oss-rs/issues/1) -* **auth:** 加上了几个 trait ([527fd7a](https://github.com/tu6ge/oss-rs/commit/527fd7a0834d990cced0aa24248373237ca24788)) -* **auth:** 减少 auth 中多个 trait 之间的耦合性 ([356d8e1](https://github.com/tu6ge/oss-rs/commit/356d8e10206fc33009a397985d867d3e9aa4fce3)) -* **auth:** 使用 Cow 减少 clone ([9917fd3](https://github.com/tu6ge/oss-rs/commit/9917fd327587569c75bf3e4eaedcd9dba7d28b13)) -* **auth:** 整理 auth 模块的代码 ([550f9ef](https://github.com/tu6ge/oss-rs/commit/550f9ef7de1e5b4ead05d05e2527ecd302a1b4ab)) -* **auth:** builder 实现 trait ([23e377d](https://github.com/tu6ge/oss-rs/commit/23e377d4aef16717954292069ed47bd52304eb0a)) -* **blocking:** add bucket,object struct in block ([17e5b32](https://github.com/tu6ge/oss-rs/commit/17e5b32a9e4ddf593004811c34f4e2e5c0e77a57)) -* **blocking:** add client fn ([a0ff03d](https://github.com/tu6ge/oss-rs/commit/a0ff03d35b4fdac649e737f381a0dfd25a9f15fb)) -* **bucket,object:** 新增了 BucketBase 等 ([7860fb8](https://github.com/tu6ge/oss-rs/commit/7860fb8f6ee0139e18f7a969ce7bb0e177183a0e)) -* **client:** 对 reqwest 中的struct 进行包裹封装 ([2473331](https://github.com/tu6ge/oss-rs/commit/24733310b100006e7b2f63b0359714839620ca72)) -* **client:** 去掉多余的 async 标记 ([fabfe98](https://github.com/tu6ge/oss-rs/commit/fabfe987037291d886211d998fccaacdd92e34e7)) -* **client:** 删除了过去的获取 canonicalized_resource 的方法 ([6c8acf4](https://github.com/tu6ge/oss-rs/commit/6c8acf4b11ade74ad1c272b6fc0821aa7255524a)) -* **client:** 支持用 config 初始化 client ([7f50de3](https://github.com/tu6ge/oss-rs/commit/7f50de366f4f9637b62dbcb5f40b9211e9dc32e2)) -* **client:** add part in Client struct ([46e17e1](https://github.com/tu6ge/oss-rs/commit/46e17e12b45c40c43bf24b53ca4a95faca7cc261)) -* **client:** client init method update ([855e3ef](https://github.com/tu6ge/oss-rs/commit/855e3efc132b96e9838d21a6a79d87b5fb0d359d)) -* **client:** upgrade builder method ([5ce269d](https://github.com/tu6ge/oss-rs/commit/5ce269d364a47ad6a01398002a7b2fcc8efdfe52)) -* **config:** add P in ObjectBase ([6638e0a](https://github.com/tu6ge/oss-rs/commit/6638e0a592cfe477a0778214eea15022d9b0d0ab)) -* **error:** 解析错误,去掉regex方案 ([9329ee2](https://github.com/tu6ge/oss-rs/commit/9329ee2026d81519094b030d86a36356d2d5d6c0)) -* **object:** add put_content_base method ([b1caf7e](https://github.com/tu6ge/oss-rs/commit/b1caf7ea7effb2aacb234982b05bf12b59c1e32b)) -* **plugin:** 清理 plugin 的残余内容 ([c27a1de](https://github.com/tu6ge/oss-rs/commit/c27a1dee5d988dc78bf7c8b6df63f2cd8ae576af)) -* 替代 object_list_query_generator 方法的方案 ([120c343](https://github.com/tu6ge/oss-rs/commit/120c3432c4c4c60d96714137357a8095525d37e6)) -* 添加了几个新类型 ([b1f2932](https://github.com/tu6ge/oss-rs/commit/b1f29324c62b24a35af3e21b1b22672208047d20)) -* 引入 Cow 智能指针 ([4e8613e](https://github.com/tu6ge/oss-rs/commit/4e8613e56a79535900a66934c2de449e10a26cf2)) -* add BucketBase,ObjectBase struct ([ce97f54](https://github.com/tu6ge/oss-rs/commit/ce97f54fc59ac9385bf115e79f425b48ae1c7679)) -* unite Arc and Rc in bucket,object ([8460f33](https://github.com/tu6ge/oss-rs/commit/8460f3374a2f9fb0afbb98d229c0f903e75487ab)) -* **client:** client结构体不再保持key ,secret ([54838aa](https://github.com/tu6ge/oss-rs/commit/54838aa561e6083fde8e75b257fdcf1132596c19)) -* **plugin:** 移除 plugin 模块 ([4460faf](https://github.com/tu6ge/oss-rs/commit/4460faff028f9856598a1a1ff7bffda793fbf4b2)) -* **plugin:** remove plugin example ([664ad06](https://github.com/tu6ge/oss-rs/commit/664ad06ab3ddcc98f61c7233584fcb831f803d93)) -* **types:** 更改方法名称 ([7f9c19b](https://github.com/tu6ge/oss-rs/commit/7f9c19b1fe106f89678b7499decf96d5d4cfb1e5)) -* **types:** BucketName 变得更安全 ([e3daec4](https://github.com/tu6ge/oss-rs/commit/e3daec482b7b599a844dbd00b47102c755687601)) -* **types:** EndPoint update to enum type ([b218e76](https://github.com/tu6ge/oss-rs/commit/b218e761ca9d99fc5ffd208224839cd0a6c47101)) - - - -## [0.7.6](https://github.com/tu6ge/oss-rs/compare/0.7.5...0.7.6) (2022-09-07) - - -### Bug Fixes - -* **error:** 解析oss错误信息修改 ([009b7b2](https://github.com/tu6ge/oss-rs/commit/009b7b276bf22aa3e63dce3c4c49ca1914022e0c)) - - -### Features - -* **auth:** add auth builder ([8151576](https://github.com/tu6ge/oss-rs/commit/8151576a80b78638dba671024e805e100e81567f)) -* **error:** OssError 添加 message 方法 ([d0938b9](https://github.com/tu6ge/oss-rs/commit/d0938b979c7d5174d0c5b26613ab47c041ad1b8c)) - - - -## [0.7.5](https://github.com/tu6ge/oss-rs/compare/0.7.4...0.7.5) (2022-09-04) - - -### Features - -* **aliyun:** 更好的阿里云错误的处理方式 ([ee97d1e](https://github.com/tu6ge/oss-rs/commit/ee97d1e845bee0c24caa259fc33374a72bd0220e)) - - - -## [0.7.4](https://github.com/tu6ge/oss-rs/compare/0.7.3...0.7.4) (2022-08-25) - - - -## [0.7.3](https://github.com/tu6ge/oss-rs/compare/0.7.2...0.7.3) (2022-08-21) - - -### Bug Fixes - -* **plugin:** 解决在多线程情况下,plugin的问题 ([8dc3d83](https://github.com/tu6ge/oss-rs/commit/8dc3d8338161300b6aa6db4d26f2611f39272c7c)) - - -### Features - -* **object:** 上传文件的路径支持特殊字符(空格等) ([a237f2f](https://github.com/tu6ge/oss-rs/commit/a237f2f1279a4da9f3186f7dd1338523a446e796)) -* **plugin:** 支持自定义扩展文件类型 ([f48957a](https://github.com/tu6ge/oss-rs/commit/f48957a0bc3e5d4ccc31fd672094376838e3d3a8)) - - - -## [0.7.2](https://github.com/tu6ge/oss-rs/compare/0.7.1...0.7.2) (2022-08-20) - - -### Bug Fixes - -* **plugin:** 解决不使用 plugin 特征时导致的问题 ([99c2544](https://github.com/tu6ge/oss-rs/commit/99c2544ed8b52f862caf8823a6388611a628268f)) - - -### Features - -* **object:** 上传文件时,传递路径的方式更加灵活 ([1cce936](https://github.com/tu6ge/oss-rs/commit/1cce9363ec1dbd10d75459bcbd5f7db3630f805a)) - - - -## [0.7.1](https://github.com/tu6ge/oss-rs/compare/0.7.0...0.7.1) (2022-07-17) - - - -# [0.7.0](https://github.com/tu6ge/oss-rs/compare/0.6.0...0.7.0) (2022-07-17) - - -### Features - -* **client:** Method 类型支持更灵活的赋值方式 ([7c5b436](https://github.com/tu6ge/oss-rs/commit/7c5b436f97817d3d869385f751752fc1a81025a6)) -* **object:** put_content arg type update ([f6f4864](https://github.com/tu6ge/oss-rs/commit/f6f4864fccf2b3681adae578243f2a9e3cd1f90f)) -* **trait:** 支持导出数据到自定义 object bucket 结构体 ([4451322](https://github.com/tu6ge/oss-rs/commit/445132277d86d9974b5bbc14fa3e634d92d8272c)) - - - -# [0.6.0](https://github.com/tu6ge/oss-rs/compare/0.5.0...0.6.0) (2022-06-26) - - -### Features - -* **async:** 异步的方法去掉前缀 async 改为默认方法 ([7fbed19](https://github.com/tu6ge/oss-rs/commit/7fbed1941afad74e4b61d8209a6a0e276398a057)) -* **blocking:** 同步方法加上 blocking 前缀 ([7def094](https://github.com/tu6ge/oss-rs/commit/7def09423198985ed746e390aaf61b82aa7d86e0)) -* **blocking:** reqwest 的 blocking 特征改为可选引用 ([cdcc197](https://github.com/tu6ge/oss-rs/commit/cdcc1970504855b034cbe68d466e6993049f8d03)) -* **stream:** 尝试实现 stream ([2d0679e](https://github.com/tu6ge/oss-rs/commit/2d0679e3183caa37579dd07e1ac3c686266bc073)) -* **sync:** 支持异步调用(所有接口) ([f12cb27](https://github.com/tu6ge/oss-rs/commit/f12cb27a0c7871d1c8c5b432e25077c615dc7e99)) - - -### Reverts - -* Revert "refactor: wip" ([35af9df](https://github.com/tu6ge/oss-rs/commit/35af9df839c35cad5464babc7b1ad229721b3b79)) - - - -# [0.5.0](https://github.com/tu6ge/oss-rs/compare/0.4.4...0.5.0) (2022-06-19) - - -### Features - -* **plugin:** 插件可查看 client 结构体内容 ([5b6c894](https://github.com/tu6ge/oss-rs/commit/5b6c89450ddcc542a2595e910427ff1a6b51067d)) -* **plugin:** 增加插件的能力 ([bc71a1f](https://github.com/tu6ge/oss-rs/commit/bc71a1fadf67217df601bc273555f3cd887efad7)) -* **plugin:** 支持插件机制 ([fb1ac8f](https://github.com/tu6ge/oss-rs/commit/fb1ac8fea8f969a67f270dc198cad9ab80c98df1)) - - - -## [0.4.4](https://github.com/tu6ge/oss-rs/compare/0.4.3...0.4.4) (2022-06-17) - - -### Bug Fixes - -* **object:** 解决put时的签名错误 ([5ea71b4](https://github.com/tu6ge/oss-rs/commit/5ea71b4ee516f6859cf5453b0b68189c1dcabceb)) - - - -## [0.4.3](https://github.com/tu6ge/oss-rs/compare/0.4.2...0.4.3) (2022-06-16) - - -### Features - -* **copy:** 复制文件功能完成 ([2114c7f](https://github.com/tu6ge/oss-rs/commit/2114c7f115f83a988fb8a6c0b1046d57cf6467fb)) -* 测试复制object 功能 ([3e9de65](https://github.com/tu6ge/oss-rs/commit/3e9de65909257a9dd3388bf923b8093822fc41e6)) -* 测试复制object 功能 ([d72fae9](https://github.com/tu6ge/oss-rs/commit/d72fae9f31b058ae9c1d0d662a2a8f5e46e00307)) - - - -## [0.4.2](https://github.com/tu6ge/oss-rs/compare/0.4.1...0.4.2) (2022-06-15) - - -### Features - -* **object:** 列表加上迭代器功能 ([1bd43d6](https://github.com/tu6ge/oss-rs/commit/1bd43d6682e76ca888e498da023da0ad05f31fab)) - - - -## [0.4.1](https://github.com/tu6ge/oss-rs/compare/0.4.0...0.4.1) (2022-06-14) - - - -# [0.4.0](https://github.com/tu6ge/oss-rs/compare/0.3.1...0.4.0) (2022-06-14) - - -### Features - -* **bucket:** bucket struct 添加 get_object_list 方法 ([8f7d4bb](https://github.com/tu6ge/oss-rs/commit/8f7d4bbe0d438e49f5f72ff9acf682e7c3afa7a1)) - - - -## [0.3.1](https://github.com/tu6ge/oss-rs/compare/0.3.0...0.3.1) (2022-06-14) - - - -# [0.3.0](https://github.com/tu6ge/oss-rs/compare/0.2.6...0.3.0) (2022-06-12) - - -### Features - -* **error:** 优化 oss 返回错误的处理方式 ([2c80270](https://github.com/tu6ge/oss-rs/commit/2c8027075a9ec469265d986ecafd788d86f08f50)) -* **error:** supplement error handler ([262b60c](https://github.com/tu6ge/oss-rs/commit/262b60cb8a5073a030376c44266840b4d2612d98)) -* **object:** 获取object 列表时加上参数支持 ([bdde53c](https://github.com/tu6ge/oss-rs/commit/bdde53cdbf866886d9455be30c6eb4c821e94bb1)) -* **objects:** 接收object 列表接口返回的 next token ([082857d](https://github.com/tu6ge/oss-rs/commit/082857d0bfee62208901007a045f07fd6474ce28)) - - - -## [0.2.6](https://github.com/tu6ge/oss-rs/compare/0.2.5...0.2.6) (2022-05-30) - - - -## [0.2.5](https://github.com/tu6ge/oss-rs/compare/0.2.4...0.2.5) (2022-05-30) - - - -## [0.2.4](https://github.com/tu6ge/oss-rs/compare/0.2.3...0.2.4) (2022-05-30) - - - -## [0.2.3](https://github.com/tu6ge/oss-rs/compare/0.2.2...0.2.3) (2022-05-30) - - - -## [0.2.2](https://github.com/tu6ge/oss-rs/compare/0.2.1...0.2.2) (2022-05-23) - - - -## [0.2.1](https://github.com/tu6ge/oss-rs/compare/0.2.0...0.2.1) (2022-05-22) - - - -# [0.2.0](https://github.com/tu6ge/oss-rs/compare/0.1.0...0.2.0) (2022-05-22) - - -### Features - -* 入口方式调整 ([8b8f3b0](https://github.com/tu6ge/oss-rs/commit/8b8f3b09b62a31cab96186312f39f8185a217e9c)) -* 删除文件完成 ([25c5891](https://github.com/tu6ge/oss-rs/commit/25c58915caf38b3bf5d90d4bd4630fc77464083b)) -* 上传文件完成 ([999b3e6](https://github.com/tu6ge/oss-rs/commit/999b3e602478aac5bf2c4569e6d2d721502f1f44)) -* 上传文件完成 ([f63d408](https://github.com/tu6ge/oss-rs/commit/f63d408f2f5d8c6dc5299b51b8176a3135e505ad)) - - - -# [0.1.0](https://github.com/tu6ge/oss-rs/compare/43decad21bf8cfe0246a39996ef1e04c737538d8...0.1.0) (2022-05-21) - - -### Features - -* 获取对象列表 ([2cca3d1](https://github.com/tu6ge/oss-rs/commit/2cca3d16359db240e3f9ddb7c35198d72d626fde)) -* **bucket:** 时间格式化 ([dd9399e](https://github.com/tu6ge/oss-rs/commit/dd9399eeaab0eefc632df2c7c87d05b553fdfb14)) -* 获取 bucket 列表成功 ([7af9fef](https://github.com/tu6ge/oss-rs/commit/7af9fef50bb41d290a838ea91e19d4134329a759)) -* 签名验证通过 ([3f867b7](https://github.com/tu6ge/oss-rs/commit/3f867b79fa7039f216ed17a48d4697dc3e4ee806)) -* auth 和 http 简单封装 ([4539583](https://github.com/tu6ge/oss-rs/commit/453958348bfb57c06dae06b75eef03fbeb9a9cb6)) -* auth 和 http 简单封装2 ([6cb8940](https://github.com/tu6ge/oss-rs/commit/6cb89400de25a733a164d485555b82b14d6ce98e)) -* auth struct 初步完成 ([43decad](https://github.com/tu6ge/oss-rs/commit/43decad21bf8cfe0246a39996ef1e04c737538d8)) -* object and bucket struct 初步完成 ([b2eb7ce](https://github.com/tu6ge/oss-rs/commit/b2eb7ce9722d8e9059e09de9b52c12e838990ed4)) - - -### Performance Improvements - -* 尝试优化 xml 读取时的性能 ([bafdbd4](https://github.com/tu6ge/oss-rs/commit/bafdbd424c1eeebc4027b95096c669182501bcbd)) - - - diff --git a/README.md b/README.md index b55ee0b..36593f4 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,60 @@ -# aliyun-oss-client +# aliyun_oss_client 打算采用一种全新的方式来实现 -aliyun OSS 的一个异步/同步客户端,包含以下功能: +**巨大更新,之前使用过的,慎重升级 0.13** -- `auth` 模块,处理 OSS 验证,可以抽离出来,独立与 `reqwest` 库配合使用 -- `traits` 模块,包含 OSS 接口返回的原始 `xml` 数据的解析方式,可以将数据方便的导入到自定义的 rust 类型中,可以独立使用 -- `client` 模块,基础部分,封装了 `reqwest` `auth` 模块,并提供了一些便捷方法 -- `bucket` 模块,包含 bucket 以及其列表的结构体 -- `object` 模块,包含 object 以及其列表的结构体 -- `file` 模块,文件上传,下载,删除等功能,可在 client, bucket, object 等结构体中复用 -- `config` 模块,OSS 配置信息,可用于从数据库读取配置等操作 +目前的实现,代码将大大简化,api接口也更加起清晰,使用了有限的 rust 语言特性,所以接口更加统一, +大大减少了外部依赖 -[![Coverage Status](https://coveralls.io/repos/github/tu6ge/oss-rs/badge.svg?branch=master)](https://coveralls.io/github/tu6ge/oss-rs?branch=master) [![Test and Publish](https://github.com/tu6ge/oss-rs/actions/workflows/publish.yml/badge.svg)](https://github.com/tu6ge/oss-rs/actions/workflows/publish.yml) [![Crate](https://img.shields.io/crates/v/aliyun-oss-client.svg)](https://crates.io/crates/aliyun-oss-client) [![MSRV](https://img.shields.io/badge/rustc-1.62.0+-ab6000.svg)](https://blog.rust-lang.org/2022/06/30/Rust-1.62.0.html) +详细使用案例如下: -> 现在的破坏性更新,都是为了 1.0 版本能够稳定 +get Buckets -## 使用方法 +```rust +struct Client { + key: String, + secret: String, +} -[查看文档](https://docs.rs/aliyun-oss-client/) - -## 运行 Bench - -```bash -rustup run nightly cargo bench +impl Client { + async fn get_buckets(&self, endpoint: EndPoint) -> Vec { + todo!() + } +} ``` -## 生成 Changelog - -```bash -conventional-changelog -p conventionalcommits -i Changelog.md -s -r=2 +get bucket info; +```rust +struct Bucket { + name: String, + endpoint: EndPoint, +} +impl Bucket{ + async fn get_info(&self, client: &Client) -> BucketInfo { + todo!() + } + async fn get_object(&self, client: &Client) -> Vec { + todo!() + } +} +``` +get object info + +```rust +struct Object { + bucket: Bucket, + path: ObjectPath, +} +impl Object { + async fn get_info(&self, client: &Client) -> ObjectInfo { + todo!() + } + + async fn upload(&self, client: &Client, content: Vec) -> Result<(), Error>{ + todo!() + } + async fn download(&self, client: &Client) -> Result, Error>{ + todo!() + } +} ``` -## 贡献代码 - -欢迎各种 PR 贡献,[贡献者指南](https://github.com/tu6ge/oss-rs/blob/master/CONTRIBUTION.md) diff --git a/build.rs b/build.rs deleted file mode 100644 index 8624248..0000000 --- a/build.rs +++ /dev/null @@ -1,9 +0,0 @@ -//extern crate rustc_version; - -//use rustc_version::{version_meta, Channel}; - -fn main() { - // if version_meta().unwrap().channel == Channel::Nightly { - // println!("cargo:rustc-cfg=feature=\"bench\""); - // } -} diff --git a/examples/bg2015071010.png b/examples/bg2015071010.png deleted file mode 100644 index 682e4a4c851fd9c64dc0ba4761250b7ebaf04336..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7217 zcmd5>cT`hfwhjn_AWcC;kSd}eT?{q!j#Q;69h52o5fJ1eMG#Pm^hgKkB_K@%LXa*c zNEHM_6CsHKflzKJkKfF^w`OL&nfK<8SMFM;?fre3YqrQiKShmQ~snWGB&uQ8Ka$fe0;nbdS&8?{A+`p{cW(*&9uO)X0m@FB4ioz?%XXCd`oakgp1K-JjB>Q-mo;KIlX9!uON z?oiaR&9aZ@Lp(8qhG(I923ZxBGi9!?v_Fy|*T!HOHlDsCHs)me5!B zCC>x7g(p>oeq8yAd+Sh^Z1JuxroE>aljU6TF}b@juEVbodK&`a)YjEdz4LU2FmoSX zpmMS2_fK8k)QB)q3eD~y!o8xZmyOut!e8ju)v@e#x|z3^59e5=Hg;BcqRdtKj@ZkL zER(^m%WuFJGG#`yEdS#0KzUwt-%}d@jGzn)VqGi=y&Z8~Sds^lu6Dp$<3?-GULZn> z$)&!vhZeI?Ze1_Ma9SFA)381|b?bE)w+Pk*E0E(_l+x=-4rQRRH~4WM_wD{W-YvR-XO1THgh8daRr5s=ci z0E83J=Vg=+gcXCj&&g)2RX`7j;5vPhNZ`5$Uj=vz-E#wxQH*|(b#rHqHe<7E+(o03 z6xNIg;nFWdW>@0AIldd?fSeJrioOz?ga^{A4F@DhHYQEnCCbLh;7orp>g?0PTa-`W z;%KCheUFGfQ0L!*68yBZEh%#%a06EX{TiPJS+5*op}M#^a~55bH(3gQ!#>wfgadMa zKs<5JI+|e~h=?&%i&1^F^CieN1@6=Va8owSzZ|Z58|NXE3Eonf&&mK>neuMNG30CB zIRGH@OWDhsKqdt3f7BSje3V_S3;c54Cn#*pQ0RT0L6$vq9L%|_Vo{El);7eM>b`^h zHd+UATay&7>C3|4m(GR>2HIk|F4o;j0$%_CQl($tHpc#taZt&9$Zt;(iBZi%#_^KQ z;6dEY$F{N%%(778W~1e?&n&g0xgy%((P$|qt8YsGes4KS()g`$>uWtCua9m(K-(@O zzk6}9jD*W07drnYDE`^0kF26jcXOfCN%fZOOHZdNh9^F~(Ch8;8|COuzVY}~+kV#& zeHfzUTA#T6-KWImQ~gyZ??RAcJ?5Y{47+)}yN>dX&C{A{wzg!{M{Y3l+A0S1Z*g7m8 za8S6#Lgbufe~g_+C6Z6?M$SS%h%ci$0KQ!YB7HO5+pZ6YAgle4L7**9j3Xe-8YVaP zpm8sn(s`2dCr0eCi9iR6j~ouD?@*JY2=qG))9eJ!_u^ln&UQ8MKGZ)19IV0qeWHJA zL&>lWk)7XzFvi*hmmQyIK(Q`Cov?%exEy&fgr$L;>|a+IBOES#eSizE62WSiP?1)jgoARBo4TFZW(h0=4l;h5NQeYWKOsUcj2VJ zSHjHr%yWM~Bq8W(!Pn3V%$?{7msTsFSgjJaG=!7>2UIE>>3hVqPUB8Pq4C>s^4YZp zHu?HX)QGeYRdpzdhUko=vv~_ao(V9|Z5DmP7e4idHS$kghtsDXRKk4kuYOV(-0sFn zL!-pb=9P(`_#~f*d8&%X7SveVwNGa%u}ROmuGL=Q5_^^%MQ4(t7Z(VBmX2EDN9B2)fMzhd)z68<82ePz~q$)Z*1ET+_J!yhN*rQic zg{xN?&v~0Kr-vCs`jN=IO%kNjVH8%N)TOMn4!O>S<@MIRuwTXVO3tPNIoUc4tkM6j z∾I`S#v&^Qwf|^tm0(s4Ei_(T3JiQ5S#VN|{vdzHpFi_=JUvE$NqiIuo0&8-=f1 zNb`EJ*+*iwpIS{?MFPM*htjEH>bFFsO;~u?lG-GO$Q`=~gy?4nM!bpT2o^>n`AV#;os8^DXA4hm2_p?T zj1KxOSPjR@J}mI%k?@dz?%jF)Nh+Sz<2&3^q^TCf4VIupl^K3)pF-`P5$mj zyqqUg($*I_nqnzvfy)f1(>~!UVgT)I4r;J;VO##PGls(!Q->&Jp(nzr0R+X;;PIF$4{%d(9hw&a9SPP$Z_aaRo>;SrSacA3v zH>}iDfZV>5Rs9*r;)KCR+uTuA$}7J%{s@;9o|Y%xV-gUE>WJ)fP(1xKe^ulY)vtfI zA)TyKuhoN;soy<#i?XHc^{re5?$v{{yVSc3&Eg;6eLx4GPNXMtKDP75CX&O+QM2r` z&}Gy-itqm?WC5}JSKIB$>=L%9pLYY+D=@FF|EN(s{)9%T+cn0{y+ezUhRAVwhu1<- zdt9j8PUy)2E0E5WQ?Zt2=sHvynwf7rQ7+1Xm7+NI=Qf_fQ--T3KZ6 zu6bwxJ^*fNM)y*CU!C5-3Pfi8J%J>G0sGhEZ_FK~0ycArCxrCqw$lK%V7 zB}-ykBL+YPUbS%zynt~!RgS~!LJt=|Bwk2s`6@V>R^Mpdo_|F~1h+LLtsBu7nh3)5 zokS{c8i-D%4-dKN_g4>x-Gu(hT_!2bTZ*C)99kStMa@haf2_L=z#n;tk_KbJ;4Ooo znwr8Y>n|Gt1*j#1>vTrSU{MMhiYm=dQpjc5Eo7dgR6Pm|BAZ}|=dhPRxRcjPY zM}OVzpuS=~`G+DYa0St|KDf7pK?#YJmBODJ0Oah<9pCB5?^!EeN>=BqF^ZCfV*nh9tU_s2$-=JwipPhvcA=03LdsrhWC84qJQ zhP!CN4F3K84Fw{rjsDNI!OlUF>q@mZj^C9tNVaM|~v+2K4A(`+m@ z{RgueQ}8YI@fi7+&wC|UrIYk?;7f~!DSk?hv;d|0o3#3uK`8t0g^Bh}EpFsdm&bl< zpx=xw{#YI84F^A$A}L8GJfv(1oWg~13oU@HGyyDKe_?{H%|l7yDpY`RZ9wQX7*9<~ zHL;_{6~b@|4&wngN|CQYVPhVW3HD4O%1IS?Fm4eBo={PB`Y$ygXqB_*gBEVUeCGQ( z29Aez^kgwIC7DnM&j3UTEd+W(9R}sF(@?YiUc3eaBS%e7eL@YO&@eKA^IrtN;`zHp ztmwa%M+IRHEx5QN{wVUf=~GXRo$)X`g407Dz8%TB1ZHx^w01NwND%0PJ=|e&45i%~kj;$ORI_v*9)ZB1xt z4y-s5&1lte=Z}rF@vq9cyDzUg19HWz0eQ)!%kNO+k#2BQ1J}YdFIadFFHvmPP2F!( z<)W0MiEQ{jNY}`kKUueTpwc`Nn2=M9in;G0>AUc3YfOAzA%2NpibsCse%);Fme+>1 zWmhWju+U|b$;ic~9BJKY<#CB4D%ek`!%3s$DMu$Fmn=f|j%TZid&ZklyzQ(ax9?eH z^@C;d<$Zb!*?j}HbMk_rETX=RoA3?oEEwb2U5RntqSY&jC@F}9AtX4lI{hWY-P7p# z7Y@jZ3WX(U^~Rm#vsGBgxt1D(>6OWP_bRJ=N=nW#iK}t}W_o1;>9>8&An%nQcSUqad_{*eoo4+lj(6 zV7c$c?GxpcrdxCpIhvJ>=vxJEpvupvF4V_=buz@$m$q?=C2Z%1-{qE^yJoNUMKe#F zdd0IJqBtTP#+IEj)R8oK?x~09Ye~YFr-sam$;Xz@DE)`@3>$GCwof7v+KETN-dy{_ zq7IJ^-90nj!d3o}sXE;TXO&zb{bSK2dmSgLU@gT8j3{F$^;*@VZysV*Lh^jxy!S~( z6!AB2eYGfYEu`wzhwcefquAvY<|7uFp7}Q=%d%=Y?Ahp^5fx?eWd5AXW~`+}P0@U; z&$;p~uD(X@q z`79%9g+c3tA2Kg@Q=0bOV?e?6n`li5Bd3*T^G_;_&iQ*%46fz=uSSY&&j!DEW5E7c z?tE;!gOyPC2QW=yq(396xn3_j`#ybzhyG{&RD7n2ox6H5a}J@O9&;w=&DFDhccub5 z6SX}!Pmvqm=>hmf^Bw5vTU*FS_N3;f10f2kcH2t6F;aCLuWZ>QJ9VoL{M{(!L(|VM ze^Oy}&R>!*$G152;nze%?RoDK%}-f=#?c1tRNO|+k{weYhz}38G`@(@`%H4v;D5BL z1KO7vJw+*YNSjUtu#d2Hvb5?=>KhqG`$@bk8{C^Aqw*CGJd~NsE3L71u7H{sl1t>} z&X1Gl{D^7^t&V}^3F3XCc~<;6 zHjXsMe8XMdRj-TX`F7ICb(i#rjU+FlzK?!s8qkWnZIY`sk{WY#oO0y!)ifsH_DeI* zSCR4NmhkS)_x;r$X1!MRe+uns;Xop#JbS#t{+PPc`$+cwpfgYJi{3lDI3q8Q&ea|7 z+kK}ASV0Pd8yK0&;!mS@d>XutvkRr}>z8|63Q_m{Xv^?Do#<7n?dZ*fy1)Fs2xMsdS1ni6KWzazj3~4upJNew*0mCMx>Ur)g6n$LQ|F5B(o9 zb*mw#YOu}r1%?Nln-?1&n+8B(${o-@UfHTKxmWD+7~~{xFmJAZyx`@rNx`tu1(PRV zbmTz*XKND%VNqYTAM&UZ1nLs_cHT?}5Jo%BP^vOhp`EC6QG?3nW*KQ(!}-Edjx6Mh zbqSgTr$N|-+5}0mWf!c6;PhwXaDrkrXHX8j6`A=oTiM0#GwK5Q@lM0n^g3iWDhm+Z z`9big6?d_F1;~-_5eBBG9PNhx7Q(Yb%s36XNb7j<`DD>ZvQ(Sr*=aKqtRZl68W^|u zzwca5SGmfaYMl>5l_jW;_d-cN(coS&jM0P#JKoEOTgn7wHGp6XQF7KmIq<=s$xs1G(fQTSav*>^dpP zPB8+=a^(5>b=L(m?9WR5N#g8tJJZwC;Z;8DG))~va}Ddqqom+l(OUD=SLN+&1n)=)&Tf1w8wv0!xEm z?CcY%dXmh`GL!=3xE!*L@Tsw()GL@aQ8eb3H6g1lyh5ZUmy|#b=Hc@D8we)*u>x*o zB|RVChdENyG~Rg^W>Bf^aqB8gB%TrivymccZEJe&A zGBB935w#*Q&aTHULqmIYa4t-I#X9{*7@u?%BxLY2M}`!X z61JZi{Ug^rDA+GIS+3)aoKgPIB>2gfjy;iPqhl}+jJN3VcJ`k;ZafjAA z<_)T~2i}8m!w8Cg&S?1< zcX+&Cm9iAVD)tH#L%&EpQ{(1AKaW}G%#IXU?Sh#2Rl1HRrJSn zC)no_<+{hSf;`q$a+^0;RaU`=5D%wd=#>DaM^~Qz+RmdU?WsX>BWYLPtbpZ{MW`vf z{0R|q`%~)C@7K%v+%$m5D^rMji;j^dKo{F^F|_>b4=I)vfnZ4b#9A>9yvK_4eP?I} zkl0MI1AaSGD_Rso!`DM|mMyUEvQ+*lm%m>T(_NoygLkaY(El>$MY?}NHSXehU?1upsRPGyOk0Y6 zH|&1?f3@nQEboj z`eivjJP8mp;93>`LoO;`S7B$du;N8(LsoTHu#Q}p)4?7Axp(YUp5V*#w*8&IiUSO= zv#Y*eo?B9X` zBzK!J0MR#>oU=px8WqNSlBH&!k9nz1A2OC+%6Nqu_@Zr>00UPG|EOBHfNj#ByM+n= zdoJ>OSbw3Zz?Tv(;8HB?$Lg5z_I2&tr~)H7@Fpwp`AbRus#!dYK{NO9w%au@rO-(I ouc2!;%4>K36~ce19dJltY}09(d^Y3cml_13t7)iFt!5woZ}G5|1poj5 diff --git a/examples/bucket.rs b/examples/bucket.rs deleted file mode 100644 index 080b7d4..0000000 --- a/examples/bucket.rs +++ /dev/null @@ -1,21 +0,0 @@ -extern crate dotenv; - -use aliyun_oss_client::blocking::builder::ClientWithMiddleware; -use aliyun_oss_client::client::Client; -use aliyun_oss_client::types::Query; -use dotenv::dotenv; - -fn main() { - dotenv().ok(); - - let client = Client::::from_env().unwrap(); - //let headers = None; - let response = client.get_bucket_info().unwrap(); - println!("bucket info: {:?}", response); - - let mut query = Query::new(); - query.insert("max-keys".to_string(), "2".to_string()); - let mut result = response.get_object_list(query).unwrap(); - println!("object list: {:?}", result); - println!("next object list: {:?}", result.next()); -} diff --git a/examples/buckets.rs b/examples/buckets.rs deleted file mode 100644 index cbf8bed..0000000 --- a/examples/buckets.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! `cargo run --example buckets --features=blocking` - -use aliyun_oss_client::blocking::builder::ClientWithMiddleware; -use aliyun_oss_client::client; -use aliyun_oss_client::types::Query; - -extern crate dotenv; - -use dotenv::dotenv; - -fn main() { - dotenv().ok(); - - let client = client::Client::::from_env().unwrap(); - //let headers = None; - let response = client.get_bucket_list().unwrap(); - println!("buckets list: {:?}", response.to_vec().first().unwrap()); - - let mut query = Query::new(); - query.insert("max-keys".to_string(), "5".to_string()); - query.insert("prefix".to_string(), "babel".to_string()); - - // let buckets = response.to_vec(); - // let the_bucket = &buckets[1]; - // println!( - // "bucket object list: {:?}", - // the_bucket.get_object_list(query) - // ); -} diff --git a/examples/custom.rs b/examples/custom.rs deleted file mode 100644 index bbd24b7..0000000 --- a/examples/custom.rs +++ /dev/null @@ -1,79 +0,0 @@ -use aliyun_oss_client::{ - decode::{RefineObject, RefineObjectList}, - object::{ExtractListError, InitObject}, - Client, DecodeListError, -}; -use dotenv::dotenv; -use thiserror::Error; - -#[derive(Debug)] -struct MyFile { - key: String, - #[allow(dead_code)] - other: String, -} - -impl RefineObject for MyFile { - fn set_key(&mut self, key: &str) -> Result<(), MyError> { - self.key = key.to_string(); - Ok(()) - } -} - -#[derive(Default, Debug)] -struct MyBucket { - name: String, - files: Vec, -} - -impl RefineObjectList for MyBucket { - fn set_name(&mut self, name: &str) -> Result<(), MyError> { - self.name = name.to_string(); - Ok(()) - } - fn set_list(&mut self, list: Vec) -> Result<(), MyError> { - self.files = list; - Ok(()) - } -} - -#[derive(Debug, Error, DecodeListError)] -#[error("my error")] -struct MyError {} - -async fn get_with_client() -> Result<(), ExtractListError> { - dotenv().ok(); - - let client = Client::from_env().unwrap(); - - // 除了设置Default 外,还可以做更多设置 - let mut bucket = MyBucket { - name: "abc".to_string(), - files: Vec::with_capacity(20), - }; - - // 利用闭包对 MyFile 做一下初始化设置 - impl InitObject for MyBucket { - fn init_object(&mut self) -> Option { - Some(MyFile { - key: String::default(), - other: "abc".to_string(), - }) - } - } - - client.base_object_list([], &mut bucket).await?; - - println!("bucket: {:?}", bucket); - - Ok(()) -} - -#[tokio::main] -pub async fn main() { - let res = get_with_client().await; - - if let Err(err) = res { - eprintln!("{}", err); - } -} diff --git a/examples/delete_file.rs b/examples/delete_file.rs deleted file mode 100644 index 9d7cd38..0000000 --- a/examples/delete_file.rs +++ /dev/null @@ -1,18 +0,0 @@ -//! `cargo run --example delete_file --features=blocking` - -use aliyun_oss_client::blocking::builder::ClientWithMiddleware; -use aliyun_oss_client::client::Client; - -extern crate dotenv; - -use aliyun_oss_client::file::BlockingFiles; -use dotenv::dotenv; - -fn main() { - dotenv().ok(); - - let client = Client::::from_env().unwrap(); - //let headers = None; - client.delete_object("examples/bg2015071010.png").unwrap(); - println!("delet file success"); -} diff --git a/examples/file.rs b/examples/file.rs deleted file mode 100644 index 4606308..0000000 --- a/examples/file.rs +++ /dev/null @@ -1,65 +0,0 @@ -use std::{fs, path::Path}; - -use aliyun_oss_client::{ - config::get_url_resource, - file::{File, FileError, GetStd}, - types::CanonicalizedResource, - BucketName, Client, EndPoint, Error as OssError, KeyId, KeySecret, -}; -use reqwest::Url; - -struct MyObject { - path: String, -} - -impl MyObject { - const KEY_ID: KeyId = KeyId::from_static("xxxxxxxxxxxxxxxx"); - const KEY_SECRET: KeySecret = KeySecret::from_static("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); - const END_POINT: EndPoint = EndPoint::CN_SHANGHAI; - const BUCKET: BucketName = unsafe { BucketName::from_static2("xxxxxx") }; - - fn new(path: &Path) -> Result { - Ok(MyObject { - path: path.to_str().unwrap().to_owned(), - }) - } -} - -impl GetStd for MyObject { - fn get_std(&self) -> Option<(Url, CanonicalizedResource)> { - let path = self.path.clone().try_into().unwrap(); - Some(get_url_resource(&Self::END_POINT, &Self::BUCKET, &path)) - } -} - -impl File for MyObject { - fn oss_client(&self) -> Client { - Client::new( - Self::KEY_ID, - Self::KEY_SECRET, - Self::END_POINT, - Self::BUCKET, - ) - } -} - -#[tokio::main] -async fn main() -> Result<(), OssError> { - for entry in fs::read_dir("examples")? { - let path = entry?.path(); - let path = path.as_path(); - - if !path.is_file() { - continue; - } - - let obj = MyObject::new(path)?; - let content = fs::read(path)?; - - let res = obj.put_oss(content, "application/pdf").await?; - - println!("result status: {}", res.status()); - } - - Ok(()) -} diff --git a/examples/files.rs b/examples/files.rs deleted file mode 100644 index 1a503c7..0000000 --- a/examples/files.rs +++ /dev/null @@ -1,70 +0,0 @@ -use std::fs; - -use aliyun_oss_client::builder::{BuilderError, RequestBuilder}; -use aliyun_oss_client::config::BucketBase; -use aliyun_oss_client::file::{AlignBuilder, FileError, Files, GetStdWithPath}; -use aliyun_oss_client::types::{object::ObjectPath, CanonicalizedResource}; -use aliyun_oss_client::{BucketName, Client, EndPoint, HeaderName, HeaderValue, Method}; -use reqwest::Url; - -struct MyClient; - -#[derive(Debug)] -struct MyError(String); - -impl From for MyError { - fn from(value: FileError) -> Self { - Self(value.to_string()) - } -} - -struct MyPath(String); - -impl AlignBuilder for MyClient { - fn builder_with_header>( - &self, - method: Method, - url: Url, - resource: CanonicalizedResource, - headers: H, - ) -> Result { - dotenv::dotenv().ok(); - Client::from_env()?.builder_with_header(method, url, resource, headers) - } -} - -impl GetStdWithPath for MyClient { - fn get_std_with_path(&self, path: MyPath) -> Option<(Url, CanonicalizedResource)> { - let bucket = std::env::var("ALIYUN_BUCKET").unwrap(); - - let end_point = EndPoint::CN_SHANGHAI; - let bucket = BucketName::new(bucket).unwrap(); - let base = BucketBase::new(bucket, end_point); - let obj_path = ObjectPath::try_from(path.0).unwrap(); - Some(base.get_url_resource_with_path(&obj_path)) - } -} - -impl GetStdWithPath for Client { - fn get_std_with_path(&self, path: MyPath) -> Option<(Url, CanonicalizedResource)> { - let bucket = std::env::var("ALIYUN_BUCKET").unwrap(); - - let end_point = EndPoint::CN_SHANGHAI; - let bucket = BucketName::new(bucket).unwrap(); - let base = BucketBase::new(bucket, end_point); - let obj_path = ObjectPath::try_from(path.0).unwrap(); - Some(base.get_url_resource_with_path(&obj_path)) - } -} - -#[tokio::main] -async fn main() { - let client = MyClient {}; - - let file = fs::read("rustfmt.toml").unwrap(); - let res = client - .put_content_base(file, "application/json", MyPath("rustfmt.toml".to_string())) - .await; - - println!("{res:?}"); -} diff --git a/examples/head_object.rs b/examples/head_object.rs deleted file mode 100644 index 597a5ec..0000000 --- a/examples/head_object.rs +++ /dev/null @@ -1,26 +0,0 @@ -use aliyun_oss_client::{errors::OssError, file::AlignBuilder, Client, Method}; -use dotenv::dotenv; - -#[tokio::main] -pub async fn main() -> Result<(), OssError> { - dotenv().ok(); - - let client = Client::from_env().unwrap(); - - let (url, resource) = client - .get_object_base("9AB932LY.jpeg")? - .get_url_resource([]); - - let headers = vec![( - "If-Unmodified-Since".parse().unwrap(), - "Sat, 01 Jan 2022 18:01:01 GMT".parse().unwrap(), - )]; - - let builder = client.builder_with_header(Method::HEAD, url, resource, headers)?; - - let response = builder.send().await?; - - println!("status: {:?}", response.status()); - - Ok(()) -} diff --git a/examples/io_write.rs b/examples/io_write.rs deleted file mode 100644 index a3ab5e1..0000000 --- a/examples/io_write.rs +++ /dev/null @@ -1,25 +0,0 @@ -use std::{io::Write, sync::Arc}; - -use aliyun_oss_client::{object::content::Content, Client}; -use dotenv::dotenv; - -#[tokio::main] -async fn main() { - dotenv().ok(); - - let client = Client::from_env().unwrap(); - let mut objcet = Content::from_client(Arc::new(client)) - .path("aaabbb3.txt") - .unwrap(); - - //objcet.part_size(100 * 1024).unwrap(); - - { - objcet - .write_all(b"abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789") - .unwrap(); - objcet.flush().unwrap() - } - - println!("finish"); -} diff --git a/examples/object_custom.rs b/examples/object_custom.rs deleted file mode 100644 index 952d61c..0000000 --- a/examples/object_custom.rs +++ /dev/null @@ -1,46 +0,0 @@ -use aliyun_oss_client::{ - decode::RefineObject, - object::{InitObject, Objects}, - types::object::{InvalidObjectDir, ObjectDir, ObjectPathInner}, - Client, -}; -use dotenv::dotenv; - -#[derive(Debug)] -enum MyObject<'a> { - File(ObjectPathInner<'a>), - Dir(ObjectDir<'a>), -} - -impl RefineObject for MyObject<'_> { - fn set_key(&mut self, key: &str) -> Result<(), InvalidObjectDir> { - *self = match key.parse() { - Ok(file) => MyObject::File(file), - _ => MyObject::Dir(key.parse()?), - }; - Ok(()) - } -} - -type MyList<'a> = Objects>; - -impl<'a> InitObject> for MyList<'a> { - fn init_object(&mut self) -> Option> { - Some(MyObject::File(ObjectPathInner::default())) - } -} - -#[tokio::main] -async fn main() { - dotenv().ok(); - - let client = Client::from_env().unwrap(); - - let mut list = MyList::default(); - - let _ = client.base_object_list([], &mut list).await; - - let _second = list.get_next_base().await; - - println!("list: {:?}", list.to_vec()); -} diff --git a/examples/object_stream.rs b/examples/object_stream.rs deleted file mode 100644 index 6f2b313..0000000 --- a/examples/object_stream.rs +++ /dev/null @@ -1,31 +0,0 @@ -use aliyun_oss_client::{Client, Query}; -use dotenv::dotenv; -use futures::{pin_mut, StreamExt}; - -#[tokio::main] -async fn main() { - dotenv().ok(); - - let client = Client::from_env().unwrap(); - - let mut query = Query::new(); - query.insert("max-keys", 1000u16); - - let object_list = client.get_object_list(query).await.unwrap(); - - println!( - "list: {:?}, token: {:?}", - object_list, - object_list.next_continuation_token_str() - ); - - let stream = object_list.into_stream(); - - pin_mut!(stream); - - let second_list = stream.next().await; - let third_list = stream.next().await; - - println!("second_list: {:?}", second_list); - println!("third_list: {:?}", third_list); -} diff --git a/examples/objects.rs b/examples/objects.rs deleted file mode 100644 index 3719d10..0000000 --- a/examples/objects.rs +++ /dev/null @@ -1,19 +0,0 @@ -//! `cargo run --example objects --features=blocking` - -use aliyun_oss_client::builder::ClientWithMiddleware; -use aliyun_oss_client::client::Client; -use aliyun_oss_client::file::Files; -use dotenv::dotenv; - -#[tokio::main] -async fn main() { - dotenv().ok(); - - let client = Client::::from_env().unwrap(); - - let response = client.get_object("app-config.json", 10..16).await.unwrap(); - println!( - "objects content: {:?}", - String::from_utf8(response).unwrap() - ); -} diff --git a/examples/parse_xml.rs b/examples/parse_xml.rs deleted file mode 100644 index 0cfe117..0000000 --- a/examples/parse_xml.rs +++ /dev/null @@ -1,85 +0,0 @@ -use aliyun_oss_client::decode::{InnerListError, RefineObject, RefineObjectList}; -use aliyun_oss_client::DecodeListError; -use thiserror::Error; - -struct MyFile { - key: String, - #[allow(dead_code)] - other: String, -} - -impl RefineObject for MyFile { - fn set_key(&mut self, key: &str) -> Result<(), MyError> { - self.key = key.to_string(); - Ok(()) - } -} - -#[derive(Default)] -struct MyBucket { - name: String, - files: Vec, -} - -impl RefineObjectList for MyBucket { - fn set_name(&mut self, name: &str) -> Result<(), MyError> { - self.name = name.to_string(); - Ok(()) - } - fn set_list(&mut self, list: Vec) -> Result<(), MyError> { - self.files = list; - Ok(()) - } -} - -#[derive(Debug, Error, DecodeListError)] -#[error("my error")] -struct MyError {} - -fn get_with_xml() -> Result<(), InnerListError> { - // 这是阿里云接口返回的原始数据 - let xml = r#" - - foo_bucket - - 100 - - false - CiphcHBzL1RhdXJpIFB1Ymxpc2ggQXBwXzAuMS42X3g2NF9lbi1VUy5tc2kQAA-- - - 9AB932LY.jpeg - 2022-06-26T09:53:21.000Z - "F75A15996D0857B16FA31A3B16624C26" - Normal - 18027 - Standard - - 3 - "#; - - // 除了设置Default 外,还可以做更多设置 - let mut bucket = MyBucket::default(); - - // 利用闭包对 MyFile 做一下初始化设置 - fn init_file(_list: &mut MyBucket) -> Option { - Some(MyFile { - key: String::default(), - other: "abc".to_string(), - }) - } - - bucket.decode(xml, init_file)?; - - assert!(bucket.name == "foo_bucket"); - assert!(bucket.files[0].key == "9AB932LY.jpeg"); - - Ok(()) -} - -pub fn main() { - let res = get_with_xml(); - - if let Err(err) = res { - eprintln!("{}", err); - } -} diff --git a/examples/put_demo1.txt b/examples/put_demo1.txt deleted file mode 100644 index e9f1435..0000000 --- a/examples/put_demo1.txt +++ /dev/null @@ -1 +0,0 @@ -put_demo1_content \ No newline at end of file diff --git a/examples/put_file.rs b/examples/put_file.rs deleted file mode 100644 index 61ac691..0000000 --- a/examples/put_file.rs +++ /dev/null @@ -1,23 +0,0 @@ -//! `cargo run --example put_file --features=blocking` -use aliyun_oss_client::blocking::builder::ClientWithMiddleware; -use aliyun_oss_client::client::Client; - -extern crate dotenv; - -use aliyun_oss_client::file::blocking::Files; -use dotenv::dotenv; -use std::path::PathBuf; - -fn main() { - dotenv().ok(); - - let client = Client::::from_env().unwrap(); - //let headers = None; - let response = client - .put_file( - PathBuf::from("examples/bg2015071010.png"), - "examples/bg2015071010.png", - ) - .unwrap(); - println!("put file result: {:?}", response); -} diff --git a/examples/sts.rs b/examples/sts.rs deleted file mode 100644 index 73f48ff..0000000 --- a/examples/sts.rs +++ /dev/null @@ -1,16 +0,0 @@ -use aliyun_oss_client::{sts::STS, BucketName, Client, EndPoint}; - -#[tokio::main] -async fn main() { - let client = Client::new_with_sts( - "STS.xxxxxxxx".into(), - "EVd6dXew6xxxxxxxxxxxxxxxxxxxxxxxxxxx".into(), - EndPoint::CN_SHANGHAI, - BucketName::new("yyyyyy").unwrap(), - "CAIS4gF1q6Ft5Bxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx".to_string(), - ) - .unwrap(); - - let builder = client.get_bucket_list().await.unwrap(); - println!("{:?}", builder); -} diff --git a/oss_derive/Cargo.toml b/oss_derive/Cargo.toml deleted file mode 100644 index aa6e646..0000000 --- a/oss_derive/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -[package] -name = "oss_derive" -version = "0.10.1" -description = "aliyun-oss-client 's macro" -edition.workspace = true -license.workspace = true -repository.workspace = true -documentation.workspace = true - -[lib] -proc-macro = true - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[[test]] -name = "run" -path = "tests/run.rs" - -[features] -default = [ - #"blocking" -] -blocking=[] - -[dependencies] -syn = {version = "1.0", features = ["full", "visit", "visit-mut"]} -quote = "1.0" - -[dependencies.proc-macro2] -version = "1.0" - -[dev-dependencies] -trybuild = {version ="1.0", features = ["diff"]} -async-trait = "^0.1" diff --git a/oss_derive/README.md b/oss_derive/README.md deleted file mode 100644 index 5607283..0000000 --- a/oss_derive/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# aliyun-oss-client macro - -This is [`aliyun-oss-client`](https://crates.io/crates/aliyun-oss-client) 's macro cargo \ No newline at end of file diff --git a/oss_derive/src/derive.rs b/oss_derive/src/derive.rs deleted file mode 100644 index f126312..0000000 --- a/oss_derive/src/derive.rs +++ /dev/null @@ -1,10 +0,0 @@ -use proc_macro::TokenStream; -use quote::quote; - -pub(crate) fn impl_custom_list_error(ast: &syn::DeriveInput) -> TokenStream { - let name = &ast.ident; - let gen = quote! { - impl aliyun_oss_client::decode::ListError for #name {} - }; - gen.into() -} diff --git a/oss_derive/src/file.rs b/oss_derive/src/file.rs deleted file mode 100644 index 84ac199..0000000 --- a/oss_derive/src/file.rs +++ /dev/null @@ -1,272 +0,0 @@ -use proc_macro2::{Ident, TokenStream}; -use quote::{quote, ToTokens}; -use syn::{ - parse::{Parse, ParseStream, Result}, - punctuated::Punctuated, - token::Comma, - visit::{self, Visit}, - FnArg, GenericParam, Generics, ItemTrait, Pat, Token, TraitItem, TraitItemMethod, WhereClause, -}; - -pub struct FileTrait { - pub(crate) input: ItemTrait, - pub(crate) methods: Vec, - pub(crate) async_methods: Vec, -} - -impl FileTrait { - pub fn get_inputs(inputs: &Punctuated) -> Vec { - inputs - .iter() - .filter(|arg| match arg { - FnArg::Receiver(_) => true, - FnArg::Typed(pattype) => match &*pattype.pat { - Pat::Ident(i) => { - let mut a = IdentWrapper::default(); - a.visit_ident(&i.ident); - !a.is_key() - } - _ => true, - }, - }) - .map(|f| f.to_token_stream()) - .collect() - } - - pub fn get_method_arg(inputs: &Punctuated) -> Vec { - inputs - .iter() - .filter(|arg| match arg { - FnArg::Receiver(_) => false, - FnArg::Typed(_) => true, - }) - .map(|arg| match arg { - FnArg::Receiver(_) => { - unreachable!("不会有这种情况"); - } - FnArg::Typed(pattype) => match &*pattype.pat { - Pat::Ident(i) => i.ident.to_token_stream(), - _ => { - panic!("没有考虑这种情况"); - } - }, - }) - .collect() - } - - pub fn params_where(generics: &Generics) -> (TokenStream, TokenStream) { - let final_params = Self::get_params(&generics.params); - let where_clause = Self::get_where_clause(&generics.where_clause); - - (final_params, where_clause) - } - - #[inline] - fn get_params(params: &Punctuated) -> TokenStream { - if params.is_empty() { - quote! { Ft } - } else { - let params: Vec<_> = params - .into_iter() - .filter(|p| match p { - GenericParam::Type(t) => t.ident != "OP", - _ => true, - }) - .map(|p| p.to_token_stream()) - .collect(); - - quote! { #(#params,)* Ft } - } - } - - #[inline] - fn get_where_clause(where_clause: &Option) -> TokenStream { - match where_clause { - Some(e) => { - let predicates = e.predicates.to_token_stream(); - quote! { where #predicates Ft: File } - } - None => { - quote! { where Ft: File } - } - } - } - - fn get_attrs(attrs: &[syn::Attribute]) -> TokenStream { - let attrs_token: Vec = attrs - .iter() - .filter(|&attr| attr.path.to_token_stream().to_string() != "doc") - .map(|res| res.to_token_stream()) - .collect(); - - quote! { #(#attrs_token)* } - } - - fn methods_to_tokens(&self, tokens: &mut TokenStream) { - if self.methods.is_empty() { - return; - } - - let mut list = Vec::with_capacity(self.methods.len()); - for TraitItemMethod { sig, attrs, .. } in &self.methods { - let method = sig; - let output = &method.output; - let method_name = &method.ident; - if method_name == "get_url" { - continue; - } - - let inputs = FileTrait::get_inputs(&method.inputs); - let method_arg: Vec = FileTrait::get_method_arg(&method.inputs); - let (final_params, where_clause) = FileTrait::params_where(&method.generics); - - let inputs_str = quote! { #(#inputs,)* }; - let method_args_str = quote! { #(#method_arg,)* }; - let filer = quote! { filer: &Ft }; - - let attrs_final = FileTrait::get_attrs(attrs); - - list.push(quote! { - #[inline] - #attrs_final - pub fn #method_name < #final_params >(#inputs_str #filer ) #output #where_clause { - let path = self.path(); - filer. #method_name ( #method_args_str ) - } - }); - } - - let res = quote! { - impl Object { - #(#list)* - } - }; - res.to_tokens(tokens); - } - - fn async_methods_to_tokens(&self, tokens: &mut TokenStream) { - if self.async_methods.is_empty() { - return; - } - - let mut list = Vec::with_capacity(self.async_methods.len()); - for TraitItemMethod { sig, attrs, .. } in &self.async_methods { - let method = sig; - let output = &method.output; - let method_name = &method.ident; - if method_name == "get_url" { - continue; - } - - let inputs = FileTrait::get_inputs(&method.inputs); - let method_arg: Vec = FileTrait::get_method_arg(&method.inputs); - let (final_params, where_clause) = FileTrait::params_where(&method.generics); - - let inputs_str = quote! { #(#inputs,)* }; - let method_args_str = quote! { #(#method_arg,)* }; - let filer = quote! { filer: &Ft , }; - - let attrs_final = FileTrait::get_attrs(attrs); - - list.push(quote! { - #[inline] - #attrs_final - pub async fn #method_name < #final_params >(#inputs_str #filer ) #output #where_clause { - let path = self.path(); - filer. #method_name ( #method_args_str ).await - } - }); - } - - let res = quote! { - impl Object { - #(#list)* - } - }; - res.to_tokens(tokens); - } -} - -impl Parse for FileTrait { - fn parse(input: ParseStream) -> Result { - Ok(Self { - input: input.parse()?, - methods: Vec::new(), - async_methods: Vec::new(), - }) - } -} - -#[derive(Default)] -struct IdentWrapper { - key: String, -} - -impl<'ast> Visit<'ast> for IdentWrapper { - fn visit_ident(&mut self, node: &'ast Ident) { - self.key = node.to_string(); - - visit::visit_ident(self, node); - } -} - -impl IdentWrapper { - fn is_key(&self) -> bool { - self.key == *"path" - } -} - -impl ToTokens for FileTrait { - fn to_tokens(&self, tokens: &mut TokenStream) { - self.input.to_tokens(tokens); - - self.methods_to_tokens(tokens); - self.async_methods_to_tokens(tokens); - } -} - -pub fn impl_object(file: &mut FileTrait, is_send: bool) { - let items = &file.input.items; - - let mut methods = Vec::new(); - - for inner in items { - if let TraitItem::Method(method) = inner { - methods.push(method.clone()); - } - } - - if is_send { - file.async_methods = methods; - } else { - file.methods = methods; - } -} - -pub mod attr { - use syn::parse::{Parse, ParseStream, Result}; - - mod kw { - syn::custom_keyword!(ASYNC); - } - - pub struct Attribute { - pub send: bool, - } - - impl Parse for Attribute { - fn parse(input: ParseStream) -> Result { - if input.is_empty() { - return Ok(Self { send: false }); - } - - let lookahead = input.lookahead1(); - if lookahead.peek(kw::ASYNC) { - input.parse::()?; - Ok(Self { send: true }) - } else { - Ok(Self { send: false }) - } - } - } -} diff --git a/oss_derive/src/gen_rc.rs b/oss_derive/src/gen_rc.rs deleted file mode 100644 index b5b9260..0000000 --- a/oss_derive/src/gen_rc.rs +++ /dev/null @@ -1,69 +0,0 @@ -use quote::ToTokens; -use syn::{ - parse::{Parse, ParseStream, Result}, - parse_quote, - visit_mut::{self, VisitMut}, - Ident, ItemImpl, PathSegment, -}; - -pub struct GenImpl { - inner: ItemImpl, -} - -impl Parse for GenImpl { - fn parse(input: ParseStream) -> Result { - Ok(Self { - inner: input.parse()?, - }) - } -} - -impl ToTokens for GenImpl { - fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - self.inner.to_tokens(tokens); - - // quote!{ - // #[cfg(feature = "blocking")] - // use aliyun_oss_client::builder::RcPointer; - // #[cfg(feature = "blocking")] - // use aliyun_oss_client::client::ClientRc; - // #[cfg(feature = "blocking")] - // use std::rc::Rc; - // }.to_tokens(tokens); - - self.extend_to_tokens(tokens); - } -} - -impl GenImpl { - fn extend_to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - let mut item = self.inner.clone(); - ReplaceArc.visit_item_impl_mut(&mut item); - - item.to_tokens(tokens); - } -} - -struct ReplaceArc; - -impl VisitMut for ReplaceArc { - fn visit_item_impl_mut(&mut self, i: &mut ItemImpl) { - i.attrs.push(parse_quote! { #[cfg(feature = "blocking")] }); - visit_mut::visit_item_impl_mut(self, i); - } - - fn visit_ident_mut(&mut self, i: &mut Ident) { - if *i == "ArcPointer" { - *i = parse_quote! {RcPointer}; - } else if *i == "Arc" { - *i = parse_quote! {Rc}; - } else if *i == "ClientArc" { - *i = parse_quote! {ClientRc} - } - visit_mut::visit_ident_mut(self, i); - } - - fn visit_path_segment_mut(&mut self, node: &mut PathSegment) { - visit_mut::visit_path_segment_mut(self, node); - } -} diff --git a/oss_derive/src/lib.rs b/oss_derive/src/lib.rs deleted file mode 100644 index 91638ff..0000000 --- a/oss_derive/src/lib.rs +++ /dev/null @@ -1,72 +0,0 @@ -use derive::impl_custom_list_error; -use file::attr::Attribute; -use file::impl_object; -use file::FileTrait; -use proc_macro::TokenStream; - -use quote::quote; -use syn::parse_macro_input; -mod file; -mod gen_rc; -use crate::gen_rc::GenImpl; - -/// # 转换 File trait 的各种方法到 Object 结构体中 -/// 例如 `Client` 结构体中有 `put_file` 方法,通过这个 macro ,可以让 Object 结构体支持 `put_file` 方法 -/// -/// 注意,之前的方法签名是这样的 `put_file(filename, path)`,由于 Object 本身有 path 属性,转换后的方法是这样的 -/// `put_file(filename, &filer)`,其中 filer 可以传入实现 `File` trait 的结构体,在 oss-rs 项目中,有 `Client`, `Bucket`, `ObjectList` -/// 等结构体已实现了该trait,可以直接传入使用,其他的也可以 -#[proc_macro_attribute] -pub fn oss_file(attr: TokenStream, input: TokenStream) -> TokenStream { - let attr = parse_macro_input!(attr as Attribute); - let mut item = parse_macro_input!(input as FileTrait); - impl_object(&mut item, attr.send); - TokenStream::from(quote!(#item)) -} - -/// # 根据 Arc 自动生成 Rc 代码 -/// 目前支持的转换为: -/// -/// ArcPointer => RcPointer -/// -/// Arc => Rc -/// -/// Arc::clone() => Rc::clone() -/// -/// ClientArc => ClientRc -/// -/// 还会在新生成的 `impl {}` 语句块之前添加 `#[cfg(feature = "blocking")]` 标记 -#[proc_macro_attribute] -pub fn oss_gen_rc(_attr: TokenStream, input: TokenStream) -> TokenStream { - let item = parse_macro_input!(input as GenImpl); - TokenStream::from(quote!(#item)) -} - -mod path_where; - -/// # 为 `OP` 自动生成 `where` 语句 -/// -/// ```rust,ignore -/// where: -/// OP: TryInto + Send + Sync, -/// >::Error: Into, -/// ``` -#[proc_macro_attribute] -pub fn path_where(_attr: TokenStream, input: TokenStream) -> TokenStream { - let item = parse_macro_input!(input as path_where::GenWhere); - TokenStream::from(quote!(#item)) -} - -mod derive; - -/// # 用于实现 `#[derive(CustomListError)]` -/// 为实现 `RefineObjectList`,`RefineBucketList` 等解析 trait 的外部类型,提供便捷的 Error 实现方式 -#[proc_macro_derive(DecodeListError)] -pub fn derive_decode_list_error(input: TokenStream) -> TokenStream { - // Construct a representation of Rust code as a syntax tree - // that we can manipulate - let ast = syn::parse(input).unwrap(); - - // Build the trait implementation - impl_custom_list_error(&ast) -} diff --git a/oss_derive/src/path_where.rs b/oss_derive/src/path_where.rs deleted file mode 100644 index 2f5db12..0000000 --- a/oss_derive/src/path_where.rs +++ /dev/null @@ -1,41 +0,0 @@ -use quote::ToTokens; -use syn::{ - parse::{Parse, ParseStream, Result}, - parse_quote, - visit_mut::{self, VisitMut}, - TraitItemMethod, WhereClause, -}; - -pub(crate) struct GenWhere(TraitItemMethod); - -impl Parse for GenWhere { - fn parse(input: ParseStream) -> Result { - Ok(Self(input.parse()?)) - } -} - -impl ToTokens for GenWhere { - fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - let mut item = self.0.clone(); - AppendWhere.visit_trait_item_method_mut(&mut item); - - item.to_tokens(tokens); - } -} - -struct AppendWhere; - -impl VisitMut for AppendWhere { - fn visit_trait_item_method_mut(&mut self, item: &mut TraitItemMethod) { - visit_mut::visit_trait_item_method_mut(self, item); - } - - fn visit_where_clause_mut(&mut self, i: &mut WhereClause) { - i.predicates - .push(parse_quote! {OP: TryInto + Send + Sync}); - i.predicates - .push(parse_quote! {>::Error: Into}); - - visit_mut::visit_where_clause_mut(self, i); - } -} diff --git a/oss_derive/tests/file.rs b/oss_derive/tests/file.rs deleted file mode 100644 index bdf5fff..0000000 --- a/oss_derive/tests/file.rs +++ /dev/null @@ -1,42 +0,0 @@ -use oss_derive::oss_file; - -fn main() { - assert!(true); -} - -#[oss_file] -pub trait File { - fn foo1>(&self, _a: String, path: OP, _b: String) -> String { - let _p = path.into(); - String::from("abc") - } - - fn foo2>(&self, _a: T) -> String { - String::from("abc") - } - - fn foo3, F>(&self, _a: T, _b: F, key: &str) -> String - where - F: Fn(&Vec) -> &'static str, - { - let mut string = String::from(key); - string.push_str("abc"); - string - } -} - -pub struct RcPointer; - -pub struct Base; - -pub struct ObjectPath; - -pub struct Object { - pub inner: T, -} - -impl Object { - pub fn path(&self) -> ObjectPath { - ObjectPath - } -} diff --git a/oss_derive/tests/file_async.rs b/oss_derive/tests/file_async.rs deleted file mode 100644 index aba82a8..0000000 --- a/oss_derive/tests/file_async.rs +++ /dev/null @@ -1,49 +0,0 @@ -use async_trait::async_trait; -//use oss_derive::oss_file; - -fn main() { - assert!(true); -} - -//#[oss_file(ASYNC)] -#[async_trait] -pub trait File: Send + Sync { - async fn foo1 + Send + Sync>( - &self, - _a: String, - path: OP, - _b: String, - ) -> String { - let _p = path.into(); - String::from("abc") - } - - fn foo2>(&self, _a: T) -> String { - String::from("abc") - } - - fn foo3, F>(&self, _a: T, _b: F, key: &str) -> String - where - F: Fn(&Vec) -> &'static str, - { - let mut string = String::from(key); - string.push_str("abc"); - string - } -} - -pub struct ArcPointer; - -pub struct Base; - -pub struct ObjectPath; - -pub struct Object { - pub inner: T, -} - -impl Object { - pub fn path(&self) -> ObjectPath { - ObjectPath - } -} diff --git a/oss_derive/tests/gen_rc_client.rs b/oss_derive/tests/gen_rc_client.rs deleted file mode 100644 index 548fb11..0000000 --- a/oss_derive/tests/gen_rc_client.rs +++ /dev/null @@ -1,63 +0,0 @@ -use oss_derive::oss_gen_rc; - -fn main() { - assert!(true); -} - -#[derive(PartialEq, Debug)] -pub struct ClientArc; - -#[derive(PartialEq, Debug)] -#[cfg(feature = "blocking")] -pub struct ClientRc; - -#[derive(PartialEq, Debug)] -pub enum Client { - Arc(ClientArc), - #[cfg(feature = "blocking")] - Rc(ClientRc), -} - -pub struct Demo { - pub key: T, - pub inner: Client, -} - -#[oss_gen_rc] -impl Demo { - pub fn set_client(&mut self, client: ClientArc) { - self.inner = Client::Arc(client); - } -} - -#[cfg(test)] -mod tests { - #[test] - fn test_set_client() { - use crate::{Client, ClientArc, Demo}; - - let mut demo = Demo { - key: ClientArc, - inner: Client::Arc(ClientArc), - }; - - demo.set_client(ClientArc); - - assert_eq!(demo.inner, Client::Arc(ClientArc)); - } - - #[cfg(feature = "blocking")] - #[test] - fn test_set_client_blocking() { - use crate::{Client, ClientRc, Demo}; - - let mut demo = Demo { - key: ClientRc, - inner: Client::Rc(ClientRc), - }; - - demo.set_client(ClientRc); - - assert_eq!(demo.inner, Client::Rc(ClientRc)); - } -} diff --git a/oss_derive/tests/gen_rc_clone.rs b/oss_derive/tests/gen_rc_clone.rs deleted file mode 100644 index 789ad85..0000000 --- a/oss_derive/tests/gen_rc_clone.rs +++ /dev/null @@ -1,81 +0,0 @@ -use oss_derive::oss_gen_rc; - -fn main() { - assert!(true); -} - -#[derive(PartialEq, Debug)] -pub struct ClientArc; - -#[derive(PartialEq, Debug)] -#[cfg(feature = "blocking")] -pub struct ClientRc; - -#[derive(PartialEq, Debug)] -pub enum Client { - Arc(ClientArc), - #[cfg(feature = "blocking")] - Rc(ClientRc), -} - -pub struct Demo { - pub key: T, - pub inner: Client, -} - -pub struct Arc; - -impl Arc { - pub fn clone(abc: ClientArc) -> ClientArc { - abc - } -} - -#[cfg(feature = "blocking")] -pub struct Rc; - -#[cfg(feature = "blocking")] -impl Rc { - pub fn clone(abc: ClientRc) -> ClientRc { - abc - } -} - -#[oss_gen_rc] -impl Demo { - pub fn set_client(&mut self, client: ClientArc) { - self.inner = Client::Arc(Arc::clone(client)); - } -} - -#[cfg(test)] -mod tests { - #[test] - fn test_set_client() { - use crate::{Client, ClientArc, Demo}; - - let mut demo = Demo { - key: ClientArc, - inner: Client::Arc(ClientArc), - }; - - demo.set_client(ClientArc); - - assert_eq!(demo.inner, Client::Arc(ClientArc)); - } - - #[cfg(feature = "blocking")] - #[test] - fn test_set_client_blocking() { - use crate::{Client, ClientRc, Demo}; - - let mut demo = Demo { - key: ClientRc, - inner: Client::Rc(ClientRc), - }; - - demo.set_client(ClientRc); - - assert_eq!(demo.inner, Client::Rc(ClientRc)); - } -} diff --git a/oss_derive/tests/gen_rc_eq.rs b/oss_derive/tests/gen_rc_eq.rs deleted file mode 100644 index 8ce0401..0000000 --- a/oss_derive/tests/gen_rc_eq.rs +++ /dev/null @@ -1,46 +0,0 @@ -use oss_derive::oss_gen_rc; - -fn main() { - assert!(true); -} - -struct ArcPointer; - -#[cfg(feature = "blocking")] -struct RcPointer; - -#[allow(dead_code)] -struct Demo { - inner: T, - string: &'static str, -} - -impl Demo { - fn new(inner: T, string: &'static str) -> Self { - Self { inner, string } - } -} - -#[oss_gen_rc] -impl PartialEq> for Demo { - fn eq(&self, other: &Demo) -> bool { - self.string == other.string - } -} - -#[test] -fn test_arc_eq() { - let val1 = Demo::new(ArcPointer, "foo1"); - let val2 = Demo::new(ArcPointer, "foo1"); - - assert!(val1 == val2); -} - -#[cfg(feature = "blocking")] -#[test] -fn test_rc_eq() { - let val1 = Demo::new(RcPointer, "foo1"); - let val2 = Demo::new(RcPointer, "foo1"); - - assert!(val1 == val2); -} diff --git a/oss_derive/tests/run.rs b/oss_derive/tests/run.rs deleted file mode 100644 index e0e8683..0000000 --- a/oss_derive/tests/run.rs +++ /dev/null @@ -1,16 +0,0 @@ -#[test] -fn run() { - let t = trybuild::TestCases::new(); - t.pass("tests/file.rs"); - t.pass("tests/gen_rc_eq.rs"); - t.pass("tests/gen_rc_client.rs"); - //t.compile_fail("tests/gen_rc_fail.rs"); -} - -#[cfg(feature = "blocking")] -#[test] -fn test_gen_rc() { - let t = trybuild::TestCases::new(); - t.pass("tests/gen_rc_eq.rs"); - t.pass("tests/gen_rc_client.rs"); -} diff --git a/rfc.md b/rfc.md new file mode 100644 index 0000000..7e33355 --- /dev/null +++ b/rfc.md @@ -0,0 +1,51 @@ +get Buckets + +```rust +struct Client { + key: String, + secret: String, +} + +impl Client { + async fn get_buckets(&self, endpoint: EndPoint) -> Vec { + todo!() + } +} +``` + +get bucket info; +```rust +struct Bucket { + name: String, + endpoint: EndPoint, +} +impl Bucket{ + async fn get_info(&self, client: &Client) -> BucketInfo { + todo!() + } + async fn get_object(&self, client: &Client) -> Vec { + todo!() + } +} +``` +get object info + +```rust +struct Object { + bucket: Bucket, + path: ObjectPath, +} +impl Object { + async fn get_info(&self, client: &Client) -> ObjectInfo { + todo!() + } + + async fn upload(&self, client: &Client, content: Vec) -> Result<(), Error>{ + todo!() + } + async fn download(&self, client: &Client) -> Result, Error>{ + todo!() + } +} +``` + diff --git a/rustfmt.toml b/rustfmt.toml deleted file mode 100644 index 37c302d..0000000 --- a/rustfmt.toml +++ /dev/null @@ -1 +0,0 @@ -#format_code_in_doc_comments=true diff --git a/src/auth.rs b/src/auth.rs deleted file mode 100644 index 8607b71..0000000 --- a/src/auth.rs +++ /dev/null @@ -1,851 +0,0 @@ -//! # Auth 模块 -//! 计算 OSS API 的签名,并将数据收集到 `http::header::HeaderMap` 中 -//! -//! ## Examples -//! ```rust -//! # use aliyun_oss_client::auth::RequestWithOSS; -//! use http::Method; -//! -//! async fn run() { -//! let client = reqwest::Client::default(); -//! let mut request = client -//! .request( -//! Method::GET, -//! "https://foo.oss-cn-shanghai.aliyuncs.com/?bucketInfo", -//! ) -//! .build() -//! .unwrap(); -//! request.with_oss("key1".into(), "secret2".into()).unwrap(); -//! let response = client.execute(request).await; -//! println!("{:?}", response); -//! } -//! ``` - -use crate::{ - types::{ - CanonicalizedResource, ContentMd5, ContentType, Date, InnerCanonicalizedResource, - InnerContentMd5, InnerDate, InnerKeyId, InnerKeySecret, KeyId, KeySecret, - CONTINUATION_TOKEN, - }, - BucketName, EndPoint, -}; -use chrono::Utc; -#[cfg(test)] -use http::header::AsHeaderName; -use http::{ - header::{HeaderMap, HeaderValue, IntoHeaderName, InvalidHeaderValue, CONTENT_TYPE}, - Method, -}; -#[cfg(test)] -use mockall::automock; -use std::fmt::{Debug, Display}; -use std::{borrow::Cow, convert::TryInto}; - -pub mod query; - -pub use query::QueryAuth; - -#[cfg(test)] -mod test; - -/// 计算 OSS 签名的数据 -#[derive(Default, Clone, Debug, PartialEq, Eq)] -pub struct InnerAuth<'a> { - access_key_id: InnerKeyId<'a>, - access_key_secret: InnerKeySecret<'a>, - method: Method, - content_md5: Option>, - date: InnerDate<'a>, - canonicalized_resource: InnerCanonicalizedResource<'a>, - headers: HeaderMap, -} -/// 静态作用域的 InnerAuth -pub type Auth = InnerAuth<'static>; - -impl<'a> InnerAuth<'a> { - fn set_key(&mut self, access_key_id: InnerKeyId<'a>) { - self.access_key_id = access_key_id; - } - - #[cfg(test)] - fn get_key(self) -> InnerKeyId<'a> { - self.access_key_id - } - - fn set_secret(&mut self, secret: InnerKeySecret<'a>) { - self.access_key_secret = secret; - } - fn set_method(&mut self, method: Method) { - self.method = method; - } - fn set_content_md5(&mut self, content_md5: ContentMd5) { - self.content_md5 = Some(content_md5) - } - fn set_date(&mut self, date: Date) { - self.date = date; - } - fn set_canonicalized_resource(&mut self, canonicalized_resource: CanonicalizedResource) { - self.canonicalized_resource = canonicalized_resource; - } - fn set_headers(&mut self, headers: HeaderMap) { - self.headers = headers; - } - fn extend_headers(&mut self, headers: HeaderMap) { - self.headers.extend(headers); - } - fn header_insert(&mut self, key: K, val: HeaderValue) { - self.headers.insert(key, val); - } - fn headers_clear(&mut self) { - self.headers.clear(); - } - - #[cfg(test)] - fn get_header(self, key: K) -> Option - where - K: AsHeaderName, - { - self.headers.get(key).cloned() - } - - #[cfg(test)] - fn header_len(&self) -> usize { - self.headers.len() - } - - #[cfg(test)] - fn header_contains_key(&self, key: K) -> bool - where - K: AsHeaderName, - { - self.headers.contains_key(key) - } -} - -#[cfg_attr(test, automock)] -trait AuthToHeaderMap { - fn get_original_header(&self) -> HeaderMap; - fn get_header_key(&self) -> Result; - fn get_header_method(&self) -> Result; - fn get_header_md5(&self) -> Option; - fn get_header_date(&self) -> Result; - fn get_header_resource(&self) -> Result; -} - -impl AuthToHeaderMap for InnerAuth<'_> { - fn get_original_header(&self) -> HeaderMap { - // 7 = 6 + 1 - let mut header = HeaderMap::with_capacity(7 + self.headers.len()); - header.extend(self.headers.clone()); - header - } - fn get_header_key(&self) -> Result { - self.access_key_id.as_ref().try_into() - } - fn get_header_method(&self) -> Result { - self.method.as_str().try_into() - } - fn get_header_md5(&self) -> Option { - self.content_md5 - .as_ref() - .and_then(|val| TryInto::::try_into(val).ok()) - } - fn get_header_date(&self) -> Result { - self.date.as_ref().try_into() - } - fn get_header_resource(&self) -> Result { - self.canonicalized_resource.as_ref().try_into() - } -} - -trait AuthToOssHeader { - fn to_oss_header(&self) -> OssHeader; -} - -impl AuthToOssHeader for InnerAuth<'_> { - /// 转化成 OssHeader - fn to_oss_header(&self) -> OssHeader { - const X_OSS_PRE: &str = "x-oss-"; - //return Some("x-oss-copy-source:/honglei123/file1.txt"); - let mut header: Vec<_> = self - .headers - .iter() - .filter(|(k, _v)| k.as_str().starts_with(X_OSS_PRE)) - .collect(); - if header.is_empty() { - return OssHeader(None); - } - - header.sort_by(|(k1, _), (k2, _)| k1.as_str().cmp(k2.as_str())); - - let header_vec: Vec<_> = header - .iter() - .filter_map(|(k, v)| { - v.to_str() - .ok() - .map(|value| k.as_str().to_owned() + ":" + value) - }) - .collect(); - - OssHeader(Some(header_vec.join(LINE_BREAK))) - } -} - -impl InnerAuth<'_> { - /// 返回携带了签名信息的 headers - pub fn get_headers(self) -> AuthResult { - let mut map = HeaderMap::from_auth(&self)?; - - let oss_header = self.to_oss_header(); - let sign_string = SignString::from_auth(self, oss_header); - map.append_sign(sign_string.into_sign().map_err(AuthError::from)?)?; - - Ok(map) - } - /// 将 Auth 信息计算后附加到 HeaderMap 上 - fn append_headers(self, headers: &mut HeaderMap) -> AuthResult<()> { - headers.append_auth(&self)?; - let oss_header = self.to_oss_header(); - let sign_string = SignString::from_auth(self, oss_header); - headers.append_sign(sign_string.into_sign().map_err(AuthError::from)?)?; - - Ok(()) - } -} - -trait AuthHeader { - fn from_auth(auth: &impl AuthToHeaderMap) -> Result - where - Self: Sized; - fn append_sign>( - &mut self, - sign: S, - ) -> Result, InvalidHeaderValue>; -} - -const ACCESS_KEY_ID: &str = "AccessKeyId"; -const VERB_IDENT: &str = "VERB"; -const CONTENT_MD5: &str = "Content-MD5"; -const DATE: &str = "Date"; -const CANONICALIZED_RESOURCE: &str = "CanonicalizedResource"; -const AUTHORIZATION: &str = "Authorization"; - -impl AuthHeader for HeaderMap { - fn from_auth(auth: &impl AuthToHeaderMap) -> Result { - let mut map = auth.get_original_header(); - - map.insert(ACCESS_KEY_ID, auth.get_header_key()?); - map.insert(VERB_IDENT, auth.get_header_method()?); - - if let Some(a) = auth.get_header_md5() { - map.insert(CONTENT_MD5, a); - } - - map.insert(DATE, auth.get_header_date()?); - map.insert(CANONICALIZED_RESOURCE, auth.get_header_resource()?); - - //println!("header list: {:?}",map); - Ok(map) - } - fn append_sign>( - &mut self, - sign: S, - ) -> Result, InvalidHeaderValue> { - let mut value: HeaderValue = sign.try_into()?; - value.set_sensitive(true); - let res = self.insert(AUTHORIZATION, value); - Ok(res) - } -} - -trait AppendAuthHeader { - fn append_auth<'a>(&'a mut self, auth: &InnerAuth<'a>) -> Result<(), InvalidHeaderValue>; -} - -impl AppendAuthHeader for HeaderMap { - fn append_auth<'a>(&'a mut self, auth: &InnerAuth<'a>) -> Result<(), InvalidHeaderValue> { - self.extend(auth.get_original_header()); - - self.insert(ACCESS_KEY_ID, auth.get_header_key()?); - self.insert(VERB_IDENT, auth.get_header_method()?); - - if let Some(a) = auth.get_header_md5() { - self.insert(CONTENT_MD5, a); - } - - self.insert(DATE, auth.get_header_date()?); - self.insert(CANONICALIZED_RESOURCE, auth.get_header_resource()?); - - Ok(()) - } -} - -/// # 前缀是 x-oss- 的 header 记录 -/// -/// 将他们按顺序组合成一个字符串,用于计算签名 -struct OssHeader(Option); - -impl OssHeader { - #[allow(dead_code)] - fn new(string: Option) -> Self { - Self(string) - } - - #[allow(dead_code)] - fn is_none(&self) -> bool { - self.0.is_none() - } - - #[inline] - fn len(&self) -> usize { - self.0.as_ref().map_or(0_usize, |str| str.len()) - } -} - -impl Display for OssHeader { - /// 转化成 SignString 需要的格式 - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - let mut content = String::with_capacity({ - let len = self.len(); - if len > 0 { - len + 2 - } else { - 0 - } - }); - if let Some(str) = &self.0 { - content.push_str(str); - content.push_str(LINE_BREAK); - } - write!(f, "{}", content) - } -} - -/// 待签名的数据 -#[derive(Debug)] -struct SignString<'a> { - data: String, - key: InnerKeyId<'a>, - secret: InnerKeySecret<'a>, -} - -const LINE_BREAK: &str = "\n"; - -impl<'a, 'b> SignString<'_> { - #[allow(dead_code)] - #[inline] - fn new(data: &'b str, key: InnerKeyId<'a>, secret: InnerKeySecret<'a>) -> SignString<'a> { - SignString { - data: data.to_owned(), - key, - secret, - } - } -} - -impl<'a> SignString<'a> { - fn from_auth(auth: InnerAuth<'a>, header: OssHeader) -> Self { - let InnerAuth { - access_key_id, - access_key_secret, - method, - content_md5, - headers, - date, - canonicalized_resource, - .. - } = auth; - let content_type = headers - .get(CONTENT_TYPE) - .map_or(ContentType::default(), |ct| { - ct.to_owned().try_into().unwrap_or_else(|_| { - unreachable!("HeaderValue always is a rightful ContentType") - }) - }); - let method = method.to_string(); - - let data = method - + LINE_BREAK - + content_md5.unwrap_or_default().as_ref() - + LINE_BREAK - + content_type.as_ref() - + LINE_BREAK - + date.as_ref() - + LINE_BREAK - + &header.to_string() - + canonicalized_resource.as_ref(); - - Self { - data, - key: access_key_id, - secret: access_key_secret, - } - } - - // 转化成签名 - fn into_sign(self) -> Result, hmac::digest::crypto_common::InvalidLength> { - Ok(Sign { - data: self.secret.encryption(self.data.as_bytes())?, - key: self.key, - }) - } -} - -/// header 中的签名 -#[derive(Debug)] -struct Sign<'a> { - data: String, - key: InnerKeyId<'a>, -} - -impl Sign<'_> { - #[cfg(test)] - fn new<'a, 'b>(data: &'b str, key: InnerKeyId<'a>) -> Sign<'a> { - Sign { - data: data.to_owned(), - key, - } - } - - #[cfg(test)] - pub fn data(&self) -> &str { - &self.data - } - - #[cfg(test)] - pub fn key_string(&self) -> String { - self.key.as_ref().to_string() - } -} - -impl TryInto for Sign<'_> { - type Error = InvalidHeaderValue; - - /// 转化成 header 中需要的格式 - fn try_into(self) -> Result { - let sign = format!("OSS {}:{}", self.key.as_ref(), self.data); - sign.parse() - } -} - -/// Auth 结构体的构建器 -#[derive(Default, Clone, Debug, PartialEq, Eq)] -pub struct AuthBuilder { - auth: Auth, -} - -impl AuthBuilder { - /// 给 key 赋值 - /// - /// ``` - /// # use aliyun_oss_client::auth::AuthBuilder; - /// let mut headers = AuthBuilder::default(); - /// headers.key("bar"); - /// headers.get_headers(); - /// ``` - #[inline] - pub fn key>(&mut self, key: K) { - self.auth.set_key(key.into()); - } - - /// 给 secret 赋值 - #[inline] - pub fn secret>(&mut self, secret: S) { - self.auth.set_secret(secret.into()); - } - - #[cfg(feature = "core")] - pub(crate) fn get_key(&self) -> &KeyId { - &self.auth.access_key_id - } - #[cfg(feature = "core")] - pub(crate) fn get_secret(&self) -> &KeySecret { - &self.auth.access_key_secret - } - - /// 给 verb 赋值 - #[inline] - pub fn method(&mut self, method: &Method) { - self.auth.set_method(method.to_owned()); - } - - /// 给 content_md5 赋值 - #[inline] - pub fn content_md5>(&mut self, content_md5: Md5) { - self.auth.set_content_md5(content_md5.into()); - } - - /// # 给 date 赋值 - /// - /// ## Example - /// ``` - /// use chrono::Utc; - /// let builder = aliyun_oss_client::auth::AuthBuilder::default().date(Utc::now()); - /// ``` - #[inline] - pub fn date>(&mut self, date: D) { - self.auth.set_date(date.into()); - } - - /// 给 content_md5 赋值 - #[inline] - pub fn canonicalized_resource>(&mut self, data: Res) { - self.auth.set_canonicalized_resource(data.into()); - } - - /// 给 Auth 附加新的 headers 信息 - #[inline] - pub fn with_headers(&mut self, headers: Option) { - if let Some(headers) = headers { - self.extend_headers(headers); - } - } - - /// 给 Auth 设置全新的 headers 信息 - #[inline] - pub fn headers(&mut self, headers: HeaderMap) { - self.auth.set_headers(headers); - } - - /// 给 Auth 附加新的 headers 信息 - #[inline] - pub fn extend_headers(&mut self, headers: HeaderMap) { - self.auth.extend_headers(headers); - } - - /// 给 header 序列添加新值 - #[inline] - pub fn header_insert(&mut self, key: K, val: HeaderValue) { - self.auth.header_insert(key, val); - } - - /// 清理 headers - #[inline] - pub fn header_clear(&mut self) { - self.auth.headers_clear(); - } - - #[allow(dead_code)] - #[inline] - fn build(self) -> Auth { - self.auth - } -} - -impl AuthBuilder { - /// 返回携带了签名信息的 headers - pub fn get_headers(self) -> AuthResult { - self.auth.get_headers() - } -} - -/// # 将 OSS 签名信息附加到 Request 中 -/// -/// 已密封,lib 外部无法实现 -pub trait RequestWithOSS: private::Sealed { - /// 输入 key,secret,以及 Request 中的 method,header,url,query - /// 等信息,计算 OSS 签名 - /// 并把签名后的 header 信息,传递给 self - fn with_oss(&mut self, key: InnerKeyId, secret: InnerKeySecret) -> AuthResult<()>; -} - -mod private { - pub trait Sealed {} - - impl Sealed for reqwest::Request {} -} - -use reqwest::{Request, Url}; - -impl RequestWithOSS for Request { - fn with_oss(&mut self, key: InnerKeyId, secret: InnerKeySecret) -> AuthResult<()> { - let mut auth = InnerAuth { - access_key_id: key, - access_key_secret: secret, - method: self.method().clone(), - date: Utc::now().into(), - ..Default::default() - }; - - auth.set_canonicalized_resource(self.url().canonicalized_resource().ok_or(AuthError { - kind: AuthErrorKind::InvalidCanonicalizedResource, - })?); - - auth.append_headers(self.headers_mut())?; - - Ok(()) - } -} - -/// 根据 Url 计算 [`CanonicalizedResource`] -/// -/// [`CanonicalizedResource`]: crate::types::CanonicalizedResource -trait GenCanonicalizedResource { - /// 计算并返回 [`CanonicalizedResource`], 无法计算则返回 `None` - /// - /// [`CanonicalizedResource`]: crate::types::CanonicalizedResource - fn canonicalized_resource(&self) -> Option; - - /// 根据 Url 计算 bucket 名称和 Endpoint - fn oss_host(&self) -> OssHost; - - /// 根据 Url 的 query 计算 [`CanonicalizedResource`] - /// - /// [`CanonicalizedResource`]: crate::types::CanonicalizedResource - fn object_list_resource(&self, bucket: &BucketName) -> CanonicalizedResource; - - /// 根据 Url 的 path 计算当前使用的 Object 文件路径 - fn object_path(&self) -> Option>; -} - -/// Oss 域名的几种状态 -#[derive(PartialEq, Debug, Eq)] -enum OssHost { - /// 有 bucket 的,包含 bucket 名字 - Bucket(BucketName), - /// 只有 endpoint - EndPoint, - /// 其他 - None, -} - -const LIST_TYPE2: &str = "list-type=2"; -const LIST_TYPE2_AND: &str = "list-type=2&"; -const COM: &str = "com"; -const ALIYUNCS: &str = "aliyuncs"; - -impl GenCanonicalizedResource for Url { - fn canonicalized_resource(&self) -> Option { - use crate::types::BUCKET_INFO; - - let bucket = match self.oss_host() { - OssHost::None => return None, - OssHost::EndPoint => return Some(CanonicalizedResource::from_endpoint()), - OssHost::Bucket(bucket) => bucket, - }; - - if self.path().is_empty() { - return None; - } - - // 没有 object 的情况 - if self.path() == "/" { - return match self.query() { - // 查询单个bucket 信息 - Some(BUCKET_INFO) => Some(CanonicalizedResource::from_bucket_name( - &bucket, - Some(BUCKET_INFO), - )), - // 查 object_list - Some(q) if q.ends_with(LIST_TYPE2) || q.contains(LIST_TYPE2_AND) => { - Some(self.object_list_resource(&bucket)) - } - // 其他情况待定 - _ => todo!("Unable to obtain can information based on existing query information"), - }; - } - - // 获取 ObjectPath 失败,返回 None,否则根据 ObjectPath 计算 CanonicalizedResource - self.object_path() - .map(|path| CanonicalizedResource::from_object_without_query(bucket.as_ref(), path)) - } - - fn oss_host(&self) -> OssHost { - use url::Host; - let domain = match self.host() { - Some(Host::Domain(domain)) => domain, - _ => return OssHost::None, - }; - - let mut url_pieces = domain.rsplit('.'); - - match (url_pieces.next(), url_pieces.next()) { - (Some(COM), Some(ALIYUNCS)) => (), - _ => return OssHost::None, - } - - match url_pieces.next() { - Some(endpoint) => match EndPoint::from_host_piece(endpoint) { - Ok(_) => (), - _ => return OssHost::None, - }, - _ => return OssHost::None, - }; - - match url_pieces.next() { - Some(bucket) => { - if let Ok(b) = BucketName::from_static(bucket) { - OssHost::Bucket(b) - } else { - OssHost::None - } - } - None => OssHost::EndPoint, - } - } - - fn object_list_resource(&self, bucket: &BucketName) -> CanonicalizedResource { - let mut query = self.query_pairs().filter(|(_, val)| !val.is_empty()); - match query.find(|(key, _)| key == CONTINUATION_TOKEN) { - Some((_, token)) => CanonicalizedResource::new(format!( - "/{}/?continuation-token={}", - bucket.as_ref(), - token - )), - None => CanonicalizedResource::new(format!("/{}/", bucket.as_ref())), - } - } - - fn object_path(&self) -> Option> { - use percent_encoding::percent_decode; - - if self.path().ends_with('/') { - return None; - } - - let input = if self.path().starts_with('/') { - &self.path()[1..] - } else { - self.path() - } - .as_bytes(); - percent_decode(input).decode_utf8().ok() - } -} - -impl GenCanonicalizedResource for Request { - fn canonicalized_resource(&self) -> Option { - self.url().canonicalized_resource() - } - - fn oss_host(&self) -> OssHost { - self.url().oss_host() - } - - fn object_list_resource(&self, bucket: &BucketName) -> CanonicalizedResource { - self.url().object_list_resource(bucket) - } - - fn object_path(&self) -> Option> { - self.url().object_path() - } -} - -/// Auth 模块的错误 -#[derive(Debug)] -#[non_exhaustive] -pub struct AuthError { - pub(crate) kind: AuthErrorKind, -} - -impl AuthError { - #[cfg(all(test, feature = "core"))] - pub(crate) fn test_new() -> Self { - Self { - kind: AuthErrorKind::InvalidCanonicalizedResource, - } - } -} - -/// Auth 模块错误的枚举 -#[derive(Debug)] -#[non_exhaustive] -pub(crate) enum AuthErrorKind { - #[doc(hidden)] - HeaderValue(http::header::InvalidHeaderValue), - #[doc(hidden)] - Hmac(hmac::digest::crypto_common::InvalidLength), - #[doc(hidden)] - InvalidCanonicalizedResource, -} - -impl std::error::Error for AuthError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - use AuthErrorKind::*; - match &self.kind { - HeaderValue(e) => Some(e), - Hmac(e) => Some(e), - InvalidCanonicalizedResource => None, - } - } -} - -impl Display for AuthError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use AuthErrorKind::*; - match self.kind { - HeaderValue(_) => f.write_str("failed to parse header value"), - Hmac(_) => f.write_str("invalid aliyun secret length"), - InvalidCanonicalizedResource => f.write_str("invalid canonicalized-resource"), - } - } -} - -impl From for AuthError { - /// ``` - /// # use aliyun_oss_client::auth::AuthError; - /// # use http::header::HeaderValue; - /// let val = HeaderValue::from_str("\n"); - /// let header_error = val.unwrap_err(); - /// let auth_error: AuthError = header_error.into(); - /// assert_eq!(format!("{}", auth_error), "failed to parse header value"); - /// ``` - fn from(value: http::header::InvalidHeaderValue) -> Self { - Self { - kind: AuthErrorKind::HeaderValue(value), - } - } -} -impl From for AuthError { - /// ``` - /// # use aliyun_oss_client::auth::AuthError; - /// let hmac_error = hmac::digest::crypto_common::InvalidLength {}; - /// let auth_error: AuthError = hmac_error.into(); - /// assert_eq!(format!("{}", auth_error), "invalid aliyun secret length"); - /// ``` - fn from(value: hmac::digest::crypto_common::InvalidLength) -> Self { - Self { - kind: AuthErrorKind::Hmac(value), - } - } -} - -type AuthResult = Result; - -#[cfg(test)] -mod builder_tests { - use http::{header::CONTENT_LANGUAGE, HeaderMap}; - - use super::AuthBuilder; - - #[test] - fn key() { - let builder = AuthBuilder::default(); - assert_eq!(builder.build().get_key().as_ref(), ""); - - let mut builder = AuthBuilder::default(); - builder.key("bar"); - assert_eq!(builder.build().get_key().as_ref(), "bar"); - } - - #[test] - fn with_headers() { - let builder = AuthBuilder::default(); - let before_len = builder.build().get_headers().unwrap().len(); - assert_eq!(before_len, 5); - - let mut builder = AuthBuilder::default(); - builder.with_headers(Some({ - let mut headers = HeaderMap::new(); - headers.insert(CONTENT_LANGUAGE, "abc".parse().unwrap()); - headers - })); - let len = builder.build().get_headers().unwrap().len(); - assert_eq!(len, 6); - - let mut builder = AuthBuilder::default(); - builder.with_headers(None); - let len = builder.build().get_headers().unwrap().len(); - assert_eq!(len, 5); - } -} diff --git a/src/auth/query.rs b/src/auth/query.rs deleted file mode 100644 index a9dd752..0000000 --- a/src/auth/query.rs +++ /dev/null @@ -1,252 +0,0 @@ -//! # 在 Url (即 query) 中包含签名 -//! -//! [aliyun docs](https://help.aliyun.com/document_detail/31952.html) -//! -//! ## 用法 -//! ``` -//! # use aliyun_oss_client::{auth::QueryAuth, EndPoint}; -//! # use chrono::Utc; -//! let key = "key".into(); -//! let secret = "secret".into(); -//! let bucket = "bucket".parse().unwrap(); -//! let auth = QueryAuth::new(&key, &secret, &EndPoint::CN_QINGDAO, &bucket); -//! let time = Utc::now().timestamp() + 3600; -//! let url = auth.to_url(&"pretty.png".parse().unwrap(), time); -//! ``` - -use url::Url; - -use crate::{ - types::{object::SetObjectPath, url_from_bucket, CanonicalizedResource}, - BucketName, EndPoint, KeyId, KeySecret, ObjectPath, -}; - -/// Query 签名器 -pub struct QueryAuth<'a> { - access_key_id: &'a KeyId, - access_secret_key: &'a KeySecret, - endpoint: &'a EndPoint, - bucket: &'a BucketName, -} - -#[cfg(feature = "core")] -use crate::{ - client::Client, - config::{BucketBase, Config}, -}; - -#[cfg(feature = "core")] -impl<'a> From<&'a Config> for QueryAuth<'a> { - #[inline] - fn from(config: &'a Config) -> Self { - Self::new( - config.as_ref(), - config.as_ref(), - config.as_ref(), - config.as_ref(), - ) - } -} -#[cfg(feature = "core")] -impl<'a, M: Default + Clone> From<&'a Client> for QueryAuth<'a> { - #[inline] - fn from(client: &'a Client) -> Self { - Self::new( - client.get_key(), - client.get_secret(), - client.as_ref(), - client.as_ref(), - ) - } -} - -impl<'a> QueryAuth<'a> { - /// 初始化 QueryAuth - #[inline] - pub fn new( - access_key_id: &'a KeyId, - access_secret_key: &'a KeySecret, - endpoint: &'a EndPoint, - bucket: &'a BucketName, - ) -> Self { - Self { - access_key_id, - access_secret_key, - endpoint, - bucket, - } - } - - /// 通过 BucketBase 初始化 - #[cfg(feature = "core")] - #[inline] - pub fn new_with_bucket( - access_key_id: &'a KeyId, - access_secret_key: &'a KeySecret, - base: &'a BucketBase, - ) -> Self { - Self::new( - access_key_id, - access_secret_key, - base.as_ref(), - base.as_ref(), - ) - } - fn get_resource(&self, path: &ObjectPath) -> CanonicalizedResource { - CanonicalizedResource::from_object_str(self.bucket.as_ref(), path.as_ref()) - } - fn get_url(&self, path: &ObjectPath) -> Url { - let mut url = url_from_bucket(self.endpoint, self.bucket); - url.set_object_path(path); - url - } - - fn sign_string(&self, path: &ObjectPath, expires: i64) -> String { - const METHOD: &str = "GET"; - const LN3: &str = "\n\n\n"; - const LN: &str = "\n"; - - let p = self.get_resource(path); - - const fn len(path: &str) -> usize { - METHOD.len() + LN.len() + LN3.len() + 10 + path.len() - } - - let mut string = String::with_capacity(len(p.as_ref())); - string += METHOD; - string += LN3; - string += &expires.to_string(); - string += LN; - string += p.as_ref(); - string - } - fn signature(&self, path: &ObjectPath, expires: i64) -> String { - #![allow(clippy::unwrap_used)] - self.access_secret_key - .encryption_string(self.sign_string(path, expires)) - .unwrap() - } - - /// 转化为带签名完整 url - pub fn to_url(&self, path: &ObjectPath, expires: i64) -> Url { - let mut url = self.get_url(path); - self.signature_url(&mut url, path, expires); - url - } - - /// 为指定的 url 附加签名信息 - pub fn signature_url(&self, url: &mut Url, path: &ObjectPath, expires: i64) { - const KEY: &str = "OSSAccessKeyId"; - const EXPIRES: &str = "Expires"; - const SIGNATURE: &str = "Signature"; - - url.query_pairs_mut() - .clear() - .append_pair(KEY, self.access_key_id.as_ref()) - .append_pair(EXPIRES, &expires.to_string()) - .append_pair(SIGNATURE, &self.signature(path, expires)); - } -} - -#[cfg(feature = "core")] -#[cfg(test)] -mod test { - use url::Url; - - use crate::{ - config::{BucketBase, Config}, - BucketName, Client, EndPoint, - }; - - use super::QueryAuth; - - fn init_config() -> Config { - Config::new( - "foo", - "foo2", - EndPoint::CN_QINGDAO, - BucketName::new("aaa").unwrap(), - ) - } - - #[test] - fn from_client() { - let client = Client::new( - "foo".into(), - "foo2".into(), - EndPoint::CN_QINGDAO, - "aaa".parse().unwrap(), - ); - - let auth = QueryAuth::from(&client); - assert_eq!(auth.access_key_id.as_ref(), "foo"); - assert_eq!(auth.access_secret_key.as_str(), "foo2"); - assert_eq!(auth.endpoint, &EndPoint::CN_QINGDAO); - assert_eq!(auth.bucket.as_ref(), "aaa"); - } - - #[test] - fn new_with_bucket() { - let key = "foo".into(); - let secret = "foo2".into(); - let base = BucketBase::new("aaa".parse().unwrap(), EndPoint::CN_QINGDAO); - - let auth = QueryAuth::new_with_bucket(&key, &secret, &base); - assert_eq!(auth.access_key_id.as_ref(), "foo"); - assert_eq!(auth.access_secret_key.as_str(), "foo2"); - assert_eq!(auth.endpoint, &EndPoint::CN_QINGDAO); - assert_eq!(auth.bucket.as_ref(), "aaa"); - } - - #[test] - fn get_resource() { - let config = init_config(); - let auth = QueryAuth::from(&config); - let res = auth.get_resource(&"img.png".parse().unwrap()); - assert_eq!(res.as_ref(), "/aaa/img.png"); - } - - #[test] - fn get_url() { - let config = init_config(); - let auth = QueryAuth::from(&config); - let url = auth.get_url(&"img.png".parse().unwrap()); - assert_eq!( - url.as_str(), - "https://aaa.oss-cn-qingdao.aliyuncs.com/img.png" - ); - } - - #[test] - fn sign_string() { - let config = init_config(); - let auth = QueryAuth::from(&config); - let string = auth.sign_string(&"img.png".parse().unwrap(), 1200); - assert_eq!(string, "GET\n\n\n1200\n/aaa/img.png"); - } - - #[test] - fn signature() { - let config = init_config(); - let auth = QueryAuth::from(&config); - let string = auth.signature(&"img.png".parse().unwrap(), 1200); - assert_eq!(string, "EQQzNJZptBDl8xJ6n2mQRG7oxkY="); - } - - #[test] - fn to_url() { - let config = init_config(); - let auth = QueryAuth::from(&config); - let string = auth.to_url(&"img.png".parse().unwrap(), 1200); - assert_eq!(string.as_str(), "https://aaa.oss-cn-qingdao.aliyuncs.com/img.png?OSSAccessKeyId=foo&Expires=1200&Signature=EQQzNJZptBDl8xJ6n2mQRG7oxkY%3D"); - } - - #[test] - fn signature_url() { - let config = init_config(); - let auth = QueryAuth::from(&config); - let mut url: Url = "https://example.com/image2.png".parse().unwrap(); - auth.signature_url(&mut url, &"img.png".parse().unwrap(), 1200); - assert_eq!(url.as_str(), "https://example.com/image2.png?OSSAccessKeyId=foo&Expires=1200&Signature=EQQzNJZptBDl8xJ6n2mQRG7oxkY%3D"); - } -} diff --git a/src/auth/test.rs b/src/auth/test.rs deleted file mode 100644 index fe731fd..0000000 --- a/src/auth/test.rs +++ /dev/null @@ -1,797 +0,0 @@ -use std::{convert::TryInto, error::Error}; - -use http::{ - header::{HeaderMap, HeaderValue, InvalidHeaderValue}, - Method, -}; -use mockall::mock; - -use crate::types::{Date, KeyId}; - -use super::{ - AppendAuthHeader, AuthBuilder, AuthError, AuthErrorKind, AuthHeader, MockAuthToHeaderMap, - OssHeader, Sign, SignString, -}; - -mod to_oss_header { - use std::convert::TryInto; - - use crate::auth::{AuthBuilder, AuthToOssHeader}; - - #[test] - fn test_none() { - let builder = AuthBuilder::default(); - let header = builder.build().to_oss_header(); - - assert!(header.is_none()); - - let mut builder = AuthBuilder::default(); - builder.header_insert("abc", "def".try_into().unwrap()); - let header = builder.build().to_oss_header(); - assert!(header.is_none()); - } - - #[test] - fn test_some() { - let mut builder = AuthBuilder::default(); - builder.header_insert("x-oss-foo", "bar".try_into().unwrap()); - builder.header_insert("x-oss-ffoo", "barbar".try_into().unwrap()); - builder.header_insert("fffoo", "aabb".try_into().unwrap()); - let header = builder.build().to_oss_header(); - let header = header.to_string(); - assert_eq!(&header, "x-oss-ffoo:barbar\nx-oss-foo:bar\n"); - } -} - -mod auth_sign_string { - use chrono::{TimeZone, Utc}; - use http::Method; - - use crate::auth::{AuthBuilder, InnerAuth}; - - #[test] - fn auth_to_sign_string() { - use http::header::CONTENT_TYPE; - let date = Utc.with_ymd_and_hms(2022, 1, 1, 18, 1, 1).unwrap(); - - let mut builder = AuthBuilder::default(); - builder.key("foo1"); - builder.secret("foo2"); - builder.method(&Method::POST); - builder.content_md5("foo4"); - builder.date(date); - builder.canonicalized_resource("foo5"); - builder.header_insert("Content-Type", "foo6".try_into().unwrap()); - - let auth = builder.build(); - - let InnerAuth { - access_key_id, - access_key_secret, - method, - content_md5, - headers, - date, - canonicalized_resource, - .. - } = auth; - - assert_eq!(access_key_id.as_ref(), "foo1"); - - assert_eq!(access_key_secret.as_str(), "foo2"); - - assert_eq!(method.to_string(), "POST".to_owned()); - - assert_eq!(content_md5.unwrap().as_ref(), "foo4"); - - assert_eq!(headers.get(CONTENT_TYPE).unwrap().as_bytes(), b"foo6"); - - assert_eq!(date.as_ref(), "Sat, 01 Jan 2022 18:01:01 GMT"); - - assert_eq!(canonicalized_resource.as_ref(), "foo5"); - } - - #[test] - fn auth_to_sign_string_none() { - use http::header::CONTENT_TYPE; - let date = Utc.with_ymd_and_hms(2022, 1, 1, 18, 1, 1).unwrap(); - - let mut builder = AuthBuilder::default(); - builder.key("foo1"); - builder.secret("foo2"); - builder.method(&Method::POST); - //.content_md5(ContentMd5::new("foo4")) - builder.date(date); - builder.canonicalized_resource("foo5"); - builder.header_insert("Content-Type", "foo6".try_into().unwrap()); - - let auth = builder.build(); - - let InnerAuth { - access_key_id, - access_key_secret, - method, - content_md5, - headers, - date, - canonicalized_resource, - .. - } = auth; - - assert_eq!(access_key_id.as_ref(), "foo1"); - - assert_eq!(access_key_secret.as_str(), "foo2"); - - assert_eq!(method.to_string(), "POST".to_owned()); - - assert!(content_md5.is_none()); - - assert_eq!(headers.get(CONTENT_TYPE).unwrap().as_bytes(), b"foo6"); - - assert_eq!(date.as_ref(), "Sat, 01 Jan 2022 18:01:01 GMT"); - - assert_eq!(canonicalized_resource.as_ref(), "foo5"); - } -} - -mod auth_builder { - use std::convert::TryInto; - - use chrono::{TimeZone, Utc}; - use http::{header::HOST, HeaderMap, Method}; - - use crate::auth::AuthBuilder; - - #[test] - fn test_key() { - let mut builder = AuthBuilder::default(); - builder.key("foo1"); - let auth = builder.build(); - - assert_eq!(auth.access_key_id.as_ref(), "foo1"); - } - - #[test] - fn test_secret() { - let mut builder = AuthBuilder::default(); - builder.secret("foo2"); - let auth = builder.build(); - - assert_eq!(auth.access_key_secret.as_str(), "foo2"); - } - - #[test] - fn test_verb() { - let mut builder = AuthBuilder::default(); - builder.method(&Method::POST); - let auth = builder.build(); - - assert_eq!(auth.method, &Method::POST); - } - - #[test] - fn test_content_md5() { - let mut builder = AuthBuilder::default(); - builder.content_md5("abc3"); - let auth = builder.build(); - - assert_eq!(auth.content_md5.unwrap().as_ref(), "abc3"); - } - - #[test] - fn test_date() { - let mut builder = AuthBuilder::default(); - let date = Utc.with_ymd_and_hms(2022, 1, 1, 18, 1, 1).unwrap(); - builder.date(date); - let auth = builder.build(); - - assert_eq!(auth.date.as_ref(), "Sat, 01 Jan 2022 18:01:01 GMT"); - } - - #[test] - fn test_canonicalized_resource() { - let mut builder = AuthBuilder::default(); - builder.canonicalized_resource("foo323"); - let auth = builder.build(); - - assert_eq!(auth.canonicalized_resource.as_ref(), "foo323"); - } - - #[test] - fn test_header() { - let mut builder = AuthBuilder::default(); - let mut header = HeaderMap::new(); - header.insert(HOST, "127.0.0.1".try_into().unwrap()); - builder.headers(header); - - let host = builder.build().get_header("HOST"); - assert!(host.is_some()); - - let host = host.unwrap(); - assert_eq!(host.to_str().unwrap(), "127.0.0.1"); - } - - #[test] - fn test_insert_header() { - let mut builder = AuthBuilder::default(); - builder.header_insert("Content-Type", "application/json".parse().unwrap()); - - let auth = builder.build(); - assert_eq!(auth.header_len(), 1); - assert!(auth.header_contains_key("Content-Type")); - } - - #[test] - fn test_clear() { - let mut builder = AuthBuilder::default(); - builder.header_insert("Content-Type", "application/json".parse().unwrap()); - builder.header_clear(); - - assert_eq!(builder.build().header_len(), 0); - } -} - -mod auth_to_header_map { - use chrono::{TimeZone, Utc}; - use http::header::HeaderValue; - use http::Method; - - use crate::auth::AuthBuilder; - use crate::auth::AuthToHeaderMap; - - #[test] - fn test_to_header_map() { - let date = Utc.with_ymd_and_hms(2022, 1, 1, 18, 1, 1).unwrap(); - - let mut builder = AuthBuilder::default(); - builder.key("foo1"); - builder.secret("foo2"); - builder.method(&Method::POST); - builder.content_md5("foo4"); - builder.date(date); - builder.canonicalized_resource("foo5"); - builder.header_insert("Content-Type", "foo6".try_into().unwrap()); - - let auth = builder.build(); - - let header = auth.get_original_header(); - assert_eq!( - header.get("Content-Type").unwrap().to_str().unwrap(), - "foo6" - ); - - let key = auth.get_header_key().unwrap(); - //let secret = auth.get_header_secret().unwrap(); - let verb = auth.get_header_method().unwrap(); - let md5 = auth.get_header_md5().unwrap(); - let date = auth.get_header_date().unwrap(); - let resource = auth.get_header_resource().unwrap(); - - assert_eq!(key, HeaderValue::from_bytes(b"foo1").unwrap()); - //assert_eq!(secret, HeaderValue::from_bytes(b"foo2").unwrap()); - assert_eq!(verb, HeaderValue::from_bytes(b"POST").unwrap()); - assert_eq!(md5, HeaderValue::from_bytes(b"foo4").unwrap()); - assert_eq!( - date, - HeaderValue::from_bytes(b"Sat, 01 Jan 2022 18:01:01 GMT").unwrap() - ); - assert_eq!(resource, HeaderValue::from_bytes(b"foo5").unwrap()); - } - - #[test] - fn test_to_header_map_none() { - let date = Utc.with_ymd_and_hms(2022, 1, 1, 18, 1, 1).unwrap(); - - let mut builder = AuthBuilder::default(); - builder.key("foo1"); - builder.secret("foo2"); - builder.method(&Method::POST); - //.content_md5(ContentMd5::new("foo4")) - builder.date(date); - builder.canonicalized_resource("foo5"); - builder.header_insert("Content-Type", "foo6".try_into().unwrap()); - - let auth = builder.build(); - - let md5 = auth.get_header_md5(); - - assert!(md5.is_none()); - } -} - -#[test] -fn header_map_from_auth() { - let mut auth = MockAuthToHeaderMap::default(); - auth.expect_get_original_header() - .times(1) - .returning(|| HeaderMap::new()); - auth.expect_get_header_key() - .times(1) - .returning(|| Ok("foo1".parse().unwrap())); - // auth.expect_get_header_secret() - // .times(1) - // .returning(|| Ok("foo2".parse().unwrap())); - auth.expect_get_header_method() - .times(1) - .returning(|| Ok("foo3".parse().unwrap())); - - auth.expect_get_header_md5().times(1).returning(|| { - let val: HeaderValue = "foo4".parse().unwrap(); - Some(val) - }); - auth.expect_get_header_date() - .times(1) - .returning(|| Ok("foo6".parse().unwrap())); - auth.expect_get_header_resource() - .times(1) - .returning(|| Ok("foo7".parse().unwrap())); - - let map = HeaderMap::from_auth(&auth); - assert!(map.is_ok()); - - let map = map.unwrap(); - assert_eq!(map.get("AccessKeyId").unwrap().to_str().unwrap(), "foo1"); - assert!(map.get("SecretAccessKey").is_none()); -} - -#[test] -fn test_append_auth() { - let mut builder = AuthBuilder::default(); - builder.key("foo1"); - builder.secret("foo2"); - builder.method(&Method::POST); - builder.content_md5("foo4"); - builder.date(unsafe { Date::from_static("foo_date") }); - builder.canonicalized_resource("foo5"); - builder.header_insert("Content-Type", "foo6".try_into().unwrap()); - let auth = builder.build(); - - let mut map = HeaderMap::new(); - map.append_auth(&auth).unwrap(); - - assert_eq!(map.len(), 6); - assert_eq!(map.get("Content-Type").unwrap(), &"foo6"); - assert_eq!(map.get("accesskeyid").unwrap(), &"foo1"); - assert!(map.get("secretaccesskey").is_none()); - assert_eq!(map.get("verb").unwrap(), &"POST"); - assert_eq!(map.get("content-md5").unwrap(), &"foo4"); - assert_eq!(map.get("date").unwrap(), &"foo_date"); - assert_eq!(map.get("canonicalizedresource").unwrap(), &"foo5"); -} - -#[test] -fn test_append_sign() { - mock! { - BarStruct {} - - impl TryInto for BarStruct { - type Error = InvalidHeaderValue; - fn try_into(self) -> Result; - } - } - - let mut myinto = MockBarStruct::new(); - myinto - .expect_try_into() - .times(1) - .returning(|| HeaderValue::from_str("foo")); - - let mut map = HeaderMap::new(); - let res = map.append_sign(myinto); - - assert!(res.is_ok()); - let val = res.unwrap(); - assert!(val.is_none()); - assert!(!map.is_empty()); - - let mut myinto = MockBarStruct::new(); - myinto.expect_try_into().times(1).returning(|| { - let val = HeaderValue::from_str("foo").unwrap(); - Ok(val) - }); - - let mut map = HeaderMap::new(); - map.insert("Authorization", "bar".try_into().unwrap()); - let res = map.append_sign(myinto); - - assert!(res.is_ok()); - let val = res.unwrap(); - assert!(matches!(val, Some(v) if v==HeaderValue::from_bytes(b"bar").unwrap())); -} - -#[test] -fn to_sign_string() { - let header = OssHeader::new(None); - let string = header.to_string(); - assert_eq!(&string, ""); - - let header = OssHeader::new(Some(String::from(""))); - let string = header.to_string(); - assert_eq!(&string, "\n"); - - let header = OssHeader::new(Some(String::from("abc"))); - let string = header.to_string(); - assert_eq!(&string, "abc\n"); -} - -#[test] -fn header_into_string() { - let header = OssHeader::new(None); - let string: String = header.to_string(); - assert_eq!(&string, ""); - - let header = OssHeader::new(Some(String::from(""))); - let string: String = header.to_string(); - assert_eq!(&string, "\n"); - - let header = OssHeader::new(Some(String::from("abc"))); - let string: String = header.to_string(); - assert_eq!(&string, "abc\n"); -} - -mod sign_string_struct { - use crate::{ - auth::SignString, - types::{KeyId, KeySecret}, - }; - - #[test] - fn test_into_sign() { - let key = KeyId::from("foo1"); - let secret = KeySecret::from("foo2"); - let sign_string = SignString::new("bar", key, secret); - - let res = sign_string.into_sign(); - assert!(res.is_ok()); - let sign = res.unwrap(); - assert_eq!(sign.data(), "gTzwiN1fRQV90YcecTvo1pH+kI8="); - assert_eq!(sign.key_string(), "foo1".to_string()); - } -} - -#[test] -fn test_sign_string_debug() { - let sign = SignString::new("abc", "key".into(), "secret".into()); - - assert_eq!( - format!("{sign:?}"), - "SignString { data: \"abc\", key: InnerKeyId(\"key\"), secret: KeySecret }" - ); -} - -#[test] -fn test_sign_debug() { - let sign = Sign::new("abc", "key".into()); - assert_eq!( - format!("{sign:?}"), - "Sign { data: \"abc\", key: InnerKeyId(\"key\") }" - ); -} - -#[test] -fn test_sign_to_headervalue() { - let key = KeyId::from("bar"); - let sign = Sign::new("foo", key); - - let val: HeaderValue = sign.try_into().unwrap(); - assert_eq!(val.to_str().unwrap(), "OSS bar:foo"); -} - -mod get_headers { - use http::{HeaderMap, Method}; - - use crate::{auth::AuthBuilder, types::Date}; - - /// 集成测试,其他的都是单元测试 - #[test] - fn test_get_headers() { - let mut builder = AuthBuilder::default(); - builder.key("foo1"); - builder.secret("foo2"); - builder.method(&Method::POST); - builder.content_md5("foo4"); - builder.date(unsafe { Date::from_static("foo_date") }); - builder.canonicalized_resource("foo5"); - builder.header_insert("Content-Type", "foo6".try_into().unwrap()); - let map = builder.build().get_headers(); - - assert!(map.is_ok()); - let map = map.unwrap(); - assert_eq!(map.len(), 7); - assert_eq!(map.get("Content-Type").unwrap(), &"foo6"); - assert_eq!(map.get("accesskeyid").unwrap(), &"foo1"); - assert!(map.get("secretaccesskey").is_none()); - assert_eq!(map.get("verb").unwrap(), &"POST"); - assert_eq!(map.get("content-md5").unwrap(), &"foo4"); - assert_eq!(map.get("date").unwrap(), &"foo_date"); - assert_eq!(map.get("canonicalizedresource").unwrap(), &"foo5"); - assert_eq!( - map.get("authorization").unwrap(), - &"OSS foo1:67qpyspFaWOYrWwahWKgNN+ngUY=" - ); - } - - #[test] - fn test_append_headers() { - let mut builder = AuthBuilder::default(); - builder.key("foo1"); - builder.secret("foo2"); - builder.method(&Method::POST); - builder.content_md5("foo4"); - builder.date(unsafe { Date::from_static("foo_date") }); - builder.canonicalized_resource("foo5"); - builder.header_insert("Content-Type", "foo6".try_into().unwrap()); - let auth = builder.build(); - - let mut map = HeaderMap::new(); - auth.append_headers(&mut map).unwrap(); - - assert_eq!(map.len(), 7); - assert_eq!(map.get("Content-Type").unwrap(), &"foo6"); - assert_eq!(map.get("accesskeyid").unwrap(), &"foo1"); - assert!(map.get("secretaccesskey").is_none()); - assert_eq!(map.get("verb").unwrap(), &"POST"); - assert_eq!(map.get("content-md5").unwrap(), &"foo4"); - assert_eq!(map.get("date").unwrap(), &"foo_date"); - assert_eq!(map.get("canonicalizedresource").unwrap(), &"foo5"); - assert_eq!( - map.get("authorization").unwrap(), - &"OSS foo1:67qpyspFaWOYrWwahWKgNN+ngUY=" - ); - } -} - -#[test] -fn oss_header_to_string() { - let header = OssHeader::new(Some("foo7".to_string())); - assert_eq!(header.to_string(), "foo7\n".to_string()); - - let header = OssHeader::new(None); - - assert_eq!(header.to_string(), "".to_string()); -} - -#[test] -fn test_error_display() { - let val = HeaderValue::from_str("\n"); - let header_error = val.unwrap_err(); - let err = AuthError { - kind: AuthErrorKind::HeaderValue(header_error), - }; - assert_eq!(format!("{}", err), "failed to parse header value"); - - let err = AuthError { - kind: AuthErrorKind::Hmac(hmac::digest::crypto_common::InvalidLength {}), - }; - assert_eq!(format!("{}", err), "invalid aliyun secret length"); - - let err = AuthError { - kind: AuthErrorKind::InvalidCanonicalizedResource, - }; - assert_eq!(format!("{}", err), "invalid canonicalized-resource"); -} - -#[test] -fn test_error_source() { - let val = HeaderValue::from_str("\n"); - let header_error = val.unwrap_err(); - let err = AuthError { - kind: AuthErrorKind::HeaderValue(header_error), - }; - assert_eq!( - format!("{}", err.source().unwrap()), - "failed to parse header value" - ); - - let err = AuthError { - kind: AuthErrorKind::Hmac(hmac::digest::crypto_common::InvalidLength {}), - }; - assert_eq!(format!("{}", err.source().unwrap()), "Invalid Length"); - - let err = AuthError { - kind: AuthErrorKind::InvalidCanonicalizedResource, - }; - assert!(err.source().is_none()); -} - -mod with_oss { - use http::{HeaderValue, Method}; - - use crate::auth::{AuthError, AuthErrorKind, RequestWithOSS}; - - #[test] - fn test() { - let client = reqwest::Client::default(); - let mut request = client - .request( - Method::GET, - "https://foo.oss-cn-shanghai.aliyuncs.com/?bucketInfo", - ) - .build() - .unwrap(); - - request.with_oss("key1".into(), "secret2".into()).unwrap(); - let header = request.headers(); - - assert!(header.len() == 5); - - assert_eq!( - header.get("accesskeyid").unwrap(), - "key1".parse::().unwrap() - ); - assert!(header.get("secretaccesskey").is_none()); - assert_eq!( - header.get("verb").unwrap(), - "GET".parse::().unwrap() - ); - assert_eq!( - header.get("canonicalizedresource").unwrap(), - "/foo/?bucketInfo".parse::().unwrap() - ); - assert!(header.get("date").is_some()); - assert!(header.get("authorization").is_some()); - } - - #[test] - fn test_err() { - let client = reqwest::Client::default(); - let mut request = client - .request( - Method::GET, - "https://foo.oss-cn-shanghai.aliyunxx.com/?bucketInfo", - ) - .build() - .unwrap(); - - let err = request - .with_oss("key1".into(), "secret2".into()) - .unwrap_err(); - - assert!(matches!( - err, - AuthError { - kind: AuthErrorKind::InvalidCanonicalizedResource, - } - )); - } -} - -mod tests_canonicalized_resource { - use http::Method; - use reqwest::Url; - - use crate::{auth::OssHost, types::CanonicalizedResource, BucketName}; - - use super::super::GenCanonicalizedResource; - - #[test] - fn test_canonicalized_resource() { - let url: Url = "https://oss2.aliyuncs.com".parse().unwrap(); - assert_eq!(url.canonicalized_resource(), None); - let url: Url = "https://oss-cn-qingdao.aliyuncs.com".parse().unwrap(); - assert_eq!( - url.canonicalized_resource(), - Some(CanonicalizedResource::default()) - ); - - let url: Url = "https://abc.oss-cn-qingdao.aliyuncs.com?bucketInfo" - .parse() - .unwrap(); - assert_eq!( - url.canonicalized_resource(), - Some(CanonicalizedResource::new("/abc/?bucketInfo")) - ); - - let url: Url = "https://abc.oss-cn-qingdao.aliyuncs.com?list-type=2&continuation-token=foo" - .parse() - .unwrap(); - assert_eq!( - url.canonicalized_resource(), - Some(CanonicalizedResource::new("/abc/?continuation-token=foo")) - ); - - let url: Url = - "https://abc.oss-cn-qingdao.aliyuncs.com?continuation-token=foo&abc=def&list-type=2" - .parse() - .unwrap(); - assert_eq!( - url.canonicalized_resource(), - Some(CanonicalizedResource::new("/abc/?continuation-token=foo")) - ); - - let url: Url = "https://abc.oss-cn-qingdao.aliyuncs.com/path1" - .parse() - .unwrap(); - assert_eq!( - url.canonicalized_resource(), - Some(CanonicalizedResource::new("/abc/path1")) - ); - } - - #[test] - fn test_oss_host() { - let url: Url = "https://192.168.3.10/path1?delimiter=5".parse().unwrap(); - assert_eq!(url.oss_host(), OssHost::None); - - let url: Url = "https://example.com/path1?delimiter=5".parse().unwrap(); - assert_eq!(url.oss_host(), OssHost::None); - - let url: Url = "https://aliyuncs.com".parse().unwrap(); - assert_eq!(url.oss_host(), OssHost::None); - - let url: Url = "https://oss-cn-qingdao.aliyuncs.com".parse().unwrap(); - assert_eq!(url.oss_host(), OssHost::EndPoint); - - let url: Url = "https://oss-abc.aliyuncs.com".parse().unwrap(); - assert_eq!(url.oss_host(), OssHost::EndPoint); - - let url: Url = "https://abc.aliyuncs.com".parse().unwrap(); - assert_eq!(url.oss_host(), OssHost::None); - - let url: Url = "https://abc.oss-cn-qingdao.aliyuncs.com".parse().unwrap(); - assert_eq!( - url.oss_host(), - OssHost::Bucket(BucketName::new("abc").unwrap()) - ); - let url: Url = "https://abc-.oss-cn-qingdao.aliyuncs.com".parse().unwrap(); - assert_eq!(url.oss_host(), OssHost::None); - } - - #[test] - fn test_object_list_resource() { - let url: Url = "https://example.com/path1?delimiter=5".parse().unwrap(); - let bucket = "abc".parse().unwrap(); - let resource = url.object_list_resource(&bucket); - assert!(resource == "/abc/"); - - let url: Url = "https://example.com/path1?continuation-token=bar&delimiter=5" - .parse() - .unwrap(); - let bucket = "abc".parse().unwrap(); - let resource = url.object_list_resource(&bucket); - assert!(resource == "/abc/?continuation-token=bar"); - } - - #[test] - fn test_object_path() { - let url: Url = "https://example.com/path1".parse().unwrap(); - assert_eq!(url.object_path().unwrap(), "path1"); - - let url: Url = "https://example.com/path1/object2".parse().unwrap(); - assert_eq!(url.object_path().unwrap(), "path1/object2"); - - let url: Url = "https://example.com/路径/object2".parse().unwrap(); - assert_eq!(url.object_path().unwrap(), "路径/object2"); - - let url: Url = "https://example.com/path1/object2?foo=bar".parse().unwrap(); - assert_eq!(url.object_path().unwrap(), "path1/object2"); - - let url: Url = "https://example.com/path1/".parse().unwrap(); - assert!(url.object_path().is_none()); - } - - #[test] - fn test_request() { - let client = reqwest::Client::default(); - let request = client - .request( - Method::GET, - "https://foo.oss-cn-shanghai.aliyuncs.com/?bucketInfo", - ) - .build() - .unwrap(); - - assert_eq!( - request.canonicalized_resource(), - request.url().canonicalized_resource(), - ); - assert_eq!(request.oss_host(), request.url().oss_host(),); - - let bucket = "abc".parse().unwrap(); - assert_eq!( - request.object_list_resource(&bucket), - request.url().object_list_resource(&bucket), - ); - assert_eq!(request.object_path(), request.url().object_path(),); - } -} diff --git a/src/blocking/builder.rs b/src/blocking/builder.rs deleted file mode 100644 index 869e0b5..0000000 --- a/src/blocking/builder.rs +++ /dev/null @@ -1,112 +0,0 @@ -use crate::{builder::BuilderError, errors::OssService}; -use http::{ - header::{HeaderMap, HeaderName, HeaderValue}, - Method, -}; -use reqwest::{ - blocking::{self, Body, Request, Response}, - IntoUrl, -}; -use std::{rc::Rc, time::Duration}; - -#[derive(Default, Clone, Debug)] -pub struct ClientWithMiddleware { - inner: blocking::Client, - middleware: Option>, -} - -pub trait Middleware: 'static + std::fmt::Debug { - fn handle(&self, request: Request) -> Result; -} - -impl ClientWithMiddleware { - pub fn new(inner: blocking::Client) -> Self { - Self { - inner, - middleware: None, - } - } - - pub fn request(&self, method: Method, url: U) -> RequestBuilder { - RequestBuilder { - inner: self.inner.request(method, url), - middleware: self.middleware.clone(), - } - } - - pub fn middleware(&mut self, middleware: Rc) { - self.middleware = Some(middleware); - } -} - -pub struct RequestBuilder { - inner: reqwest::blocking::RequestBuilder, - middleware: Option>, -} - -impl RequestBuilder { - #[allow(dead_code)] - pub(crate) fn header(self, key: K, value: V) -> Self - where - HeaderName: TryFrom, - >::Error: Into, - HeaderValue: TryFrom, - >::Error: Into, - { - RequestBuilder { - inner: self.inner.header(key, value), - ..self - } - } - - pub(crate) fn headers(self, headers: HeaderMap) -> Self { - RequestBuilder { - inner: self.inner.headers(headers), - ..self - } - } - - pub(crate) fn body>(self, body: T) -> Self { - RequestBuilder { - inner: self.inner.body(body), - ..self - } - } - - pub(crate) fn timeout(self, timeout: Duration) -> Self { - RequestBuilder { - inner: self.inner.timeout(timeout), - ..self - } - } - - #[allow(dead_code)] - pub(crate) fn build(self) -> reqwest::Result { - self.inner.build() - } - - /// 发送请求,获取响应后,直接返回 Response - pub fn send(self) -> Result { - match self.middleware { - Some(m) => m.handle(self.inner.build().unwrap()), - None => self.inner.send().map_err(BuilderError::from), - } - } - - /// 发送请求,获取响应后,解析 xml 文件,如果有错误,返回 Err 否则返回 Response - pub fn send_adjust_error(self) -> Result { - match self.middleware { - Some(m) => m.handle(self.inner.build().unwrap()), - None => check_http_status(self.inner.send().map_err(BuilderError::from)?), - } - } -} - -pub(crate) fn check_http_status(response: Response) -> Result { - if response.status().is_success() { - return Ok(response); - } - let url = response.url().clone(); - let status = response.status(); - Err(OssService::new2(response.text()?, &status, url).into()) -} diff --git a/src/blocking/mod.rs b/src/blocking/mod.rs deleted file mode 100644 index 43dc69f..0000000 --- a/src/blocking/mod.rs +++ /dev/null @@ -1,163 +0,0 @@ -/*! -# 阻塞模式 - -## 使用方法 - -### 初始化配置信息 - -- 方式一 - -```rust -use std::env::set_var; -set_var("ALIYUN_KEY_ID", "foo1"); -set_var("ALIYUN_KEY_SECRET", "foo2"); -set_var("ALIYUN_ENDPOINT", "qingdao"); -set_var("ALIYUN_BUCKET", "foo4"); -let client = aliyun_oss_client::ClientRc::from_env(); -``` - -- 方式二 - -在项目根目录下创建 `.env` 文件(需将其加入 .gitignore ),内容: - -```bash -ALIYUN_KEY_ID=xxxxxxx -ALIYUN_KEY_SECRET=yyyyyyyyyyyyyy -ALIYUN_ENDPOINT=https://oss-cn-shanghai.aliyuncs.com -ALIYUN_BUCKET=zzzzzzzzzz -``` - -在需要使用 OSS 的地方,这样设置: -```rust -use dotenv::dotenv; -dotenv().ok(); -let client = aliyun_oss_client::ClientRc::from_env(); -``` - -- 方式三 - -```rust -let client = aliyun_oss_client::ClientRc::new( - "key1".into(), - "secret1".into(), - "qingdao".parse().unwrap(), - "my-bucket".parse().unwrap() -); -``` - -### 查询所有的 bucket 信息 - -```rust -# use std::env::set_var; -# set_var("ALIYUN_KEY_ID", "foo1"); -# set_var("ALIYUN_KEY_SECRET", "foo2"); -# set_var("ALIYUN_ENDPOINT", "qingdao"); -# set_var("ALIYUN_BUCKET", "foo4"); -# let client = aliyun_oss_client::ClientRc::from_env().unwrap(); -let response = client.get_bucket_list(); -println!("buckets list: {:?}", response); -``` - -### 获取 bucket 信息 -```rust -# use std::env::set_var; -# set_var("ALIYUN_KEY_ID", "foo1"); -# set_var("ALIYUN_KEY_SECRET", "foo2"); -# set_var("ALIYUN_ENDPOINT", "qingdao"); -# set_var("ALIYUN_BUCKET", "foo4"); -# let client = aliyun_oss_client::ClientRc::from_env().unwrap(); -let response = client.get_bucket_info(); -println!("bucket info: {:?}", response); -``` - -### 查询当前 bucket 中的 object 列表 -```rust -# use std::env::set_var; -# set_var("ALIYUN_KEY_ID", "foo1"); -# set_var("ALIYUN_KEY_SECRET", "foo2"); -# set_var("ALIYUN_ENDPOINT", "qingdao"); -# set_var("ALIYUN_BUCKET", "foo4"); -# let client = aliyun_oss_client::ClientRc::from_env().unwrap(); -let response = client.get_object_list([]); -println!("objects list: {:?}", response); -``` - -### 也可以使用 bucket struct 查询 object 列表 - -```ignore -# use std::env::set_var; -# set_var("ALIYUN_KEY_ID", "foo1"); -# set_var("ALIYUN_KEY_SECRET", "foo2"); -# set_var("ALIYUN_ENDPOINT", "qingdao"); -# set_var("ALIYUN_BUCKET", "foo4"); -# let client = aliyun_oss_client::ClientRc::from_env().unwrap(); -let query = vec![ - ("max-keys".into(), "5".into()), - ("prefix".into(), "babel".into()) -]; - -let mut result = client.get_bucket_info().unwrap().get_object_list(query).unwrap(); - -println!("object list : {:?}", result); - -// 翻页功能 获取下一页数据 -println!("next object list: {:?}", result.next()); -``` - -### 上传文件 -```rust -# use std::env::set_var; -# set_var("ALIYUN_KEY_ID", "foo1"); -# set_var("ALIYUN_KEY_SECRET", "foo2"); -# set_var("ALIYUN_ENDPOINT", "qingdao"); -# set_var("ALIYUN_BUCKET", "foo4"); -# let client = aliyun_oss_client::ClientRc::from_env().unwrap(); -use aliyun_oss_client::file::blocking::Files; -client.put_file("examples/bg2015071010.png", "examples/bg2015071010.png"); - -// or 上传文件内容 -let file_content = std::fs::read("examples/bg2015071010.png").unwrap(); -client.put_content(file_content, "examples/bg2015071010.png", |_|Some("image/png")); - -// or 自定义上传文件 Content-Type -let file_content = std::fs::read("examples/bg2015071010.png").unwrap(); -client.put_content_base(file_content, "image/png", "examples/bg2015071010.png"); -``` - -### 删除文件 -```rust -# use std::env::set_var; -# set_var("ALIYUN_KEY_ID", "foo1"); -# set_var("ALIYUN_KEY_SECRET", "foo2"); -# set_var("ALIYUN_ENDPOINT", "qingdao"); -# set_var("ALIYUN_BUCKET", "foo4"); -# let client = aliyun_oss_client::ClientRc::from_env().unwrap(); -use aliyun_oss_client::file::blocking::Files; -client.delete_object("examples/bg2015071010.png"); -``` -*/ - -#[doc(hidden)] -pub mod builder; - -use crate::config::Config; -use crate::types::{BucketName, EndPoint, KeyId, KeySecret}; - -use self::builder::ClientWithMiddleware; - -/// 初始化同步 Client 的函数 -pub fn client( - access_key_id: ID, - access_key_secret: S, - endpoint: E, - bucket: B, -) -> crate::client::Client -where - ID: Into, - S: Into, - E: Into, - B: Into, -{ - let config = Config::new(access_key_id, access_key_secret, endpoint, bucket); - crate::client::Client::::from_config(config) -} diff --git a/src/bucket.rs b/src/bucket.rs index ebc7b44..97748bf 100644 --- a/src/bucket.rs +++ b/src/bucket.rs @@ -1,788 +1,458 @@ -//! # bucket 操作模块 -//! 包含查询账号下的所有bucket ,bucket明细 - -#[cfg(feature = "blocking")] -use crate::builder::RcPointer; -use crate::builder::{ArcPointer, BuilderError, PointerFamily}; -use crate::client::ClientArc; -#[cfg(feature = "blocking")] -use crate::client::ClientRc; -use crate::config::BucketBase; -use crate::decode::{InnerItemError, ListError, RefineBucket, RefineBucketList, RefineObjectList}; -#[cfg(feature = "blocking")] -use crate::file::blocking::AlignBuilder as BlockingAlignBuilder; -use crate::file::AlignBuilder; -use crate::object::{ExtractListError, InitObject, Object, ObjectList, Objects, StorageClass}; -use crate::types::{ - CanonicalizedResource, InvalidBucketName, InvalidEndPoint, Query, QueryKey, QueryValue, - BUCKET_INFO, +use std::str::FromStr; + +use chrono::{DateTime, Utc}; +use reqwest::Method; +use serde::{de::DeserializeOwned, Deserialize}; +use serde_xml_rs::from_str; +use url::Url; + +use crate::{ + client::Client, + error::OssError, + object::{Object, Objects}, + types::{CanonicalizedResource, EndPoint, ObjectQuery, StorageClass}, }; -use crate::{BucketName, EndPoint}; - -use chrono::{DateTime, NaiveDateTime, Utc}; -use http::Method; -use oss_derive::oss_gen_rc; -use std::error::Error; -use std::fmt::{self, Display}; -use std::num::ParseIntError; -#[cfg(feature = "blocking")] -use std::rc::Rc; -use std::sync::Arc; -#[cfg(test)] -mod test; - -/// # 存储 Bucket 列表的 struct -#[derive(Clone, Hash)] -#[non_exhaustive] -pub struct ListBuckets> { - prefix: String, - marker: String, - max_keys: u16, - is_truncated: bool, - next_marker: String, - id: String, - display_name: String, - /// 存放单个 bucket 类型的 vec 集合 - buckets: Vec, - client: PointerSel::PointerType, -} - -/// # bucket list struct -/// before name : `ListBuckets` -//pub type Buckets = ListBuckets; - -/// sync ListBuckets alias -pub type Buckets> = ListBuckets; -/// blocking ListBuckets alias -#[cfg(feature = "blocking")] -pub type BucketsBlocking> = ListBuckets; - -/// 内置的存放单个 bucket 的类型 -#[derive(Clone, Hash)] -#[non_exhaustive] -pub struct Bucket { - pub(crate) base: BucketBase, - // bucket_info: Option>, - // bucket: Option>, - creation_date: DateTime, - //pub extranet_endpoint: String, - // owner 存放Bucket拥有者信息的容器。父节点:BucketInfo.Bucket - // access_control_list; - // pub grant: Grant, - // pub data_redundancy_type: Option, - storage_class: StorageClass, - // pub versioning: &'a str, - // ServerSideEncryptionRule, - // ApplyServerSideEncryptionByDefault, - // pub sse_algorithm: &'a str, - // pub kms_master_key_id: Option<&'a str>, - // pub cross_region_replication: &'a str, - // pub transfer_acceleration: &'a str, - pub(crate) client: PointerSel::PointerType, +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Bucket { + name: String, + endpoint: EndPoint, } -impl fmt::Debug for ListBuckets { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("ListBuckets") - .field("prefix", &self.prefix) - .field("marker", &self.marker) - .field("max_keys", &self.max_keys) - .field("is_truncated", &self.is_truncated) - .field("next_marker", &self.next_marker) - .field("id", &self.id) - .field("display_name", &self.display_name) - .field("buckets", &self.buckets) - .finish() - } -} - -impl Default for ListBuckets { - fn default() -> Self { - Self { - prefix: Default::default(), - marker: Default::default(), - max_keys: Default::default(), - is_truncated: Default::default(), - next_marker: Default::default(), - id: Default::default(), - display_name: Default::default(), - buckets: Default::default(), - client: T::PointerType::default(), - } - } -} +type NextContinuationToken = String; -#[oss_gen_rc] -impl ListBuckets { - fn from_client(client: Arc) -> Self { - Self { - client, - ..Default::default() +impl Bucket { + pub fn new>(name: N, endpoint: EndPoint) -> Bucket { + Bucket { + name: name.into(), + endpoint, } } - /// 获取 prefix - pub fn prefix_string(&self) -> &String { - &self.prefix - } - /// 获取 marker - pub fn marker_string(&self) -> &String { - &self.marker + pub(crate) fn as_str(&self) -> &str { + &self.name } - /// 获取 next_marker - pub fn next_marker_string(&self) -> &String { - &self.next_marker - } + /// # 返回 bucket 对应的链接地址 + /// 可以是内网地址,默认为外网地址 + /// ``` + /// # use aliyun_oss_client::{Bucket, EndPoint}; + /// # use url::Url; + /// let bucket = Bucket::new("foo", EndPoint::CN_QINGDAO); + /// assert_eq!(bucket.to_url(), Url::parse("https://foo.oss-cn-qingdao.aliyuncs.com").unwrap()); + /// ``` + pub fn to_url(&self) -> Url { + const HTTPS: &str = "https://"; + let url = self.endpoint.to_url().to_string(); + let name_str = self.name.to_string(); - /// 获取 id 和 display_name - pub fn info_string(&self) -> (&String, &String) { - (&self.id, &self.display_name) - } + let mut name = String::from(HTTPS); + name.push_str(&name_str); + name.push('.'); - /// 返回 bucket 的 Vec 集合 - pub fn to_vec(self) -> Vec { - self.buckets - } + let url = url.replace(HTTPS, &name); - /// 返回 bucket 数量 - pub fn len(&self) -> usize { - self.buckets.len() + Url::parse(&url).unwrap() } - /// 返回 bucket 是否为空 - pub fn is_empty(&self) -> bool { - self.buckets.is_empty() - } -} + /// 调用 api 导出 bucket 详情信息到自定义类型 + /// + /// aliyun api 返回的 xml 是如下格式: + /// ```xml + /// + /// Disabled + /// false + /// + /// 2016-11-05T13:10:10.000Z + /// Disabled + /// LRS + /// oss-cn-shanghai.aliyuncs.com + /// oss-cn-shanghai-internal.aliyuncs.com + /// oss-cn-shanghai + /// honglei123 + /// rg-acfmoiyerp5judy + /// Standard + /// Disabled + /// + /// 34773519 + /// 34773519 + /// + /// + /// public-read + /// + /// + /// None + /// + /// + /// honglei123 + /// oss-accesslog/ + /// + /// + /// ``` + /// 该方法返回的类型可以是如下结构体: + /// + /// ```rust + /// use serde::Deserialize; + /// #[derive(Debug, Deserialize)] + /// struct DemoData { + /// Name: String, + /// } + /// ``` + + pub async fn export_info(&self, client: &Client) -> Result { + const BUCKET_INFO: &str = "bucketInfo"; + + let mut url = self.to_url(); + url.set_query(Some(BUCKET_INFO)); + let method = Method::GET; + let resource = CanonicalizedResource::from_bucket_info(self); + + let header_map = client.authorization(method, resource)?; + + let content = reqwest::Client::new() + .get(url) + .headers(header_map) + .send() + .await? + .text() + .await?; -impl fmt::Debug for Bucket { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Bucket") - .field("base", &self.base) - .field("creation_date", &self.creation_date) - //.field("extranet_endpoint", &self.extranet_endpoint) - .field("storage_class", &self.storage_class) - .finish() - } -} + //println!("{}", content); -impl Default for Bucket { - fn default() -> Self { - Self { - base: BucketBase::default(), - creation_date: DateTime::::from_utc( - #[allow(clippy::unwrap_used)] - NaiveDateTime::from_timestamp_opt(0, 0).unwrap(), - Utc, - ), - //extranet_endpoint: String::default(), - storage_class: StorageClass::default(), - client: T::PointerType::default(), + #[derive(Debug, Deserialize)] + struct BucketInfo { + #[serde(rename = "Bucket")] + bucket: T, } - } -} + let res: BucketInfo = from_str(&content)?; -impl AsRef for Bucket { - fn as_ref(&self) -> &BucketBase { - &self.base + Ok(res.bucket) } -} -impl AsRef for Bucket { - fn as_ref(&self) -> &BucketName { - self.base.as_ref() - } -} + pub async fn get_info(&self, client: &Client) -> Result { + const BUCKET_INFO: &str = "bucketInfo"; -impl AsRef for Bucket { - fn as_ref(&self) -> &EndPoint { - self.base.as_ref() - } -} + let mut url = self.to_url(); + url.set_query(Some(BUCKET_INFO)); + let method = Method::GET; + let resource = CanonicalizedResource::from_bucket_info(self); -impl RefineBucket for Bucket { - fn set_name(&mut self, name: &str) -> Result<(), BucketError> { - self.base - .set_name(name.parse::().map_err(|e| BucketError { - source: name.to_string(), - kind: BucketErrorKind::BucketName(e), - })?); - Ok(()) - } + let header_map = client.authorization(method, resource)?; - fn set_location(&mut self, location: &str) -> Result<(), BucketError> { - self.base - .set_endpoint(location.parse::().map_err(|e| BucketError { - source: location.to_string(), - kind: BucketErrorKind::EndPoint(e), - })?); - Ok(()) - } + let content = reqwest::Client::new() + .get(url) + .headers(header_map) + .send() + .await? + .text() + .await?; - fn set_creation_date(&mut self, creation_date: &str) -> Result<(), BucketError> { - self.creation_date = creation_date.parse().map_err(|e| BucketError { - source: creation_date.to_string(), - kind: BucketErrorKind::Chrono(e), - })?; - Ok(()) + println!("{content}"); + Self::parse_info_xml(content) + } + + fn parse_info_xml(xml: String) -> Result { + let creation_date = match Self::parse_item(&xml, "CreationDate") { + Some(d) => d, + None => return Err(OssError::NoFoundCreationDate), + }; + let creation_date = creation_date.parse()?; + let storage_class = match Self::parse_item(&xml, "StorageClass") { + Some(s) => s, + None => return Err(OssError::NoFoundStorageClass), + }; + let storage_class = match StorageClass::new(storage_class) { + Some(s) => s, + None => return Err(OssError::NoFoundStorageClass), + }; + let data_redundancy_type = match Self::parse_item(&xml, "DataRedundancyType") { + Some(s) => s, + None => return Err(OssError::NoFoundDataRedundancyType), + }; + let data_redundancy_type = match DataRedundancyType::from_str(data_redundancy_type) { + Ok(d) => d, + Err(_) => return Err(OssError::NoFoundDataRedundancyType), + }; + + Ok(BucketInfo { + creation_date, + storage_class, + data_redundancy_type, + }) } - fn set_storage_class(&mut self, storage_class: &str) -> Result<(), BucketError> { - self.storage_class = StorageClass::new(storage_class).ok_or(BucketError { - source: storage_class.to_string(), - kind: BucketErrorKind::InvalidStorageClass, - })?; - Ok(()) + pub(crate) fn parse_item<'a>(xml: &'a str, field: &str) -> Option<&'a str> { + let start_tag = { + let mut s = String::from("<"); + s += field; + s += ">"; + s + }; + let start_index = xml.find(&start_tag); + + let end_tag = { + let mut s = String::from(""; + s + }; + let end_index = xml.find(&end_tag); + + match (start_index, end_index) { + (Some(start), Some(end)) => { + let s = &xml[start + field.len() + 2..end]; + Some(s) + } + _ => None, + } } -} -/// decode xml to bucket error type -#[derive(Debug)] -#[non_exhaustive] -pub struct BucketError { - source: String, - kind: BucketErrorKind, -} + /// 调用 aliyun api 返回 object 列表到自定义类型,它还会返回用于翻页的 `NextContinuationToken` + /// + /// aliyun api 返回的 xml 是如下格式: + /// ```xml + /// + /// 9AB932LY.jpeg + /// 2022-06-26T09:53:21.000Z + /// "F75A15996D0857B16FA31A3B16624C26" + /// Normal + /// 18027 + /// Standard + /// + /// ``` + /// 该方法返回的类型可以是如下结构体: + /// ```rust + /// use serde::Deserialize; + /// #[derive(Debug, Deserialize)] + /// struct MyObject { + /// Key: String, + /// } + /// ``` + pub async fn export_objects( + &self, + query: &ObjectQuery, + client: &Client, + ) -> Result<(Vec, NextContinuationToken), OssError> { + let mut url = self.to_url(); + url.set_query(Some(&query.to_oss_query())); + let method = Method::GET; + let resource = CanonicalizedResource::from_object_list(self, query.get_next_token()); + + let header_map = client.authorization(method, resource)?; + + let content = reqwest::Client::new() + .get(url) + .headers(header_map) + .send() + .await? + .text() + .await?; + + //println!("{content}"); -impl BucketError { - #[cfg(test)] - pub(crate) fn test_new() -> Self { - Self { - source: "foo".to_string(), - kind: BucketErrorKind::InvalidStorageClass, + #[derive(Debug, Deserialize)] + struct ListBucketResult { + #[serde(rename = "Contents")] + contents: Vec, + #[serde(rename = "NextContinuationToken")] + next_token: String, } - } -} + let res: ListBucketResult = from_str(&content)?; -impl Display for BucketError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "decode bucket xml faild, gived str: {}", self.source) + Ok((res.contents, res.next_token)) } -} -impl Error for BucketError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - use BucketErrorKind::*; - match &self.kind { - BucketName(e) => Some(e), - EndPoint(e) => Some(e), - Chrono(e) => Some(e), - InvalidStorageClass => None, - } + pub async fn get_objects( + &self, + query: &ObjectQuery, + client: &Client, + ) -> Result { + let mut url = self.to_url(); + url.set_query(Some(&query.to_oss_query())); + let method = Method::GET; + let resource = CanonicalizedResource::from_object_list(self, query.get_next_token()); + + let header_map = client.authorization(method, resource)?; + + let content = reqwest::Client::new() + .get(url) + .headers(header_map) + .send() + .await? + .text() + .await?; + + //println!("{content}"); + + let list = Self::parse_xml_objects(&content)?; + + let token = Self::parse_item(&content, "NextContinuationToken").map(|t| t.to_owned()); + + Ok(Objects::new(list, token)) } -} -/// decode xml to bucket error enum -#[derive(Debug)] -#[non_exhaustive] -enum BucketErrorKind { - /// when covert bucket name failed ,return this error - BucketName(InvalidBucketName), + pub(crate) fn parse_xml_objects(xml: &str) -> Result, OssError> { + let mut start_positions = vec![]; + let mut end_positions = vec![]; + let mut start = 0; + let mut pattern = ""; - /// when covert endpoint failed ,return this error - EndPoint(InvalidEndPoint), + while let Some(pos) = xml[start..].find(pattern) { + start_positions.push(start + pos); + start += pos + pattern.len(); + } + start = 0; + pattern = ""; + while let Some(pos) = xml[start..].find(pattern) { + end_positions.push(start + pos); + start += pos + pattern.len(); + } - /// when covert creation_date failed ,return this error - Chrono(chrono::ParseError), + let mut list = vec![]; + for i in 0..start_positions.len() { + let path = &xml[start_positions[i] + 5..end_positions[i]]; + list.push(Object::new(path.to_owned())) + } - /// when failed to get storage_class, return this error - InvalidStorageClass, + Ok(list) + } } -// 如果要改成 pub ,为了兼容,则应该改成 struct -/// decode xml to bucket list error collection -#[derive(Debug, thiserror::Error)] -#[non_exhaustive] -enum BucketListError { - /// it has only parse max_keys - #[error("covert max_keys failed")] - ParseInt(#[from] ParseIntError), +#[derive(Debug)] +pub struct BucketInfo { + //base: Bucket, + creation_date: DateTime, + storage_class: StorageClass, + data_redundancy_type: DataRedundancyType, } -impl ListError for BucketListError {} -impl Bucket { - /// 初始化 Bucket +impl BucketInfo { pub fn new( - base: BucketBase, creation_date: DateTime, storage_class: StorageClass, - client: T::PointerType, + data_redundancy_type: DataRedundancyType, ) -> Self { - Self { - base, + BucketInfo { creation_date, storage_class, - client, + data_redundancy_type, } } - /// 获取 bucket 创建时间 pub fn creation_date(&self) -> &DateTime { &self.creation_date } - - /// 获取 storage_class pub fn storage_class(&self) -> &StorageClass { &self.storage_class } - - /// 读取 bucket 基本信息 - pub fn base(&self) -> &BucketBase { - &self.base - } -} - -#[oss_gen_rc] -impl Bucket { - /// 获取 Bucket 的 Client 信息 - pub(crate) fn client(&self) -> Arc { - Arc::clone(&self.client) - } - - fn from_client(client: Arc) -> Self { - Self { - client, - ..Default::default() - } - } - - fn from_list(list: &mut ListBuckets) -> Option { - Some(Self { - client: list.client.clone(), - ..Default::default() - }) - } -} - -impl Bucket { - /// # 查询 Object 列表 - /// - /// 参数 query 有多种写法: - /// - `[]` 查所有 - /// - `[("max-keys".into(), "5".into())]` 数组(不可变长度),最大可支持 size 为 8 的数组 - /// - `[("max-keys".into(), "5".into()), ("prefix".into(), "babel".into())]` 数组(不可变长度) - /// - `vec![("max-keys".into(), "5".into())]` Vec(可变长度) - /// - `vec![("max-keys".into(), 5u8.into())]` 数字类型 - /// - `vec![("max-keys".into(), 1000u16.into())]` u16 数字类型 - #[inline(always)] - pub async fn get_object_list>( - &self, - query: Q, - ) -> Result { - self.get_object_list2(Query::from_iter(query)).await - } - - /// # 查询 Object 列表 - pub async fn get_object_list2(&self, query: Query) -> Result { - let bucket_arc = Arc::new(self.base.clone()); - - let mut list = Objects::::from_bucket(self, query.get_max_keys()); - - let (bucket_url, resource) = bucket_arc.get_url_resource(&query); - let response = self.builder(Method::GET, bucket_url, resource)?; - let content = response.send_adjust_error().await?; - list.decode(&content.text().await?, ObjectList::init_object)?; - - list.set_search_query(query); - - Ok(list) + pub fn data_redundancy_type(&self) -> &DataRedundancyType { + &self.data_redundancy_type } } -#[cfg(feature = "blocking")] -impl Bucket { - /// 查询默认 bucket 的文件列表 - /// - /// 查询条件参数有多种方式,具体参考 [`get_object_list`](#method.get_object_list) 文档 - pub fn get_object_list>( - &self, - query: Q, - ) -> Result, ExtractListError> { - let query = Query::from_iter(query); - - let bucket_arc = Rc::new(self.base.clone()); - - let mut list = ObjectList::::from_bucket(&self, query.get_max_keys()); - - let (bucket_url, resource) = bucket_arc.get_url_resource(&query); - - let response = self.builder(Method::GET, bucket_url, resource)?; - let content = response.send_adjust_error()?; - - list.decode(&content.text()?, ObjectList::::init_object)?; - list.set_search_query(query); - - Ok(list) - } +#[derive(Default, Debug)] +pub enum Grant { + #[default] + Private, + PublicRead, + PublicReadWrite, } -impl, E: Error + 'static> - RefineBucketList for ListBuckets -{ - fn set_prefix(&mut self, prefix: &str) -> Result<(), BucketListError> { - self.prefix = prefix.to_owned(); - Ok(()) - } - - fn set_marker(&mut self, marker: &str) -> Result<(), BucketListError> { - self.marker = marker.to_owned(); - Ok(()) - } - - fn set_max_keys(&mut self, max_keys: &str) -> Result<(), BucketListError> { - self.max_keys = max_keys.parse()?; - Ok(()) - } - - fn set_is_truncated(&mut self, is_truncated: bool) -> Result<(), BucketListError> { - self.is_truncated = is_truncated; - Ok(()) - } - - fn set_next_marker(&mut self, marker: &str) -> Result<(), BucketListError> { - self.next_marker = marker.to_owned(); - Ok(()) - } - - fn set_id(&mut self, id: &str) -> Result<(), BucketListError> { - self.id = id.to_owned(); - Ok(()) - } - - fn set_display_name(&mut self, display_name: &str) -> Result<(), BucketListError> { - self.display_name = display_name.to_owned(); - Ok(()) - } - - fn set_list(&mut self, list: Vec) -> Result<(), BucketListError> { - self.buckets = list; - Ok(()) - } +#[derive(Clone, Debug, Default)] +pub enum DataRedundancyType { + #[default] + LRS, + ZRS, } -impl InitObject for ListBuckets { - fn init_object(&mut self) -> Option { - Bucket::::from_list(self) - } -} +impl FromStr for DataRedundancyType { + type Err = OssError; -#[cfg(feature = "blocking")] -impl InitObject> for ListBuckets { - fn init_object(&mut self) -> Option> { - Bucket::::from_list(self) + fn from_str(s: &str) -> Result { + match s { + "LRS" => Ok(DataRedundancyType::LRS), + "ZRS" => Ok(DataRedundancyType::ZRS), + _ => Err(OssError::NoFoundDataRedundancyType), + } } } -impl ClientArc { - /// 从 OSS 获取 bucket 列表 - pub async fn get_bucket_list(self) -> Result { - let client_arc = Arc::new(self); - - let mut bucket_list = ListBuckets::::from_client(client_arc.clone()); - client_arc.base_bucket_list(&mut bucket_list).await?; - - Ok(bucket_list) - } - - /// 从 OSS 获取 bucket 列表,并存入自定义类型中 - pub async fn base_bucket_list( - &self, - list: &mut List, - ) -> Result<(), ExtractListError> - where - List: RefineBucketList + InitObject, - Item: RefineBucket, - E: ListError, - ItemErr: Error + 'static, - { - let response = self - .builder( - Method::GET, - self.get_endpoint_url(), - CanonicalizedResource::default(), - )? - .send_adjust_error() - .await?; - - list.decode(&response.text().await?, List::init_object)?; - - Ok(()) - } - - /// 从 OSS 上获取默认的 bucket 信息 - pub async fn get_bucket_info(self) -> Result { - let arc_client = Arc::new(self); - - let mut bucket = Bucket::::from_client(arc_client.clone()); - - arc_client.base_bucket_info(&mut bucket).await?; - - Ok(bucket) - } - - /// # 从 OSS 上获取 bucket 的信息,并存入自定义的类型中 - /// 默认获取 Client 中的默认 bucket 信息,如需获取其他 bucket,可调用 `set_bucket` 更改后调用 - /// - /// 也可以调用 `set_endpoint` 更改可用区 - pub async fn base_bucket_info( - &self, - bucket: &mut Bucket, - ) -> Result<(), ExtractItemError> - where - Bucket: RefineBucket, - E: Error + 'static, - { - let base = self.get_bucket_base(); - let mut bucket_url = base.to_url(); - let query = Some(BUCKET_INFO); - bucket_url.set_query(query); +#[cfg(test)] +mod tests { + use serde::Deserialize; - let canonicalized = CanonicalizedResource::from_bucket(base, query); + use crate::{ + client::init_client, + types::{EndPoint, ObjectQuery}, + }; - let response = self.builder(Method::GET, bucket_url, canonicalized)?; - let content = response.send_adjust_error().await?; + use super::Bucket; - bucket.decode(&content.text().await?)?; + #[tokio::test] + async fn test_get_info() { + let bucket = Bucket::new("honglei123", EndPoint::CN_SHANGHAI); + let info = bucket.get_info(&init_client()).await.unwrap(); - Ok(()) + //assert_eq!(list.len(), 2); } -} -/// 为 [`base_bucket_info`] 方法,返回一个统一的 Error -/// -/// [`base_bucket_info`]: crate::client::Client::base_bucket_info -#[derive(Debug)] -#[non_exhaustive] -pub struct ExtractItemError { - pub(crate) kind: ExtractItemErrorKind, -} - -#[derive(Debug)] -#[non_exhaustive] -pub(crate) enum ExtractItemErrorKind { - #[doc(hidden)] - Builder(BuilderError), - - Reqwest(reqwest::Error), - - #[doc(hidden)] - Decode(InnerItemError), -} + #[tokio::test] + async fn test_export_info() { + let bucket = Bucket::new("honglei123", EndPoint::CN_SHANGHAI); -impl From for ExtractItemError { - fn from(value: BuilderError) -> Self { - use ExtractItemErrorKind::*; - Self { - kind: Builder(value), + #[derive(Debug, Deserialize)] + struct DemoData { + Name: String, } - } -} -impl From for ExtractItemError { - fn from(value: reqwest::Error) -> Self { - use ExtractItemErrorKind::*; - Self { - kind: Reqwest(value), - } - } -} -impl From for ExtractItemError { - fn from(value: InnerItemError) -> Self { - use ExtractItemErrorKind::*; - Self { - kind: Decode(value), - } - } -} -impl Display for ExtractItemError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use ExtractItemErrorKind::*; - match &self.kind { - Builder(_) => "builder error".fmt(f), - Reqwest(_) => "reqwest error".fmt(f), - Decode(_) => "decode xml failed".fmt(f), - } - } -} -impl Error for ExtractItemError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - use ExtractItemErrorKind::*; - match &self.kind { - Builder(b) => Some(b), - Reqwest(b) => Some(b), - Decode(d) => d.get_source(), - } - } -} + let res: DemoData = bucket.export_info(&init_client()).await.unwrap(); -#[cfg(feature = "blocking")] -impl ClientRc { - /// 获取 bucket 列表 - pub fn get_bucket_list(self) -> Result, ExtractListError> { - let client_arc = Rc::new(self); - - let mut bucket_list = ListBuckets::::from_client(client_arc.clone()); - client_arc.base_bucket_list(&mut bucket_list)?; - - Ok(bucket_list) + println!("{:?}", res); } - /// 获取 bucket 列表,可存储为自定义的类型 - #[inline] - pub fn base_bucket_list( - &self, - list: &mut List, - ) -> Result<(), ExtractListError> - where - List: RefineBucketList + InitObject, - Item: RefineBucket, - E: ListError, - ItemErr: Error + 'static, - { - let response = self - .builder( - Method::GET, - self.get_endpoint_url(), - CanonicalizedResource::default(), - )? - .send_adjust_error()?; - - list.decode(&response.text()?, List::init_object)?; - - Ok(()) - } - - /// 获取当前的 bucket 的信息 - pub fn get_bucket_info(self) -> Result, ExtractItemError> { - let arc_client = Rc::new(self); - - let mut bucket = Bucket::::from_client(arc_client.clone()); + #[tokio::test] + async fn test_export_objects() { + let bucket = Bucket::new("honglei123", EndPoint::CN_SHANGHAI); + let condition = { + let mut map = ObjectQuery::new(); + map.insert(ObjectQuery::MAX_KEYS, "5"); + map + }; - arc_client.base_bucket_info(&mut bucket)?; - - Ok(bucket) - } - - /// 获取某一个 bucket 的信息,并存储到自定义的类型 - #[inline] - pub fn base_bucket_info(&self, bucket: &mut Bucket) -> Result<(), ExtractItemError> - where - Bucket: RefineBucket, - E: Error + 'static, - { - let base = self.get_bucket_base(); - let mut bucket_url = base.to_url(); - let query = Some(BUCKET_INFO); - bucket_url.set_query(query); - - let canonicalized = CanonicalizedResource::from_bucket(base, query); - - let response = self.builder(Method::GET, bucket_url, canonicalized)?; - let content = response.send_adjust_error()?; + #[derive(Debug, Deserialize)] + struct MyObject { + Key: String, + } - bucket.decode(&content.text()?)?; + let (list, token): (Vec, String) = bucket + .export_objects(&condition, &init_client()) + .await + .unwrap(); - Ok(()) + println!("{list:?}, token:{token}"); } -} -impl PartialEq> for Bucket { - #[inline] - fn eq(&self, other: &Bucket) -> bool { - self.base == other.base - && self.creation_date == other.creation_date - && self.storage_class == other.storage_class - } -} + #[tokio::test] + async fn test_get_objects() { + let bucket = Bucket::new("honglei123", EndPoint::CN_SHANGHAI); + let mut condition = { + let mut map = ObjectQuery::new(); + map.insert(ObjectQuery::MAX_KEYS, "5"); + map + }; -impl PartialEq> for Bucket { - #[inline] - fn eq(&self, other: &DateTime) -> bool { - &self.creation_date == other - } -} + let list = bucket + .get_objects(&condition, &init_client()) + .await + .unwrap(); -impl PartialEq for Bucket { - #[inline] - fn eq(&self, other: &BucketBase) -> bool { - &self.base == other + println!("{list:?}"); + condition.insert_next_token(list.next_token().unwrap().to_owned()); + let second_list2 = bucket + .get_objects(&condition, &init_client()) + .await + .unwrap(); + println!("second_list: {:?}", second_list2); + // let second_list = list.next_list(&condition, &init_client()).await.unwrap(); + // println!("second_list: {:?}", second_list); } } - -// #[doc(hidden)] -// #[derive(Default)] -// pub enum Grant { -// #[default] -// Private, -// PublicRead, -// PublicReadWrite, -// } - -// #[doc(hidden)] -// #[derive(Clone, Debug, Default)] -// pub enum DataRedundancyType { -// #[default] -// LRS, -// ZRS, -// } - -// #[doc(hidden)] -// #[derive(Default, Clone, Debug)] -// pub struct BucketListObjectParms<'a> { -// pub list_type: u8, -// pub delimiter: &'a str, -// pub continuation_token: &'a str, -// pub max_keys: u32, -// pub prefix: &'a str, -// pub encoding_type: &'a str, -// pub fetch_owner: bool, -// } - -// #[doc(hidden)] -// #[derive(Default, Clone, Debug)] -// pub struct BucketListObject<'a> { -// //pub content: -// pub common_prefixes: &'a str, -// pub delimiter: &'a str, -// pub encoding_type: &'a str, -// pub display_name: &'a str, -// pub etag: &'a str, -// pub id: &'a str, -// pub is_truncated: bool, -// pub key: &'a str, -// pub last_modified: &'a str, -// pub list_bucket_result: Option<&'a str>, -// pub start_after: Option<&'a str>, -// pub max_keys: u32, -// pub name: &'a str, -// // pub owner: &'a str, -// pub prefix: &'a str, -// pub size: u64, -// pub storage_class: &'a str, -// pub continuation_token: Option<&'a str>, -// pub key_count: i32, -// pub next_continuation_token: Option<&'a str>, -// pub restore_info: Option<&'a str>, -// } - -// #[doc(hidden)] -// #[derive(Clone, Debug)] -// pub struct BucketStat { -// pub storage: u64, -// pub object_count: u32, -// pub multipart_upload_count: u32, -// pub live_channel_count: u32, -// pub last_modified_time: u16, -// pub standard_storage: u64, -// pub standard_object_count: u32, -// pub infrequent_access_storage: u64, -// pub infrequent_access_real_storage: u64, -// pub infrequent_access_object_count: u64, -// pub archive_storage: u64, -// pub archive_real_storage: u64, -// pub archive_object_count: u64, -// pub cold_archive_storage: u64, -// pub cold_archive_real_storage: u64, -// pub cold_archive_object_count: u64, -// } diff --git a/src/bucket/test.rs b/src/bucket/test.rs deleted file mode 100644 index 3596ebe..0000000 --- a/src/bucket/test.rs +++ /dev/null @@ -1,713 +0,0 @@ -use std::error::Error; -use std::sync::Arc; - -use crate::bucket::{Bucket, BucketError, BucketErrorKind}; -use crate::builder::{ArcPointer, BuilderError, ClientWithMiddleware}; -use crate::decode::{RefineBucket, RefineBucketList}; -use crate::object::StorageClass; -use crate::tests::object::assert_object_list; -use crate::types::object::CommonPrefixes; -use crate::{BucketName, EndPoint, Query, QueryKey}; - -use async_trait::async_trait; -use chrono::{DateTime, NaiveDateTime, Utc}; -use http::HeaderValue; -use reqwest::{Request, Response}; - -use crate::client::ClientArc; -use crate::{builder::Middleware, client::Client}; - -use super::ListBuckets; - -#[test] -fn test_list_from_client() { - let client = Client::::new( - "foo1".into(), - "foo2".into(), - "https://oss-cn-shanghai.aliyuncs.com".parse().unwrap(), - "foo4".parse().unwrap(), - ); - let client = Arc::new(client); - let list = ListBuckets::::from_client(client); - assert_eq!(list.client.bucket, unsafe { - BucketName::from_static2("foo4") - }); -} - -#[test] -fn test_bucket_from_client() { - let client = Client::::new( - "foo1".into(), - "foo2".into(), - "https://oss-cn-shanghai.aliyuncs.com".parse().unwrap(), - "foo4".parse().unwrap(), - ); - let client = Arc::new(client); - let bucket = Bucket::::from_client(client); - assert_eq!(bucket.client.bucket, unsafe { - BucketName::from_static2("foo4") - }); -} - -#[tokio::test] -async fn test_get_bucket_list() { - #[derive(Debug)] - struct MyMiddleware {} - - #[async_trait] - impl Middleware for MyMiddleware { - async fn handle(&self, request: Request) -> Result { - //println!("request {:?}", request); - assert_eq!(request.method(), "GET"); - assert_eq!( - *request.url(), - "https://oss-cn-shanghai.aliyuncs.com/".parse().unwrap() - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some(&HeaderValue::from_str("/").unwrap()) - ); - use http::response::Builder; - let response = Builder::new() - .status(200) - //.url(url.clone()) - .body("foo") - .unwrap(); - let response = Response::from(response); - Ok(response) - } - } - - let client = Client::::new( - "foo1".into(), - "foo2".into(), - "https://oss-cn-shanghai.aliyuncs.com".parse().unwrap(), - "foo4".parse().unwrap(), - ) - .middleware(Arc::new(MyMiddleware {})); - - let res = client.get_bucket_list().await; - - //println!("{:?}", res); - assert_eq!( - format!("{:?}", res), - r#"Ok(ListBuckets { prefix: "", marker: "", max_keys: 0, is_truncated: false, next_marker: "", id: "", display_name: "", buckets: [] })"# - ); -} - -#[cfg(feature = "blocking")] -#[test] -fn test_get_blocking_bucket_list() { - use crate::blocking::builder::Middleware; - use crate::client::ClientRc; - use reqwest::blocking::{Request, Response}; - use std::rc::Rc; - - #[derive(Debug)] - struct MyMiddleware {} - - impl Middleware for MyMiddleware { - fn handle(&self, request: Request) -> Result { - //println!("request {:?}", request); - assert_eq!(request.method(), "GET"); - assert_eq!( - *request.url(), - "https://oss-cn-shanghai.aliyuncs.com/".parse().unwrap() - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some(&HeaderValue::from_str("/").unwrap()) - ); - use http::response::Builder; - let response = Builder::new() - .status(200) - //.url(url.clone()) - .body("foo") - .unwrap(); - let response = Response::from(response); - Ok(response) - } - } - - let client = ClientRc::new( - "foo1".into(), - "foo2".into(), - "https://oss-cn-shanghai.aliyuncs.com".parse().unwrap(), - "foo4".parse().unwrap(), - ) - .middleware(Rc::new(MyMiddleware {})); - - let res = client.get_bucket_list(); - - //println!("{:?}", res); - assert_eq!( - format!("{:?}", res), - r#"Ok(ListBuckets { prefix: "", marker: "", max_keys: 0, is_truncated: false, next_marker: "", id: "", display_name: "", buckets: [] })"# - ); -} - -#[tokio::test] -async fn test_get_bucket_info() { - // use crate::bucket::Bucket; - // use crate::types::{BucketName}; - // use crate::config::{BucketBase}; - #[derive(Debug)] - struct MyMiddleware {} - - #[async_trait] - impl Middleware for MyMiddleware { - async fn handle(&self, request: Request) -> Result { - //println!("request {:?}", request); - assert_eq!(request.method(), "GET"); - assert_eq!( - *request.url(), - "https://foo4.oss-cn-shanghai.aliyuncs.com/?bucketInfo" - .parse() - .unwrap() - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some(&HeaderValue::from_str("/foo4/?bucketInfo").unwrap()) - ); - use http::response::Builder; - let response = Builder::new() - .status(200) - //.url(url.clone()) - .body( - r#" - - - Disabled - - 2016-11-05T13:10:10.000Z - Disabled - LRS - oss-cn-shanghai.aliyuncs.com - oss-cn-shanghai-internal.aliyuncs.com - oss-cn-shanghai - barname - aaa - Standard - Disabled - - 22222 - 33333 - - - public-read - - - None - - - - - - - "#, - ) - .unwrap(); - let response = Response::from(response); - Ok(response) - } - } - - let client = Client::::new( - "foo1".into(), - "foo2".into(), - "https://oss-cn-shanghai.aliyuncs.com".parse().unwrap(), - "foo4".parse().unwrap(), - ) - .middleware(Arc::new(MyMiddleware {})); - - let res = client.get_bucket_info().await; - - //println!("{:?}", res); - assert_eq!( - format!("{:?}", res), - r#"Ok(Bucket { base: BucketBase { endpoint: EndPoint { kind: CnShanghai, is_internal: false }, name: BucketName("barname") }, creation_date: 2016-11-05T13:10:10Z, storage_class: StorageClass { kind: Standard } })"# - ); -} - -#[cfg(feature = "blocking")] -#[test] -fn test_get_blocking_bucket_info() { - use crate::blocking::builder::Middleware; - use crate::client::ClientRc; - use reqwest::blocking::{Request, Response}; - use std::rc::Rc; - - #[derive(Debug)] - struct MyMiddleware {} - - impl Middleware for MyMiddleware { - fn handle(&self, request: Request) -> Result { - //println!("request {:?}", request); - assert_eq!(request.method(), "GET"); - assert_eq!( - *request.url(), - "https://foo4.oss-cn-shanghai.aliyuncs.com/?bucketInfo" - .parse() - .unwrap() - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some(&HeaderValue::from_str("/foo4/?bucketInfo").unwrap()) - ); - use http::response::Builder; - let response = Builder::new() - .status(200) - //.url(url.clone()) - .body( - r#" - - - Disabled - - 2016-11-05T13:10:10.000Z - Disabled - LRS - oss-cn-shanghai.aliyuncs.com - oss-cn-shanghai-internal.aliyuncs.com - oss-cn-shanghai - barname - aaa - Standard - Disabled - - 22222 - 33333 - - - public-read - - - None - - - - - - - "#, - ) - .unwrap(); - let response = Response::from(response); - Ok(response) - } - } - - let client = ClientRc::new( - "foo1".into(), - "foo2".into(), - "https://oss-cn-shanghai.aliyuncs.com".parse().unwrap(), - "foo4".parse().unwrap(), - ) - .middleware(Rc::new(MyMiddleware {})); - - let res = client.get_bucket_info(); - - //println!("{:?}", res); - assert_eq!( - format!("{:?}", res), - r#"Ok(Bucket { base: BucketBase { endpoint: EndPoint { kind: CnShanghai, is_internal: false }, name: BucketName("barname") }, creation_date: 2016-11-05T13:10:10Z, storage_class: StorageClass { kind: Standard } })"# - ); -} - -#[tokio::test] -async fn test_get_object_list() { - #[derive(Debug)] - struct MyMiddleware {} - - #[async_trait] - impl Middleware for MyMiddleware { - async fn handle(&self, request: Request) -> Result { - //println!("request {:?}", request); - assert_eq!(request.method(), "GET"); - assert_eq!( - *request.url(), - "https://abc.oss-cn-shanghai.aliyuncs.com/?list-type=2&max-keys=5" - .parse() - .unwrap() - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some(&HeaderValue::from_str("/abc/").unwrap()) - ); - use http::response::Builder; - let response = Builder::new() - .status(200) - //.url(url.clone()) - .body( - r#" - - barname - - 100 - - false - - 9AB932LY.jpeg - 2022-06-26T09:53:21.000Z - "F75A15996D0857B16FA31A3B16624C26" - Normal - 18027 - Standard - - 23 - "#, - ) - .unwrap(); - let response = Response::from(response); - Ok(response) - } - } - - let client = ClientArc::new( - "foo1".into(), - "foo2".into(), - "https://oss-cn-shanghai.aliyuncs.com".parse().unwrap(), - "foo4".parse().unwrap(), - ) - .middleware(Arc::new(MyMiddleware {})); - - let bucket = bucket(client); - - let res = bucket - .get_object_list(vec![("max-keys".into(), "5".into())]) - .await; - - assert!(res.is_ok()); - let list = res.unwrap(); - assert_object_list::( - list, - EndPoint::CN_SHANGHAI, - "abc".parse().unwrap(), - None, - 100, - 23, - String::default(), - CommonPrefixes::from_iter([]), - Query::from_iter([(QueryKey::MAX_KEYS, 5u16)]), - ); -} - -#[tokio::test] -async fn test_error_get_object_list() { - #[derive(Debug)] - struct MyMiddleware {} - - #[async_trait] - impl Middleware for MyMiddleware { - async fn handle(&self, _request: Request) -> Result { - use http::response::Builder; - let response = Builder::new() - .status(200) - //.url(url.clone()) - .body( - r#" - - barname - - aaa - - false - - 9AB932LY.jpeg - 2022-06-26T09:53:21.000Z - "F75A15996D0857B16FA31A3B16624C26" - Normal - 18027 - Standard - - 23 - "#, - ) - .unwrap(); - let response = Response::from(response); - Ok(response) - } - } - - let client = ClientArc::new( - "foo1".into(), - "foo2".into(), - "https://oss-cn-shanghai.aliyuncs.com".parse().unwrap(), - "foo4".parse().unwrap(), - ) - .middleware(Arc::new(MyMiddleware {})); - - let bucket = bucket(client); - - let res = bucket - .get_object_list(vec![("max-keys".into(), "5".into())]) - .await; - - let err = res.unwrap_err(); - assert_eq!(format!("{err}"), "decode xml failed"); - assert_eq!( - format!("{}", err.source().unwrap()), - "parse max-keys failed, gived str: aaa" - ); -} - -fn bucket(client: Client) -> Bucket { - let naive = NaiveDateTime::parse_from_str("2022/10/6 20:40:00", "%Y/%m/%d %H:%M:%S").unwrap(); - let creation_date = DateTime::from_utc(naive, Utc); - - Bucket::::new( - "abc.oss-cn-shanghai.aliyuncs.com".parse().unwrap(), - creation_date, - StorageClass::ARCHIVE, - Arc::new(client), - ) -} - -#[cfg(feature = "blocking")] -#[test] -fn test_get_blocking_object_list() { - use crate::blocking::builder::Middleware; - use crate::builder::RcPointer; - use crate::client::ClientRc; - use crate::tests::object::assert_object_list; - use crate::types::object::CommonPrefixes; - use crate::{EndPoint, Query, QueryKey}; - use reqwest::blocking::{Request, Response}; - use std::rc::Rc; - - #[derive(Debug)] - struct MyMiddleware {} - - impl Middleware for MyMiddleware { - fn handle(&self, request: Request) -> Result { - //println!("request {:?}", request); - assert_eq!(request.method(), "GET"); - assert_eq!( - *request.url(), - "https://abc.oss-cn-shanghai.aliyuncs.com/?list-type=2&max-keys=5" - .parse() - .unwrap() - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some(&HeaderValue::from_str("/abc/").unwrap()) - ); - use http::response::Builder; - let response = Builder::new() - .status(200) - //.url(url.clone()) - .body( - r#" - - barname - - 100 - - false - - 9AB932LY.jpeg - 2022-06-26T09:53:21.000Z - "F75A15996D0857B16FA31A3B16624C26" - Normal - 18027 - Standard - - 23 - "#, - ) - .unwrap(); - let response = Response::from(response); - Ok(response) - } - } - - let client = ClientRc::new( - "foo1".into(), - "foo2".into(), - "https://oss-cn-shanghai.aliyuncs.com".parse().unwrap(), - "foo4".parse().unwrap(), - ) - .middleware(Rc::new(MyMiddleware {})); - - let naive = NaiveDateTime::parse_from_str("2022/10/6 20:40:00", "%Y/%m/%d %H:%M:%S").unwrap(); - let creation_date = DateTime::from_utc(naive, Utc); - - let bucket = Bucket::::new( - "abc.oss-cn-shanghai.aliyuncs.com".parse().unwrap(), - creation_date, - StorageClass::ARCHIVE, - Rc::new(client), - ); - - let res = bucket.get_object_list(vec![("max-keys".into(), "5".into())]); - - assert!(res.is_ok()); - let list = res.unwrap(); - assert_object_list::( - list, - EndPoint::CN_SHANGHAI, - "abc".parse().unwrap(), - None, - 100, - 23, - String::default(), - CommonPrefixes::from_iter([]), - Query::from_iter([(QueryKey::MAX_KEYS, 5u16)]), - ); -} - -#[test] -fn test_set_storage_class() { - let mut bucket = Bucket::::default(); - - assert!(bucket.set_storage_class("archive").is_ok()); - - let err = bucket.set_storage_class("eeeeee").unwrap_err(); - assert!(matches!( - err, - BucketError { - kind: BucketErrorKind::InvalidStorageClass, - .. - } - )); -} - -#[test] -fn test_refine_bucket() { - let mut list = ListBuckets::::default(); - list.set_prefix("foo1").unwrap(); - list.set_marker("foo2").unwrap(); - list.set_max_keys("10").unwrap(); - list.set_is_truncated(true).unwrap(); - list.set_next_marker("foo3").unwrap(); - list.set_id("foo4").unwrap(); - list.set_display_name("foo5").unwrap(); - - assert_eq!(list.prefix, "foo1"); - assert_eq!(list.marker, "foo2"); - assert_eq!(list.max_keys, 10); - assert_eq!(list.is_truncated, true); - assert_eq!(list.next_marker, "foo3"); - assert_eq!(list.id, "foo4"); - assert_eq!(list.display_name, "foo5"); -} - -#[cfg(feature = "blocking")] -#[test] -fn test_default_list_bucket() { - use crate::builder::RcPointer; - - use super::ListBuckets; - - let list = ListBuckets::::default(); - - assert!(list.buckets.len() == 0); -} - -mod test_extract_item_error { - use std::error::Error; - - use crate::{ - bucket::ExtractItemError, builder::BuilderError, decode::InnerItemError, - tests::reqwest_error, - }; - - #[test] - fn from_builder() { - let err = ExtractItemError::from(BuilderError::bar()); - - assert_eq!(format!("{err}"), "builder error"); - assert_eq!(format!("{}", err.source().unwrap()), "bar"); - assert_eq!( - format!("{:?}", err), - "ExtractItemError { kind: Builder(BuilderError { kind: Bar }) }" - ); - } - - #[tokio::test] - async fn from_reqwest() { - let err = ExtractItemError::from(reqwest_error().await); - - assert_eq!(format!("{}", err), "reqwest error"); - assert_eq!( - format!("{}", err.source().unwrap()), - "error decoding response body: expected value at line 1 column 1" - ); - } - - #[test] - fn from_decode() { - let err = ExtractItemError::from(InnerItemError::new()); - - assert_eq!(format!("{err}"), "decode xml failed"); - assert_eq!(format!("{}", err.source().unwrap()), "demo"); - assert_eq!( - format!("{:?}", err), - "ExtractItemError { kind: Decode(InnerItemError(MyError)) }" - ); - } -} - -mod test_bucket_error { - - use super::super::*; - #[test] - fn display() { - let mut bucket = Bucket::::default(); - let error = RefineBucket::::set_name(&mut bucket, "-abc").unwrap_err(); - assert_eq!( - error.to_string(), - "decode bucket xml faild, gived str: -abc" - ); - assert_eq!( - format!("{}", error.source().unwrap()), - "bucket name only allow `alphabet, digit, -`, and must not with `-` prefix or `-` suffix" - ); - - let error = RefineBucket::::set_location(&mut bucket, "oss").unwrap_err(); - assert_eq!(error.to_string(), "decode bucket xml faild, gived str: oss"); - assert_eq!( - format!("{}", error.source().unwrap()), - "endpoint must not with `-` prefix or `-` suffix or `oss-` prefix" - ); - - let error = RefineBucket::::set_creation_date(&mut bucket, "oss").unwrap_err(); - assert_eq!(error.to_string(), "decode bucket xml faild, gived str: oss"); - assert_eq!( - format!("{}", error.source().unwrap()), - "input contains invalid characters" - ); - - assert!(RefineBucket::::set_storage_class(&mut bucket, "").is_err()); - let error = RefineBucket::::set_storage_class(&mut bucket, "xxx").unwrap_err(); - assert_eq!(error.to_string(), "decode bucket xml faild, gived str: xxx"); - assert!(error.source().is_none()); - assert_eq!( - format!("{error:?}"), - "BucketError { source: \"xxx\", kind: InvalidStorageClass }" - ); - } -} - -mod test_bucket_list_error { - use super::super::*; - - fn assert_impl() {} - - #[test] - fn test_bucket_list_error() { - assert_impl::(); - - let err = i32::from_str_radix("a12", 10).unwrap_err(); - let err = BucketListError::ParseInt(err); - assert_eq!(format!("{err}"), "covert max_keys failed"); - assert_eq!( - format!("{}", err.source().unwrap()), - "invalid digit found in string" - ); - assert_eq!( - format!("{err:?}"), - "ParseInt(ParseIntError { kind: InvalidDigit })" - ); - } -} diff --git a/src/builder.rs b/src/builder.rs deleted file mode 100644 index b059447..0000000 --- a/src/builder.rs +++ /dev/null @@ -1,294 +0,0 @@ -//! 封装了 reqwest::RequestBuilder 模块 - -use async_trait::async_trait; -use http::Method; -use reqwest::{ - header::{HeaderMap, HeaderName, HeaderValue}, - Body, IntoUrl, -}; -#[cfg(feature = "blocking")] -use std::rc::Rc; -use std::{ - error::Error, - io::{self, ErrorKind}, -}; -use std::{fmt::Display, sync::Arc, time::Duration}; - -use crate::auth::AuthError; -#[cfg(feature = "blocking")] -use crate::blocking::builder::ClientWithMiddleware as BlockingClientWithMiddleware; -use crate::{ - client::Client as AliClient, - config::{BucketBase, InvalidConfig}, - errors::OssService, -}; -use reqwest::{Client, Request, Response}; - -#[cfg(test)] -pub(crate) mod test; - -pub trait PointerFamily: private::Sealed -where - Self::Bucket: std::fmt::Debug + Clone + Default + std::hash::Hash, - Self::PointerType: Default, -{ - type PointerType; - type Bucket; -} - -mod private { - pub trait Sealed {} -} - -#[derive(Default, Debug)] -pub struct ArcPointer; - -impl private::Sealed for ArcPointer {} - -impl PointerFamily for ArcPointer { - type PointerType = Arc>; - type Bucket = Arc; -} - -#[cfg(feature = "blocking")] -#[derive(Default, Debug)] -pub struct RcPointer; - -#[cfg(feature = "blocking")] -impl private::Sealed for RcPointer {} - -#[cfg(feature = "blocking")] -impl PointerFamily for RcPointer { - type PointerType = Rc>; - type Bucket = Rc; -} - -#[derive(Default, Clone, Debug)] -pub struct ClientWithMiddleware { - inner: Client, - middleware: Option>, -} - -#[async_trait] -pub trait Middleware: 'static + Send + Sync + std::fmt::Debug { - async fn handle(&self, request: Request) -> Result; -} - -impl ClientWithMiddleware { - pub fn new(inner: Client) -> Self { - Self { - inner, - middleware: None, - } - } - - pub fn request(&self, method: Method, url: U) -> RequestBuilder { - RequestBuilder { - inner: self.inner.request(method, url), - middleware: self.middleware.clone(), - } - } - - pub fn middleware(&mut self, middleware: Arc) { - self.middleware = Some(middleware); - } -} - -pub struct RequestBuilder { - inner: reqwest::RequestBuilder, - middleware: Option>, -} - -impl RequestBuilder { - #[allow(dead_code)] - pub(crate) fn header(self, key: K, value: V) -> Self - where - HeaderName: TryFrom, - >::Error: Into, - HeaderValue: TryFrom, - >::Error: Into, - { - RequestBuilder { - inner: self.inner.header(key, value), - ..self - } - } - - pub(crate) fn headers(self, headers: HeaderMap) -> Self { - RequestBuilder { - inner: self.inner.headers(headers), - ..self - } - } - - pub(crate) fn body>(self, body: T) -> Self { - RequestBuilder { - inner: self.inner.body(body), - ..self - } - } - - pub(crate) fn timeout(self, timeout: Duration) -> Self { - RequestBuilder { - inner: self.inner.timeout(timeout), - ..self - } - } - - #[allow(dead_code)] - pub(crate) fn build(self) -> reqwest::Result { - self.inner.build() - } - - /// 发送请求,获取响应后,直接返回 Response - pub async fn send(self) -> Result { - match self.middleware { - Some(m) => { - m.handle(self.inner.build().map_err(BuilderError::from)?) - .await - } - None => self.inner.send().await.map_err(BuilderError::from), - } - } - - /// 发送请求,获取响应后,解析 xml 文件,如果有错误,返回 Err 否则返回 Response - pub async fn send_adjust_error(self) -> Result { - match self.middleware { - Some(m) => { - m.handle(self.inner.build().map_err(BuilderError::from)?) - .await - } - None => check_http_status(self.inner.send().await.map_err(BuilderError::from)?) - .await - .map_err(BuilderError::from), - } - } -} - -#[derive(Debug)] -#[non_exhaustive] -pub struct BuilderError { - pub(crate) kind: BuilderErrorKind, -} - -impl BuilderError { - #[cfg(test)] - pub(crate) fn bar() -> Self { - Self { - kind: BuilderErrorKind::Bar, - } - } - - pub(crate) fn from_reqwest(reqwest: reqwest::Error) -> Self { - Self { - kind: BuilderErrorKind::Reqwest(Box::new(reqwest)), - } - } -} - -#[derive(Debug)] -#[non_exhaustive] -pub(crate) enum BuilderErrorKind { - Reqwest(Box), - - OssService(Box), - - Auth(Box), - - Config(Box), - - #[cfg(test)] - Bar, -} - -impl Display for BuilderError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use BuilderErrorKind::*; - match &self.kind { - Reqwest(_) => "reqwest error".fmt(f), - OssService(_) => "http status is not success".fmt(f), - Auth(_) => "aliyun auth failed".fmt(f), - Config(_) => "oss config error".fmt(f), - #[cfg(test)] - Bar => "bar".fmt(f), - } - } -} - -impl Error for BuilderError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - use BuilderErrorKind::*; - match &self.kind { - Reqwest(e) => Some(e), - OssService(e) => Some(e), - Auth(e) => Some(e), - Config(e) => Some(e), - #[cfg(test)] - Bar => None, - } - } -} - -impl From for BuilderError { - fn from(value: reqwest::Error) -> Self { - Self { - kind: BuilderErrorKind::Reqwest(Box::new(value)), - } - } -} -impl From for BuilderError { - fn from(value: OssService) -> Self { - Self { - kind: BuilderErrorKind::OssService(Box::new(value)), - } - } -} - -impl From for BuilderError { - fn from(value: AuthError) -> Self { - Self { - kind: BuilderErrorKind::Auth(Box::new(value)), - } - } -} -impl From for BuilderError { - fn from(value: InvalidConfig) -> Self { - Self { - kind: BuilderErrorKind::Config(Box::new(value)), - } - } -} - -impl From for io::Error { - fn from(BuilderError { kind }: BuilderError) -> Self { - match kind { - BuilderErrorKind::Reqwest(req) => reqwest_to_io(*req), - BuilderErrorKind::OssService(e) => Self::from(*e), - BuilderErrorKind::Auth(auth) => Self::new(ErrorKind::PermissionDenied, auth), - BuilderErrorKind::Config(conf) => Self::new(ErrorKind::InvalidInput, conf), - #[cfg(test)] - BuilderErrorKind::Bar => unreachable!("only used in tests"), - } - } -} - -pub(crate) fn reqwest_to_io(req: reqwest::Error) -> io::Error { - let kind = if req.is_timeout() { - ErrorKind::TimedOut - } else if req.is_connect() { - ErrorKind::ConnectionAborted - } else { - ErrorKind::Other - }; - io::Error::new(kind, req) -} - -pub(crate) async fn check_http_status(response: Response) -> Result { - if response.status().is_success() { - return Ok(response); - } - let url = response.url().clone(); - let status = response.status(); - let text = response.text().await?; - Err(OssService::new2(text, &status, url).into()) -} diff --git a/src/builder/test.rs b/src/builder/test.rs deleted file mode 100644 index ceaec37..0000000 --- a/src/builder/test.rs +++ /dev/null @@ -1,108 +0,0 @@ -use super::ArcPointer; - -#[test] -fn debug_arc_pointer() { - let pointer = ArcPointer; - - assert_eq!(format!("{pointer:?}"), "ArcPointer"); -} - -#[test] -#[cfg(feature = "blocking")] -fn debug_rc_pointer() { - use super::RcPointer; - let pointer = RcPointer; - - assert_eq!(format!("{pointer:?}"), "RcPointer"); -} - -mod error { - use std::error::Error; - - use crate::{ - builder::{BuilderError, BuilderErrorKind}, - config::get_endpoint, - errors::OssService, - tests::reqwest_error, - }; - - #[tokio::test] - async fn from_reqwest() { - let builder_err = BuilderError::from(reqwest_error().await); - assert_eq!(format!("{builder_err}"), "reqwest error"); - assert_eq!( - format!("{}", builder_err.source().unwrap()), - "error decoding response body: expected value at line 1 column 1" - ); - assert_eq!(format!("{:?}", builder_err), "BuilderError { kind: Reqwest(reqwest::Error { kind: Decode, source: Error(\"expected value\", line: 1, column: 1) }) }"); - } - - #[test] - fn from_oss() { - let err = BuilderError { - kind: BuilderErrorKind::OssService(Box::new(OssService::default())), - }; - assert_eq!(format!("{err}"), "http status is not success"); - assert_eq!(format!("{}", err.source().unwrap()), "OssService { code: \"Undefined\", status: 200, message: \"Parse aliyun response xml error message failed.\", request_id: \"XXXXXXXXXXXXXXXXXXXXXXXX\", url: \"https://oss.aliyuncs.com/\" }"); - - fn bar() -> BuilderError { - OssService::default().into() - } - - assert_eq!(format!("{:?}", bar()), "BuilderError { kind: OssService(OssService { code: \"Undefined\", status: 200, message: \"Parse aliyun response xml error message failed.\", request_id: \"XXXXXXXXXXXXXXXXXXXXXXXX\", url: Url { scheme: \"https\", cannot_be_a_base: false, username: \"\", password: None, host: Some(Domain(\"oss.aliyuncs.com\")), port: None, path: \"/\", query: None, fragment: None } }) }") - } - - #[test] - fn from_auth() { - use std::error::Error; - - use crate::{ - auth::{AuthError, AuthErrorKind}, - builder::BuilderErrorKind, - }; - - let err = BuilderError { - kind: BuilderErrorKind::Auth(Box::new(AuthError { - kind: AuthErrorKind::InvalidCanonicalizedResource, - })), - }; - assert_eq!(format!("{err}"), "aliyun auth failed"); - assert_eq!( - format!("{}", err.source().unwrap()), - "invalid canonicalized-resource" - ); - - fn bar() -> BuilderError { - AuthError { - kind: AuthErrorKind::InvalidCanonicalizedResource, - } - .into() - } - assert_eq!( - format!("{:?}", bar()), - "BuilderError { kind: Auth(AuthError { kind: InvalidCanonicalizedResource }) }" - ); - } - - #[test] - fn from_config() { - let err = get_endpoint("oss").unwrap_err(); - let err = BuilderError { - kind: BuilderErrorKind::Config(Box::new(err)), - }; - assert_eq!(format!("{err}"), "oss config error"); - assert_eq!( - format!("{}", err.source().unwrap()), - "get config failed, source: oss" - ); - - fn bar() -> BuilderError { - let err = get_endpoint("oss").unwrap_err(); - err.into() - } - assert_eq!( - format!("{:?}", bar()), - "BuilderError { kind: Config(InvalidConfig { source: \"oss\", kind: EndPoint(InvalidEndPoint) }) }" - ); - } -} diff --git a/src/client.rs b/src/client.rs index adfa021..718a9b4 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,470 +1,280 @@ -//! # 对 reqwest 进行了简单的封装,加上了 OSS 的签名验证功能 - -use crate::auth::AuthBuilder; -#[cfg(feature = "blocking")] -use crate::blocking::builder::ClientWithMiddleware as BlockingClientWithMiddleware; -#[cfg(test)] -use crate::builder::Middleware; -use crate::builder::{ArcPointer, BuilderError, ClientWithMiddleware, RequestBuilder}; -use crate::config::{get_bucket, get_endpoint, get_env, BucketBase, Config, InvalidConfig}; -use crate::consts::{TRUE1, TRUE2, TRUE3, TRUE4}; -use crate::file::AlignBuilder; -use crate::types::{ - object::{InvalidObjectPath, ObjectBase, ObjectPath}, - BucketName, CanonicalizedResource, EndPoint, KeyId, KeySecret, +use chrono::Utc; +use reqwest::{ + header::{HeaderMap, CONTENT_TYPE}, + Method, }; +use serde::{de::DeserializeOwned, Deserialize}; +use serde_xml_rs::from_str; -use chrono::{DateTime, Utc}; -use http::{ - header::{HeaderMap, HeaderName}, - HeaderValue, Method, +use crate::{ + bucket::Bucket, + error::OssError, + types::{CanonicalizedResource, EndPoint, Key, Secret}, }; -use reqwest::Url; -use std::env; -#[cfg(all(feature = "blocking", test))] -use std::rc::Rc; -#[cfg(test)] -use std::sync::Arc; -use std::time::Duration; - -/// # 构造请求的客户端结构体 -#[non_exhaustive] -#[derive(Default, Debug, Clone, PartialEq, Eq)] -pub struct Client { - auth_builder: AuthBuilder, - client_middleware: M, - pub(crate) endpoint: EndPoint, - pub(crate) bucket: BucketName, - timeout: Option, -} -impl AsMut> for Client { - fn as_mut(&mut self) -> &mut Option { - &mut self.timeout - } +/// 存放 key, secret 以及默认 bucket 信息,几乎每个 api 都会用到它的引用 +pub struct Client { + key: Key, + secret: Secret, + bucket: Option, } -impl AsRef for Client { - fn as_ref(&self) -> &EndPoint { - &self.endpoint - } -} -impl AsRef for Client { - fn as_ref(&self) -> &BucketName { - &self.bucket - } -} -impl AsMut for Client { - fn as_mut(&mut self) -> &mut BucketName { - &mut self.bucket +impl Client { + pub fn new(key: Key, secret: Secret) -> Client { + Self { + key, + secret, + bucket: None, + } } -} -impl Client { - /// 使用基本配置信息初始化 Client - pub fn new( - access_key_id: KeyId, - access_key_secret: KeySecret, - endpoint: EndPoint, - bucket: BucketName, - ) -> Self { - let mut auth_builder = AuthBuilder::default(); - auth_builder.key(access_key_id); - auth_builder.secret(access_key_secret); - - Self::from_builder(auth_builder, endpoint, bucket) + /// 设置默认的 bucket(bucket 也会包含 endpoint 信息) + /// 当设置的时候,会返回上次设置的值,默认值为 None + /// ``` + /// # use aliyun_oss_client::{Client, Key, Secret, EndPoint, Bucket}; + /// # let mut client = Client::new(Key::new("foo"), Secret::new("bar")); + /// # let bucket = Bucket::new("bucket1", EndPoint::CN_QINGDAO); + /// # let bucket2 = Bucket::new("bucket2", EndPoint::CN_QINGDAO); + /// assert!(client.bucket().is_none()); + /// let res = client.set_bucket(bucket.clone()); + /// assert!(res.is_none()); + /// assert_eq!(client.bucket(), Some(&bucket)); + /// + /// let res2 = client.set_bucket(bucket2.clone()); + /// assert_eq!(res2, Some(bucket)); + /// assert_eq!(client.bucket(), Some(&bucket2)); + /// ``` + pub fn set_bucket(&mut self, bucket: Bucket) -> Option { + self.bucket.replace(bucket) } - /// - bucket: bar - /// - endpoint: qingdao - #[cfg(test)] - pub fn test_init() -> Self { - Self::new( - "foo1".into(), - "foo2".into(), - EndPoint::CN_QINGDAO, - "bar".try_into().unwrap(), - ) + /// 返回当前设置的 bucket 信息 + pub fn bucket(&self) -> Option<&Bucket> { + self.bucket.as_ref() } - /// 使用 [`Config`] 中的配置初始化 Client - /// - /// [`Config`]: crate::config::Config - pub fn from_config(config: Config) -> Self { - let (key, secret, bucket, endpoint) = config.get_all(); + pub(crate) fn authorization( + &self, + method: Method, + resource: CanonicalizedResource, + ) -> Result { + const LINE_BREAK: &str = "\n"; + + let date = now(); + let content_type = "text/xml"; + + let sign = { + let mut string = method.as_str().to_owned(); + string += LINE_BREAK; + string += LINE_BREAK; + string += content_type; + string += LINE_BREAK; + string += date.as_str(); + string += LINE_BREAK; + string += resource.as_str(); - let mut auth_builder = AuthBuilder::default(); - auth_builder.key(key); - auth_builder.secret(secret); + let encry = self.secret.encryption(string.as_bytes()).unwrap(); + + format!("OSS {}:{}", self.key.as_str(), encry) + }; + let header_map = { + let mut headers = HeaderMap::new(); + headers.insert("AccessKeyId", self.key.as_str().try_into()?); + headers.insert("VERB", method.as_str().try_into()?); + headers.insert("Date", date.try_into()?); + headers.insert("Authorization", sign.try_into()?); + headers.insert(CONTENT_TYPE, content_type.try_into()?); + headers.insert( + "CanonicalizedResource", + resource.as_str().try_into().unwrap(), + ); + headers + }; - Self::from_builder(auth_builder, endpoint, bucket) + Ok(header_map) } - /// # 通过环境变量初始化 Client + /// 调用 api 导出 bucket 列表信息到自定义类型 /// - /// 如果在 Aliyun ECS 上,可将环境变量 `ALIYUN_OSS_INTERNAL` - /// 设置为 `true` / `1` / `yes` / `Y` ,即可使用 internal 网络请求 OSS 接口 - /// - /// 示例 + /// aliyun api 返回的 xml 是如下格式: + /// ```xml + /// + /// + /// 2020-09-13T03:14:54.000Z + /// oss-cn-shanghai.aliyuncs.com + /// oss-cn-shanghai-internal.aliyuncs.com + /// oss-cn-shanghai + /// aliyun-wb-kpbf3 + /// cn-shanghai + /// Standard + /// + /// ``` + /// 该方法返回的类型可以是如下结构体: /// ```rust - /// use std::env::set_var; - /// set_var("ALIYUN_KEY_ID", "foo1"); - /// set_var("ALIYUN_KEY_SECRET", "foo2"); - /// set_var("ALIYUN_ENDPOINT", "qingdao"); - /// set_var("ALIYUN_BUCKET", "foo4"); - /// - /// # use aliyun_oss_client::client::Client; - /// use aliyun_oss_client::builder::ClientWithMiddleware; - /// let client = Client::::from_env(); - /// assert!(client.is_ok()); + /// use serde::Deserialize; + /// #[derive(Debug, Deserialize)] + /// struct MyBucket { + /// Comment: String, + /// CreationDate: String, + /// ExtranetEndpoint: String, + /// IntranetEndpoint: String, + /// Location: String, + /// Name: String, + /// Region: String, + /// StorageClass: String, + /// } + /// // 或者 + /// #[derive(Debug, Deserialize)] + /// struct MyBucket2 { + /// Location: String, + /// Name: String, + /// } /// ``` - pub fn from_env() -> Result { - let key_id = get_env("ALIYUN_KEY_ID")?; - let key_secret = get_env("ALIYUN_KEY_SECRET")?; - let endpoint = get_env("ALIYUN_ENDPOINT")?; - let bucket = get_env("ALIYUN_BUCKET")?; - - let mut auth_builder = AuthBuilder::default(); - auth_builder.key(key_id); - auth_builder.secret(key_secret); - - let mut endpoint = get_endpoint(&endpoint)?; - - if let Ok(is_internal) = env::var("ALIYUN_OSS_INTERNAL") { - if is_internal == TRUE1 - || is_internal == TRUE2 - || is_internal == TRUE3 - || is_internal == TRUE4 - { - endpoint.set_internal(true); - } + pub async fn export_buckets( + &self, + endpoint: &EndPoint, + ) -> Result, OssError> { + let url = endpoint.to_url(); + let method = Method::GET; + let resource = CanonicalizedResource::default(); + + let header_map = self.authorization(method, resource)?; + + let content = reqwest::Client::new() + .get(url) + .headers(header_map) + .send() + .await? + .text() + .await?; + + //println!("{}", content); + + #[derive(Debug, Deserialize)] + struct ListAllMyBucketsResult { + #[serde(rename = "Buckets")] + buckets: Buckets, } - Ok(Self::from_builder( - auth_builder, - endpoint, - get_bucket(&bucket)?, - )) - } - - #[doc(hidden)] - #[inline] - pub fn from_builder(auth_builder: AuthBuilder, endpoint: EndPoint, bucket: BucketName) -> Self { - Self { - auth_builder, - client_middleware: M::default(), - endpoint, - bucket, - timeout: None, + #[derive(Debug, Deserialize)] + struct Buckets { + #[serde(rename = "Bucket")] + bucket: Vec, } - } -} -impl Client { - pub(crate) fn get_bucket_name(&self) -> &BucketName { - &self.bucket - } - /// 返回默认可用区,默认 bucket 的 BucketBase - pub fn get_bucket_base(&self) -> BucketBase { - BucketBase::new(self.bucket.to_owned(), self.endpoint.to_owned()) - } - - /// 获取默认的 bucket 的 url - pub fn get_bucket_url(&self) -> Url { - self.get_bucket_base().to_url() - } + let xml_res: ListAllMyBucketsResult = from_str(&content)?; - pub(crate) fn get_key(&self) -> &KeyId { - self.auth_builder.get_key() + Ok(xml_res.buckets.bucket) } - pub(crate) fn get_secret(&self) -> &KeySecret { - self.auth_builder.get_secret() - } - - // pub(crate) fn get_endpoint(&self) -> &EndPoint { - // &self.endpoint - // } - /// 获取默认的可用区的 url - pub fn get_endpoint_url(&self) -> Url { - self.endpoint.to_url() - } + pub async fn get_buckets(&self, endpoint: &EndPoint) -> Result, OssError> { + let url = endpoint.to_url(); + let method = Method::GET; + let resource = CanonicalizedResource::default(); - /// 更改默认 bucket - pub fn set_bucket(&mut self, bucket: BucketName) { - self.bucket = bucket; - } + let header_map = self.authorization(method, resource)?; - /// 更改默认 endpoint - pub fn set_endpoint(&mut self, endpoint: EndPoint) { - self.endpoint = endpoint; - } + let content = reqwest::Client::new() + .get(url) + .headers(header_map) + .send() + .await? + .text() + .await?; - /// 设置 timeout - pub fn timeout(&mut self, timeout: Duration) { - self.timeout = Some(timeout); - } + // println!("{content}"); - /// 根据默认的 bucket,endpoint 和提供的文件路径,获取 ObjectBase - #[inline] - pub fn get_object_base

(&self, path: P) -> Result - where - P: TryInto, - P::Error: Into, - { - ObjectBase::::from_bucket(self.get_bucket_base(), path) + Self::parse_xml(content, endpoint) } -} - -#[cfg(not(test))] -#[inline] -fn now() -> DateTime { - Utc::now() -} -#[cfg(test)] -fn now() -> DateTime { - use chrono::NaiveDateTime; - let naive = NaiveDateTime::parse_from_str("2022/10/6 20:40:00", "%Y/%m/%d %H:%M:%S").unwrap(); - DateTime::from_utc(naive, Utc) -} + fn parse_xml(xml: String, endpoint: &EndPoint) -> Result, OssError> { + let mut start_positions = vec![]; + let mut end_positions = vec![]; + let mut start = 0; + let mut pattern = ""; + let pattern_len = pattern.len(); -/// 异步 Client 别名 -pub type ClientArc = Client; + while let Some(pos) = xml[start..].find(pattern) { + start_positions.push(start + pos); + start += pos + pattern.len(); + } + start = 0; + pattern = ""; + while let Some(pos) = xml[start..].find(pattern) { + end_positions.push(start + pos); + start += pos + pattern.len(); + } -impl Client { - /// # 用于模拟请求 OSS 接口 - /// 默认直接请求 OSS 接口,如果设置中间件,则可以中断请求,对 Request 做一些断言,对 Response 做一些模拟操作 - #[cfg(test)] - pub(crate) fn middleware(mut self, middleware: Arc) -> Self { - self.client_middleware.middleware(middleware); - self - } -} + debug_assert!(start_positions.len() == end_positions.len()); -impl AlignBuilder for Client { - /// # 构造自定义的接口请求方法 - /// 比如在上传完文件时,返回自己期望的数据,而不是仅返回 etag 信息 - /// - /// ## 例子是一个获取 object 元信息的接口 - /// ``` - /// use aliyun_oss_client::{errors::OssError, file::AlignBuilder, Client, Method}; - /// use dotenv::dotenv; - /// - /// async fn run() -> Result<(), OssError> { - /// dotenv().ok(); - /// let client = Client::from_env().unwrap(); - /// - /// let (url, resource) = client - /// .get_object_base("9AB932LY.jpeg")? - /// .get_url_resource([]); - /// - /// let headers = vec![( - /// "If-Unmodified-Since".parse().unwrap(), - /// "Sat, 01 Jan 2022 18:01:01 GMT".parse().unwrap(), - /// )]; - /// - /// let builder = client.builder_with_header(Method::HEAD, url, resource, headers)?; - /// - /// let response = builder.send().await?; - /// - /// println!("status: {:?}", response.status()); - /// - /// Ok(()) - /// } - /// ``` - /// ## 参数 - /// - method 接口请求方式 - /// - url 要请求的接口,包含 query 参数等信息 - /// - resource 阿里云接口需要提供的统一的信息,[`CanonicalizedResource`] 提供了 bucket ,object 等各种生成方式,如果无法满足 - /// 还可以自己用 trait 来自定义 - /// - /// ## 返回值 - /// 返回值是一个封装了 reqwest::Builder 构造器,[`RequestBuilder`], 提供两个方法 `send` 和 `send_adjust_error` - /// - /// - `send` 方法,直接返回 `reqwest::Response` - /// - `send_adjust_error` 方法,会对 api 返回结果进行处理,如果 HTTP 状态码正常(200>= && <300) 则,返回 Ok, - /// 否则,会对返回的 xml 异常数据进行解析,返回 Err([`OssService`]) - /// - /// [`RequestBuilder`]: crate::builder::RequestBuilder - /// [`OssService`]: crate::errors::OssService - /// [`CanonicalizedResource`]: crate::types::CanonicalizedResource - fn builder_with_header>( - &self, - method: Method, - url: Url, - resource: CanonicalizedResource, - headers: H, - ) -> Result { - let mut auth_builder = self.auth_builder.clone(); - auth_builder.method(&method); - auth_builder.date(now()); - auth_builder.canonicalized_resource(resource); - auth_builder.extend_headers(HeaderMap::from_iter(headers)); - - let mut builder = self - .client_middleware - .request(method, url) - .headers(auth_builder.get_headers()?); - - if let Some(timeout) = self.timeout { - builder = builder.timeout(timeout); - }; + let mut bucket = vec![]; + for i in 0..start_positions.len() { + let name = &xml[start_positions[i] + pattern_len..end_positions[i]]; + bucket.push(Bucket::new(name.to_owned(), endpoint.clone())) + } - Ok(builder) + Ok(bucket) } } -#[cfg(all(feature = "blocking", test))] -use crate::blocking::builder::Middleware as BlockingMiddleware; -#[cfg(feature = "blocking")] -use crate::blocking::builder::RequestBuilder as BlockingRequestBuilder; - -/// 同步的 Client 别名 -#[cfg(feature = "blocking")] -pub type ClientRc = Client; - -#[cfg(feature = "blocking")] -impl Client { - /// # 用于模拟请求 OSS 接口 - /// 默认直接请求 OSS 接口,如果设置中间件,则可以中断请求,对 Request 做一些断言,对 Response 做一些模拟操作 - #[cfg(test)] - pub(crate) fn middleware(mut self, middleware: Rc) -> Self { - self.client_middleware.middleware(middleware); - self - } +fn now() -> String { + Utc::now().format("%a, %d %b %Y %T GMT").to_string() } -#[cfg(feature = "blocking")] -impl crate::file::blocking::AlignBuilder for Client { - /// # 向 OSS 发送请求的封装 - /// 参数包含请求的: - /// - /// - method - /// - url - /// - headers (可选) - /// - [CanonicalizedResource](https://help.aliyun.com/document_detail/31951.html#section-rvv-dx2-xdb) - /// - /// 返回值是一个 reqwest 的请求创建器 `reqwest::blocking::RequestBuilder` - /// - /// 返回后,可以再加请求参数,然后可选的进行发起请求 - #[inline] - fn builder_with_header>( - &self, - method: Method, - url: Url, - resource: CanonicalizedResource, - headers: H, - ) -> Result { - let method = method; - let mut auth_builder = self.auth_builder.clone(); - auth_builder.method(&method); - auth_builder.date(now()); - auth_builder.canonicalized_resource(resource); - auth_builder.extend_headers(HeaderMap::from_iter(headers)); - - let mut builder = self - .client_middleware - .request(method, url) - .headers(auth_builder.get_headers()?); - - if let Some(timeout) = self.timeout { - builder = builder.timeout(timeout); - }; - - Ok(builder) - } -} +// fn now() -> DateTime { +// use chrono::NaiveDateTime; +// let naive = NaiveDateTime::parse_from_str("2022/10/6 20:40:00", "%Y/%m/%d %H:%M:%S").unwrap(); +// DateTime::from_utc(naive, Utc) +// } #[cfg(test)] mod tests { - use http::Method; + use crate::{client::init_client, types::EndPoint}; - use super::*; - use crate::{ - builder::ArcPointer, - config::{BucketBase, Config}, - file::AlignBuilder, - types::object::ObjectBase, - BucketName, - }; + #[tokio::test] + async fn test_get_buckets() { + let list = init_client() + .get_buckets(&EndPoint::CN_QINGDAO) + .await + .unwrap(); - use std::time::Duration; - - #[test] - fn from_config() { - let config = Config::try_new("foo1", "foo2", "qingdao", "foo4").unwrap(); - let client = ClientArc::from_config(config); - - assert_eq!(client.bucket, "foo4".parse::().unwrap()); + assert_eq!(list.len(), 2); } - #[test] - fn timeout() { - let config = Config::try_new("foo1", "foo2", "qingdao", "foo4").unwrap(); - let mut client = ClientArc::from_config(config); - - assert!(client.timeout.is_none()); - - client.timeout(Duration::new(10, 0)); - - assert!(client.timeout.is_some()); - - assert_eq!(client.timeout, Some(Duration::new(10, 0))); - } + #[tokio::test] + async fn parse_xml() { + use serde::Deserialize; + + #[derive(Debug, Deserialize)] + struct MyBucket { + Comment: String, + CreationDate: String, + ExtranetEndpoint: String, + IntranetEndpoint: String, + Location: String, + Name: String, + Region: String, + StorageClass: String, + } - #[test] - fn test_timeout_with_builder() { - let mut client = ClientArc::test_init(); - client.timeout(Duration::new(11, 0)); - let (url, resource) = client - .get_object_base("9AB932LY.jpeg") - .unwrap() - .get_url_resource([]); - let builder = client.builder_with_header(Method::HEAD, url, resource, []); - let builder = builder.unwrap(); - - let request = builder.build().unwrap(); - let timeout = request.timeout().unwrap().to_owned(); - assert_eq!(timeout, Duration::new(11, 0)); - } + let list: Vec = init_client() + .export_buckets(&EndPoint::CN_QINGDAO) + .await + .unwrap(); - #[cfg(feature = "blocking")] - #[test] - fn test_timeout_with_builder_blocking() { - use crate::file::blocking::AlignBuilder; - - let mut client = ClientRc::test_init(); - client.timeout(Duration::new(11, 0)); - let (url, resource) = client - .get_object_base("9AB932LY.jpeg") - .unwrap() - .get_url_resource([]); - let builder = client.builder_with_header(Method::HEAD, url, resource, []); - let builder = builder.unwrap(); - - let request = builder.build().unwrap(); - let timeout = request.timeout().unwrap().to_owned(); - assert_eq!(timeout, Duration::new(11, 0)); + println!("{list:?}"); } +} - #[test] - fn get_object_base() { - use std::sync::Arc; +#[cfg(test)] +pub fn init_client() -> Client { + use std::env; - let config = Config::try_new("foo1", "foo2", "qingdao", "foo4").unwrap(); - let client = ClientArc::from_config(config); + use dotenv::dotenv; - let base = client.get_object_base("file111").unwrap(); + dotenv().ok(); + let key = env::var("ALIYUN_KEY_ID").unwrap(); + let secret = env::var("ALIYUN_KEY_SECRET").unwrap(); - let base2 = ObjectBase::::new( - Arc::new(BucketBase::new( - "foo4".parse().unwrap(), - "qingdao".parse().unwrap(), - )), - "file111", - ) - .unwrap(); - assert!(base == base2); - } + Client::new(Key::new(key), Secret::new(secret)) } diff --git a/src/config.rs b/src/config.rs deleted file mode 100644 index 92b5dd7..0000000 --- a/src/config.rs +++ /dev/null @@ -1,855 +0,0 @@ -//! 配置类型 - -use crate::{ - client::Client, - consts::{TRUE1, TRUE2, TRUE3, TRUE4}, - types::{ - core::SetOssQuery, - object::{ObjectPathInner, SetObjectPath}, - url_from_bucket, BucketName, CanonicalizedResource, EndPoint, InvalidBucketName, - InvalidEndPoint, KeyId, KeySecret, - }, - Query, -}; -use reqwest::Url; -use std::{ - env::{self, VarError}, - error::Error, - fmt::Display, - str::FromStr, -}; -use thiserror::Error; - -const HTTPS: &str = "https://"; -const OSS_HYPHEN: &str = "oss-"; - -/// OSS 配置信息 -#[derive(Clone, Debug, PartialEq, Eq, Default, Hash)] -pub struct Config { - pub(crate) key: KeyId, - pub(crate) secret: KeySecret, - pub(crate) endpoint: EndPoint, - pub(crate) bucket: BucketName, -} - -impl AsRef for Config { - fn as_ref(&self) -> &KeyId { - &self.key - } -} - -impl AsRef for Config { - fn as_ref(&self) -> &KeySecret { - &self.secret - } -} - -impl AsRef for Config { - fn as_ref(&self) -> &EndPoint { - &self.endpoint - } -} - -impl AsRef for Config { - fn as_ref(&self) -> &BucketName { - &self.bucket - } -} - -impl Config { - /// 初始化 OSS 配置信息 - pub fn new(key: ID, secret: S, endpoint: E, bucket: B) -> Config - where - ID: Into, - S: Into, - E: Into, - B: Into, - { - Config { - key: key.into(), - secret: secret.into(), - endpoint: endpoint.into(), - bucket: bucket.into(), - } - } - - /// use env init Config - pub fn from_env() -> Result { - let key_id = get_env("ALIYUN_KEY_ID")?; - let key_secret = get_env("ALIYUN_KEY_SECRET")?; - - let endpoint = EndPoint::from_env().map_err(|e| InvalidConfig { - source: String::default(), - kind: InvalidConfigKind::EndPoint(e), - })?; - Ok(Config { - key: key_id.into(), - secret: key_secret.into(), - endpoint, - bucket: BucketName::from_env().map_err(|e| InvalidConfig { - source: String::default(), - kind: InvalidConfigKind::BucketName(e), - })?, - }) - } - - /// 初始化 OSS 配置信息 - /// - /// [未稳定] 暂不公开 - /// - /// 支持更宽泛的输入类型 - #[cfg(test)] - pub(crate) fn try_new( - key: ID, - secret: S, - endpoint: E, - bucket: B, - ) -> Result - where - ID: Into, - S: Into, - E: TryInto + Display + Clone, - E::Error: Into, - B: TryInto + Display + Clone, - B::Error: Into, - { - Ok(Config { - key: key.into(), - secret: secret.into(), - endpoint: endpoint.clone().try_into().map_err(|e| InvalidConfig { - source: endpoint.to_string(), - kind: InvalidConfigKind::EndPoint(e.into()), - })?, - bucket: bucket.clone().try_into().map_err(|e| InvalidConfig { - source: bucket.to_string(), - kind: InvalidConfigKind::BucketName(e.into()), - })?, - }) - } - - pub(crate) fn get_all(self) -> (KeyId, KeySecret, BucketName, EndPoint) { - (self.key, self.secret, self.bucket, self.endpoint) - } - - #[allow(dead_code)] - pub(crate) fn get_all_ref(&self) -> (&KeyId, &KeySecret, &BucketName, &EndPoint) { - (&self.key, &self.secret, &self.bucket, &self.endpoint) - } -} - -pub(crate) fn get_env(name: &str) -> Result { - env::var(name).map_err(|e| InvalidConfig { - source: name.to_owned(), - kind: InvalidConfigKind::VarError(e), - }) -} - -pub(crate) fn get_endpoint(name: &str) -> Result { - EndPoint::try_from(name).map_err(|e| InvalidConfig { - source: name.to_string(), - kind: InvalidConfigKind::EndPoint(e), - }) -} - -pub(crate) fn get_bucket(name: &str) -> Result { - BucketName::try_from(name).map_err(|e| InvalidConfig { - source: name.to_string(), - kind: InvalidConfigKind::BucketName(e), - }) -} - -/// Config 错误信息集合 -#[derive(Debug, PartialEq, Clone)] -#[non_exhaustive] -pub struct InvalidConfig { - source: String, - kind: InvalidConfigKind, -} - -impl InvalidConfig { - #[cfg(test)] - pub(crate) fn test_bucket() -> Self { - Self { - source: "bar".into(), - kind: InvalidConfigKind::BucketName(InvalidBucketName::new()), - } - } - - #[cfg(all(test, feature = "env_test"))] - pub(crate) fn get_source(self) -> String { - self.source - } - - #[cfg(all(test, feature = "env_test"))] - pub(crate) fn kind(&self) -> &InvalidConfigKind { - &self.kind - } -} - -impl Display for InvalidConfig { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use InvalidConfigKind::*; - match &self.kind { - EndPoint(_) | BucketName(_) => write!(f, "get config failed, source: {}", self.source), - VarError(_) => write!(f, "get config failed, env name: {}", self.source), - } - } -} -impl Error for InvalidConfig { - fn source(&self) -> Option<&(dyn Error + 'static)> { - use InvalidConfigKind::*; - match &self.kind { - EndPoint(e) => Some(e), - BucketName(e) => Some(e), - VarError(e) => Some(e), - } - } -} - -#[derive(Debug, PartialEq, Clone)] -#[non_exhaustive] -pub(crate) enum InvalidConfigKind { - /// 非法的可用区 - EndPoint(InvalidEndPoint), - - /// 非法的 bucket 名称 - BucketName(InvalidBucketName), - - /// 非法的环境变量 - VarError(VarError), -} - -/// # Bucket 元信息 -/// 包含所属 bucket 名以及所属的 endpoint -#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)] -pub struct BucketBase { - endpoint: EndPoint, - name: BucketName, -} - -impl AsMut for BucketBase { - fn as_mut(&mut self) -> &mut EndPoint { - &mut self.endpoint - } -} - -impl AsMut for BucketBase { - fn as_mut(&mut self) -> &mut BucketName { - &mut self.name - } -} - -impl AsRef for BucketBase { - fn as_ref(&self) -> &EndPoint { - &self.endpoint - } -} - -impl AsRef for BucketBase { - fn as_ref(&self) -> &BucketName { - &self.name - } -} - -impl FromStr for BucketBase { - type Err = InvalidBucketBase; - /// 通过域名获取 - /// 举例 - /// ``` - /// # use aliyun_oss_client::config::BucketBase; - /// # use aliyun_oss_client::types::EndPoint; - /// # use std::borrow::Cow; - /// let bucket: BucketBase = "abc.oss-cn-shanghai.aliyuncs.com".parse().unwrap(); - /// assert_eq!(bucket.name(), "abc"); - /// assert_eq!(bucket.endpoint(), EndPoint::SHANGHAI); - /// - /// assert!("abc*#!".parse::().is_err()); - /// assert!("abc".parse::().is_err()); - /// ``` - fn from_str(domain: &str) -> Result { - fn valid_character(c: char) -> bool { - match c { - _ if c.is_ascii_lowercase() => true, - _ if c.is_numeric() => true, - '-' => true, - '.' => true, - _ => false, - } - } - if !domain.chars().all(valid_character) { - return Err(InvalidBucketBase::new_with_str( - domain, - InvalidBucketBaseKind::Tacitly, - )); - } - - let (bucket, endpoint) = domain - .split_once('.') - .ok_or(InvalidBucketBase::new_with_str( - domain, - InvalidBucketBaseKind::Tacitly, - ))?; - let endpoint = match endpoint.find('.') { - Some(s) => &endpoint[0..s], - None => endpoint, - }; - - Ok(Self { - name: BucketName::from_static(bucket) - .map_err(|e| InvalidBucketBase::from_kind(bucket, e))?, - endpoint: EndPoint::new(endpoint.trim_start_matches(OSS_HYPHEN)) - .map_err(|e| InvalidBucketBase::from_kind(endpoint, e))?, - }) - } -} - -impl TryFrom<&str> for BucketBase { - type Error = InvalidBucketBase; - fn try_from(str: &str) -> Result { - str.parse() - } -} - -impl From> for BucketBase { - fn from( - Client { - bucket, endpoint, .. - }: Client, - ) -> Self { - Self::new(bucket, endpoint) - } -} - -/// Bucket 元信息的错误集 -#[derive(Debug, Clone)] -#[non_exhaustive] -pub struct InvalidBucketBase { - source: String, - kind: InvalidBucketBaseKind, -} - -impl InvalidBucketBase { - pub(crate) fn new(source: String, kind: InvalidBucketBaseKind) -> Self { - Self { source, kind } - } - - pub(crate) fn new_with_str(source: &str, kind: InvalidBucketBaseKind) -> Self { - Self::new(source.to_string(), kind) - } - - pub(crate) fn from_kind>(source: &str, kind: K) -> Self { - Self::new(source.to_string(), kind.into()) - } - - /// 原始字符串 - pub fn source_string(self) -> String { - self.source - } -} - -impl Display for InvalidBucketBase { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "get bucket base faild, source: {}", self.source) - } -} -impl Error for InvalidBucketBase { - fn source(&self) -> Option<&(dyn Error + 'static)> { - use InvalidBucketBaseKind::*; - match &self.kind { - Tacitly => None, - EndPoint(e) => Some(e), - BucketName(e) => Some(e), - } - } -} - -/// Bucket 元信息的错误集 -#[derive(Error, Debug, Clone)] -#[non_exhaustive] -pub(crate) enum InvalidBucketBaseKind { - #[doc(hidden)] - #[error("bucket url must like with https://yyy.xxx.aliyuncs.com")] - Tacitly, - - #[doc(hidden)] - #[error("{0}")] - EndPoint(#[from] InvalidEndPoint), - - #[doc(hidden)] - #[error("{0}")] - BucketName(#[from] InvalidBucketName), -} - -impl BucketBase { - /// 初始化 - pub fn new(name: BucketName, endpoint: EndPoint) -> Self { - Self { name, endpoint } - } - - /// # 调整 API 指向是否为内网 - /// - /// 当在 Aliyun ECS 上执行时,设为 true 会更高效,默认是 false - pub fn set_internal(&mut self, is_internal: bool) { - self.endpoint.is_internal = is_internal; - } - - /// # 通过环境变量初始化 - /// ## 举例 - /// ``` - /// # use aliyun_oss_client::config::BucketBase; - /// use std::env::set_var; - /// set_var("ALIYUN_ENDPOINT", "qingdao"); - /// set_var("ALIYUN_BUCKET", "foo1"); - /// assert!(BucketBase::from_env().is_ok()); - /// ``` - /// - /// 如果在 Aliyun ECS 上,可将环境变量 `ALIYUN_OSS_INTERNAL` - /// 设置为 `true` / `1` / `yes` / `Y` ,即可使用 internal 网络请求 OSS 接口 - pub fn from_env() -> Result { - let endpoint = env::var("ALIYUN_ENDPOINT").map_err(|e| InvalidConfig { - source: "ALIYUN_ENDPOINT".to_string(), - kind: InvalidConfigKind::VarError(e), - })?; - let mut endpoint = EndPoint::from_str(&endpoint).map_err(|e| InvalidConfig { - source: endpoint, - kind: InvalidConfigKind::EndPoint(e), - })?; - - if let Ok(is_internal) = env::var("ALIYUN_OSS_INTERNAL") { - if is_internal == TRUE1 - || is_internal == TRUE2 - || is_internal == TRUE3 - || is_internal == TRUE4 - { - endpoint.set_internal(true); - } - } - - let bucket = env::var("ALIYUN_BUCKET").map_err(|e| InvalidConfig { - source: "ALIYUN_BUCKET".to_string(), - kind: InvalidConfigKind::VarError(e), - })?; - Ok(Self { - name: BucketName::from_str(&bucket).map_err(|e| InvalidConfig { - source: bucket, - kind: InvalidConfigKind::BucketName(e), - })?, - endpoint, - }) - } - - /// 返回 bucket 名称的引用 - #[inline] - pub fn name(&self) -> &str { - self.name.as_ref() - } - - /// 返回 BucketName 引用 - /// ``` - /// # use aliyun_oss_client::config::BucketBase; - /// # use aliyun_oss_client::BucketName; - /// use std::env::set_var; - /// set_var("ALIYUN_ENDPOINT", "qingdao"); - /// set_var("ALIYUN_BUCKET", "foo1"); - /// assert_eq!( - /// *BucketBase::from_env().unwrap().get_name(), - /// BucketName::new("foo1").unwrap() - /// ); - /// ``` - #[inline] - pub fn get_name(&self) -> &BucketName { - &self.name - } - - /// 获取 Bucket 元信息中的可用区 - #[inline] - pub fn endpoint(self) -> EndPoint { - self.endpoint - } - - /// 获取 EndPoint 引用 - #[inline] - pub fn endpoint_ref(&self) -> &EndPoint { - &self.endpoint - } - - /// 设置 bucket name - /// - /// ``` - /// # use aliyun_oss_client::config::BucketBase; - /// use aliyun_oss_client::types::BucketName; - /// let mut bucket = BucketBase::default(); - /// bucket.set_name("abc".parse::().unwrap()); - /// assert_eq!(bucket.name(), "abc"); - /// ``` - pub fn set_name>(&mut self, name: N) { - self.name = name.into(); - } - - /// 为 Bucket 元信息设置可用区 - pub fn set_endpoint>(&mut self, endpoint: E) { - self.endpoint = endpoint.into(); - } - - /// 设置 bucket name - /// - /// ``` - /// # use aliyun_oss_client::config::BucketBase; - /// let mut bucket = BucketBase::default(); - /// assert!(bucket.try_set_name("abc").is_ok()); - /// assert_eq!(bucket.name(), "abc"); - /// ``` - pub fn try_set_name>(&mut self, name: N) -> Result<(), N::Error> { - self.name = name.try_into()?; - Ok(()) - } - - /// 设置 endpoint - /// - /// ``` - /// # use aliyun_oss_client::config::BucketBase; - /// # use aliyun_oss_client::EndPoint; - /// let mut bucket = BucketBase::default(); - /// assert!(bucket.try_set_endpoint("hangzhou").is_ok()); - /// assert_eq!(bucket.endpoint(), EndPoint::HANGZHOU); - /// ``` - pub fn try_set_endpoint>(&mut self, endpoint: E) -> Result<(), E::Error> { - self.endpoint = endpoint.try_into()?; - Ok(()) - } - - /// 获取url - /// 举例 - /// ``` - /// # use aliyun_oss_client::config::BucketBase; - /// use aliyun_oss_client::types::BucketName; - /// let mut bucket = BucketBase::default(); - /// bucket.set_name("abc".parse::().unwrap()); - /// bucket.try_set_endpoint("shanghai").unwrap(); - /// let url = bucket.to_url(); - /// assert_eq!(url.as_str(), "https://abc.oss-cn-shanghai.aliyuncs.com/"); - /// ``` - /// - /// > 因为 BucketName,EndPoint 声明时已做限制,所以 BucketBase 可以安全的转换成 url - pub fn to_url(&self) -> Url { - let endpoint = self.endpoint.to_url(); - let url = endpoint.to_string(); - let name_str = self.name.to_string(); - - let mut name = String::from(HTTPS); - name.push_str(&name_str); - name.push('.'); - - let url = url.replace(HTTPS, &name); - Url::parse(&url).unwrap_or_else(|_| panic!("covert to url failed, url string: {}", url)) - } - - /// 根据查询参数,获取当前 bucket 的接口请求参数( url 和 CanonicalizedResource) - #[inline] - pub fn get_url_resource(&self, query: &Query) -> (Url, CanonicalizedResource) { - let mut url = self.to_url(); - url.set_oss_query(query); - - let resource = CanonicalizedResource::from_bucket_query(self, query); - - (url, resource) - } - - /// 根据查询参数,获取当前 bucket 的接口请求参数( url 和 CanonicalizedResource) - pub fn get_url_resource_with_path( - &self, - path: &ObjectPathInner, - ) -> (Url, CanonicalizedResource) { - let mut url = self.to_url(); - url.set_object_path(path); - - let resource = CanonicalizedResource::from_object((self.name(), path.as_ref()), []); - - (url, resource) - } -} - -pub use crate::types::{get_url_resource, get_url_resource2}; - -#[doc(hidden)] -pub(crate) fn get_url_resource_with_bucket( - endpoint: &EndPoint, - bucket: &BucketName, - query: &Query, -) -> (Url, CanonicalizedResource) { - let url = url_from_bucket(endpoint, bucket); - - let resource = CanonicalizedResource::from_bucket_query2(bucket, query); - - (url, resource) -} - -#[doc(hidden)] -#[allow(dead_code)] -pub(crate) fn get_url_resource_with_bucket2, B: AsRef>( - endpoint: E, - bucket: B, - query: &Query, -) -> (Url, CanonicalizedResource) { - get_url_resource_with_bucket(endpoint.as_ref(), bucket.as_ref(), query) -} - -impl PartialEq for BucketBase { - /// # 相等比较 - /// ``` - /// # use aliyun_oss_client::config::BucketBase; - /// use aliyun_oss_client::types::BucketName; - /// use reqwest::Url; - /// let mut bucket = BucketBase::default(); - /// bucket.set_name("abc".parse::().unwrap()); - /// bucket.try_set_endpoint("shanghai").unwrap(); - /// assert!(bucket == Url::parse("https://abc.oss-cn-shanghai.aliyuncs.com/").unwrap()); - /// ``` - #[inline] - fn eq(&self, other: &Url) -> bool { - &self.to_url() == other - } -} - -#[cfg(test)] -mod tests { - use std::borrow::Cow; - - use crate::types::EndPointKind; - - use super::*; - - #[test] - fn test_config_try_new() { - let err = Config::try_new("foo", "foo", "_aa", "abc"); - let err = err.unwrap_err(); - assert!(matches!( - err, - InvalidConfig { - kind: InvalidConfigKind::EndPoint(_), - .. - } - )); - - let err = Config::try_new("foo", "foo", "qingdao", "-abc"); - let err = err.unwrap_err(); - assert!(matches!( - err, - InvalidConfig { - kind: InvalidConfigKind::BucketName(_), - .. - } - )); - } - - fn assert_as_ref_keyid>(k: K) { - k.as_ref(); - } - fn assert_as_ref_key_secret>(k: K) { - k.as_ref(); - } - fn assert_as_ref_endpoint>(k: K) { - k.as_ref(); - } - fn assert_as_ref_bucket>(k: K) { - k.as_ref(); - } - - #[test] - fn test_config_as_ref() { - let config = Config::default(); - assert_as_ref_keyid(&config); - assert_as_ref_key_secret(&config); - assert_as_ref_endpoint(&config); - assert_as_ref_bucket(&config); - } - - #[test] - fn test_set_internal() { - let mut base = BucketBase::new("abc".try_into().unwrap(), "qingdao".try_into().unwrap()); - let BucketBase { endpoint, .. } = base.clone(); - assert!(endpoint.is_internal == false); - - base.set_internal(true); - let BucketBase { endpoint, .. } = base; - assert!(endpoint.is_internal == true); - } - - #[test] - fn test_invalid_config() { - let error = get_endpoint("oss").unwrap_err(); - assert_eq!(format!("{error}"), "get config failed, source: oss"); - assert_eq!( - format!("{}", error.source().unwrap()), - "endpoint must not with `-` prefix or `-` suffix or `oss-` prefix" - ); - - let error = get_bucket("-oss").unwrap_err(); - assert_eq!(format!("{error}"), "get config failed, source: -oss"); - assert_eq!( - format!("{}", error.source().unwrap()), - "bucket name only allow `alphabet, digit, -`, and must not with `-` prefix or `-` suffix" - ); - - let err = get_env("aaa").unwrap_err(); - assert_eq!(format!("{}", err), "get config failed, env name: aaa"); - assert_eq!( - format!("{}", err.source().unwrap()), - "environment variable not found" - ); - } - - #[test] - fn test_base_as() { - fn assert_as_mut_endpoint>(e: &mut E) { - e.as_mut(); - } - fn assert_as_mut_name>(e: &mut E) { - e.as_mut(); - } - fn assert_as_endpoint>(e: &E) { - e.as_ref(); - } - fn assert_as_name>(e: &E) { - e.as_ref(); - } - - let mut base = BucketBase::default(); - - assert_as_mut_endpoint(&mut base); - assert_as_mut_name(&mut base); - assert_as_endpoint(&base); - assert_as_name(&base); - } - - #[test] - fn test_get_url_resource_with_path() { - let base = BucketBase::new("abc".try_into().unwrap(), EndPoint::CN_BEIJING); - - let path = "path".try_into().unwrap(); - let (url, resource) = base.get_url_resource_with_path(&path); - - assert_eq!( - url, - Url::parse("https://abc.oss-cn-beijing.aliyuncs.com/path").unwrap() - ); - assert_eq!(resource, "/abc/path"); - } - - #[test] - fn test_get_url_resource_with_bucket() { - let endpoint = EndPoint::CN_BEIJING; - let bucket = BucketName::new("abc").unwrap(); - let query = Query::new(); - - let (url, resource) = get_url_resource_with_bucket(&endpoint, &bucket, &query); - assert_eq!( - url, - Url::parse("https://abc.oss-cn-beijing.aliyuncs.com").unwrap() - ); - assert_eq!(resource, "/abc/"); - } - - #[test] - fn test_bucketbase_to_url() { - let base = BucketBase::new("foo1".parse().unwrap(), EndPoint::CN_QINGDAO); - let url = base.to_url(); - assert_eq!( - url, - Url::parse("https://foo1.oss-cn-qingdao.aliyuncs.com").unwrap() - ); - - let base = BucketBase::new("foo1".parse().unwrap(), { - let mut end = EndPoint::CN_QINGDAO; - end.set_internal(true); - end - }); - let url = base.to_url(); - assert_eq!( - url, - Url::parse("https://foo1.oss-cn-qingdao-internal.aliyuncs.com").unwrap() - ); - } - - #[test] - fn test_invalid_bucket_base() { - let error = InvalidEndPoint::new(); - let base_err = InvalidBucketBase { - source: "abc".to_string(), - kind: error.into(), - }; - assert_eq!(format!("{base_err}"), "get bucket base faild, source: abc"); - assert_eq!( - format!("{}", base_err.source().unwrap()), - "endpoint must not with `-` prefix or `-` suffix or `oss-` prefix" - ); - - let error = InvalidBucketName::new(); - let error2 = InvalidBucketBase { - source: "abc".to_string(), - kind: error.into(), - }; - assert_eq!(format!("{error2}"), "get bucket base faild, source: abc"); - assert_eq!( - format!("{}", error2.source().unwrap()), - "bucket name only allow `alphabet, digit, -`, and must not with `-` prefix or `-` suffix" - ); - - let error2 = InvalidBucketBase { - source: "abc".to_string(), - kind: InvalidBucketBaseKind::Tacitly, - }; - assert_eq!(format!("{error2}"), "get bucket base faild, source: abc"); - assert!(error2.source().is_none()); - } - - #[test] - fn test_bucket_base_from_str() { - let err = BucketBase::from_str("-abc.oss-cn-qingdao"); - let err = err.unwrap_err(); - assert!(matches!( - err, - InvalidBucketBase { - kind: InvalidBucketBaseKind::BucketName(_), - .. - } - )); - - let err = BucketBase::from_str("abc.oss-cn-qing-"); - let err = err.unwrap_err(); - assert!(matches!( - err, - InvalidBucketBase { - kind: InvalidBucketBaseKind::EndPoint(_), - .. - } - )); - - let bucket: BucketBase = "abc.oss-cn-jinan.aliyuncs.com".parse().unwrap(); - assert_eq!(bucket.name(), "abc"); - assert_eq!( - bucket.endpoint(), - EndPoint { - kind: EndPointKind::Other(Cow::Borrowed("cn-jinan")), - is_internal: false, - } - ); - - let bucket: BucketBase = "abc.oss-cn-jinan".parse().unwrap(); - assert_eq!(bucket.name(), "abc"); - assert_eq!( - bucket.endpoint(), - EndPoint { - kind: EndPointKind::Other(Cow::Borrowed("cn-jinan")), - is_internal: false, - } - ); - } - - #[test] - fn test_bucket_base_eq_url() { - let base = BucketBase::default(); - let url = Url::parse("https://a.oss-cn-hangzhou.aliyuncs.com/").unwrap(); - assert!(base == url); - } -} diff --git a/src/consts.rs b/src/consts.rs deleted file mode 100644 index bcc9451..0000000 --- a/src/consts.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub const TRUE1: &str = "true"; -pub const TRUE2: &str = "1"; -pub const TRUE3: &str = "yes"; -pub const TRUE4: &str = "Y"; diff --git a/src/decode.rs b/src/decode.rs deleted file mode 100644 index 1363d8d..0000000 --- a/src/decode.rs +++ /dev/null @@ -1,681 +0,0 @@ -//! # 解析 aliyun OSS 接口返回的 xml 原始数据的 trait -//! 开发者可利用该 trait 将 xml 高效地转化为 rust 的 struct 或者 enum 类型 -//! -//! 本 trait 是零拷贝的,所以可以做到很高效 -//! -//! ## 示例 -//! ``` -//! use aliyun_oss_client::decode::{RefineObject, RefineObjectList}; -//! use thiserror::Error; -//! -//! struct MyFile { -//! key: String, -//! #[allow(dead_code)] -//! other: String, -//! } -//! impl RefineObject for MyFile { -//! -//! fn set_key(&mut self, key: &str) -> Result<(), MyError> { -//! self.key = key.to_string(); -//! Ok(()) -//! } -//! } -//! -//! #[derive(Default)] -//! struct MyBucket { -//! name: String, -//! files: Vec, -//! } -//! -//! impl RefineObjectList for MyBucket { -//! -//! fn set_name(&mut self, name: &str) -> Result<(), MyError> { -//! self.name = name.to_string(); -//! Ok(()) -//! } -//! fn set_list(&mut self, list: Vec) -> Result<(), MyError> { -//! self.files = list; -//! Ok(()) -//! } -//! } -//! -//! use aliyun_oss_client::DecodeListError; -//! -//! // 自定义的 Error 需要实现这个 Trait,用于内部解析方法在调用时,统一处理异常 -//! #[derive(Debug, Error, DecodeListError)] -//! #[error("my error")] -//! struct MyError {} -//! -//! fn get_with_xml() -> Result<(), aliyun_oss_client::decode::InnerListError> { -//! // 这是阿里云接口返回的原始数据 -//! let xml = r#" -//! -//! foo_bucket -//! -//! 100 -//! -//! false -//! CiphcHBzL1RhdXJpIFB1Ymxpc2ggQXBwXzAuMS42X3g2NF9lbi1VUy5tc2kQAA-- -//! -//! 9AB932LY.jpeg -//! 2022-06-26T09:53:21.000Z -//! "F75A15996D0857B16FA31A3B16624C26" -//! Normal -//! 18027 -//! Standard -//! -//! 3 -//! "#; -//! -//! // 除了设置Default 外,还可以做更多设置 -//! let mut bucket = MyBucket::default(); -//! -//! // 利用闭包对 MyFile 做一下初始化设置 -//! // 可以根据传入的列表信息,为元素添加更多能力 -//! fn init_file(_list: &mut MyBucket) -> Option { -//! Some(MyFile { -//! key: String::default(), -//! other: "abc".to_string(), -//! }) -//! } -//! -//! bucket.decode(xml, init_file)?; -//! -//! assert!(bucket.name == "foo_bucket"); -//! assert!(bucket.files[0].key == "9AB932LY.jpeg"); -//! -//! Ok(()) -//! } -//! -//! let res = get_with_xml(); -//! -//! if let Err(err) = res { -//! eprintln!("{}", err); -//! } -//! ``` - -use std::borrow::Cow; -use std::error::Error as StdError; -use std::fmt::Display; -use std::num::ParseIntError; - -use quick_xml::{events::Event, Reader}; - -use crate::types::InvalidEndPoint; -#[cfg(feature = "core")] -use crate::{ - errors::OssError, - types::object::{InvalidObjectDir, InvalidObjectPath}, -}; - -#[cfg(test)] -mod test; - -const PREFIX: &[u8] = b"Prefix"; -const COMMON_PREFIX: &[u8] = b"CommonPrefixes"; -const NAME: &[u8] = b"Name"; -const MAX_KEYS: &[u8] = b"MaxKeys"; -const KEY_COUNT: &[u8] = b"KeyCount"; -const IS_TRUNCATED: &[u8] = b"IsTruncated"; -const NEXT_CONTINUATION_TOKEN: &[u8] = b"NextContinuationToken"; -const KEY: &[u8] = b"Key"; -const LAST_MODIFIED: &[u8] = b"LastModified"; -const E_TAG: &[u8] = b"ETag"; -const TYPE: &[u8] = b"Type"; -const SIZE: &[u8] = b"Size"; -const STORAGE_CLASS: &[u8] = b"StorageClass"; -const BUCKET: &[u8] = b"Bucket"; - -const CREATION_DATE: &[u8] = b"CreationDate"; -const EXTRANET_ENDPOINT: &[u8] = b"ExtranetEndpoint"; -const INTRANET_ENDPOINT: &[u8] = b"IntranetEndpoint"; -const LOCATION: &[u8] = b"Location"; - -const MARKER: &[u8] = b"Marker"; -const NEXT_MARKER: &[u8] = b"NextMarker"; -const ID: &[u8] = b"ID"; -const DISPLAY_NAME: &[u8] = b"DisplayName"; -const CONTENTS: &[u8] = b"Contents"; - -const TRUE: &str = "true"; - -/// 将一个 object 的数据写入到 rust 类型 -pub trait RefineObject { - /// 提取 key - fn set_key(&mut self, _key: &str) -> Result<(), Error> { - Ok(()) - } - - /// 提取最后修改时间 - fn set_last_modified(&mut self, _last_modified: &str) -> Result<(), Error> { - Ok(()) - } - - /// 提取 etag - fn set_etag(&mut self, _etag: &str) -> Result<(), Error> { - Ok(()) - } - - /// 提取 type - fn set_type(&mut self, _type: &str) -> Result<(), Error> { - Ok(()) - } - - /// 提取 size - fn set_size(&mut self, _size: &str) -> Result<(), Error> { - Ok(()) - } - - /// 提取 storage_class - fn set_storage_class(&mut self, _storage_class: &str) -> Result<(), Error> { - Ok(()) - } - - /// 对单个 objcet 部分的 xml 内容进行解析 - fn decode(&mut self, xml: &str) -> Result<(), InnerItemError> { - let mut reader = Reader::from_str(xml); - let mut buf = Vec::with_capacity(xml.len()); - loop { - match reader.read_event_into(&mut buf) { - Ok(Event::Start(e)) => match e.name().as_ref() { - KEY => self.set_key(&reader.read_text(e.to_end().name())?)?, - LAST_MODIFIED => { - self.set_last_modified(&reader.read_text(e.to_end().name())?)? - } - E_TAG => { - let tag = reader.read_text(e.to_end().name())?; - self.set_etag(tag.trim_matches('"'))?; - } - TYPE => self.set_type(&reader.read_text(e.to_end().name())?)?, - SIZE => { - self.set_size(&reader.read_text(e.to_end().name())?)?; - } - STORAGE_CLASS => { - self.set_storage_class(&reader.read_text(e.to_end().name())?)?; - } - _ => (), - }, - Ok(Event::Eof) => { - break; - } // exits the loop when reaching end of file - Err(e) => { - return Err(InnerItemError::from(e)); - } - _ => (), // - } - buf.clear(); - } - Ok(()) - } -} - -/// 将 object 列表写入到 rust 类型 -pub trait RefineObjectList -where - T: RefineObject, - Error: ListError, - ItemErr: StdError + 'static, -{ - /// 提取 bucket 名 - fn set_name(&mut self, _name: &str) -> Result<(), Error> { - Ok(()) - } - - /// 提取前缀 - fn set_prefix(&mut self, _prefix: &str) -> Result<(), Error> { - Ok(()) - } - - /// 提取文件目录 - fn set_common_prefix(&mut self, _list: &[Cow<'_, str>]) -> Result<(), Error> { - Ok(()) - } - - /// 提取 max_keys - fn set_max_keys(&mut self, _max_keys: &str) -> Result<(), Error> { - Ok(()) - } - - /// 提取 key_count - fn set_key_count(&mut self, _key_count: &str) -> Result<(), Error> { - Ok(()) - } - - /// 提取翻页信息 token - fn set_next_continuation_token_str(&mut self, _token: &str) -> Result<(), Error> { - Ok(()) - } - - /// 提取 object 列表 - fn set_list(&mut self, _list: Vec) -> Result<(), Error> { - Ok(()) - } - - /// 用于解析 common prefix - fn decode_common_prefix(&mut self, xml: &str) -> Result<(), InnerListError> { - let mut reader = Reader::from_str(xml); - let mut buf = Vec::with_capacity(xml.len()); - let mut prefix_vec = Vec::new(); - - loop { - match reader.read_event_into(&mut buf) { - Ok(Event::Start(e)) => { - if e.name().as_ref() == PREFIX { - prefix_vec.push(reader.read_text(e.to_end().name())?); - } - } - Ok(Event::Eof) => { - break; - } // exits the loop when reaching end of file - Err(e) => { - return Err(InnerListError::from(e)); - } - _ => (), // There are several other `Event`s we do not consider here - } - buf.clear(); - } - self.set_common_prefix(&prefix_vec)?; - - Ok(()) - } - - /// # 由 xml 转 struct 的底层实现 - /// - `init_object` 用于初始化 object 结构体的方法 - fn decode(&mut self, xml: &str, init_object: F) -> Result<(), InnerListError> - where - F: for<'a> Fn(&'a mut Self) -> Option, - { - //println!("from_xml: {:#}", xml); - let mut result = Vec::new(); - let mut reader = Reader::from_str(xml); - reader.trim_text(true); - let mut buf = Vec::with_capacity(xml.len()); - - loop { - match reader.read_event_into(&mut buf) { - Ok(Event::Start(e)) => { - match e.name().as_ref() { - COMMON_PREFIX => { - self.decode_common_prefix(&reader.read_text(e.to_end().name())?)?; - } - PREFIX => { - self.set_prefix(&reader.read_text(e.to_end().name())?)?; - } - NAME => self.set_name(&reader.read_text(e.to_end().name())?)?, - MAX_KEYS => self.set_max_keys(&reader.read_text(e.to_end().name())?)?, - KEY_COUNT => self.set_key_count(&reader.read_text(e.to_end().name())?)?, - IS_TRUNCATED => { - //is_truncated = reader.read_text(e.to_end().name())?.to_string() == TRUE - } - NEXT_CONTINUATION_TOKEN => { - self.set_next_continuation_token_str( - &reader.read_text(e.to_end().name())?, - )?; - } - CONTENTS => { - // 标签内部的数据对应单个 object 信息 - let mut object = - init_object(self).ok_or(InnerListError::init_error(true))?; - object.decode(&reader.read_text(e.to_end().name())?)?; - result.push(object); - } - _ => (), - } - } - Ok(Event::Eof) => { - self.set_list(result)?; - break; - } // exits the loop when reaching end of file - Err(e) => { - return Err(InnerListError::from(e)); - } - _ => (), // There are several other `Event`s we do not consider here - } - buf.clear(); - } - - Ok(()) - } -} - -/// 将一个 bucket 的数据写入到 rust 类型 -pub trait RefineBucket { - /// 提取 bucket name - fn set_name(&mut self, _name: &str) -> Result<(), Error> { - Ok(()) - } - - /// 提取 bucket 创建时间 - fn set_creation_date(&mut self, _creation_date: &str) -> Result<(), Error> { - Ok(()) - } - - /// 提取 location - fn set_location(&mut self, _location: &str) -> Result<(), Error> { - Ok(()) - } - - /// 提取 extranet_endpoint - fn set_extranet_endpoint(&mut self, _extranet_endpoint: &str) -> Result<(), Error> { - Ok(()) - } - - /// 提取 intranet_endpoint - fn set_intranet_endpoint(&mut self, _intranet_endpoint: &str) -> Result<(), Error> { - Ok(()) - } - - /// 提取 storage_class - fn set_storage_class(&mut self, _storage_class: &str) -> Result<(), Error> { - Ok(()) - } - - /// 解析 OSS 接口返回的 xml 数据 - fn decode(&mut self, xml: &str) -> Result<(), InnerItemError> { - //println!("from_xml: {:#}", xml); - let mut reader = Reader::from_str(xml); - reader.trim_text(true); - let mut buf = Vec::with_capacity(xml.len()); - - loop { - match reader.read_event_into(&mut buf) { - Ok(Event::Start(e)) => match e.name().as_ref() { - NAME => self.set_name(&reader.read_text(e.to_end().name())?)?, - CREATION_DATE => { - self.set_creation_date(&reader.read_text(e.to_end().name())?)? - } - EXTRANET_ENDPOINT => { - self.set_extranet_endpoint(&reader.read_text(e.to_end().name())?)? - } - INTRANET_ENDPOINT => { - self.set_intranet_endpoint(&reader.read_text(e.to_end().name())?)? - } - LOCATION => self.set_location(&reader.read_text(e.to_end().name())?)?, - STORAGE_CLASS => { - self.set_storage_class(&reader.read_text(e.to_end().name())?)? - } - _ => (), - }, - Ok(Event::Eof) => { - break; - } // exits the loop when reaching end of file - Err(e) => { - return Err(InnerItemError::from(e)); - } - _ => (), // There are several other `Event`s we do not consider here - } - buf.clear(); - } - Ok(()) - } -} - -/// 将 bucket 列表的数据写入到 rust 类型 -pub trait RefineBucketList, Error, ItemErr = Error> -where - Error: ListError, - ItemErr: StdError + 'static, -{ - /// 提取 prefix - fn set_prefix(&mut self, _prefix: &str) -> Result<(), Error> { - Ok(()) - } - - /// 提取 marker - fn set_marker(&mut self, _marker: &str) -> Result<(), Error> { - Ok(()) - } - - /// 提取 max_keys - fn set_max_keys(&mut self, _max_keys: &str) -> Result<(), Error> { - Ok(()) - } - - /// 提取 is_truncated - fn set_is_truncated(&mut self, _is_truncated: bool) -> Result<(), Error> { - Ok(()) - } - - /// 提取 next_marker - fn set_next_marker(&mut self, _next_marker: &str) -> Result<(), Error> { - Ok(()) - } - - /// 提取 id - fn set_id(&mut self, _id: &str) -> Result<(), Error> { - Ok(()) - } - - /// 提取 display_name - fn set_display_name(&mut self, _display_name: &str) -> Result<(), Error> { - Ok(()) - } - - /// 提取 bucket 列表 - fn set_list(&mut self, _list: Vec) -> Result<(), Error> { - Ok(()) - } - - /// 解析 OSS 接口返回的 xml 数据 - fn decode(&mut self, xml: &str, init_bucket: F) -> Result<(), InnerListError> - where - F: for<'a> Fn(&'a mut Self) -> Option, - { - let mut result = Vec::new(); - let mut reader = Reader::from_str(xml); - reader.trim_text(true); - let mut buf = Vec::with_capacity(xml.len()); - - loop { - match reader.read_event_into(&mut buf) { - Ok(Event::Start(e)) => match e.name().as_ref() { - PREFIX => self.set_prefix(&reader.read_text(e.to_end().name())?)?, - MARKER => self.set_marker(&reader.read_text(e.to_end().name())?)?, - MAX_KEYS => self.set_max_keys(&reader.read_text(e.to_end().name())?)?, - IS_TRUNCATED => { - self.set_is_truncated(reader.read_text(e.to_end().name())? == TRUE)?; - } - NEXT_MARKER => self.set_next_marker(&reader.read_text(e.to_end().name())?)?, - ID => self.set_id(&reader.read_text(e.to_end().name())?)?, - DISPLAY_NAME => self.set_display_name(&reader.read_text(e.to_end().name())?)?, - BUCKET => { - // 标签内部的数据对应单个 bucket 信息 - let mut bucket = - init_bucket(self).ok_or(InnerListError::init_error(false))?; - bucket.decode(&reader.read_text(e.to_end().name())?)?; - result.push(bucket); - } - _ => (), - }, - Ok(Event::Eof) => { - self.set_list(result)?; - break; - } // exits the loop when reaching end of file - Err(e) => { - return Err(InnerListError::from(e)); - } - _ => (), // There are several other `Event`s we do not consider here - } - buf.clear(); - } - Ok(()) - } -} - -/// # Object 的 Error 中间层 -/// 当外部实现 [`RefineObject`] 时,所使用的 Error ,可先转换为这个, -/// 变成一个已知的 Error 类型 -/// -/// [`RefineObject`]: crate::decode::RefineObject -#[derive(Debug)] -#[doc(hidden)] -#[non_exhaustive] -pub struct InnerItemError(Box); - -impl From for InnerItemError { - fn from(err: T) -> Self { - Self(Box::new(err)) - } -} - -impl Display for InnerItemError { - fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(fmt, "{}", self.0) - } -} - -impl InnerItemError { - #[cfg(test)] - pub(crate) fn new() -> Self { - #[derive(Debug)] - struct MyError; - impl Display for MyError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - "demo".fmt(f) - } - } - impl StdError for MyError {} - - Self(Box::new(MyError {})) - } - - pub fn get_source(&self) -> Option<&(dyn StdError + 'static)> { - Some(self.0.as_ref()) - } -} - -/// 当外部要实现 [`RefineObjectList`] 时,Error 类需要实现此 Trait -/// -/// [`RefineObjectList`]: crate::decode::RefineObjectList -pub trait ListError: StdError + 'static {} - -impl ListError for ParseIntError {} - -impl ListError for InvalidEndPoint {} - -#[cfg(feature = "core")] -impl ListError for InvalidObjectPath {} -#[cfg(feature = "core")] -impl ListError for InvalidObjectDir {} -#[cfg(feature = "core")] -impl ListError for chrono::ParseError {} -#[cfg(feature = "core")] -impl ListError for OssError {} - -impl From for InnerListError { - fn from(err: T) -> InnerListError { - Self { - kind: ListErrorKind::Custom(Box::new(err)), - } - } -} - -/// # ObjectList 的 Error 中间层 -/// 当外部实现 [`RefineObjectList`] 时,所使用的 Error ,可先转换为这个, -/// 变成一个已知的 Error 类型 -/// -/// [`RefineObjectList`]: crate::decode::RefineObjectList -#[derive(Debug)] -#[non_exhaustive] -pub struct InnerListError { - kind: ListErrorKind, -} - -impl Display for InnerListError { - fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { - use ListErrorKind::*; - match &self.kind { - Item(item) => write!(fmt, "{}", item.0), - Xml(xml) => write!(fmt, "{xml}"), - Custom(out) => write!(fmt, "{out}"), - InitItemFailed(is_object) => { - if *is_object { - write!(fmt, "init_object failed") - } else { - write!(fmt, "init_bucket failed") - } - } - } - } -} - -impl InnerListError { - #[cfg(test)] - #[allow(dead_code)] - pub(crate) fn from_xml() -> Self { - Self { - kind: ListErrorKind::Xml(Box::new(quick_xml::Error::TextNotFound)), - } - } - - // fn custom(err: E) -> Self { - // Self { - // kind: ListErrorKind::Custom(Box::new(err)), - // } - // } - - fn init_error(is_object: bool) -> Self { - Self { - kind: ListErrorKind::InitItemFailed(is_object), - } - } - - #[cfg(test)] - #[allow(dead_code)] - pub(crate) fn from_custom() -> Self { - #[derive(Debug)] - struct MyError; - impl Display for MyError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - "custom".fmt(f) - } - } - impl StdError for MyError {} - Self { - kind: ListErrorKind::Custom(Box::new(MyError {})), - } - } - - /// 获取更详细的错误信息 - pub fn get_source(&self) -> Option<&(dyn StdError + 'static)> { - use ListErrorKind::*; - match &self.kind { - Item(item) => item.get_source(), - Xml(xml) => Some(xml), - Custom(out) => Some(out.as_ref()), - InitItemFailed(_) => None, - } - } -} - -impl From for InnerListError { - fn from(value: InnerItemError) -> Self { - Self { - kind: ListErrorKind::Item(value), - } - } -} - -impl From for InnerListError { - fn from(value: quick_xml::Error) -> Self { - Self { - kind: ListErrorKind::Xml(Box::new(value)), - } - } -} - -#[doc(hidden)] -#[derive(Debug)] -#[non_exhaustive] -enum ListErrorKind { - #[non_exhaustive] - Item(InnerItemError), - - #[non_exhaustive] - Xml(Box), - - #[non_exhaustive] - Custom(Box), - - InitItemFailed(bool), -} diff --git a/src/decode/test.rs b/src/decode/test.rs deleted file mode 100644 index 8fbc55d..0000000 --- a/src/decode/test.rs +++ /dev/null @@ -1,863 +0,0 @@ -static mut OBEJCT_ITEM_ID: i8 = 0; - -use thiserror::Error; - -use crate::decode::ListError; - -#[derive(Debug, Error)] -#[error("custom")] -struct MyError {} - -impl ListError for MyError {} - -mod object_list_xml { - - use super::MyError; - - #[cfg(feature = "core")] - use crate::object::{Object, ObjectList}; - - use super::OBEJCT_ITEM_ID; - - #[test] - fn from_xml() { - use crate::decode::RefineObject; - use crate::decode::RefineObjectList; - - #[derive(Default)] - struct ObjectA {} - - impl RefineObject for ObjectA { - fn set_key(&mut self, key: &str) -> Result<(), MyError> { - unsafe { - if OBEJCT_ITEM_ID == 0 { - assert_eq!(key, "9AB932LY.jpeg"); - } else if OBEJCT_ITEM_ID == 1 { - assert_eq!(key, "CHANGELOG.md"); - } else if OBEJCT_ITEM_ID == 2 { - assert_eq!(key, "LICENSE"); - } - } - Ok(()) - } - fn set_last_modified(&mut self, last_modified: &str) -> Result<(), MyError> { - unsafe { - if OBEJCT_ITEM_ID == 0 { - assert_eq!(last_modified, "2022-06-26T09:53:21.000Z"); - } else if OBEJCT_ITEM_ID == 1 { - assert_eq!(last_modified, "2022-06-12T06:11:06.000Z"); - } else if OBEJCT_ITEM_ID == 2 { - assert_eq!(last_modified, "2022-06-12T06:11:06.000Z"); - } - } - Ok(()) - } - fn set_etag(&mut self, etag: &str) -> Result<(), MyError> { - unsafe { - if OBEJCT_ITEM_ID == 0 { - assert_eq!(etag, "F75A15996D0857B16FA31A3B16624C26"); - } else if OBEJCT_ITEM_ID == 1 { - assert_eq!(etag, "09C37AC5B145D368D52D0AAB58B25213"); - } else if OBEJCT_ITEM_ID == 2 { - assert_eq!(etag, "2CBAB10A50CC6905EA2D7CCCEF31A6C9"); - } - } - Ok(()) - } - fn set_type(&mut self, _type: &str) -> Result<(), MyError> { - unsafe { - if OBEJCT_ITEM_ID == 0 { - assert_eq!(_type, "Normal"); - } else if OBEJCT_ITEM_ID == 1 { - assert_eq!(_type, "Normal"); - } else if OBEJCT_ITEM_ID == 2 { - assert_eq!(_type, "Normal"); - } - } - Ok(()) - } - fn set_size(&mut self, size: &str) -> Result<(), MyError> { - unsafe { - if OBEJCT_ITEM_ID == 0 { - assert_eq!(size, "18027"); - } else if OBEJCT_ITEM_ID == 1 { - assert_eq!(size, "40845"); - } else if OBEJCT_ITEM_ID == 2 { - assert_eq!(size, "1065"); - } - } - Ok(()) - } - fn set_storage_class(&mut self, storage_class: &str) -> Result<(), MyError> { - assert_eq!(storage_class, "Standard"); - unsafe { - OBEJCT_ITEM_ID += 1; - } - Ok(()) - } - } - struct ListB {} - impl RefineObjectList for ListB { - fn set_name(&mut self, name: &str) -> Result<(), MyError> { - assert_eq!(name, "foo_bucket"); - Ok(()) - } - fn set_prefix(&mut self, prefix: &str) -> Result<(), MyError> { - assert_eq!(prefix, "foo_prefix"); - Ok(()) - } - fn set_max_keys(&mut self, max_keys: &str) -> Result<(), MyError> { - assert_eq!(max_keys, "100"); - Ok(()) - } - fn set_key_count(&mut self, key_count: &str) -> Result<(), MyError> { - assert_eq!(key_count, "3"); - Ok(()) - } - fn set_next_continuation_token_str(&mut self, token: &str) -> Result<(), MyError> { - assert!(token.is_empty()); - Ok(()) - } - // fn set_list(self, list: Vec) -> Result{ - // Ok(self) - // } - } - - let xml = r#" - - foo_bucket - foo_prefix - 100 - - false - - 9AB932LY.jpeg - 2022-06-26T09:53:21.000Z - "F75A15996D0857B16FA31A3B16624C26" - Normal - 18027 - Standard - - - CHANGELOG.md - 2022-06-12T06:11:06.000Z - "09C37AC5B145D368D52D0AAB58B25213" - Normal - 40845 - Standard - - - LICENSE - 2022-06-12T06:11:06.000Z - "2CBAB10A50CC6905EA2D7CCCEF31A6C9" - Normal - 1065 - Standard - - 3 - "#; - - // let base = BucketBase::new("abc".try_into().unwrap(), EndPoint::QINGDAO); - - let mut list = ListB {}; - - let res = list.decode(xml, |_| -> Option { Some(ObjectA {}) }); - - assert!(res.is_ok()); - } - - // bench result 5,210 ns/iter (+/- 151) - // update to &str 4,262 ns/iter (+/- 96) - // update to &mut self 3,718 ns/iter (+/- 281) - #[cfg(test)] - #[cfg(feature = "bench")] - #[bench] - fn from_xml_bench(b: &mut test::Bencher) { - use crate::decode::RefineObject; - use crate::decode::RefineObjectList; - - #[derive(Default)] - struct ObjectA {} - - impl RefineObject for ObjectA { - type Bucket = Arc; - type Error = OssError; - fn set_key(&mut self, key: &str) -> Result<(), OssError> { - Ok(()) - } - fn set_last_modified(&mut self, last_modified: &str) -> Result<(), OssError> { - Ok(()) - } - fn set_etag(&mut self, etag: &str) -> Result<(), OssError> { - Ok(()) - } - fn set_type(&mut self, _type: &str) -> Result<(), OssError> { - Ok(()) - } - fn set_size(&mut self, size: &str) -> Result<(), OssError> { - Ok(()) - } - fn set_storage_class(&mut self, storage_class: &str) -> Result<(), OssError> { - Ok(()) - } - fn set_bucket(self, bucket: Arc) -> Self { - self - } - } - struct ListB {} - impl RefineObjectList for ListB { - type Error = OssError; - } - - let xml = r#" - - foo_bucket - - 100 - - false - - 9AB932LY.jpeg - 2022-06-26T09:53:21.000Z - "F75A15996D0857B16FA31A3B16624C26" - Normal - 18027 - Standard - - - CHANGELOG.md - 2022-06-12T06:11:06.000Z - "09C37AC5B145D368D52D0AAB58B25213" - Normal - 40845 - Standard - - - LICENSE - 2022-06-12T06:11:06.000Z - "2CBAB10A50CC6905EA2D7CCCEF31A6C9" - Normal - 1065 - Standard - - 3 - "#; - - let mut list = ListB {}; - - b.iter(|| { - let base = BucketBase::new("abc".parse().unwrap(), EndPoint::QINGDAO); - list.decode(xml, Arc::new(base)); - }) - } - - // fn init_object_list(token: Option, list: Vec>) -> ObjectList { - // let client = ClientRc::new( - // "foo1".into(), - // "foo2".into(), - // "https://oss-cn-shanghai.aliyuncs.com".try_into().unwrap(), - // "foo4".try_into().unwrap(), - // ); - - // let object_list = ObjectList::::new( - // BucketBase::from_str("abc.oss-cn-shanghai.aliyuncs.com").unwrap(), - // String::from("foo2"), - // 100, - // 200, - // list, - // token, - // Rc::new(client), - // [], - // ); - - // object_list - // } - - #[cfg(feature = "core")] - #[allow(dead_code)] - fn init_object_list(token: Option, list: Vec) -> ObjectList { - use crate::builder::ArcPointer; - use crate::client::Client; - use std::sync::Arc; - - let client = Client::new( - "foo1".into(), - "foo2".into(), - "https://oss-cn-shanghai.aliyuncs.com".parse().unwrap(), - "foo4".parse().unwrap(), - ); - - let object_list = ObjectList::::new( - "abc.oss-cn-shanghai.aliyuncs.com".parse().unwrap(), - Some("foo2/".parse().unwrap()), - 100, - 200, - list, - token, - Arc::new(client), - [], - ); - - object_list - } - - // update to &mut self 5,015 ns/iter (+/- 212) - #[cfg(test)] - #[cfg(feature = "bench")] - #[bench] - fn from_xml_bench_real_object(b: &mut test::Bencher) { - use crate::decode::RefineObject; - use crate::decode::RefineObjectList; - - let xml = r#" - - foo_bucket - - 100 - - false - - 9AB932LY.jpeg - 2022-06-26T09:53:21.000Z - "F75A15996D0857B16FA31A3B16624C26" - Normal - 18027 - Standard - - - CHANGELOG.md - 2022-06-12T06:11:06.000Z - "09C37AC5B145D368D52D0AAB58B25213" - Normal - 40845 - Standard - - - LICENSE - 2022-06-12T06:11:06.000Z - "2CBAB10A50CC6905EA2D7CCCEF31A6C9" - Normal - 1065 - Standard - - 3 - "#; - - let mut list = init_object_list(None, vec![]); - b.iter(|| { - let base = BucketBase::new("abc".parse().unwrap(), EndPoint::QINGDAO); - list.decode(xml, Arc::new(base)); - }) - } - - #[test] - fn from_xml_has_next() { - use crate::decode::RefineObject; - use crate::decode::RefineObjectList; - - #[derive(Default)] - struct ObjectA {} - - impl RefineObject for ObjectA {} - - struct ListB {} - impl RefineObjectList for ListB { - fn set_next_continuation_token_str(&mut self, token: &str) -> Result<(), MyError> { - assert!( - token == "CiphcHBzL1RhdXJpIFB1Ymxpc2ggQXBwXzAuMS42X3g2NF9lbi1VUy5tc2kQAA--" - ); - Ok(()) - } - } - - let xml = r#" - - foo_bucket - - 100 - - false - CiphcHBzL1RhdXJpIFB1Ymxpc2ggQXBwXzAuMS42X3g2NF9lbi1VUy5tc2kQAA-- - - 9AB932LY.jpeg - 2022-06-26T09:53:21.000Z - "F75A15996D0857B16FA31A3B16624C26" - Normal - 18027 - Standard - - - CHANGELOG.md - 2022-06-12T06:11:06.000Z - "09C37AC5B145D368D52D0AAB58B25213" - Normal - 40845 - Standard - - - LICENSE - 2022-06-12T06:11:06.000Z - "2CBAB10A50CC6905EA2D7CCCEF31A6C9" - Normal - 1065 - Standard - - 3 - "#; - - //let base = BucketBase::new("abc".try_into().unwrap(), EndPoint::QINGDAO); - - let mut list = ListB {}; - - fn init_object(_list: &mut ListB) -> Option { - Some(ObjectA {}) - } - - let res = list.decode(xml, init_object); - - assert!(res.is_ok()); - } -} - -mod bucket_xml { - use super::MyError; - - #[test] - fn from_xml() { - use crate::decode::RefineBucket; - - struct BucketA {} - - impl RefineBucket for BucketA { - fn set_name(&mut self, name: &str) -> Result<(), MyError> { - assert_eq!(name, "foo"); - Ok(()) - } - fn set_creation_date(&mut self, creation_date: &str) -> Result<(), MyError> { - assert_eq!(creation_date, "2016-11-05T13:10:10.000Z"); - Ok(()) - } - fn set_location(&mut self, location: &str) -> Result<(), MyError> { - assert_eq!(location, "oss-cn-shanghai"); - Ok(()) - } - fn set_extranet_endpoint(&mut self, extranet_endpoint: &str) -> Result<(), MyError> { - assert_eq!(extranet_endpoint, "oss-cn-shanghai.aliyuncs.com"); - Ok(()) - } - fn set_intranet_endpoint(&mut self, intranet_endpoint: &str) -> Result<(), MyError> { - assert_eq!(intranet_endpoint, "oss-cn-shanghai-internal.aliyuncs.com"); - Ok(()) - } - fn set_storage_class(&mut self, storage_class: &str) -> Result<(), MyError> { - assert_eq!(storage_class, "Standard"); - Ok(()) - } - } - - let xml = r#" - - - Disabled - - 2016-11-05T13:10:10.000Z - Disabled - LRS - oss-cn-shanghai.aliyuncs.com - oss-cn-shanghai-internal.aliyuncs.com - oss-cn-shanghai - foo - rg-foobar - Standard - Disabled - - 100889 - 3004212 - - - public-read - - - None - - - - - - - "#; - - let info = BucketA {}.decode(xml); - - assert!(info.is_ok()); - } -} -static mut BUCKETS_ITEM_ID: i8 = 0; -mod bucket_list_xml { - use super::MyError; - - use super::BUCKETS_ITEM_ID; - - #[test] - fn from_xml() { - use crate::decode::{RefineBucket, RefineBucketList}; - - #[derive(Default)] - struct BucketA {} - - impl RefineBucket for BucketA { - fn set_name(&mut self, name: &str) -> Result<(), MyError> { - unsafe { - if BUCKETS_ITEM_ID == 0 { - assert_eq!(name, "foo124442"); - } else if BUCKETS_ITEM_ID == 1 { - assert_eq!(name, "foo342390bar"); - } - } - - Ok(()) - } - fn set_creation_date(&mut self, creation_date: &str) -> Result<(), MyError> { - unsafe { - if BUCKETS_ITEM_ID == 0 { - assert_eq!(creation_date, "2020-09-13T03:14:54.000Z"); - } else if BUCKETS_ITEM_ID == 1 { - assert_eq!(creation_date, "2016-11-05T13:10:10.000Z"); - } - } - Ok(()) - } - fn set_location(&mut self, location: &str) -> Result<(), MyError> { - unsafe { - if BUCKETS_ITEM_ID == 0 { - assert_eq!(location, "oss-cn-shanghai"); - } else if BUCKETS_ITEM_ID == 1 { - assert_eq!(location, "oss-cn-shanghai"); - } - } - Ok(()) - } - fn set_extranet_endpoint(&mut self, extranet_endpoint: &str) -> Result<(), MyError> { - assert_eq!(extranet_endpoint, "oss-cn-shanghai.aliyuncs.com"); - Ok(()) - } - fn set_intranet_endpoint(&mut self, intranet_endpoint: &str) -> Result<(), MyError> { - assert_eq!(intranet_endpoint, "oss-cn-shanghai-internal.aliyuncs.com"); - Ok(()) - } - fn set_storage_class(&mut self, storage_class: &str) -> Result<(), MyError> { - assert_eq!(storage_class, "Standard"); - unsafe { - BUCKETS_ITEM_ID += 1; - } - Ok(()) - } - } - - struct ListA {} - - impl RefineBucketList for ListA { - fn set_prefix(&mut self, prefix: &str) -> Result<(), MyError> { - assert_eq!(prefix, ""); - Ok(()) - } - fn set_marker(&mut self, marker: &str) -> Result<(), MyError> { - assert_eq!(marker, ""); - Ok(()) - } - fn set_max_keys(&mut self, max_keys: &str) -> Result<(), MyError> { - assert_eq!(max_keys, ""); - Ok(()) - } - fn set_is_truncated(&mut self, is_truncated: bool) -> Result<(), MyError> { - assert_eq!(is_truncated, false); - Ok(()) - } - fn set_next_marker(&mut self, next_marker: &str) -> Result<(), MyError> { - assert_eq!(next_marker, ""); - Ok(()) - } - fn set_id(&mut self, id: &str) -> Result<(), MyError> { - assert_eq!(id, "100861222333"); - Ok(()) - } - fn set_display_name(&mut self, display_name: &str) -> Result<(), MyError> { - assert_eq!(display_name, "100861222"); - Ok(()) - } - } - - let xml = r#" - - - 100861222333 - 100861222 - - - - - 2020-09-13T03:14:54.000Z - oss-cn-shanghai.aliyuncs.com - oss-cn-shanghai-internal.aliyuncs.com - oss-cn-shanghai - foo124442 - cn-shanghai - Standard - - - - 2016-11-05T13:10:10.000Z - oss-cn-shanghai.aliyuncs.com - oss-cn-shanghai-internal.aliyuncs.com - oss-cn-shanghai - foo342390bar - cn-shanghai - Standard - - - "#; - - let mut list = ListA {}; - let res = list.decode(xml, |_| Some(BucketA {})); - - assert!(res.is_ok()); - } -} - -mod some_tests { - - use std::{borrow::Cow, fmt}; - - use crate::{ - decode::{InnerItemError, InnerListError, ListErrorKind, RefineObject, RefineObjectList}, - types::InvalidEndPoint, - }; - - use super::*; - - #[test] - fn test_one_object_decode() { - struct ObjectA {} - impl RefineObject for ObjectA { - fn set_key(&mut self, key: &str) -> Result<(), MyError> { - assert!(key == "LICENSE"); - Ok(()) - } - fn set_last_modified(&mut self, last_modified: &str) -> Result<(), MyError> { - assert!(last_modified == "2022-06-12T06:11:06.000Z"); - Ok(()) - } - fn set_etag(&mut self, etag: &str) -> Result<(), MyError> { - assert!(etag == "2CBAB10A50CC6905EA2D7CCCEF31A6C9"); - Ok(()) - } - fn set_type(&mut self, _type: &str) -> Result<(), MyError> { - assert!(_type == "Normal"); - Ok(()) - } - fn set_size(&mut self, size: &str) -> Result<(), MyError> { - assert!(size == "1065"); - Ok(()) - } - fn set_storage_class(&mut self, storage_class: &str) -> Result<(), MyError> { - assert!(storage_class == "Standard"); - Ok(()) - } - } - - #[derive(Debug)] - struct MyError {} - - impl From for MyError { - fn from(_: InnerItemError) -> Self { - MyError {} - } - } - - impl From for MyError { - fn from(_: quick_xml::Error) -> Self { - MyError {} - } - } - - impl fmt::Display for MyError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "demo") - } - } - impl std::error::Error for MyError {} - - let xml = r#"LICENSE - 2022-06-12T06:11:06.000Z - "2CBAB10A50CC6905EA2D7CCCEF31A6C9" - Normal - 1065 - Standard"#; - - let mut object = ObjectA {}; - let _ = object.decode(xml); - } - - #[test] - fn test_common_prefixes() { - use std::error::Error; - struct ObjectA {} - impl RefineObject for ObjectA {} - - struct ListA {} - - #[derive(Debug)] - struct MyError {} - - impl From for MyError { - fn from(_: InnerItemError) -> Self { - MyError {} - } - } - - impl From for MyError { - fn from(_: quick_xml::Error) -> Self { - MyError {} - } - } - - impl fmt::Display for MyError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "demo") - } - } - - impl Error for MyError {} - impl ListError for MyError {} - - impl RefineObjectList for ListA { - fn set_prefix(&mut self, prefix: &str) -> Result<(), MyError> { - assert!(prefix == "bar"); - Ok(()) - } - - fn set_common_prefix(&mut self, list: &[Cow<'_, str>]) -> Result<(), MyError> { - assert!(list[0] == "foo1/"); - assert!(list[1] == "foo2/"); - Ok(()) - } - } - - let xml = r#" - - bar - - 9AB932LY.jpeg - - - 9AB932LY.jpeg - - - foo1/ - foo2/ - - - "#; - - let mut list = ListA {}; - - let res = list.decode(xml, |_| -> Option { Some(ObjectA {}) }); - - assert!(res.is_ok()); - } - - #[test] - fn test_item_from() { - let string = InvalidEndPoint::new(); - let err = InnerItemError::from(string); - assert_eq!( - format!("{err}"), - "endpoint must not with `-` prefix or `-` suffix or `oss-` prefix" - ); - } - - #[test] - fn test_error_item_source() { - let err = InnerItemError::new(); - assert_eq!(format!("{}", err.get_source().unwrap()), "demo"); - } - - #[test] - fn test_list_from() { - let string = InvalidEndPoint::new(); - let err: InnerListError = string.into(); - assert_eq!( - format!("{err}"), - "endpoint must not with `-` prefix or `-` suffix or `oss-` prefix" - ); - } - - #[test] - fn test_error_list_display() { - let err = InnerItemError::new(); - let err_list: InnerListError = err.into(); - assert_eq!(err_list.to_string(), "demo"); - - let err = InnerListError::from_xml(); - assert_eq!(format!("{err}"), "Cannot read text, expecting Event::Text"); - - let string = InvalidEndPoint::new(); - let kind = ListErrorKind::Custom(Box::new(string)); - - let err = InnerListError { kind }; - assert_eq!( - format!("{err}"), - "endpoint must not with `-` prefix or `-` suffix or `oss-` prefix" - ); - } - - #[test] - fn test_error_list_get_source() { - let err = InnerItemError::new(); - let err_list: InnerListError = err.into(); - assert_eq!(format!("{}", err_list.get_source().unwrap()), "demo"); - - let err_list = InnerListError::from_xml(); - assert_eq!( - format!("{}", err_list.get_source().unwrap()), - "Cannot read text, expecting Event::Text" - ); - - let err_list = InnerListError::from_custom(); - assert_eq!(format!("{}", err_list.get_source().unwrap()), "custom"); - } - - #[test] - fn test_error_list_from_item() { - let err = InnerListError { - kind: ListErrorKind::Xml(Box::new(quick_xml::Error::TextNotFound)), - }; - assert_eq!(format!("{err}"), "Cannot read text, expecting Event::Text"); - - fn bar() -> InnerListError { - InnerItemError::new().into() - } - - assert_eq!( - format!("{:?}", bar()), - "InnerListError { kind: Item(InnerItemError(MyError)) }" - ); - } - - #[test] - fn test_error_list_from_xml() { - let err = InnerListError { - kind: ListErrorKind::Xml(Box::new(quick_xml::Error::TextNotFound)), - }; - assert_eq!(format!("{err}"), "Cannot read text, expecting Event::Text"); - - fn bar() -> InnerListError { - quick_xml::Error::TextNotFound.into() - } - - assert_eq!( - format!("{:?}", bar()), - "InnerListError { kind: Xml(TextNotFound) }" - ); - } -} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..cd4d3d9 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,44 @@ +use std::{ + fmt::{self, Display}, + num::ParseIntError, +}; + +use reqwest::header::{InvalidHeaderValue, ToStrError}; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum OssError { + Reqwest(#[from] reqwest::Error), + + HeaderValue(#[from] InvalidHeaderValue), + + Chrono(#[from] chrono::ParseError), + + ToStrError(#[from] ToStrError), + + NoFoundCreationDate, + + NoFoundStorageClass, + + NoFoundDataRedundancyType, + + NoFoundContentLength, + + NoFoundEtag, + + NoFoundLastModified, + + ParseIntError(#[from] ParseIntError), + + Upload(String), + + NoFoundBucket, + + ParseXml(#[from] serde_xml_rs::Error), +} + +impl Display for OssError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + "oss error".fmt(f) + } +} diff --git a/src/errors.rs b/src/errors.rs deleted file mode 100644 index c5e83de..0000000 --- a/src/errors.rs +++ /dev/null @@ -1,467 +0,0 @@ -//! 异常处理模块 - -use http::StatusCode; -use reqwest::Url; -use std::{ - fmt::{self, Display}, - io, -}; -use thiserror::Error; - -#[cfg(feature = "decode")] -use crate::decode::{InnerItemError, InnerListError}; -use crate::{ - auth::AuthError, - bucket::{BucketError, ExtractItemError}, - builder::BuilderError, - config::InvalidConfig, - file::FileError, - object::{ExtractListError, ObjectListError}, - types::{ - object::{InvalidObjectDir, InvalidObjectPath}, - InvalidBucketName, InvalidEndPoint, - }, -}; - -/// aliyun-oss-client Error -#[derive(Debug)] -#[non_exhaustive] -pub struct OssError { - kind: OssErrorKind, -} - -impl Display for OssError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.kind.fmt(f) - } -} -impl std::error::Error for OssError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - use OssErrorKind::*; - match &self.kind { - Io(e) => Some(e), - #[cfg(test)] - Dotenv(e) => Some(e), - Builder(e) => Some(e), - EndPoint(e) => Some(e), - BucketName(e) => Some(e), - Config(e) => Some(e), - ObjectPath(e) => Some(e), - ObjectDir(e) => Some(e), - BuildInItemError(e) => Some(e), - InnerList(e) => e.get_source(), - InnerItem(e) => e.get_source(), - ExtractList(e) => Some(e), - ExtractItem(e) => Some(e), - File(e) => Some(e), - Auth(e) => Some(e), - Bucket(e) => Some(e), - ObjectList(e) => Some(e), - } - } -} - -impl> From for OssError { - fn from(value: T) -> Self { - Self { kind: value.into() } - } -} - -/// 内置的 Error 集合 -#[derive(Debug, Error)] -#[non_exhaustive] -enum OssErrorKind { - #[error("io error")] - Io(#[from] io::Error), - - #[doc(hidden)] - #[error("dotenv error")] - #[cfg(test)] - Dotenv(#[from] dotenv::Error), - - #[doc(hidden)] - #[error("builder error")] - Builder(#[from] BuilderError), - - #[doc(hidden)] - #[error("invalid endpoint")] - EndPoint(#[from] InvalidEndPoint), - - #[doc(hidden)] - #[error("invalid bucket name")] - BucketName(#[from] InvalidBucketName), - - #[doc(hidden)] - #[error("invalid config")] - Config(#[from] InvalidConfig), - - #[doc(hidden)] - #[error("invalid object path")] - ObjectPath(#[from] InvalidObjectPath), - - #[doc(hidden)] - #[error("invalid object dir")] - ObjectDir(#[from] InvalidObjectDir), - - #[doc(hidden)] - #[error("build in item error")] - BuildInItemError(#[from] crate::object::BuildInItemError), - - #[cfg(feature = "decode")] - #[doc(hidden)] - #[error("decode into list error")] - InnerList(InnerListError), - - #[cfg(feature = "decode")] - #[doc(hidden)] - #[error("decode into list error")] - InnerItem(InnerItemError), - - #[doc(hidden)] - #[error("extract list error")] - ExtractList(#[from] ExtractListError), - - #[doc(hidden)] - #[error("extract item error")] - ExtractItem(#[from] ExtractItemError), - - #[error("file error")] - File(#[from] FileError), - - #[error("auth error")] - Auth(#[from] AuthError), - - // bucket 还有其他 Error - #[error("bucket error")] - Bucket(#[from] BucketError), - - #[error("object list error")] - ObjectList(#[from] ObjectListError), -} - -#[cfg(feature = "decode")] -impl From for OssErrorKind { - fn from(value: InnerListError) -> Self { - Self::InnerList(value) - } -} - -#[cfg(feature = "decode")] -impl From for OssErrorKind { - fn from(value: InnerItemError) -> Self { - Self::InnerItem(value) - } -} - -/// # 保存并返回 OSS 服务端返回是数据 -/// 当服务器返回的状态码不在 200<=x 且 x<300 范围时,则会返回此错误 -/// -/// 如果解析 xml 格式错误,则会返回默认值,默认值的 status = 200 -#[derive(Debug, Error, PartialEq, Eq, Hash)] -pub struct OssService { - pub(crate) code: String, - status: StatusCode, - message: String, - request_id: String, - url: Url, -} - -impl Default for OssService { - fn default() -> Self { - Self { - code: "Undefined".to_owned(), - status: StatusCode::default(), - message: "Parse aliyun response xml error message failed.".to_owned(), - request_id: "XXXXXXXXXXXXXXXXXXXXXXXX".to_owned(), - url: { - #[allow(clippy::unwrap_used)] - "https://oss.aliyuncs.com".parse().unwrap() - }, - } - } -} - -impl fmt::Display for OssService { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("OssService") - .field("code", &self.code) - .field("status", &self.status) - .field("message", &self.message) - .field("request_id", &self.request_id) - .field("url", &self.url.as_str()) - .finish() - } -} - -impl AsRef for OssService { - fn as_ref(&self) -> &Url { - &self.url - } -} - -impl<'a> OssService { - /// 解析 oss 的错误信息 - pub fn new(source: &'a str, status: &StatusCode, url: &Url) -> Self { - match ( - source.find(""), - source.find(""), - source.find(""), - source.find(""), - source.find(""), - source.find(""), - ) { - ( - Some(code0), - Some(code1), - Some(message0), - Some(message1), - Some(request_id0), - Some(request_id1), - ) => { - let code = &source[code0 + 6..code1].to_owned(); - let mut message = source[message0 + 9..message1].to_owned(); - if code == "SignatureDoesNotMatch" { - message.push_str(&format!( - "expect sign string is \"{}\"", - Self::sign_string(source) - )); - } - Self { - code: code.to_owned(), - status: *status, - message, - request_id: source[request_id0 + 11..request_id1].to_owned(), - url: url.to_owned(), - } - } - _ => Self::default(), - } - } - - /// 解析 oss 的错误信息 - pub fn new2(source: String, status: &StatusCode, url: Url) -> Self { - match ( - source.find(""), - source.find(""), - source.find(""), - source.find(""), - source.find(""), - source.find(""), - ) { - ( - Some(code0), - Some(code1), - Some(message0), - Some(message1), - Some(request_id0), - Some(request_id1), - ) => { - let code = &source[code0 + 6..code1].to_owned(); - let mut message = source[message0 + 9..message1].to_owned(); - if code == "SignatureDoesNotMatch" { - message.push_str(&format!( - "expect sign string is \"{}\"", - Self::sign_string(&source) - )); - } - Self { - code: code.to_owned(), - status: *status, - message, - request_id: source[request_id0 + 11..request_id1].to_owned(), - url, - } - } - _ => Self::default(), - } - } - - /// 返回报错接口的 url - pub fn url(&self) -> &Url { - &self.url - } - - fn sign_string(s: &str) -> &str { - if let (Some(start), Some(end)) = (s.find(""), s.find("")) { - &s[start + 14..end] - } else { - &s[0..0] - } - } -} - -impl From for std::io::Error { - fn from(err: OssService) -> Self { - use std::io::ErrorKind; - let kind = if err.status.is_client_error() { - ErrorKind::PermissionDenied - } else if err.status.is_server_error() { - ErrorKind::ConnectionReset - } else { - ErrorKind::ConnectionAborted - }; - Self::new(kind, err) - } -} - -/// 内置的 Result -pub type OssResult = Result; - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn oss_service_display() { - assert_eq!( - format!( - "{}", - OssService { - code: "abc".to_owned(), - status: StatusCode::OK, - message: "mes1".to_owned(), - request_id: "xx".to_owned(), - url: "https://oss.aliyuncs.com".parse().unwrap() - } - ), - "OssService { code: \"abc\", status: 200, message: \"mes1\", request_id: \"xx\", url: \"https://oss.aliyuncs.com/\" }" - ); - } - - #[test] - fn oss_service_default() { - let oss = OssService::default(); - assert_eq!(oss.code, "Undefined".to_string()); - assert_eq!(oss.status, StatusCode::OK); - assert_eq!( - oss.message, - "Parse aliyun response xml error message failed.".to_owned() - ); - assert_eq!(oss.request_id, "XXXXXXXXXXXXXXXXXXXXXXXX".to_owned()); - } - - #[test] - fn test_oss_service_fmt() { - let oss_err = OssService { - code: "OSS_TEST_CODE".to_string(), - status: StatusCode::default(), - message: "foo_msg".to_string(), - request_id: "foo_req_id".to_string(), - url: "https://oss.aliyuncs.com".parse().unwrap(), - }; - - assert_eq!( - format!("{}", oss_err), - "OssService { code: \"OSS_TEST_CODE\", status: 200, message: \"foo_msg\", request_id: \"foo_req_id\", url: \"https://oss.aliyuncs.com/\" }" - .to_string() - ); - let url = oss_err.as_ref(); - assert_eq!(*url, Url::parse("https://oss.aliyuncs.com").unwrap()); - let url = oss_err.url(); - assert_eq!(*url, Url::parse("https://oss.aliyuncs.com").unwrap()); - } - - #[test] - fn test_oss_service_new() { - let content = r#" - - RequestTimeTooSkewed - bar - 63145DB90BFD85303279D56B - xxx.oss-cn-shanghai.aliyuncs.com - 900000 - 2022-09-04T07:11:33.000Z - 2022-09-04T08:11:37.000Z - - "#; - let url = "https://oss.aliyuncs.com".parse().unwrap(); - let service = OssService::new(content, &StatusCode::default(), &url); - assert_eq!(service.code, format!("RequestTimeTooSkewed")); - assert_eq!(service.message, format!("bar")); - assert_eq!(service.request_id, format!("63145DB90BFD85303279D56B")) - } - - #[test] - fn test_sign_match() { - let xml = r#" - - SignatureDoesNotMatch - The request signature we calculated does not match the signature you provided. Check your key and signing method. - 64C9CF1C8B62C239371D3E6B - xxx.oss-cn-shanghai.aliyuncs.com - 9js44GwYF9P2ZFs4 - ZZ3e/hrGjFpOxRkDg+ugKVGMyoc= - PUT - - -Wed, 02 Aug 2023 03:35:56 GMT -/xxx/aaabbb.txt?partNumber=1 - 50 55 54 0A 0A 0A 57 65 64 2C 20 30 32 20 41 75 67 20 32 30 32 33 20 30 33 3A 33 35 3A 35 36 20 47 4D 54 0A 2F 68 6F 6E 67 6C 65 69 31 32 33 2F 61 61 61 62 62 62 2E 74 78 74 3F 70 61 72 74 4E 75 6D 62 65 72 3D 31 - 0002-00000040 - "#; - let url = "https://oss.aliyuncs.com".parse().unwrap(); - let service = OssService::new(xml, &StatusCode::default(), &url); - assert_eq!(service.code, format!("SignatureDoesNotMatch")); - assert_eq!(service.message, format!("The request signature we calculated does not match the signature you provided. Check your key and signing method.expect sign string is \"PUT\n\n\nWed, 02 Aug 2023 03:35:56 GMT\n/xxx/aaabbb.txt?partNumber=1\"")); - assert_eq!(service.request_id, format!("64C9CF1C8B62C239371D3E6B")); - - let service = OssService::new2(xml.to_owned(), &StatusCode::default(), url); - assert_eq!(service.code, format!("SignatureDoesNotMatch")); - assert_eq!(service.message, format!("The request signature we calculated does not match the signature you provided. Check your key and signing method.expect sign string is \"PUT\n\n\nWed, 02 Aug 2023 03:35:56 GMT\n/xxx/aaabbb.txt?partNumber=1\"")); - assert_eq!(service.request_id, format!("64C9CF1C8B62C239371D3E6B")) - } - - #[test] - fn oss_service_new() { - let url = "https://oss.aliyuncs.com".parse().unwrap(); - assert_eq!( - OssService::new("abc", &StatusCode::OK, &url), - OssService::default() - ); - - assert_eq!( - OssService::new2("abc".to_string(), &StatusCode::OK, url), - OssService::default() - ); - } - - #[test] - fn sign_string() { - assert_eq!(OssService::sign_string("aaa"), ""); - } - - #[test] - fn from_oss_service() { - use std::io::{Error, ErrorKind}; - let url: Url = "https://oss.aliyuncs.com".parse().unwrap(); - let oss = OssService { - code: "aaa".to_string(), - message: "aaa".to_string(), - status: StatusCode::BAD_REQUEST, - request_id: "bbb".to_string(), - url: url.clone(), - }; - let io_err = Error::from(oss); - assert_eq!(io_err.kind(), ErrorKind::PermissionDenied); - - let oss = OssService { - code: "aaa".to_string(), - message: "aaa".to_string(), - status: StatusCode::BAD_GATEWAY, - request_id: "bbb".to_string(), - url: url.clone(), - }; - let io_err = Error::from(oss); - assert_eq!(io_err.kind(), ErrorKind::ConnectionReset); - - let oss = OssService { - code: "aaa".to_string(), - message: "aaa".to_string(), - status: StatusCode::NOT_MODIFIED, - request_id: "bbb".to_string(), - url: url.clone(), - }; - let io_err = Error::from(oss); - assert_eq!(io_err.kind(), ErrorKind::ConnectionAborted); - } -} diff --git a/src/file.rs b/src/file.rs deleted file mode 100644 index a703535..0000000 --- a/src/file.rs +++ /dev/null @@ -1,974 +0,0 @@ -//! # OSS 文件相关操作 -//! -//! [`File`] 是一个文件操作的工具包,包含上传,下载,删除功能,开发者可以方便的调用使用 -//! -//! ```rust -//! use std::{fs, io, path::Path}; -//! -//! use aliyun_oss_client::{ -//! config::get_url_resource, -//! errors::OssError, -//! file::{File, FileError, GetStd}, -//! types::CanonicalizedResource, -//! BucketName, Client, EndPoint, KeyId, KeySecret, -//! }; -//! use reqwest::Url; -//! -//! struct MyObject { -//! path: String, -//! } -//! -//! impl MyObject { -//! const KEY_ID: KeyId = KeyId::from_static("xxxxxxxxxxxxxxxx"); -//! const KEY_SECRET: KeySecret = KeySecret::from_static("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); -//! const END_POINT: EndPoint = EndPoint::SHANGHAI; -//! const BUCKET: BucketName = unsafe { BucketName::from_static2("xxxxxx") }; -//! -//! fn new(path: &Path) -> Result { -//! Ok(MyObject { -//! path: path.to_str().unwrap().to_owned(), -//! }) -//! } -//! } -//! -//! impl GetStd for MyObject { -//! fn get_std(&self) -> Option<(Url, CanonicalizedResource)> { -//! let path = self.path.clone().try_into().unwrap(); -//! Some(get_url_resource(&Self::END_POINT, &Self::BUCKET, &path)) -//! } -//! } -//! -//! impl File for MyObject { -//! fn oss_client(&self) -> Client { -//! Client::new( -//! Self::KEY_ID, -//! Self::KEY_SECRET, -//! Self::END_POINT, -//! Self::BUCKET, -//! ) -//! } -//! } -//! -//! async fn run() -> Result<(), OssError> { -//! for entry in fs::read_dir("examples")? { -//! let path = entry?.path(); -//! let path = path.as_path(); -//! -//! if !path.is_file() { -//! continue; -//! } -//! -//! let obj = MyObject::new(path)?; -//! let content = fs::read(path)?; -//! -//! let res = obj -//! .put_oss(content, "application/pdf") -//! .await -//! .map_err(OssError::from)?; -//! -//! println!("result status: {}", res.status()); -//! } -//! -//! Ok(()) -//! } -//! ``` -//! [`File`]: crate::file::File - -use async_trait::async_trait; -use http::{ - header::{HeaderName, CONTENT_LENGTH, CONTENT_TYPE}, - HeaderValue, Method, -}; -use reqwest::{Response, Url}; - -use crate::{ - bucket::Bucket, - builder::{ArcPointer, BuilderError, RequestBuilder}, - object::{Object, ObjectList}, - types::object::{ObjectBase, ObjectPath}, - types::{CanonicalizedResource, ContentRange}, -}; -#[cfg(feature = "put_file")] -use infer::Infer; - -#[cfg(test)] -mod test; - -const ETAG: &str = "ETag"; -const RANGE: &str = "Range"; - -/// # 文件的相关操作 -/// -/// 包括 上传,下载,删除等功能 -#[async_trait] -pub trait File: GetStd -where - Client: Files, -{ - /// 指定发起 OSS 接口调用的客户端 - fn oss_client(&self) -> Client; - - /// 上传文件内容到 OSS 上面 - async fn put_oss(&self, content: Vec, content_type: &str) -> Result { - let (url, canonicalized) = self.get_std().ok_or(FileError { - kind: FileErrorKind::NotFoundCanonicalizedResource, - })?; - - let content_length = content.len().to_string(); - let headers = vec![ - (CONTENT_LENGTH, header_from_content_length(&content_length)?), - ( - CONTENT_TYPE, - content_type.parse().map_err(|e| FileError { - kind: FileErrorKind::InvalidContentType(e), - })?, - ), - ]; - - self.oss_client() - .builder_with_header(Method::PUT, url, canonicalized, headers)? - .body(content) - .send_adjust_error() - .await - .map_err(FileError::from) - } - - /// # 获取 OSS 上文件的部分或全部内容 - /// 参数可指定范围: - /// - `..` 获取文件的所有内容,常规大小的文件,使用这个即可 - /// - `..100`, `100..200`, `200..` 可用于获取文件的部分内容,一般用于大文件 - async fn get_oss(&self, range: R) -> Result, FileError> - where - R: Into> + Send + Sync, - ContentRange: Into, - { - let (url, canonicalized) = self.get_std().ok_or(FileError { - kind: FileErrorKind::NotFoundCanonicalizedResource, - })?; - - let list: Vec<(_, HeaderValue)> = vec![( - { - #[allow(clippy::unwrap_used)] - RANGE.parse().unwrap() - }, - range.into().into(), - )]; - - let content = self - .oss_client() - .builder_with_header(Method::GET, url, canonicalized, list)? - .send_adjust_error() - .await? - .bytes() - .await?; - - Ok(content.to_vec()) - } - - /// # 从 OSS 中删除文件 - async fn delete_oss(&self) -> Result<(), FileError> { - let (url, canonicalized) = self.get_std().ok_or(FileError { - kind: FileErrorKind::NotFoundCanonicalizedResource, - })?; - - self.oss_client() - .builder(Method::DELETE, url, canonicalized)? - .send_adjust_error() - .await?; - - Ok(()) - } -} - -/// 获取请求 OSS 接口需要的信息 -pub trait GetStd { - /// 获取 `Url` 和 `CanonicalizedResource` - fn get_std(&self) -> Option<(Url, CanonicalizedResource)>; -} - -impl GetStd for ObjectBase { - fn get_std(&self) -> Option<(Url, CanonicalizedResource)> { - Some(self.get_url_resource([])) - } -} - -impl GetStd for Object { - fn get_std(&self) -> Option<(Url, CanonicalizedResource)> { - Some(self.base.get_url_resource([])) - } -} - -/// 根据给定路径,获取请求 OSS 接口需要的信息 -pub trait GetStdWithPath { - /// 根据 path 获取 `Url` 和 `CanonicalizedResource` - fn get_std_with_path(&self, _path: Path) -> Option<(Url, CanonicalizedResource)>; -} - -#[doc(hidden)] -pub mod std_path_impl { - #[cfg(feature = "blocking")] - use crate::builder::RcPointer; - #[cfg(feature = "blocking")] - use crate::client::ClientRc; - - use super::{GetStd, GetStdWithPath}; - use crate::{ - bucket::Bucket, - builder::ArcPointer, - client::ClientArc, - config::{get_url_resource2 as get_url_resource, BucketBase}, - decode::RefineObject, - object::{BuildInItemError, ObjectList}, - types::{object::ObjectBase, CanonicalizedResource}, - ObjectPath, - }; - use oss_derive::oss_gen_rc; - use reqwest::Url; - - /// # 用于在 Client 上对文件进行操作 - /// - /// 文件路径可以是 `String` 类型 - /// - /// [`ObjectPath`]: crate::ObjectPath - #[oss_gen_rc] - impl GetStdWithPath for ClientArc { - fn get_std_with_path(&self, path: String) -> Option<(Url, CanonicalizedResource)> { - let object_path = path.try_into().ok()?; - Some(get_url_resource(self, self, &object_path)) - } - } - - /// # 用于在 Client 上对文件进行操作 - /// - /// 文件路径可以是 `&str` 类型 - /// - /// [`ObjectPath`]: crate::ObjectPath - #[oss_gen_rc] - impl GetStdWithPath<&str> for ClientArc { - fn get_std_with_path(&self, path: &str) -> Option<(Url, CanonicalizedResource)> { - let object_path = path.try_into().ok()?; - Some(get_url_resource(self, self, &object_path)) - } - } - - /// # 用于在 Client 上对文件进行操作 - /// - /// 文件路径可以是 [`ObjectPath`] 类型 - /// - /// [`ObjectPath`]: crate::ObjectPath - #[oss_gen_rc] - impl GetStdWithPath for ClientArc { - fn get_std_with_path(&self, path: ObjectPath) -> Option<(Url, CanonicalizedResource)> { - Some(get_url_resource(self, self, &path)) - } - } - - /// # 用于在 Client 上对文件进行操作 - /// - /// 文件路径可以是 [`&ObjectPath`] 类型 - /// - /// [`&ObjectPath`]: crate::ObjectPath - #[oss_gen_rc] - impl> GetStdWithPath for ClientArc { - fn get_std_with_path(&self, path: Path) -> Option<(Url, CanonicalizedResource)> { - Some(get_url_resource(self, self, path.as_ref())) - } - } - - /// # 用于在 Bucket 上对文件进行操作 - /// - /// 文件路径可以是 `String` 类型 - impl> GetStdWithPath for B { - fn get_std_with_path(&self, path: String) -> Option<(Url, CanonicalizedResource)> { - let path = path.try_into().ok()?; - Some(self.as_ref().get_url_resource_with_path(&path)) - } - } - - /// # 用于在 Bucket 上对文件进行操作 - /// - /// 文件路径可以是 `&str` 类型 - impl> GetStdWithPath<&str> for B { - fn get_std_with_path(&self, path: &str) -> Option<(Url, CanonicalizedResource)> { - let path = path.try_into().ok()?; - Some(self.as_ref().get_url_resource_with_path(&path)) - } - } - - /// # 用于在 Bucket 上对文件进行操作 - /// - /// 文件路径可以是 [`ObjectPath`] 类型 - /// - /// [`ObjectPath`]: crate::ObjectPath - impl> GetStdWithPath for B { - #[inline] - fn get_std_with_path(&self, path: ObjectPath) -> Option<(Url, CanonicalizedResource)> { - Some(self.as_ref().get_url_resource_with_path(&path)) - } - } - - /// # 用于在 Bucket 上对文件进行操作 - /// - /// 文件路径可以是 [`&ObjectPath`] 类型 - /// - /// [`&ObjectPath`]: crate::ObjectPath - impl> GetStdWithPath<&ObjectPath> for B { - #[inline] - fn get_std_with_path(&self, path: &ObjectPath) -> Option<(Url, CanonicalizedResource)> { - Some(self.as_ref().get_url_resource_with_path(path)) - } - } - - /// # 用于在 Bucket 上对文件进行操作 - /// - /// 文件路径可以是 [`ObjectBase`] 类型 - /// - /// [`ObjectBase`]: crate::types::object::ObjectBase - #[oss_gen_rc] - impl GetStdWithPath> for Bucket { - #[inline] - fn get_std_with_path( - &self, - base: ObjectBase, - ) -> Option<(Url, CanonicalizedResource)> { - Some(base.get_url_resource([])) - } - } - - /// # 用于在 Bucket 上对文件进行操作 - /// - /// 文件路径可以是 [`&ObjectBase`] 类型 - /// - /// [`&ObjectBase`]: crate::types::object::ObjectBase - #[oss_gen_rc] - impl GetStdWithPath<&ObjectBase> for Bucket { - #[inline] - fn get_std_with_path( - &self, - base: &ObjectBase, - ) -> Option<(Url, CanonicalizedResource)> { - Some(base.get_url_resource([])) - } - } - - /// # 用于在 ObjectList 上对文件进行操作 - /// - /// 文件路径可以是实现 [`GetStd`] 特征的类型 - /// - /// [`GetStd`]: crate::file::GetStd - /// - /// TODO remove Send + Sync - impl + Send + Sync, U: GetStd> GetStdWithPath - for ObjectList - { - #[inline] - fn get_std_with_path(&self, path: U) -> Option<(Url, CanonicalizedResource)> { - path.get_std() - } - } -} - -/// 默认 content-type -pub const DEFAULT_CONTENT_TYPE: &str = "application/octet-stream"; - -/// # 文件集合的相关操作 -/// 在对文件执行相关操作的时候,需要指定文件路径 -/// -/// 包括 上传,下载,删除等功能 -/// 在 [`Client`],[`Bucket`], [`ObjectList`] 等结构体中均已实现,其中 Client 是在默认的 bucket 上操作文件, -/// 而 Bucket, ObjectList 则是在当前的 bucket 上操作文件 -/// -/// [`Client`]: crate::client::Client -/// [`Bucket`]: crate::bucket::Bucket -/// [`ObjectList`]: crate::object::ObjectList -#[async_trait] -pub trait Files: AlignBuilder + GetStdWithPath -where - Path: Send + Sync + 'static, -{ - /// # 默认的文件类型 - /// 在上传文件时,如果找不到合适的 mime 类型,可以使用 - const DEFAULT_CONTENT_TYPE: &'static str = DEFAULT_CONTENT_TYPE; - - /// # 上传文件到 OSS - /// - /// 需指定文件的路径 - /// - /// *如果获取不到文件类型,则使用默认的文件类型,如果您不希望这么做,可以使用 `put_content_base` 方法* - #[cfg(feature = "put_file")] - async fn put_file< - P: Into + std::convert::AsRef + Send + Sync, - >( - &self, - file_name: P, - path: Path, - ) -> Result { - let file_content = std::fs::read(file_name).map_err(|e| FileError { - kind: error_impl::FileErrorKind::FileRead(e), - })?; - - let get_content_type = - |content: &Vec| Infer::new().get(content).map(|con| con.mime_type()); - - self.put_content(file_content, path, get_content_type).await - } - - /// # 上传文件内容到 OSS - /// - /// 需指定要上传的文件内容 - /// 以及根据文件内容获取文件类型的闭包 - /// - /// *如果获取不到文件类型,则使用默认的文件类型,如果您不希望这么做,可以使用 `put_content_base` 方法* - /// - /// # Examples - /// - /// 上传 tauri 升级用的签名文件 - /// ```no_run - /// # #[tokio::main] - /// # async fn main(){ - /// use infer::Infer; - /// # use dotenv::dotenv; - /// # dotenv().ok(); - /// # let client = aliyun_oss_client::Client::from_env().unwrap(); - /// use aliyun_oss_client::file::Files; - /// - /// fn sig_match(buf: &[u8]) -> bool { - /// return buf.len() >= 3 && buf[0] == 0x64 && buf[1] == 0x57 && buf[2] == 0x35; - /// } - /// let mut infer = Infer::new(); - /// infer.add("application/pgp-signature", "sig", sig_match); - /// - /// let get_content_type = |content: &Vec| match infer.get(content) { - /// Some(con) => Some(con.mime_type()), - /// None => None, - /// }; - /// let content: Vec = String::from("dW50cnVzdGVkIGNvbW1lbnQ6IHNpxxxxxxxxx").into_bytes(); - /// let res = client - /// .put_content(content, "xxxxxx.msi.zip.sig", get_content_type) - /// .await; - /// assert!(res.is_ok()); - /// # } - /// ``` - async fn put_content( - &self, - content: Vec, - path: Path, - get_content_type: F, - ) -> Result - where - F: Fn(&Vec) -> Option<&'static str> + Send + Sync, - { - let content_type = get_content_type(&content).unwrap_or(Self::DEFAULT_CONTENT_TYPE); - - let content = self.put_content_base(content, content_type, path).await?; - - let result = content - .headers() - .get(ETAG) - .ok_or(FileError { - kind: FileErrorKind::EtagNotFound, - })? - .to_str() - .map_err(|e| FileError { - kind: FileErrorKind::InvalidEtag(e), - })?; - - // TODO change to result[1..33].to_string() - // 不能使用该方案,返回的etag长度不固定 - Ok(result.to_string()) - } - - /// 最核心的上传文件到 OSS 的方法 - async fn put_content_base( - &self, - content: Vec, - content_type: &str, - path: Path, - ) -> Result { - let (url, canonicalized) = self.get_std_with_path(path).ok_or(FileError { - kind: FileErrorKind::NotFoundCanonicalizedResource, - })?; - - let content_length = content.len().to_string(); - let headers = vec![ - (CONTENT_LENGTH, header_from_content_length(&content_length)?), - ( - CONTENT_TYPE, - content_type.parse().map_err(|e| FileError { - kind: FileErrorKind::InvalidContentType(e), - })?, - ), - ]; - - self.builder_with_header(Method::PUT, url, canonicalized, headers)? - .body(content) - //.timeout(std::time::Duration::new(3, 0)) - .send_adjust_error() - .await - .map_err(FileError::from) - } - - /// # 获取 OSS 上文件的部分或全部内容 - async fn get_object(&self, path: Path, range: R) -> Result, FileError> - where - R: Into> + Send + Sync, - ContentRange: Into, - { - let (url, canonicalized) = self.get_std_with_path(path).ok_or(FileError { - kind: FileErrorKind::NotFoundCanonicalizedResource, - })?; - - let list: Vec<(_, HeaderValue)> = vec![( - { - #[allow(clippy::unwrap_used)] - RANGE.parse().unwrap() - }, - range.into().into(), - )]; - - let content = self - .builder_with_header(Method::GET, url, canonicalized, list)? - .send_adjust_error() - .await? - .bytes() - .await?; - - Ok(content.to_vec()) - } - - /// # 删除 OSS 上的文件 - async fn delete_object(&self, path: Path) -> Result<(), FileError> { - let (url, canonicalized) = self.get_std_with_path(path).ok_or(FileError { - kind: FileErrorKind::NotFoundCanonicalizedResource, - })?; - - self.builder(Method::DELETE, url, canonicalized)? - .send_adjust_error() - .await?; - - Ok(()) - } -} - -fn header_from_content_length(content: &str) -> Result { - HeaderValue::from_str(content).map_err(|e| FileError { - kind: FileErrorKind::InvalidContentLength(e), - }) -} - -/// # 为更多的类型实现 上传,下载,删除等功能 -/// -/// 在 [`Client`],[`Bucket`], [`ObjectList`] 等结构体中均已实现,其中 Client 是在默认的 bucket 上操作文件, -/// 而 Bucket, ObjectList 则是在当前的 bucket 上操作文件 -/// -/// [`Client`]: crate::client::Client -/// [`Bucket`]: crate::bucket::Bucket -/// [`ObjectList`]: crate::object::ObjectList -impl> Files

for T {} - -/// 文件模块的 Error -#[derive(Debug)] -pub struct FileError { - kind: error_impl::FileErrorKind, -} - -impl FileError { - #[cfg(test)] - pub(crate) fn test_new() -> Self { - Self { - kind: error_impl::FileErrorKind::EtagNotFound, - } - } -} - -/// 文件模块的 Error 实现方法 -mod error_impl { - use std::{error::Error, fmt::Display, io::ErrorKind}; - - use http::header::InvalidHeaderValue; - - use crate::builder::{reqwest_to_io, BuilderError}; - - use super::FileError; - - impl Display for FileError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use FileErrorKind::*; - match &self.kind { - #[cfg(feature = "put_file")] - FileRead(_) => write!(f, "file read failed"), - InvalidContentLength(_) => write!(f, "invalid content length"), - InvalidContentType(_) => write!(f, "invalid content type"), - Build(to) => write!(f, "{to}"), - Reqwest(_) => write!(f, "reqwest error"), - EtagNotFound => write!(f, "failed to get etag"), - InvalidEtag(_) => write!(f, "invalid etag"), - NotFoundCanonicalizedResource => write!(f, "not found canonicalized-resource"), - } - } - } - - impl Error for FileError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - use FileErrorKind::*; - match &self.kind { - #[cfg(feature = "put_file")] - FileRead(e) => Some(e), - InvalidContentLength(e) | InvalidContentType(e) => Some(e), - Build(e) => e.source(), - Reqwest(e) => Some(e), - InvalidEtag(e) => Some(e), - EtagNotFound | NotFoundCanonicalizedResource => None, - } - } - } - - #[derive(Debug)] - pub(super) enum FileErrorKind { - #[cfg(feature = "put_file")] - FileRead(std::io::Error), - InvalidContentLength(InvalidHeaderValue), - InvalidContentType(InvalidHeaderValue), - Build(BuilderError), - Reqwest(reqwest::Error), - EtagNotFound, - InvalidEtag(http::header::ToStrError), - NotFoundCanonicalizedResource, - } - - impl From for FileError { - fn from(value: BuilderError) -> Self { - Self { - kind: FileErrorKind::Build(value), - } - } - } - - impl From for FileError { - fn from(value: reqwest::Error) -> Self { - Self { - kind: FileErrorKind::Reqwest(value), - } - } - } - - impl From for std::io::Error { - fn from(FileError { kind }: FileError) -> Self { - kind.into() - } - } - - impl From for std::io::Error { - fn from(value: FileErrorKind) -> Self { - match value { - #[cfg(feature = "put_file")] - FileErrorKind::FileRead(e) => e, - FileErrorKind::InvalidContentLength(_) => { - Self::new(ErrorKind::InvalidData, "invalid content length") - } - FileErrorKind::InvalidContentType(_) => { - Self::new(ErrorKind::InvalidData, "invalid content type") - } - FileErrorKind::Build(e) => e.into(), - FileErrorKind::Reqwest(e) => reqwest_to_io(e), - FileErrorKind::EtagNotFound => Self::new(ErrorKind::Interrupted, "etag not found"), - FileErrorKind::InvalidEtag(_) => Self::new(ErrorKind::Interrupted, "invalid etag"), - FileErrorKind::NotFoundCanonicalizedResource => { - Self::new(ErrorKind::InvalidData, "not found canonicalized resource") - } - } - } - } -} - -/// # 对齐 [`Client`],[`Bucket`], [`ObjectList`] 等结构体的 trait -/// -/// 用于他们方便的实现 [`Files`] trait -/// -/// [`Files`]: self::Files -/// [`Client`]: crate::client::Client -/// [`Bucket`]: crate::bucket::Bucket -/// [`ObjectList`]: crate::object::ObjectList -pub trait AlignBuilder: Send + Sync { - /// 根据具体的 API 接口参数,返回请求的构建器(不带 headers) - #[inline] - fn builder( - &self, - method: Method, - url: Url, - resource: CanonicalizedResource, - ) -> Result { - self.builder_with_header(method, url, resource, []) - } - - /// 根据具体的 API 接口参数,返回请求的构建器 - fn builder_with_header>( - &self, - method: Method, - url: Url, - resource: CanonicalizedResource, - headers: H, - ) -> Result; -} - -impl AlignBuilder for Bucket { - #[inline] - fn builder_with_header>( - &self, - method: Method, - url: Url, - resource: CanonicalizedResource, - headers: H, - ) -> Result { - self.client() - .builder_with_header(method, url, resource, headers) - } -} - -impl AlignBuilder for ObjectList { - #[inline] - fn builder_with_header>( - &self, - method: Method, - url: Url, - resource: CanonicalizedResource, - headers: H, - ) -> Result { - self.client() - .builder_with_header(method, url, resource, headers) - } -} - -#[cfg(feature = "blocking")] -pub use blocking::Files as BlockingFiles; - -use self::error_impl::FileErrorKind; - -/// 同步的文件模块 -#[cfg(feature = "blocking")] -pub mod blocking { - - use super::{ - error_impl::FileErrorKind, header_from_content_length, FileError, GetStdWithPath, ETAG, - RANGE, - }; - use crate::{ - blocking::builder::RequestBuilder, - bucket::Bucket, - builder::{BuilderError, RcPointer}, - object::ObjectList, - types::{CanonicalizedResource, ContentRange}, - }; - use http::{ - header::{HeaderName, CONTENT_LENGTH, CONTENT_TYPE}, - HeaderValue, Method, - }; - #[cfg(feature = "put_file")] - use infer::Infer; - use reqwest::{blocking::Response, Url}; - - /// # 文件集合的相关操作 - /// 在对文件执行相关操作的时候,需要指定文件路径 - pub trait Files: AlignBuilder + GetStdWithPath { - /// # 默认的文件类型 - /// 在上传文件时,如果找不到合适的 mime 类型,可以使用 - const DEFAULT_CONTENT_TYPE: &'static str = super::DEFAULT_CONTENT_TYPE; - - /// # 上传文件到 OSS - /// - /// 需指定文件的路径 - #[cfg(feature = "put_file")] - fn put_file + std::convert::AsRef>( - &self, - file_name: P, - path: Path, - ) -> Result { - let file_content = std::fs::read(file_name).map_err(|e| FileError { - kind: FileErrorKind::FileRead(e), - })?; - - let get_content_type = - |content: &Vec| Infer::new().get(content).map(|con| con.mime_type()); - - self.put_content(file_content, path, get_content_type) - } - - /// # 上传文件内容到 OSS - /// - /// 需指定要上传的文件内容 - /// 以及根据文件内容获取文件类型的闭包 - /// - /// # Examples - /// - /// 上传 tauri 升级用的签名文件 - /// ```no_run - /// # fn main(){ - /// use infer::Infer; - /// # use dotenv::dotenv; - /// # dotenv().ok(); - /// # let client = aliyun_oss_client::ClientRc::from_env().unwrap(); - /// use crate::aliyun_oss_client::file::BlockingFiles; - /// - /// fn sig_match(buf: &[u8]) -> bool { - /// return buf.len() >= 3 && buf[0] == 0x64 && buf[1] == 0x57 && buf[2] == 0x35; - /// } - /// let mut infer = Infer::new(); - /// infer.add("application/pgp-signature", "sig", sig_match); - /// - /// let get_content_type = |content: &Vec| match infer.get(content) { - /// Some(con) => Some(con.mime_type()), - /// None => None, - /// }; - /// let content: Vec = String::from("dW50cnVzdGVkIGNvbW1lbnQ6IHNpxxxxxxxxx").into_bytes(); - /// let res = client.put_content(content, "xxxxxx.msi.zip.sig", get_content_type); - /// assert!(res.is_ok()); - /// # } - /// ``` - fn put_content( - &self, - content: Vec, - path: Path, - get_content_type: F, - ) -> Result - where - F: Fn(&Vec) -> Option<&'static str>, - { - let content_type = get_content_type(&content).unwrap_or(Self::DEFAULT_CONTENT_TYPE); - - let content = self.put_content_base(content, content_type, path)?; - - let result = content - .headers() - .get(ETAG) - .ok_or(FileError { - kind: FileErrorKind::EtagNotFound, - })? - .to_str() - .map_err(|e| FileError { - kind: FileErrorKind::InvalidEtag(e), - })?; - - Ok(result.to_string()) - } - - /// 最原始的上传文件的方法 - fn put_content_base( - &self, - content: Vec, - content_type: &str, - path: Path, - ) -> Result { - let (url, canonicalized) = self.get_std_with_path(path).ok_or(FileError { - kind: FileErrorKind::NotFoundCanonicalizedResource, - })?; - - let content_length = content.len().to_string(); - let headers = vec![ - (CONTENT_LENGTH, header_from_content_length(&content_length)?), - ( - CONTENT_TYPE, - content_type.parse().map_err(|e| FileError { - kind: FileErrorKind::InvalidContentType(e), - })?, - ), - ]; - - let response = self - .builder_with_header(Method::PUT, url, canonicalized, headers)? - .body(content); - - response.send_adjust_error().map_err(FileError::from) - } - - /// # 获取文件内容 - fn get_object(&self, path: Path, range: R) -> Result, FileError> - where - R: Into>, - ContentRange: Into, - { - let (url, canonicalized) = self.get_std_with_path(path).ok_or(FileError { - kind: FileErrorKind::NotFoundCanonicalizedResource, - })?; - - let headers: Vec<(_, HeaderValue)> = vec![( - { - #[allow(clippy::unwrap_used)] - RANGE.parse().unwrap() - }, - range.into().into(), - )]; - - Ok(self - .builder_with_header(Method::GET, url, canonicalized, headers)? - .send_adjust_error()? - .bytes()? - .to_vec()) - } - - /// # 删除 OSS 上的文件 - fn delete_object(&self, path: Path) -> Result<(), FileError> { - let (url, canonicalized) = self.get_std_with_path(path).ok_or(FileError { - kind: FileErrorKind::NotFoundCanonicalizedResource, - })?; - - self.builder(Method::DELETE, url, canonicalized)? - .send_adjust_error()?; - - Ok(()) - } - } - - impl> Files

for T {} - - /// 对 Client 中的请求构建器进行抽象 - pub trait AlignBuilder { - /// 根据具体的 API 接口参数,返回请求的构建器(不带 headers) - #[inline] - fn builder( - &self, - method: Method, - url: Url, - resource: CanonicalizedResource, - ) -> Result { - self.builder_with_header(method, url, resource, []) - } - - /// 根据具体的 API 接口参数,返回请求的构建器 - fn builder_with_header>( - &self, - method: Method, - url: Url, - resource: CanonicalizedResource, - headers: H, - ) -> Result; - } - - /// # 对齐 Client, Bucket, ObjectList 等结构体的 trait - /// - /// 用于他们方便的实现 [`File`](./trait.File.html) trait - impl AlignBuilder for Bucket { - fn builder_with_header>( - &self, - method: Method, - url: Url, - resource: CanonicalizedResource, - headers: H, - ) -> Result { - self.client() - .builder_with_header(method, url, resource, headers) - } - } - - impl AlignBuilder for ObjectList { - fn builder_with_header>( - &self, - method: Method, - url: Url, - resource: CanonicalizedResource, - headers: H, - ) -> Result { - self.client() - .builder_with_header(method, url, resource, headers) - } - } -} diff --git a/src/file/test.rs b/src/file/test.rs deleted file mode 100644 index 53b4ca9..0000000 --- a/src/file/test.rs +++ /dev/null @@ -1,467 +0,0 @@ -use crate::file::{error_impl::FileErrorKind, Files}; - -use super::header_from_content_length; - -mod tests_get_std { - use reqwest::Url; - use std::sync::Arc; - - use crate::file::GetStd; - use crate::{ - builder::ArcPointer, - object::Object, - types::{object::ObjectBase, CanonicalizedResource}, - }; - - #[test] - fn test_object_base() { - let mut path = ObjectBase::::default(); - path.set_path("path1").unwrap(); - let bucket = Arc::new("abc.qingdao".parse().unwrap()); - path.set_bucket(bucket); - - let res = path.get_std(); - - assert!(res.is_some()); - let (url, resource) = res.unwrap(); - - assert_eq!( - url, - Url::parse("https://abc.oss-cn-qingdao.aliyuncs.com/path1").unwrap() - ); - assert_eq!(resource, CanonicalizedResource::new("/abc/path1")); - } - - #[test] - fn test_object() { - let object = Object::::default(); - let res = object.get_std(); - assert!(res.is_some()); - let (url, resource) = res.unwrap(); - - assert_eq!( - url, - Url::parse("https://a.oss-cn-hangzhou.aliyuncs.com/").unwrap() - ); - assert_eq!(resource, CanonicalizedResource::new("/a/")); - } - - #[test] - fn test_object_ref() { - let object = Object::::default(); - let res = GetStd::get_std(&object); - assert!(res.is_some()); - let (url, resource) = res.unwrap(); - - assert_eq!( - url, - Url::parse("https://a.oss-cn-hangzhou.aliyuncs.com/").unwrap() - ); - assert_eq!(resource, CanonicalizedResource::new("/a/")); - } -} - -mod test_get_std_with_path { - use reqwest::Url; - - use crate::{ - bucket::Bucket, - builder::ArcPointer, - client::ClientArc, - file::GetStdWithPath, - object::Object, - types::{object::ObjectBase, CanonicalizedResource}, - ObjectPath, - }; - - fn assert_url_resource( - result: Option<(Url, CanonicalizedResource)>, - url: &str, - resource: &str, - ) { - let (u, r) = result.unwrap(); - - assert_eq!(u, Url::parse(url).unwrap()); - assert_eq!(r, resource); - } - - #[test] - fn test_client() { - let client = ClientArc::test_init(); - assert_url_resource( - client.get_std_with_path("path1".to_owned()), - "https://bar.oss-cn-qingdao.aliyuncs.com/path1", - "/bar/path1", - ); - - assert_url_resource( - client.get_std_with_path("path1"), - "https://bar.oss-cn-qingdao.aliyuncs.com/path1", - "/bar/path1", - ); - - assert_url_resource( - client.get_std_with_path("path1".parse::().unwrap()), - "https://bar.oss-cn-qingdao.aliyuncs.com/path1", - "/bar/path1", - ); - - assert_url_resource( - client.get_std_with_path(Object::::test_path("path1")), - "https://bar.oss-cn-qingdao.aliyuncs.com/path1", - "/bar/path1", - ); - } - - #[test] - fn test_as_bucket_base() { - let bucket = Bucket::::default(); - assert_url_resource( - bucket.get_std_with_path("path1".to_string()), - "https://a.oss-cn-hangzhou.aliyuncs.com/path1", - "/a/path1", - ); - - assert_url_resource( - bucket.get_std_with_path("path1"), - "https://a.oss-cn-hangzhou.aliyuncs.com/path1", - "/a/path1", - ); - assert_url_resource( - bucket.get_std_with_path("path1".parse::().unwrap()), - "https://a.oss-cn-hangzhou.aliyuncs.com/path1", - "/a/path1", - ); - - let path = "path1".parse::().unwrap(); - assert_url_resource( - bucket.get_std_with_path(&path), - "https://a.oss-cn-hangzhou.aliyuncs.com/path1", - "/a/path1", - ); - } - - #[test] - fn test_bucket() { - let bucket = Bucket::::default(); - assert_url_resource( - bucket.get_std_with_path({ - let mut obj = ObjectBase::::default(); - obj.set_path("path1").unwrap(); - obj - }), - "https://a.oss-cn-hangzhou.aliyuncs.com/path1", - "/a/path1", - ); - - let mut obj = ObjectBase::::default(); - obj.set_path("path1").unwrap(); - - assert_url_resource( - bucket.get_std_with_path(&obj), - "https://a.oss-cn-hangzhou.aliyuncs.com/path1", - "/a/path1", - ); - } -} - -mod test_try { - use std::sync::Arc; - - use crate::builder::ArcPointer; - use crate::file::error_impl::FileErrorKind; - use crate::file::{FileError, Files}; - use crate::types::object::{ObjectBase, ObjectPath}; - use crate::Client; - - fn init_client() -> Client { - Client::test_init() - } - - #[tokio::test] - async fn try_delete() { - let client = init_client(); - - struct MyPath; - impl TryFrom for ObjectBase { - type Error = MyError; - fn try_from(_path: MyPath) -> Result { - Ok(ObjectBase::::new2( - Arc::new("abc".parse().unwrap()), - "cde".parse().unwrap(), - )) - } - } - - struct MyError; - - impl Into for MyError { - fn into(self) -> FileError { - FileError { - kind: FileErrorKind::EtagNotFound, - } - } - } - - //let _ = FileAs::::delete_object_as(&client, "abc".to_string()).await; - let _ = client - .delete_object("abc".parse::().unwrap()) - .await; - } -} - -mod error { - use std::error::Error; - - use http::HeaderValue; - - use crate::{ - builder::{BuilderError, BuilderErrorKind}, - file::{error_impl::FileErrorKind, FileError}, - tests::reqwest_error, - }; - - #[cfg(feature = "put_file")] - #[test] - fn test_file_read() { - use std::io::ErrorKind; - let io_err = std::io::Error::new(ErrorKind::Other, "oh no!"); - let err = FileError { - kind: FileErrorKind::FileRead(io_err), - }; - - assert_eq!(format!("{err}"), "file read failed"); - assert_eq!(format!("{}", err.source().unwrap()), "oh no!"); - assert_eq!( - format!("{:?}", err), - "FileError { kind: FileRead(Custom { kind: Other, error: \"oh no!\" }) }" - ); - } - - #[test] - fn test_header_value() { - let header_err = HeaderValue::from_bytes(b"\n").unwrap_err(); - let err = FileError { - kind: FileErrorKind::InvalidContentLength(header_err), - }; - - assert_eq!(format!("{err}"), "invalid content length"); - assert_eq!( - format!("{}", err.source().unwrap()), - "failed to parse header value" - ); - assert_eq!( - format!("{:?}", err), - "FileError { kind: InvalidContentLength(InvalidHeaderValue) }" - ); - - let header_err = HeaderValue::from_bytes(b"\n").unwrap_err(); - let err = FileError { - kind: FileErrorKind::InvalidContentType(header_err), - }; - - assert_eq!(format!("{err}"), "invalid content type"); - assert_eq!( - format!("{}", err.source().unwrap()), - "failed to parse header value" - ); - assert_eq!( - format!("{:?}", err), - "FileError { kind: InvalidContentType(InvalidHeaderValue) }" - ); - } - - #[test] - fn test_build() { - let err = FileError::from(BuilderError { - kind: BuilderErrorKind::Bar, - }); - - assert_eq!(format!("{err}"), "bar"); - assert!(err.source().is_none()); - - fn bar() -> FileError { - BuilderError { - kind: BuilderErrorKind::Bar, - } - .into() - } - assert_eq!( - format!("{:?}", bar()), - "FileError { kind: Build(BuilderError { kind: Bar }) }" - ); - } - - #[tokio::test] - async fn test_reqwest() { - let err = FileError::from(reqwest_error().await); - assert_eq!(format!("{err}"), "reqwest error"); - assert_eq!( - format!("{}", err.source().unwrap()), - "error decoding response body: expected value at line 1 column 1" - ); - assert_eq!(format!("{:?}", err), "FileError { kind: Reqwest(reqwest::Error { kind: Decode, source: Error(\"expected value\", line: 1, column: 1) }) }"); - } - - #[test] - fn test_etag() { - let err = FileError { - kind: FileErrorKind::EtagNotFound, - }; - assert_eq!(format!("{err}"), "failed to get etag"); - assert!(err.source().is_none()); - - let value = http::header::HeaderValue::from_str("ñ").unwrap(); - let err = value.to_str().unwrap_err(); - let err = FileError { - kind: FileErrorKind::InvalidEtag(err), - }; - assert_eq!(format!("{err}"), "invalid etag"); - assert_eq!( - format!("{}", err.source().unwrap()), - "failed to convert header to a str" - ); - assert_eq!( - format!("{err:?}"), - "FileError { kind: InvalidEtag(ToStrError { _priv: () }) }" - ); - } - - #[test] - fn none_resource() { - let err = FileError { - kind: FileErrorKind::NotFoundCanonicalizedResource, - }; - assert_eq!(format!("{err}"), "not found canonicalized-resource"); - assert!(err.source().is_none()); - assert_eq!( - format!("{err:?}"), - "FileError { kind: NotFoundCanonicalizedResource }" - ); - } - - #[test] - #[cfg(feature = "put_file")] - fn into_io_error_file() { - use std::io::{Error, ErrorKind}; - let file = FileError { - kind: FileErrorKind::FileRead(Error::new(ErrorKind::Other, "other")), - }; - assert_eq!(Error::from(file).to_string(), "other"); - } - - #[test] - fn into_io_error() { - use std::io::Error; - - let value = HeaderValue::from_bytes(b"\n").unwrap_err(); - let file = FileError { - kind: FileErrorKind::InvalidContentLength(value), - }; - let io = Error::from(file); - assert_eq!(io.to_string(), "invalid content length"); - - let value = HeaderValue::from_bytes(b"\n").unwrap_err(); - let file = FileError { - kind: FileErrorKind::InvalidContentType(value), - }; - let io = Error::from(file); - assert_eq!(io.to_string(), "invalid content type"); - - // TODO 未完成 - } -} - -#[tokio::test] -async fn test_put_content_base_error() { - use crate::client::ClientArc; - let client = ClientArc::test_init(); - - let err = client - .put_content_base("aa".into(), "image/jpg", "aaa/") - .await - .unwrap_err(); - assert!(matches!( - err.kind, - FileErrorKind::NotFoundCanonicalizedResource - )); - - let err = client - .put_content_base("aa".into(), "\n", "aaa") - .await - .unwrap_err(); - assert!(matches!(err.kind, FileErrorKind::InvalidContentType(_))); -} - -#[test] -fn test_header_from_content_length() { - let err = header_from_content_length("\n").unwrap_err(); - assert!(matches!(err.kind, FileErrorKind::InvalidContentLength(_))); -} - -#[tokio::test] -async fn test_get_object_error() { - use crate::client::ClientArc; - let client = ClientArc::test_init(); - let err = client.get_object("aaa/", ..).await.unwrap_err(); - assert!(matches!( - err.kind, - FileErrorKind::NotFoundCanonicalizedResource - )); -} - -#[tokio::test] -async fn test_delete_object_error() { - use crate::client::ClientArc; - let client = ClientArc::test_init(); - let err = client.delete_object("aaa/").await.unwrap_err(); - assert!(matches!( - err.kind, - FileErrorKind::NotFoundCanonicalizedResource - )); -} - -#[cfg(feature = "blocking")] -mod blocking_files_trait { - use crate::{ - file::{error_impl::FileErrorKind, BlockingFiles}, - ClientRc, - }; - - #[test] - fn test_put_content_base_error() { - let client = ClientRc::test_init(); - let err = client - .put_content_base("aa".into(), "image/jpg", "aaa/") - .unwrap_err(); - assert!(matches!( - err.kind, - FileErrorKind::NotFoundCanonicalizedResource - )); - - let err = client - .put_content_base("aa".into(), "\n", "aaa") - .unwrap_err(); - assert!(matches!(err.kind, FileErrorKind::InvalidContentType(_))); - } - - #[test] - fn test_get_object_error() { - let client = ClientRc::test_init(); - let err = client.get_object("aaa/", ..).unwrap_err(); - assert!(matches!( - err.kind, - FileErrorKind::NotFoundCanonicalizedResource - )); - } - - #[test] - fn test_delete_object_error() { - let client = ClientRc::test_init(); - let err = client.delete_object("aaa/").unwrap_err(); - assert!(matches!( - err.kind, - FileErrorKind::NotFoundCanonicalizedResource - )); - } -} diff --git a/src/lib.rs b/src/lib.rs index f66638c..5fd2a43 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,331 +1,13 @@ -/*! -# 一个 aliyun OSS 的客户端 - -## 使用方法 - -1. 在 `cargo.toml` 中添加如下依赖项 - -```toml -[dependencies] -aliyun-oss-client = "^0.11" -``` - -2. 初始化配置信息 - -- 方式一 - -```rust -use std::env::set_var; -set_var("ALIYUN_KEY_ID", "foo1"); -set_var("ALIYUN_KEY_SECRET", "foo2"); -set_var("ALIYUN_ENDPOINT", "qingdao"); -set_var("ALIYUN_BUCKET", "foo4"); -let client = aliyun_oss_client::Client::from_env(); -``` - -- 方式二 - -在项目根目录下创建 `.env` 文件(需将其加入 .gitignore ),内容: - -```bash -ALIYUN_KEY_ID=xxxxxxx -ALIYUN_KEY_SECRET=yyyyyyyyyyyyyy -ALIYUN_ENDPOINT=https://oss-cn-shanghai.aliyuncs.com -ALIYUN_BUCKET=zzzzzzzzzz -``` - -在需要使用 OSS 的地方,这样设置: -```rust -use dotenv::dotenv; -dotenv().ok(); -let client = aliyun_oss_client::Client::from_env(); -``` - -- 方式三 - -```rust -use aliyun_oss_client::BucketName; -let bucket = BucketName::new("bbb").unwrap(); -let client = aliyun_oss_client::Client::new( - "key1".into(), - "secret1".into(), - "qingdao".try_into().unwrap(), - bucket -); -``` - -## 支持内网访问 Version +0.9 - -~~在阿里云的 ECS 上请求 OSS 接口,使用内网 API 有更高的效率,只需要在 ECS 上设置 `ALIYUN_OSS_INTERNAL` 环境变量为 `true` 即可~~ - -从 `0.12` 版本开始,只有在 [`Client::from_env`] 和 [`BucketBase::from_env`] 这两个方法中 `ALIYUN_OSS_INTERNAL` 环境变量才起作用, -其他地方,请使用 [`EndPoint::set_internal`] 进行切换 - -### 查询所有的 bucket 信息 - -了解更多,请查看 [`get_bucket_list`] - -```rust -# #[tokio::main] -# async fn main(){ - # use std::env::set_var; - # set_var("ALIYUN_KEY_ID", "foo1"); - # set_var("ALIYUN_KEY_SECRET", "foo2"); - # set_var("ALIYUN_ENDPOINT", "qingdao"); - # set_var("ALIYUN_BUCKET", "foo4"); - # let client = aliyun_oss_client::Client::from_env().unwrap(); - client.get_bucket_list().await; -# } -``` - -### 获取 bucket 信息 - -了解更多,请查看 [`get_bucket_info`] - -```rust -# #[tokio::main] -# async fn main(){ - # use std::env::set_var; - # set_var("ALIYUN_KEY_ID", "foo1"); - # set_var("ALIYUN_KEY_SECRET", "foo2"); - # set_var("ALIYUN_ENDPOINT", "qingdao"); - # set_var("ALIYUN_BUCKET", "foo4"); - # let client = aliyun_oss_client::Client::from_env().unwrap(); - let response = client.get_bucket_info().await; - println!("bucket info: {:?}", response); -# } -``` - -### 查询当前 bucket 中的 object 列表 - -了解更多,请查看 [`get_object_list`] - -查询条件参数有多种方式,具体参考 [`Bucket::get_object_list`] 文档 -```rust -# #[tokio::main] -# async fn main(){ - # use std::env::set_var; - # set_var("ALIYUN_KEY_ID", "foo1"); - # set_var("ALIYUN_KEY_SECRET", "foo2"); - # set_var("ALIYUN_ENDPOINT", "qingdao"); - # set_var("ALIYUN_BUCKET", "foo4"); - # let client = aliyun_oss_client::Client::from_env().unwrap(); - let response = client.get_object_list([]).await; - println!("objects list: {:?}", response); -# } -``` - -### 也可以使用 bucket struct 查询 object 列表 - -了解更多,请查看 [`Bucket::get_object_list`] -```rust,ignore -# #[tokio::main] -# async fn main(){ - # use std::env::set_var; - # set_var("ALIYUN_KEY_ID", "foo1"); - # set_var("ALIYUN_KEY_SECRET", "foo2"); - # set_var("ALIYUN_ENDPOINT", "qingdao"); - # set_var("ALIYUN_BUCKET", "foo4"); - # let client = aliyun_oss_client::Client::from_env().unwrap(); - let query = [("max-keys".into(), "5".into()), ("prefix".into(), "babel".into())]; - let result = client.get_bucket_info().await.unwrap().get_object_list(query).await; - - println!("object list : {:?}", result); -# } -``` - -### 上传文件 - -了解更多,请查看 [`put_file`], [`put_content`], [`put_content_base`] -```rust -# #[tokio::main] -# async fn main(){ - # use std::env::set_var; - # set_var("ALIYUN_KEY_ID", "foo1"); - # set_var("ALIYUN_KEY_SECRET", "foo2"); - # set_var("ALIYUN_ENDPOINT", "qingdao"); - # set_var("ALIYUN_BUCKET", "foo4"); - # let client = aliyun_oss_client::Client::from_env().unwrap(); - # use aliyun_oss_client::types::object::ObjectPath; - - use aliyun_oss_client::file::Files; - client.put_file("examples/bg2015071010.png", "examples/bg2015071010.png").await; - - let path: ObjectPath = "examples/bg2015071010.png".parse().unwrap(); - client.put_file("examples/bg2015071010.png", path).await; - - // or 上传文件内容 - let file_content = std::fs::read("examples/bg2015071010.png").unwrap(); - client - .put_content(file_content, "examples/bg2015071010.png", |_| { - Some("image/png") - }) - .await; - - // or 自定义上传文件 Content-Type - let file_content = std::fs::read("examples/bg2015071010.png").unwrap(); - client - .put_content_base(file_content, "image/png", "examples/bg2015071010.png") - .await; -# } -``` - -### 下载文件 - -了解更多,请查看 [`get_object`] -```rust -# #[tokio::main] -# async fn main(){ - # use std::env::set_var; - # set_var("ALIYUN_KEY_ID", "foo1"); - # set_var("ALIYUN_KEY_SECRET", "foo2"); - # set_var("ALIYUN_ENDPOINT", "qingdao"); - # set_var("ALIYUN_BUCKET", "foo4"); - # let client = aliyun_oss_client::Client::from_env().unwrap(); - # use aliyun_oss_client::types::object::ObjectPath; - use aliyun_oss_client::file::Files; - - // 获取完整文件 - let content = client.get_object("bar.json", ..).await; - - // 获取文件一部分 - let content = client.get_object("bar.json".to_string(), ..100).await; - let path: ObjectPath = "bar.json".parse().unwrap(); - let content = client.get_object(path, 100..).await; - let content = client.get_object("bar.json", 100..200).await; -# } -``` - -### 删除文件 - -了解更多,请查看 [`delete_object`] -```rust -# #[tokio::main] -# async fn main(){ - # use std::env::set_var; - # set_var("ALIYUN_KEY_ID", "foo1"); - # set_var("ALIYUN_KEY_SECRET", "foo2"); - # set_var("ALIYUN_ENDPOINT", "qingdao"); - # set_var("ALIYUN_BUCKET", "foo4"); - # let client = aliyun_oss_client::Client::from_env().unwrap(); - use aliyun_oss_client::file::Files; - client.delete_object("examples/bg2015071010.png").await; -# } -``` - -[`get_bucket_list`]: crate::client::Client::get_bucket_list -[`get_bucket_info`]: crate::client::Client::get_bucket_info -[`get_object_list`]: crate::client::Client::get_object_list -[`Bucket::get_object_list`]: crate::bucket::Bucket::get_object_list -[`put_file`]: crate::file::Files::put_file -[`put_content`]: crate::file::Files::put_content -[`put_content_base`]: crate::file::Files::put_content_base -[`get_object`]: crate::file::Files::get_object -[`delete_object`]: crate::file::Files::delete_object -[`Client::from_env`]: crate::client::Client::from_env -[`BucketBase::from_env`]: crate::config::BucketBase::from_env -[`EndPoint::set_internal`]: crate::EndPoint::set_internal -*/ - -#![cfg_attr(all(feature = "bench", test), feature(test))] -#![warn(missing_docs)] -#![warn(clippy::unwrap_used)] - -// #![doc(html_playground_url = "https://play.rust-lang.org/")] - -#[cfg(all(feature = "bench", test))] -extern crate test; - -#[cfg(feature = "auth")] -pub mod auth; - -#[cfg(feature = "blocking")] -pub mod blocking; - -#[cfg(feature = "core")] pub mod bucket; - -#[cfg(feature = "core")] -#[doc(hidden)] -pub mod builder; - -#[cfg(feature = "core")] pub mod client; - -#[cfg(feature = "core")] -pub mod config; - -#[allow(dead_code)] -mod consts; - -#[cfg(feature = "decode")] -pub mod decode; - -#[cfg(feature = "core")] -pub mod errors; - -#[cfg(feature = "core")] -pub mod file; - -#[cfg(feature = "core")] +pub mod error; pub mod object; - -#[cfg(feature = "sts")] -pub mod sts; - pub mod types; -#[cfg(test)] -#[allow(dead_code)] -mod tests; - -#[cfg(feature = "core")] -pub use client::ClientArc as Client; -#[cfg(feature = "blocking")] -pub use client::ClientRc; -#[cfg(feature = "core")] -pub use errors::{OssError as Error, OssResult as Result}; -#[cfg(feature = "core")] -pub use http::{ - header::{HeaderMap, HeaderName, HeaderValue}, - Method, -}; - -// TODO 修改文档错误 -#[cfg(feature = "decode")] -pub use oss_derive::DecodeListError; - -pub use types::{ - object::{ObjectDir, ObjectPath}, - BucketName, EndPoint, KeyId, KeySecret, -}; -#[cfg(feature = "core")] -pub use types::{Query, QueryKey, QueryValue}; - -#[cfg(all(doctest, not(tarpaulin)))] -#[doc = include_str!("../README.md")] -pub struct ReadmeDoctests; - -/** # 主要入口 - -*/ -#[cfg(feature = "core")] -pub fn client( - access_key_id: ID, - access_key_secret: S, - endpoint: E, - bucket: B, -) -> client::Client -where - ID: Into, - S: Into, - E: Into, - B: Into, -{ - use config::Config; - let config = Config::new(access_key_id, access_key_secret, endpoint, bucket); - client::Client::::from_config(config) -} - -#[cfg(feature = "core")] -use builder::ClientWithMiddleware; +pub use bucket::Bucket; +pub use bucket::BucketInfo; +pub use client::Client; +pub use object::Object; +pub use object::ObjectInfo; +pub use object::Objects; +pub use types::{EndPoint, Key, Secret}; diff --git a/src/object.rs b/src/object.rs index 3362f42..97aacbd 100644 --- a/src/object.rs +++ b/src/object.rs @@ -1,1605 +1,243 @@ -//! # Object 相关功能 -//! `ObjectList` 对应文件列表,`Object` 对应的是单个文件对象 -//! -//! `ObjectList` 也可以支持自定义的类型存储单个文件对象,例如 [issue 12] 提到的,有些时候, -//! Oss 接口不仅返回文件路径,还会返回目录路径,可以使用如下例子进行适配 -//! -//! ```rust,no_run -//! use aliyun_oss_client::{ -//! decode::RefineObject, -//! object::{Objects, InitObject}, -//! types::object::{InvalidObjectDir, ObjectDir, ObjectPath}, -//! BucketName, Client, -//! }; -//! use dotenv::dotenv; -//! -//! #[derive(Debug)] -//! enum MyObject { -//! File(ObjectPath), -//! Dir(ObjectDir<'static>), -//! } -//! -//! impl RefineObject for MyObject { -//! fn set_key(&mut self, key: &str) -> Result<(), InvalidObjectDir> { -//! *self = match key.parse() { -//! Ok(file) => MyObject::File(file), -//! _ => MyObject::Dir(key.parse()?), -//! }; -//! -//! Ok(()) -//! } -//! } -//! -//! type MyList = Objects; -//! -//! // 可以根据传入的列表信息,为元素添加更多能力 -//! impl InitObject for MyList { -//! fn init_object(&mut self) -> Option { -//! Some(MyObject::File(ObjectPath::default())) -//! } -//! } -//! -//! #[tokio::main] -//! async fn main() { -//! dotenv().ok(); -//! -//! let client = Client::from_env().unwrap(); -//! -//! let mut list = MyList::default(); -//! -//! let _ = client.base_object_list([], &mut list).await; -//! // 第二页数据 -//! let second = list.get_next_base().await; -//! -//! println!("list: {:?}", list.to_vec()); -//! } -//! ``` -//! [issue 12]: https://github.com/tu6ge/oss-rs/issues/12 +use chrono::{DateTime, Utc}; +use reqwest::{header::CONTENT_LENGTH, Method}; -use crate::bucket::Bucket; -#[cfg(feature = "blocking")] -use crate::builder::RcPointer; -use crate::builder::{ArcPointer, BuilderError, PointerFamily}; -use crate::client::ClientArc; -#[cfg(feature = "blocking")] -use crate::client::ClientRc; -use crate::config::BucketBase; -use crate::decode::{InnerListError, ListError, RefineObject, RefineObjectList}; -#[cfg(feature = "blocking")] -use crate::file::blocking::AlignBuilder as BlockingAlignBuilder; -use crate::file::AlignBuilder; -use crate::types::object::ObjectPathInner; -use crate::types::{ - core::SetOssQuery, - object::{ - CommonPrefixes, InvalidObjectDir, InvalidObjectPath, ObjectBase, ObjectDir, ObjectPath, - }, - CanonicalizedResource, Query, QueryKey, QueryValue, CONTINUATION_TOKEN, +use crate::{ + client::Client, + error::OssError, + types::{CanonicalizedResource, ObjectQuery}, }; -use crate::{BucketName, Client, EndPoint, KeyId, KeySecret}; -use async_stream::try_stream; -use chrono::{DateTime, NaiveDateTime, Utc}; -use futures_core::stream::Stream; -use http::Method; -use oss_derive::oss_gen_rc; -use url::Url; -#[cfg(feature = "blocking")] -use std::rc::Rc; -use std::{ - error::Error, - fmt::{self, Display}, - num::ParseIntError, - sync::Arc, - vec::IntoIter, -}; - -pub mod content; -pub use content::Content; - -#[cfg(test)] -mod test; - -/// # 存放对象列表的结构体 -/// before name is `ObjectList` -/// TODO impl core::ops::Index -#[derive(Clone)] -#[non_exhaustive] -pub struct ObjectList> { - pub(crate) bucket: BucketBase, - prefix: Option>, - max_keys: u32, - key_count: u64, - /// 存放单个文件对象的 Vec 集合 - object_list: Vec, - next_continuation_token: String, - common_prefixes: CommonPrefixes, - client: P::PointerType, - search_query: Query, -} - -/// sync ObjectList alias -pub type Objects> = ObjectList; -/// blocking ObjectList alias -#[cfg(feature = "blocking")] -pub type ObjectsBlocking> = ObjectList; - -/// 存放单个对象的结构体 -#[derive(Clone, Debug)] -#[non_exhaustive] -pub struct Object { - pub(crate) base: ObjectBase, - last_modified: DateTime, - etag: String, - _type: String, - size: u64, - storage_class: StorageClass, -} - -/// 异步的 Object struct -pub type ObjectArc = Object; - -impl fmt::Debug for ObjectList { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("ObjectList") - .field("bucket", &self.bucket) - .field("prefix", &self.prefix) - .field("max_keys", &self.max_keys) - .field("key_count", &self.key_count) - .field("next_continuation_token", &self.next_continuation_token) - .field("common_prefixes", &self.common_prefixes) - .field("search_query", &self.search_query) - .finish() - } -} - -impl Default for ObjectList { - fn default() -> Self { - Self { - bucket: BucketBase::default(), - prefix: Option::default(), - max_keys: u32::default(), - key_count: u64::default(), - object_list: Vec::new(), - next_continuation_token: String::default(), - common_prefixes: CommonPrefixes::default(), - client: P::PointerType::default(), - search_query: Query::default(), - //init_fn: Box::default(), - } - } -} - -impl AsMut for ObjectList { - fn as_mut(&mut self) -> &mut Query { - &mut self.search_query - } -} - -impl AsRef for ObjectList { - fn as_ref(&self) -> &BucketBase { - &self.bucket - } -} - -impl AsRef for ObjectList { - fn as_ref(&self) -> &BucketName { - self.bucket.as_ref() - } -} - -impl AsRef for ObjectList { - fn as_ref(&self) -> &EndPoint { - self.bucket.as_ref() - } +#[derive(Debug)] +pub struct Objects { + //bucket: Bucket, + list: Vec, + next_token: Option, } -impl ObjectList { - /// 文件列表的初始化方法 - #[allow(clippy::too_many_arguments)] - pub fn new>( - bucket: BucketBase, - prefix: Option>, - max_keys: u32, - key_count: u64, - object_list: Vec, - next_continuation_token: Option, - client: T::PointerType, - search_query: Q, - ) -> Self { - Self { - bucket, - prefix, - max_keys, - key_count, - object_list, - next_continuation_token: next_continuation_token.unwrap_or_default(), - common_prefixes: CommonPrefixes::default(), - client, - search_query: Query::from_iter(search_query), - //init_fn: Box::default(), - } - } - - /// 返回 bucket 元信息的引用 - pub fn bucket(&self) -> &BucketBase { - &self.bucket - } - - /// 返回 prefix 的引用 - pub fn prefix(&self) -> &Option> { - &self.prefix - } - - /// 获取文件夹下的子文件夹名,子文件夹下递归的所有文件和文件夹不包含在这里。 - pub fn common_prefixes(&self) -> &CommonPrefixes { - &self.common_prefixes - } - - /// 设置 common_prefixes 信息 - pub fn set_common_prefixes>>(&mut self, prefixes: P) { - self.common_prefixes = CommonPrefixes::from_iter(prefixes); - } - - /// 返回 max_keys - pub fn max_keys(&self) -> &u32 { - &self.max_keys - } - - /// 返回 key_count - pub fn key_count(&self) -> &u64 { - &self.key_count +impl Objects { + pub fn new(list: Vec, next_token: Option) -> Objects { + Objects { list, next_token } } - /// # 返回下一个 continuation_token - /// 用于翻页使用 - pub fn next_continuation_token_str(&self) -> &String { - &self.next_continuation_token + pub fn next_token(&self) -> Option<&String> { + self.next_token.as_ref() } - /// 返回查询条件 - pub fn search_query(&self) -> &Query { - &self.search_query - } - - /// # 下一页的查询条件 - /// - /// 如果有下一页,返回 Some(Query) - /// 如果没有下一页,则返回 None - pub fn next_query(&self) -> Option { - if !self.next_continuation_token.is_empty() { - let mut search_query = self.search_query.clone(); - search_query.insert(CONTINUATION_TOKEN, self.next_continuation_token.to_owned()); - Some(search_query) - } else { - None - } - } - - /// 将 object 列表转化为迭代器 - pub fn object_iter(self) -> IntoIter { - self.object_list.into_iter() + pub fn len(&self) -> usize { + self.list.len() } -} -#[oss_gen_rc] -impl ObjectList { - /// 设置 Client - pub(crate) fn set_client(&mut self, client: Arc) { - self.client = client; + pub fn is_empty(&self) -> bool { + self.list.is_empty() } - pub(crate) fn from_bucket( - bucket: &Bucket, - capacity: usize, - ) -> ObjectList { - ObjectList:: { - bucket: bucket.base.clone(), - client: Arc::clone(&bucket.client), - object_list: Vec::with_capacity(capacity), - ..Default::default() + pub async fn next_list( + self, + query: &ObjectQuery, + client: &Client, + ) -> Result { + let mut q = query.clone(); + if let Some(token) = self.next_token { + q.insert(ObjectQuery::CONTINUATION_TOKEN, token); } - } - - /// 获取 Client 引用 - pub(crate) fn client(&self) -> Arc { - Arc::clone(&self.client) - } - - fn clone_base(&self) -> Self { - Self { - client: Arc::clone(&self.client), - bucket: self.bucket.clone(), - search_query: self.search_query.clone(), - max_keys: self.max_keys, - object_list: Vec::with_capacity(self.max_keys as usize), - ..Default::default() + match client.bucket() { + Some(bucket) => bucket.get_objects(&q, client).await, + None => Err(OssError::NoFoundBucket), } } } -/// # 初始化 object 项目的接口 -/// -/// 根据 object 列表类型初始化一个 object 数据 -/// -/// 当对 OSS xml 数据进行解析时,每解析一个 object 时, -/// 会先调用此 trait 中的 init_object 初始化 object 类型,再解析 xml 数据 -/// -/// Target 为 object 的具体类型 -pub trait InitObject { - /// 初始化 object 的类型 - /// - /// 当返回 None 时,解析 OSS xml 数据时,会抛出 "init_object failed" 错误 - fn init_object(&mut self) -> Option; -} - -impl InitObject> for ObjectList> { - fn init_object(&mut self) -> Option> { - Some(Object::from_bucket(Arc::new(self.bucket.clone()))) - } +#[derive(Debug)] +pub struct Object { + path: String, } -#[cfg(feature = "blocking")] -impl InitObject> for ObjectList> { - fn init_object(&mut self) -> Option> { - Some(Object::::from_bucket(Rc::new( - self.bucket.clone(), - ))) - } -} - -impl ObjectList { - /// 异步获取下一页的数据 - /// TODO 改为 unstable - pub async fn get_next_list(&self) -> Result, ExtractListError> { - match self.next_query() { - None => Err(ExtractListError { - kind: ExtractListErrorKind::NoMoreFile, - }), - Some(query) => { - let mut url = self.bucket.to_url(); - url.set_oss_query(&query); - - let canonicalized = CanonicalizedResource::from_bucket_query(&self.bucket, &query); - - let response = self - .builder(Method::GET, url, canonicalized)? - .send_adjust_error() - .await?; - - let mut list = ObjectList:: { - client: self.client(), - bucket: self.bucket.clone(), - object_list: Vec::with_capacity(query.get_max_keys()), - ..Default::default() - }; - - list.decode(&response.text().await?, Self::init_object)?; - - list.set_search_query(query); - Ok(list) - } - } +impl Object { + pub fn new(path: String) -> Object { + Object { path } } + pub async fn get_info(&self, client: &Client) -> Result { + let bucket = match client.bucket() { + Some(bucket) => bucket, + None => return Err(OssError::NoFoundBucket), + }; + let mut url = bucket.to_url(); + url.set_path(&self.path); + url.set_query(Some("objectMeta")); + let method = Method::GET; + let resource = + CanonicalizedResource::new(format!("/{}/{}?objectMeta", bucket.as_str(), self.path)); + + let header_map = client.authorization(method, resource)?; + + let response = reqwest::Client::new() + .get(url) + .headers(header_map) + .send() + .await?; + + let headers = response.headers(); + + let content_length = match headers.get(CONTENT_LENGTH) { + Some(v) => v, + None => return Err(OssError::NoFoundContentLength), + }; + let etag = match headers.get("etag") { + Some(v) => v, + None => return Err(OssError::NoFoundEtag), + }; + let last_modified = match headers.get("last-modified") { + Some(v) => v, + None => return Err(OssError::NoFoundLastModified), + }; + let last_modified = last_modified.to_str()?; - /// # 将 object_list 转化为 stream, 返回第二页,第三页... 的内容 - /// - /// *不够完善,最后一次迭代返回的是 `Some(Err(OssError::WithoutMore))`,而不是 `None`* - /// - /// ## 用法 - /// - /// 1. 添加依赖 - /// ```toml - /// [dependencies] - /// futures="0.3" - /// ``` - /// 2. 将返回结果 pin 住 - /// ```no_run - /// # use dotenv::dotenv; - /// # use aliyun_oss_client::Client; - /// # #[tokio::main] - /// # async fn main() { - /// # dotenv().ok(); - /// use futures::{pin_mut, StreamExt}; - /// # let client = Client::from_env().unwrap(); - /// # let query = [("max-keys".into(), 100u8.into())]; - /// # let object_list = client.get_object_list(query).await.unwrap(); - /// let stream = object_list.into_stream(); - /// pin_mut!(stream); - /// - /// let second_list = stream.next().await; - /// let third_list = stream.next().await; - /// println!("second_list: {:?}", second_list); - /// println!("third_list: {:?}", third_list); - /// # } - /// ``` - pub fn into_stream(self) -> impl Stream> { - try_stream! { - let result = self.get_next_list().await?; - yield result; - } + let date = DateTime::parse_from_rfc2822(last_modified)?; + Ok(ObjectInfo { + last_modified: date.with_timezone(&Utc), + etag: etag.to_str()?.to_string(), + size: content_length.to_str()?.parse()?, + }) } -} -impl ObjectList -where - Self: InitObject, -{ - /// # 自定义 Item 时,获取下一页数据 - /// 当没有下一页查询条件时 返回 `None` - /// 否则尝试获取下一页数据,成功返回 `Some(Ok(_))`, 失败返回 `Some(Err(_))` - pub async fn get_next_base(&mut self) -> Option> - where - Item: RefineObject, - E: Error + 'static, - { - match self.next_query() { - None => None, - Some(query) => { - let mut list = self.clone_base(); - list.search_query = query.clone(); - let res = self.client().base_object_list(query, &mut list).await; - Some(res.map(|_| list)) - } + pub async fn upload(&self, content: Vec, client: &Client) -> Result<(), OssError> { + let bucket = match client.bucket() { + Some(bucket) => bucket, + None => return Err(OssError::NoFoundBucket), + }; + let mut url = bucket.to_url(); + url.set_path(&self.path); + let method = Method::PUT; + let resource = CanonicalizedResource::new(format!("/{}/{}", bucket.as_str(), self.path)); + + let header_map = client.authorization(method, resource)?; + + let response = reqwest::Client::new() + .put(url) + .headers(header_map) + .body(content) + .send() + .await?; + + if response.status().is_success() { + Ok(()) + } else { + let body = response.text().await?; + Err(OssError::Upload(body)) } } -} - -#[cfg(feature = "blocking")] -impl ObjectList { - /// 从 OSS 获取 object 列表信息 - pub fn get_object_list(&self) -> Result { - let mut list = ObjectList::::clone_base(self); - - let (bucket_url, resource) = self.bucket.get_url_resource(&self.search_query); - - let response = self - .builder(Method::GET, bucket_url, resource)? - .send_adjust_error()?; - - list.decode(&response.text()?, ObjectList::::init_object) - .map_err(ExtractListError::from)?; - - Ok(list) - } -} - -impl ObjectList { - /// 设置查询条件 - #[inline] - pub fn set_search_query(&mut self, search_query: Query) { - self.search_query = search_query; - } - - /// 设置 bucket 元信息 - pub fn set_bucket(&mut self, bucket: BucketBase) { - self.bucket = bucket; - } - - /// 获取 bucket 名称 - pub fn bucket_name(&self) -> &str { - self.bucket.name() - } - - /// 返回 object 的 Vec 集合 - pub fn to_vec(self) -> Vec { - self.object_list - } - - /// 返回文件数量 - pub fn len(&self) -> usize { - self.object_list.len() - } - - /// 返回是否存在文件 - pub fn is_empty(&self) -> bool { - self.object_list.is_empty() - } -} + pub async fn download(&self, client: &Client) -> Result, OssError> { + let bucket = match client.bucket() { + Some(bucket) => bucket, + None => return Err(OssError::NoFoundBucket), + }; + let mut url = bucket.to_url(); + url.set_path(&self.path); + let method = Method::GET; + let resource = CanonicalizedResource::new(format!("/{}/{}", bucket.as_str(), self.path)); -impl Default for Object { - fn default() -> Self { - Object { - base: ObjectBase::::default(), - last_modified: DateTime::::from_utc( - #[allow(clippy::unwrap_used)] - NaiveDateTime::from_timestamp_opt(0, 0).unwrap(), - Utc, - ), - etag: String::default(), - _type: String::default(), - size: 0, - storage_class: StorageClass::default(), - } - } -} + let header_map = client.authorization(method, resource)?; -impl AsRef for Object { - fn as_ref(&self) -> &ObjectPath { - self.base.as_ref() - } -} + let response = reqwest::Client::new() + .get(url) + .headers(header_map) + .send() + .await? + .text() + .await?; -impl AsRef> for Object { - fn as_ref(&self) -> &DateTime { - &self.last_modified + Ok(response.into()) } } -impl AsRef for Object { - fn as_ref(&self) -> &StorageClass { - &self.storage_class - } +#[derive(Debug)] +pub struct ObjectInfo { + last_modified: DateTime, + etag: String, + size: u64, } - -impl Object { - /// 初始化 Object 结构体 - pub fn new( - bucket: T::Bucket, - path: ObjectPath, - last_modified: DateTime, - etag: String, - _type: String, - size: u64, - storage_class: StorageClass, - ) -> Self { - let base = ObjectBase::::new2(bucket, path); - Self { - base, +impl ObjectInfo { + pub fn new(last_modified: DateTime, etag: String, size: u64) -> Self { + ObjectInfo { last_modified, etag, - _type, size, - storage_class, - } - } - - pub(crate) fn from_bucket(bucket: T::Bucket) -> Self { - Self { - base: ObjectBase::::init_with_bucket(bucket), - ..Default::default() } } - /// 读取 Object 元信息 - #[inline] - pub fn base(&self) -> &ObjectBase { - &self.base - } - - /// 设置 Object 元信息 - #[inline] - pub fn set_base(&mut self, base: ObjectBase) { - self.base = base; - } - - /// 读取最后修改时间 - #[inline] pub fn last_modified(&self) -> &DateTime { &self.last_modified } - /// 设置最后修改时间 - #[inline] - pub fn set_last_modified(&mut self, last_modified: DateTime) { - self.last_modified = last_modified; - } - - /// 读取 etag 信息 - #[inline] - pub fn etag(&self) -> &String { - &self.etag - } - - /// 设置 etag - #[inline] - pub fn set_etag(&mut self, etag: String) { - self.etag = etag - } - - /// 读取 type - #[inline] - pub fn get_type_string(&self) -> &String { - &self._type - } - - /// 设置 type - #[inline] - pub fn set_type_string(&mut self, _type: String) { - self._type = _type; - } - - /// 读取文件 size - #[inline] pub fn size(&self) -> u64 { self.size } - /// 设置文件 size - #[inline] - pub fn set_size(&mut self, size: u64) { - self.size = size; - } - - /// 读取 storage_class - #[inline] - pub fn storage_class(&self) -> &StorageClass { - &self.storage_class - } - - /// 设置 storage_class - #[inline] - pub fn set_storage_class(&mut self, storage_class: StorageClass) { - self.storage_class = storage_class; - } - - /// 获取一部分数据 - pub fn pieces( - self, - ) -> ( - ObjectBase, - DateTime, - String, - String, - u64, - StorageClass, - ) { - ( - self.base, - self.last_modified, - self.etag, - self._type, - self.size, - self.storage_class, - ) - } - - /// 读取 文件路径 - pub fn path(&self) -> ObjectPath { - self.base.path() - } - - #[doc(hidden)] - pub fn path_string(&self) -> String { - self.base.path().to_string() - } -} - -impl Object { - #[cfg(test)] - pub fn test_path(path: &'static str) -> Self { - let mut object = Self::default(); - object.set_base(ObjectBase::::new2( - Arc::new(BucketBase::default()), - path.try_into().unwrap(), - )); - object - } -} - -impl From> for ObjectPathInner<'static> { - #[inline] - fn from(obj: Object) -> Self { - obj.base.path - } -} - -#[oss_gen_rc] -impl Object { - /// # Object 构建器 - /// 用例 - /// ``` - /// # use aliyun_oss_client::{config::BucketBase, ObjectPath, object::{ObjectArc, StorageClass},EndPoint}; - /// # use chrono::{DateTime, NaiveDateTime, Utc}; - /// let bucket = BucketBase::new( - /// "bucket-name".parse().unwrap(), - /// EndPoint::CN_QINGDAO, - /// ); - /// let mut builder = ObjectArc::builder("abc".parse::().unwrap()); - /// - /// builder - /// .bucket_base(bucket) - /// .last_modified(DateTime::::from_utc( - /// NaiveDateTime::from_timestamp_opt(123000, 0).unwrap(), - /// Utc, - /// )) - /// .etag("foo1".to_owned()) - /// .set_type("foo2".to_owned()) - /// .size(123) - /// .storage_class(StorageClass::IA); - /// - /// let object = builder.build(); - /// ``` - pub fn builder(path: ObjectPath) -> ObjectBuilder { - ObjectBuilder::::new(Arc::default(), path) - } - - /// 带签名的 Url 链接 - pub fn to_sign_url(&self, key: &KeyId, secret: &KeySecret, expires: i64) -> Url { - self.base.to_sign_url(key, secret, expires) - } -} - -/// Object 结构体的构建器 -pub struct ObjectBuilder { - object: Object, -} - -impl ObjectBuilder { - /// 初始化 Object 构建器 - pub fn new(bucket: T::Bucket, path: ObjectPath) -> Self { - let base = ObjectBase::::new2(bucket, path); - Self { - object: Object { - base, - last_modified: DateTime::::from_utc( - #[allow(clippy::unwrap_used)] - NaiveDateTime::from_timestamp_opt(0, 0).unwrap(), - Utc, - ), - ..Default::default() - }, - } - } - - /// 设置元信息 - pub fn bucket(&mut self, bucket: T::Bucket) -> &mut Self { - self.object.base.set_bucket(bucket); - self - } - - /// 设置 last_modified - pub fn last_modified(&mut self, date: DateTime) -> &mut Self { - self.object.last_modified = date; - self - } - - /// 设置 etag - pub fn etag(&mut self, etag: String) -> &mut Self { - self.object.etag = etag; - self - } - - /// 设置 type - pub fn set_type(&mut self, _type: String) -> &mut Self { - self.object._type = _type; - self - } - - /// 设置 size - pub fn size(&mut self, size: u64) -> &mut Self { - self.object.size = size; - self - } - - /// 设置 storage_class - pub fn storage_class(&mut self, storage_class: StorageClass) -> &mut Self { - self.object.storage_class = storage_class; - self - } - - /// 返回 object - pub fn build(self) -> Object { - self.object - } -} - -#[oss_gen_rc] -impl ObjectBuilder { - /// 设置元信息 - pub fn bucket_base(&mut self, base: BucketBase) -> &mut Self { - self.object.base.set_bucket(Arc::new(base)); - self - } -} - -impl RefineObject for Object { - #[inline] - fn set_key(&mut self, key: &str) -> Result<(), BuildInItemError> { - self.base - .set_path(key.to_owned()) - .map_err(|e| BuildInItemError { - source: key.to_string(), - kind: BuildInItemErrorKind::BasePath(e), - }) - } - - #[inline] - fn set_last_modified(&mut self, value: &str) -> Result<(), BuildInItemError> { - self.last_modified = value.parse().map_err(|e| BuildInItemError { - source: value.to_string(), - kind: BuildInItemErrorKind::LastModified(e), - })?; - Ok(()) - } - - #[inline] - fn set_etag(&mut self, value: &str) -> Result<(), BuildInItemError> { - self.etag = value.to_string(); - Ok(()) - } - - #[inline] - fn set_type(&mut self, value: &str) -> Result<(), BuildInItemError> { - self._type = value.to_string(); - Ok(()) - } - - #[inline] - fn set_size(&mut self, size: &str) -> Result<(), BuildInItemError> { - self.size = size.parse().map_err(|e| BuildInItemError { - source: size.to_string(), - kind: BuildInItemErrorKind::Size(e), - })?; - Ok(()) - } - - #[inline] - fn set_storage_class(&mut self, storage_class: &str) -> Result<(), BuildInItemError> { - self.storage_class = StorageClass::new(storage_class).ok_or(BuildInItemError { - source: storage_class.to_string(), - kind: BuildInItemErrorKind::InvalidStorageClass, - })?; - Ok(()) - } -} - -/// Xml 转化为内置 Object 时的错误集合 -#[derive(Debug)] -#[non_exhaustive] -pub struct BuildInItemError { - source: String, - kind: BuildInItemErrorKind, -} - -impl BuildInItemError { - #[cfg(test)] - pub(crate) fn test_new() -> Self { - Self { - source: "foo".to_string(), - kind: BuildInItemErrorKind::InvalidStorageClass, - } - } - - pub(crate) fn new>(kind: K, source: &str) -> Self { - Self { - source: source.to_owned(), - kind: kind.into(), - } - } -} - -impl Display for BuildInItemError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use BuildInItemErrorKind::*; - let kind = match &self.kind { - Size(_) => "size", - BasePath(_) => "base-path", - LastModified(_) => "last-modified", - InvalidStorageClass => "storage-class", - }; - write!(f, "parse {kind} failed, gived str: {}", self.source) - } -} - -impl Error for BuildInItemError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - use BuildInItemErrorKind::*; - match &self.kind { - Size(e) => Some(e), - BasePath(e) => Some(e), - LastModified(e) => Some(e), - InvalidStorageClass => None, - } - } -} - -/// Xml 转化为内置 Object 时的错误集合 -#[derive(Debug)] -#[non_exhaustive] -pub(crate) enum BuildInItemErrorKind { - /// 转换数字类型的错误 - Size(ParseIntError), - - /// 转换为 ObjectPath 时的错误 - BasePath(InvalidObjectPath), - - /// 转换日期格式的错误 - LastModified(chrono::ParseError), - - // /// 接收 Xml 转换时的错误 - // Xml(quick_xml::Error), - /// 非法的 StorageClass - InvalidStorageClass, -} - -impl From for BuildInItemErrorKind { - fn from(value: InvalidObjectPath) -> Self { - Self::BasePath(value) - } -} - -impl, E: Error + 'static> - RefineObjectList for ObjectList -{ - #[inline] - fn set_key_count(&mut self, key_count: &str) -> Result<(), ObjectListError> { - self.key_count = key_count.parse().map_err(|e| ObjectListError { - source: key_count.to_owned(), - kind: ObjectListErrorKind::KeyCount(e), - })?; - Ok(()) - } - - #[inline] - fn set_prefix(&mut self, prefix: &str) -> Result<(), ObjectListError> { - if prefix.is_empty() { - self.prefix = None; - } else { - let mut string = String::from(prefix); - string += "/"; - self.prefix = Some(string.parse().map_err(|e| ObjectListError { - source: prefix.to_owned(), - kind: ObjectListErrorKind::Prefix(e), - })?) - } - Ok(()) - } - - #[inline] - fn set_common_prefix( - &mut self, - list: &[std::borrow::Cow<'_, str>], - ) -> Result<(), ObjectListError> { - self.common_prefixes = Vec::with_capacity(list.len()); - for val in list.iter() { - self.common_prefixes - .push(val.parse().map_err(|e| ObjectListError { - source: val.to_string(), - kind: ObjectListErrorKind::CommonPrefix(e), - })?); - } - Ok(()) - } - - #[inline] - fn set_max_keys(&mut self, max_keys: &str) -> Result<(), ObjectListError> { - self.max_keys = max_keys.parse().map_err(|e| ObjectListError { - source: max_keys.to_string(), - kind: ObjectListErrorKind::MaxKeys(e), - })?; - Ok(()) - } - - #[inline] - fn set_next_continuation_token_str(&mut self, token: &str) -> Result<(), ObjectListError> { - self.next_continuation_token = token.to_owned(); - Ok(()) - } - - #[inline] - fn set_list(&mut self, list: Vec) -> Result<(), ObjectListError> { - self.object_list = list; - Ok(()) - } -} - -/// decode xml to object list error collection -#[derive(Debug)] -#[non_exhaustive] -pub struct ObjectListError { - source: String, - kind: ObjectListErrorKind, -} - -impl ObjectListError { - #[cfg(test)] - pub(crate) fn test_new() -> Self { - Self { - source: "foo".to_string(), - kind: ObjectListErrorKind::Bar, - } - } -} - -impl Display for ObjectListError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use ObjectListErrorKind::*; - let kind: &str = match &self.kind { - KeyCount(_) => "key-count", - Prefix(_) => "prefix", - CommonPrefix(_) => "common-prefix", - MaxKeys(_) => "max-keys", - #[cfg(test)] - Bar => "bar", - }; - write!(f, "parse {kind} failed, gived str: {}", self.source) - } -} - -impl Error for ObjectListError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - use ObjectListErrorKind::*; - match &self.kind { - KeyCount(e) | MaxKeys(e) => Some(e), - Prefix(e) | CommonPrefix(e) => Some(e), - #[cfg(test)] - Bar => None, - } + pub fn etag(&self) -> &str { + &self.etag } } -impl ListError for ObjectListError {} - -/// decode xml to object list error collection -#[derive(Debug)] -#[non_exhaustive] -enum ObjectListErrorKind { - /// when covert key_count failed ,return this error - KeyCount(ParseIntError), - /// when covert prefix failed ,return this error - Prefix(InvalidObjectDir), - /// when covert common_prefix failed ,return this error - CommonPrefix(InvalidObjectDir), - /// when covert max_keys failed ,return this error - MaxKeys(ParseIntError), - #[cfg(test)] - Bar, -} - -impl Client { - /// 查询默认 bucket 的文件列表 - /// - /// 查询条件参数有多种方式,具体参考 [`get_object_list`] 文档 - /// - /// [`get_object_list`]: crate::bucket::Bucket::get_object_list - #[inline(always)] - pub async fn get_object_list>( - &self, - query: Q, - ) -> Result { - self.get_object_list2(Query::from_iter(query)).await - } - - /// 查询默认 bucket 的文件列表 - pub async fn get_object_list2(&self, query: Query) -> Result { - let bucket = BucketBase::new(self.bucket.to_owned(), self.endpoint.to_owned()); - - let (bucket_url, resource) = bucket.get_url_resource(&query); - - let mut list = ObjectList:: { - object_list: Vec::with_capacity(query.get_max_keys()), - bucket, - ..Default::default() - }; - - let response = self.builder(Method::GET, bucket_url, resource)?; - let content = response.send_adjust_error().await?; - - list.decode( - &content.text().await?, - ObjectList::::init_object, - )?; - - list.set_client(Arc::new(self.clone())); - list.set_search_query(query); - - Ok(list) - } +#[cfg(test)] +mod tests { + use super::Object; + use crate::{ + bucket::Bucket, + client::{init_client, Client}, + types::{EndPoint, ObjectQuery}, + }; - /// # 可将 object 列表导出到外部类型(关注便捷性) - /// - /// 从 Client 中的默认 bucket 中获取,如需获取其他 bucket 的,可调用 `set_bucket` 更改后调用 - /// - /// 也可以通过调用 `set_endpoint` 更改可用区 - /// - /// 可以参考下面示例,或者项目中的 `examples/custom.rs` - /// ## 示例 - /// ```rust - /// use aliyun_oss_client::{ - /// decode::{ListError, RefineObject, RefineObjectList}, - /// object::{ExtractListError, InitObject}, - /// Client, - /// }; - /// use dotenv::dotenv; - /// use thiserror::Error; - /// - /// #[derive(Debug)] - /// struct MyFile { - /// key: String, - /// #[allow(dead_code)] - /// other: String, - /// } - /// impl RefineObject for MyFile { - /// fn set_key(&mut self, key: &str) -> Result<(), MyError> { - /// self.key = key.to_string(); - /// Ok(()) - /// } - /// } - /// - /// #[derive(Default, Debug)] - /// struct MyBucket { - /// name: String, - /// files: Vec, - /// } - /// - /// impl RefineObjectList for MyBucket { - /// fn set_name(&mut self, name: &str) -> Result<(), MyError> { - /// self.name = name.to_string(); - /// Ok(()) - /// } - /// fn set_list(&mut self, list: Vec) -> Result<(), MyError> { - /// self.files = list; - /// Ok(()) - /// } - /// } - /// - /// #[derive(Debug, Error)] - /// #[error("my error")] - /// enum MyError {} - /// - /// impl ListError for MyError {} - /// - /// async fn run() -> Result<(), ExtractListError> { - /// dotenv().ok(); - /// use aliyun_oss_client::BucketName; - /// - /// let client = Client::from_env().unwrap(); - /// - /// // 除了设置Default 外,还可以做更多设置 - /// let mut bucket = MyBucket { - /// name: "abc".to_string(), - /// files: Vec::with_capacity(20), - /// }; - /// - /// // 利用闭包对 MyFile 做一下初始化设置 - /// impl InitObject for MyBucket { - /// fn init_object(&mut self) -> Option { - /// Some(MyFile { - /// key: String::default(), - /// other: "abc".to_string(), - /// }) - /// } - /// } - /// - /// client.base_object_list([], &mut bucket).await?; - /// - /// println!("bucket: {:?}", bucket); - /// - /// Ok(()) - /// } - /// ``` - #[inline] - pub async fn base_object_list< - Q: IntoIterator, - List, - Item, - E: ListError, - ItemErr: Error + 'static, - >( - &self, - query: Q, - list: &mut List, - ) -> Result<(), ExtractListError> - where - List: RefineObjectList + InitObject, - Item: RefineObject, - { - let query = Query::from_iter(query); + fn set_client() -> Client { + let mut client = init_client(); + client.set_bucket(Bucket::new("honglei123", EndPoint::CN_SHANGHAI)); - self.base_object_list2(&query, list).await + client } - /// # 可将 object 列表导出到外部类型(关注性能) - /// - /// 从 Client 中的默认 bucket 中获取,如需获取其他 bucket 的,可调用 `set_bucket` 更改后调用 - /// - /// 也可以通过调用 `set_endpoint` 更改可用区 - pub async fn base_object_list2( - &self, - query: &Query, - list: &mut List, - ) -> Result<(), ExtractListError> - where - List: RefineObjectList + InitObject, - Item: RefineObject, - { - let bucket = self.get_bucket_base(); - let (bucket_url, resource) = bucket.get_url_resource(query); - - let response = self.builder(Method::GET, bucket_url, resource)?; - let content = response.send_adjust_error().await?; + #[tokio::test] + async fn test_object_info() { + let object = Object::new("app-config.json".into()); - list.decode(&content.text().await?, List::init_object)?; + let info = object.get_info(&set_client()).await.unwrap(); - Ok(()) + println!("{info:?}"); } - /// # 获取包含自定义类型的 object 集合 - /// 其包含在 [`ObjectList`] 对象中 - /// - /// [`ObjectList`]: crate::object::ObjectList - pub async fn get_custom_object( - &self, - query: &Query, - ) -> Result, ExtractListError> - where - Item: RefineObject, - Objects: InitObject, - ItemErr: Error + 'static, - { - let mut list = Objects::::default(); - - self.base_object_list2(query, &mut list).await?; - - Ok(list) - } -} + #[tokio::test] + async fn test_upload() { + let object = Object::new("abc.txt".into()); -/// 为 [`base_object_list`] 方法,返回一个统一的 Error -/// -/// [`base_object_list`]: crate::client::Client::base_object_list -#[derive(Debug)] -#[non_exhaustive] -pub struct ExtractListError { - pub(crate) kind: ExtractListErrorKind, -} + let info = object.upload("aaa".into(), &set_client()).await.unwrap(); -impl ExtractListError { - /// 判断 Error 类型是否为 "没有更多信息" - pub fn is_no_more(&self) -> bool { - matches!(self.kind, ExtractListErrorKind::NoMoreFile) + println!("{info:?}"); } -} - -/// [`ExtractListError`] 类型的枚举 -/// -/// [`ExtractListError`]: crate::object::ExtractListError -#[derive(Debug)] -#[non_exhaustive] -pub(crate) enum ExtractListErrorKind { - #[doc(hidden)] - Builder(BuilderError), - - #[doc(hidden)] - Reqwest(reqwest::Error), - /// 解析 xml 错误 - Decode(InnerListError), + #[tokio::test] + async fn test_down() { + let object = Object::new("abc.txt".into()); - /// 用于 Stream - NoMoreFile, -} + let info = object.download(&set_client()).await.unwrap(); -impl From for ExtractListError { - fn from(value: InnerListError) -> Self { - use ExtractListErrorKind::*; - Self { - kind: Decode(value), - } + println!("{info:?}"); } -} -impl From for ExtractListError { - fn from(value: BuilderError) -> Self { - use ExtractListErrorKind::*; - Self { - kind: Builder(value), - } - } -} -impl From for ExtractListError { - fn from(value: reqwest::Error) -> Self { - use ExtractListErrorKind::*; - Self { - kind: Reqwest(value), - } - } -} -impl Display for ExtractListError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use ExtractListErrorKind::*; - match &self.kind { - Builder(_) => "builder error".fmt(f), - Reqwest(_) => "reqwest error".fmt(f), - Decode(_) => "decode xml failed".fmt(f), - NoMoreFile => "no more file".fmt(f), - } - } -} -impl Error for ExtractListError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - use ExtractListErrorKind::*; - match &self.kind { - Builder(e) => Some(e), - Reqwest(e) => Some(e), - Decode(e) => e.get_source(), - NoMoreFile => None, - } - } -} -#[cfg(feature = "blocking")] -impl ClientRc { - /// 查询默认 bucket 的文件列表 - /// - /// 查询条件参数有多种方式,具体参考 [`get_object_list`](../bucket/struct.Bucket.html#method.get_object_list) 文档 - pub fn get_object_list>( - self, - query: Q, - ) -> Result, ExtractListError> { - let query = Query::from_iter(query); - - let bucket = BucketBase::new(self.bucket.to_owned(), self.endpoint.to_owned()); - - let (bucket_url, resource) = bucket.get_url_resource(&query); - - let mut list = ObjectList:: { - object_list: Vec::with_capacity(query.get_max_keys()), - bucket, - ..Default::default() + #[tokio::test] + async fn test_next_list() { + let client = set_client(); + let condition = { + let mut map = ObjectQuery::new(); + map.insert(ObjectQuery::MAX_KEYS, "5"); + map }; + let first_list = client + .bucket() + .unwrap() + .get_objects(&condition, &client) + .await + .unwrap(); - let response = self.builder(Method::GET, bucket_url, resource)?; - let content = response.send_adjust_error()?; - - list.decode(&content.text()?, ObjectList::::init_object)?; - - list.set_client(Rc::new(self)); - list.set_search_query(query); - - Ok(list) - } - - /// 可将 object 列表导出到外部 struct - #[inline] - pub fn base_object_list< - Q: IntoIterator, - List, - Item, - F, - E: ListError, - ItemErr: Error + 'static, - >( - &self, - query: Q, - list: &mut List, - init_object: F, - ) -> Result<(), ExtractListError> - where - List: RefineObjectList, - Item: RefineObject, - F: Fn(&mut List) -> Option, - { - let bucket = BucketBase::new(self.bucket.clone(), self.endpoint.to_owned()); - - let query = Query::from_iter(query); - let (bucket_url, resource) = bucket.get_url_resource(&query); - - let response = self.builder(Method::GET, bucket_url, resource)?; - let content = response.send_adjust_error()?; - - list.decode(&content.text()?, init_object)?; - - Ok(()) - } -} - -#[cfg(feature = "blocking")] -impl Iterator for ObjectList { - type Item = ObjectList; - fn next(&mut self) -> Option { - if !self.next_continuation_token.is_empty() { - self.search_query - .insert(CONTINUATION_TOKEN, self.next_continuation_token.to_owned()); - self.get_object_list().ok() - } else { - None - } - } -} - -// use std::task::Poll::{self, Ready}; - -// impl Stream for ObjectList { -// type Item = ObjectList; + let second_list = first_list.next_list(&condition, &client).await.unwrap(); -// fn poll_next( -// self: std::pin::Pin<&mut Self>, -// cx: &mut std::task::Context<'_>, -// ) -> Poll> { -// match self.next_query() { -// Some(query) => { -// let mut url = self.bucket.to_url(); -// url.set_search_query(&query); - -// let canonicalized = CanonicalizedResource::from_bucket_query(&self.bucket, &query); - -// let builder = self.builder(Method::GET, url, canonicalized); -// match builder { -// Err(err) => Ready(None), -// Ok(builder) => { -// let waker = cx.waker().clone(); - -// std::thread::spawn(move || { -// let response = builder.send_adjust_error(); - -// let response = futures::executor::block_on(response); -// let text = response.unwrap().text(); -// let text = futures::executor::block_on(text); - -// let text = text.unwrap(); - -// let bucket_arc = Arc::new(self.bucket); - -// let init_object = || { -// let object = Object::::default(); -// object.base.set_bucket(bucket_arc.clone()); -// object -// }; - -// self.decode(&text, init_object).unwrap(); - -// self.set_search_query(query); - -// waker.wake(); -// }); - -// Poll::Pending -// } -// } -// } -// None => Ready(None), -// } -// } -// } - -#[oss_gen_rc] -impl PartialEq> for Object { - #[inline] - fn eq(&self, other: &Object) -> bool { - self.base == other.base - && self.last_modified == other.last_modified - && self.etag == other.etag - && self._type == other._type - && self.size == other.size - && self.storage_class == other.storage_class + println!("{:?}", second_list); } } - -impl PartialEq> for Object { - #[inline] - fn eq(&self, other: &DateTime) -> bool { - &self.last_modified == other - } -} - -impl PartialEq for Object { - #[inline] - fn eq(&self, other: &u64) -> bool { - &self.size == other - } -} - -#[oss_gen_rc] -impl PartialEq> for Object { - #[inline] - fn eq(&self, other: &ObjectBase) -> bool { - &self.base == other - } -} - -/// 未来计划支持的功能 -#[derive(Default)] -#[doc(hidden)] -pub struct PutObject<'a> { - pub forbid_overwrite: bool, - pub server_side_encryption: Option, - pub server_side_data_encryption: Option, - pub server_side_encryption_key_id: Option<&'a str>, - pub object_acl: ObjectAcl, - pub storage_class: StorageClass, - pub tagging: Option<&'a str>, -} - -/// 未来计划支持的功能 -#[derive(Default)] -#[doc(hidden)] -pub enum Encryption { - #[default] - Aes256, - Kms, - Sm4, -} - -/// 未来计划支持的功能 -#[derive(Default)] -#[doc(hidden)] -pub enum ObjectAcl { - #[default] - Default, - Private, - PublicRead, - PublicReadWrite, -} - -/// 存储类型 -#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)] -#[non_exhaustive] -pub struct StorageClass { - kind: StorageClassKind, -} - -#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)] -#[non_exhaustive] -enum StorageClassKind { - /// Standard 默认 - #[default] - Standard, - /// IA - IA, - /// Archive - Archive, - /// ColdArchive - ColdArchive, -} - -impl StorageClass { - /// Archive - pub const ARCHIVE: Self = Self { - kind: StorageClassKind::Archive, - }; - /// IA - pub const IA: Self = Self { - kind: StorageClassKind::IA, - }; - /// Standard - pub const STANDARD: Self = Self { - kind: StorageClassKind::Standard, - }; - /// ColdArchive - pub const COLD_ARCHIVE: Self = Self { - kind: StorageClassKind::ColdArchive, - }; - - /// init StorageClass - pub fn new(s: &str) -> Option { - let start_char = s.chars().next()?; - - let kind = match start_char { - 'a' | 'A' => StorageClassKind::Archive, - 'i' | 'I' => StorageClassKind::IA, - 's' | 'S' => StorageClassKind::Standard, - 'c' | 'C' => StorageClassKind::ColdArchive, - _ => return None, - }; - Some(Self { kind }) - } -} - -/// 未来计划支持的功能 -#[derive(Default)] -#[doc(hidden)] -pub struct CopyObject<'a> { - pub forbid_overwrite: bool, - pub copy_source: &'a str, - pub copy_source_if_match: Option<&'a str>, - pub copy_source_if_none_match: Option<&'a str>, - pub copy_source_if_unmodified_since: Option<&'a str>, - pub copy_source_if_modified_since: Option<&'a str>, - pub metadata_directive: CopyDirective, - pub server_side_encryption: Option, - pub server_side_encryption_key_id: Option<&'a str>, - pub object_acl: ObjectAcl, - pub storage_class: StorageClass, - pub tagging: Option<&'a str>, - pub tagging_directive: CopyDirective, -} - -/// 未来计划支持的功能 -#[derive(Default)] -#[doc(hidden)] -pub enum CopyDirective { - #[default] - Copy, - Replace, -} diff --git a/src/object/content.rs b/src/object/content.rs deleted file mode 100644 index 2e92ae5..0000000 --- a/src/object/content.rs +++ /dev/null @@ -1,1068 +0,0 @@ -//! # 读写 object 内容 (实现 std Write/Read) -//! -//! ## 写入数据 -//! ```rust,no_run -//! # use aliyun_oss_client::{Client, object::Content}; -//! # use std::sync::Arc; -//! use std::io::Write; -//! fn main() -> std::io::Result<()> { -//! dotenv::dotenv().ok(); -//! let client = Client::from_env().unwrap(); -//! -//! let mut object = Content::from_client(Arc::new(client)).path("path1.txt").unwrap(); -//! object.write(b"abc")?; -//! object.flush(); -//! -//! Ok(()) -//! } -//! ``` -//! -//! ## 读取数据 -//! ```rust,no_run -//! # use aliyun_oss_client::{Client, Query}; -//! use aliyun_oss_client::object::content::List; -//! use std::io::Read; -//! #[tokio::main] -//! async fn main() -> std::io::Result<()> { -//! dotenv::dotenv().ok(); -//! let client = Client::from_env().unwrap(); -//! -//! let list: List = client -//! .get_custom_object(&Query::default()) -//! .await -//! .unwrap(); -//! let mut vec = list.to_vec(); -//! let mut object = &mut vec[0]; -//! let mut buffer = [0; 10]; -//! object.read(&mut buffer)?; -//! -//! println!("{:?}", buffer); -//! -//! Ok(()) -//! } -//! ``` - -use std::{ - error::Error, - fmt::Display, - io::{Read, Result as IoResult, Seek, SeekFrom, Write}, - ops::{Deref, DerefMut}, - sync::Arc, -}; - -use futures::executor::block_on; -use http::{header::CONTENT_LENGTH, HeaderValue, Method}; -use url::Url; - -use crate::{ - builder::BuilderError, - decode::RefineObject, - file::{AlignBuilder, DEFAULT_CONTENT_TYPE}, - types::{ - object::{InvalidObjectPath, SetObjectPath}, - CanonicalizedResource, - }, - Client, ObjectPath, -}; - -use super::{BuildInItemError, InitObject, Objects}; - -#[cfg(feature = "blocking")] -pub mod blocking; - -#[cfg(not(test))] -use crate::file::Files; -#[cfg(test)] -use mock::Files; - -#[cfg(test)] -mod mock; -#[cfg(test)] -mod test_suite; -#[cfg(all(test, feature = "blocking"))] -mod test_suite_block; - -/// # object 内容 -/// [OSS 分片上传文档](https://help.aliyun.com/zh/oss/user-guide/multipart-upload-12) -#[derive(Default)] -pub struct Content { - client: Arc, - inner: Inner, -} - -/// # 内部 -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Inner { - path: ObjectPath, - content_size: u64, - current_pos: u64, - content_part: Vec>, - content_type: &'static str, - upload_id: String, - /// 分片上传返回的 etag - etag_list: Vec<(u16, HeaderValue)>, - part_size: usize, -} - -impl Write for Content { - // 写入缓冲区 - fn write(&mut self, buf: &[u8]) -> IoResult { - self.inner.write(buf) - } - - // 按分片数量选择上传 OSS 的方式 - fn flush(&mut self) -> IoResult<()> { - let len = self.content_part.len(); - - //println!("len: {}", len); - - if len == 0 { - return Ok(()); - } - if len == 1 { - return block_on(self.upload()); - } - - block_on(self.upload_multi()) - } -} - -impl Read for Content { - fn read(&mut self, buf: &mut [u8]) -> IoResult { - let len = buf.len(); - if len as u64 > Inner::MAX_SIZE { - return Err(ContentError::new(ContentErrorKind::OverflowMaxSize).into()); - } - - let end = self.current_pos + len as u64; - let vec = block_on( - self.client - .get_object(self.path.clone(), self.current_pos..end - 1), - )?; - - let len = vec.len().min(buf.len()); - buf[..len].copy_from_slice(&vec[..len]); - - Ok(len) - } -} - -impl Seek for Content { - fn seek(&mut self, pos: SeekFrom) -> IoResult { - self.inner.seek(pos) - } -} - -impl Deref for Content { - type Target = Inner; - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl DerefMut for Content { - fn deref_mut(&mut self) -> &mut Inner { - &mut self.inner - } -} - -/// 带内容的 object 列表 -pub type List = Objects; - -impl InitObject for List { - fn init_object(&mut self) -> Option { - Some(Content { - client: self.client(), - ..Default::default() - }) - } -} - -impl Content { - /// 从 client 创建 - pub fn from_client(client: Arc) -> Content { - Content { - client, - ..Default::default() - } - } - /// 设置 ObjectPath - pub fn path

(mut self, path: P) -> Result - where - P: TryInto, - P::Error: Into, - { - self.path = path.try_into().map_err(Into::into)?; - self.content_type_with_path(); - Ok(self) - } - - fn part_canonicalized(&self, query: &str) -> (Url, CanonicalizedResource) { - let mut url = self.client.get_bucket_url(); - url.set_object_path(&self.path); - url.set_query(Some(query)); - - let bucket = self.client.get_bucket_name(); - ( - url, - CanonicalizedResource::new(format!("/{}/{}?{}", bucket, self.path.as_ref(), query)), - ) - } - async fn upload(&mut self) -> IoResult<()> { - assert!(self.content_part.len() == 1); - let content = self.content_part.pop().expect("content_part len is not 1"); - self.client - .put_content_base(content, self.content_type, self.path.clone()) - .await - .map(|_| ()) - .map_err(Into::into) - } - - async fn upload_multi(&mut self) -> IoResult<()> { - self.init_multi().await?; - - let mut i = 1; - let mut size: u64 = 0; - self.content_part.reverse(); - while let Some(item) = self.content_part.pop() { - size += item.len() as u64; - self.upload_part(i, item).await?; - i += 1; - } - - self.complete_multi().await?; - self.content_size = size; - Ok(()) - } - - /// 初始化批量上传 - async fn init_multi(&mut self) -> Result<(), ContentError> { - const UPLOADS: &str = "uploads"; - - let (url, resource) = self.part_canonicalized(UPLOADS); - let xml = self - .client - .builder(Method::POST, url, resource)? - .send_adjust_error() - .await? - .text() - .await?; - - self.parse_upload_id(&xml) - } - /// 上传分块 - async fn upload_part(&mut self, index: u16, buf: Vec) -> Result<(), ContentError> { - const ETAG: &str = "ETag"; - - if self.upload_id.is_empty() { - return Err(ContentError::new(ContentErrorKind::NoFoundUploadId)); - } - - if self.etag_list.len() >= Inner::MAX_PARTS_COUNT as usize { - return Err(ContentError::new(ContentErrorKind::OverflowMaxPartsCount)); - } - if buf.len() > Inner::PART_SIZE_MAX { - return Err(ContentError::new(ContentErrorKind::OverflowPartSize)); - } - - let (url, resource) = - self.part_canonicalized(&format!("partNumber={}&uploadId={}", index, self.upload_id)); - - let content_length = buf.len().to_string(); - let headers = vec![( - CONTENT_LENGTH, - HeaderValue::from_str(&content_length) - .expect("content length must be a valid header value"), - )]; - - let resp = self - .client - .builder_with_header(Method::PUT, url, resource, headers)? - .body(buf) - .send_adjust_error() - .await?; - - let etag = resp - .headers() - .get(ETAG) - .ok_or(ContentError::new(ContentErrorKind::NoFoundEtag))?; - - //println!("etag: {:?}", etag); - - // 59A2A10DD1686F679EE885FC1EBA5183 - //let etag = &(etag.to_str().unwrap())[1..33]; - - self.etag_list.push((index, etag.to_owned())); - - Ok(()) - } - /// 完成分块上传 - async fn complete_multi(&mut self) -> Result<(), ContentError> { - if self.upload_id.is_empty() { - return Err(ContentError::new(ContentErrorKind::NoFoundUploadId)); - } - - let xml = self.etag_list_xml()?; - - let (url, resource) = self.part_canonicalized(&format!("uploadId={}", self.upload_id)); - - let content_length = xml.len().to_string(); - let headers = vec![( - CONTENT_LENGTH, - HeaderValue::from_str(&content_length) - .expect("content length must be a valid header value"), - )]; - - let _resp = self - .client - .builder_with_header(Method::POST, url, resource, headers)? - .body(xml) - .send_adjust_error() - .await?; - - self.etag_list.clear(); - self.upload_id = String::default(); - - Ok(()) - } - /// 取消分块上传 - pub async fn abort_multi(&mut self) -> Result<(), ContentError> { - if self.upload_id.is_empty() { - return Err(ContentError::new(ContentErrorKind::NoFoundUploadId)); - } - let query = format!("uploadId={}", self.upload_id); - - let (url, resource) = self.part_canonicalized(&query); - let _resp = self - .client - .builder(Method::DELETE, url, resource)? - .send_adjust_error() - .await?; - - //println!("resp: {:?}", resp); - self.etag_list.clear(); - self.upload_id = String::default(); - - Ok(()) - } -} - -impl Seek for Inner { - fn seek(&mut self, pos: SeekFrom) -> IoResult { - use std::io::{Error, ErrorKind}; - let n = match pos { - SeekFrom::Start(p) => p, - SeekFrom::End(p) => { - if self.content_size == 0 { - return Err(Error::new(ErrorKind::InvalidData, "content size is 0")); - } - // self.content_size - p - i64::try_from(self.content_size) - .and_then(|v| u64::try_from(v - p)) - .map_err(|e| Error::new(ErrorKind::InvalidData, e))? - } - // self.current_pos + n - SeekFrom::Current(n) => i64::try_from(self.current_pos) - .and_then(|v| u64::try_from(v + n)) - .map_err(|e| Error::new(ErrorKind::InvalidData, e))?, - }; - self.current_pos = n; - Ok(n) - } -} - -// impl Drop for Content { -// fn drop(&mut self) { -// if self.upload_id.is_empty() == false { -// self.abort_multi(); -// } -// } -// } - -mod private { - use super::Content; - - pub trait Sealed {} - - impl Sealed for Content {} - - #[cfg(feature = "blocking")] - impl Sealed for super::blocking::Content {} -} - -impl + private::Sealed> RefineObject for T { - #[inline] - fn set_key(&mut self, key: &str) -> Result<(), BuildInItemError> { - self.path = key.parse().map_err(|e| BuildInItemError::new(e, key))?; - self.content_type_with_path(); - Ok(()) - } - /// 提取 size - fn set_size(&mut self, size: &str) -> Result<(), BuildInItemError> { - if let Ok(size) = size.parse() { - self.content_size = size; - } - Ok(()) - } -} - -impl Default for Inner { - fn default() -> Self { - Self { - path: ObjectPath::default(), - content_size: u64::default(), - current_pos: 0, - content_part: Vec::default(), - content_type: Self::DEFAULT_CONTENT_TYPE, - upload_id: String::default(), - etag_list: Vec::default(), - part_size: 200 * 1024 * 1024, // 200M - } - } -} - -fn get_content_type(filename: &str) -> &'static str { - match filename.rsplit('.').next() { - Some(str) => match str.to_lowercase().as_str() { - "jpg" => "image/jpeg", - "pdf" => "application/pdf", - "png" => "image/png", - "gif" => "image/gif", - "bmp" => "image/bmp", - "zip" => "application/zip", - "tar" => "application/x-tar", - "gz" => "application/gzip", - "txt" => "text/plain", - "mp3" => "audio/mpeg", - "wav" => "audio/wave", - "mp4" => "video/mp4", - "mov" => "video/quicktime", - "avi" => "video/x-msvideo", - "wmv" => "video/x-ms-wmv", - "html" => "text/html", - "js" => "application/javascript", - "css" => "text/css", - "php" => "application/x-httpd-php", - _ => DEFAULT_CONTENT_TYPE, - }, - None => DEFAULT_CONTENT_TYPE, - } -} - -impl Inner { - const DEFAULT_CONTENT_TYPE: &str = DEFAULT_CONTENT_TYPE; - - /// 最大存储容量 48.8 TB, 49664 = 1024 * 48.5 - #[cfg(not(test))] - const MAX_SIZE: u64 = 1024 * 1024 * 1024 * 49_664; - /// 最大 part 数量 - #[cfg(not(test))] - const MAX_PARTS_COUNT: u16 = 10000; - /// 单个 part 的最小尺寸 100K - #[cfg(not(test))] - const PART_SIZE_MIN: usize = 102400; - /// 单个 part 的最大尺寸 5G - #[cfg(not(test))] - const PART_SIZE_MAX: usize = 1024 * 1024 * 1024 * 5; - - /// 最大存储容量 48.8 TB, 49664 = 1024 * 48.5 - #[cfg(test)] - const MAX_SIZE: u64 = 200; - /// 最大 part 数量 - #[cfg(test)] - const MAX_PARTS_COUNT: u16 = 10; - /// 单个 part 的最小尺寸 100K - #[cfg(test)] - const PART_SIZE_MIN: usize = 10; - /// 单个 part 的最大尺寸 5G - #[cfg(test)] - const PART_SIZE_MAX: usize = 20; - - /// 设置 ObjectPath - pub fn path

(mut self, path: P) -> Result - where - P: TryInto, - P::Error: Into, - { - self.path = path.try_into().map_err(Into::into)?; - self.content_type_with_path(); - Ok(self) - } - - fn content_type_with_path(&mut self) { - self.content_type = get_content_type(self.path.as_ref()); - } - - /// 设置 content_type - pub fn content_type(&mut self, content_type: &'static str) { - self.content_type = content_type; - } - - // 写入缓冲区 - fn write(&mut self, buf: &[u8]) -> IoResult { - let part_total = self.content_part.len(); - if part_total >= Inner::MAX_PARTS_COUNT as usize { - return Err(ContentError::new(ContentErrorKind::OverflowMaxPartsCount).into()); - } - - let part_size = self.part_size; - if let Some(part) = self.content_part.last_mut() { - let part_len = part.len(); - if part_len < part_size { - let mid = part_size - part_len; - let left = &buf[..mid.min(buf.len())]; - - part.append(&mut left.to_vec()); - - return Ok(left.len()); - } - } - - let con = &buf[..buf.len().min(self.part_size)]; - self.content_part.push({ - let mut vec = Vec::with_capacity(self.part_size); - vec.extend(con); - vec - }); - - Ok(con.len()) - } - - /// 设置分块的尺寸 - pub fn part_size(&mut self, size: usize) -> Result<(), ContentError> { - if (Self::PART_SIZE_MIN..=Self::PART_SIZE_MAX).contains(&size) { - self.part_size = size; - Ok(()) - } else { - Err(ContentError::new(ContentErrorKind::OverflowPartSize)) - } - } - fn parse_upload_id(&mut self, xml: &str) -> Result<(), ContentError> { - if let (Some(start), Some(end)) = (xml.find(""), xml.find("")) { - self.upload_id = xml[start + 10..end].to_owned(); - Ok(()) - } else { - Err(ContentError::new(ContentErrorKind::NoFoundUploadId)) - } - } - fn etag_list_xml(&self) -> Result { - if self.etag_list.is_empty() { - return Err(ContentError::new(ContentErrorKind::EtagListEmpty)); - } - let mut list = String::new(); - for (index, etag) in self.etag_list.iter() { - list.push_str(&format!( - "{}{}", - index, - etag.to_str().expect("etag covert str failed") - )); - } - - Ok(format!( - "{}", - list - )) - } - - /// 清空缓冲区 - pub fn part_clear(&mut self) { - self.content_part.clear(); - } -} - -impl From for Content { - fn from(value: Client) -> Self { - Content { - client: Arc::new(value), - ..Default::default() - } - } -} - -/// object Content 的错误信息 -#[derive(Debug)] -#[non_exhaustive] -pub struct ContentError { - kind: ContentErrorKind, -} - -/// object Content 的错误信息 -#[derive(Debug)] -#[non_exhaustive] -enum ContentErrorKind { - /// not found upload id - NoFoundUploadId, - - /// builder request failed - Builder(BuilderError), - - /// not found etag - NoFoundEtag, - - /// overflow max parts count - OverflowMaxPartsCount, - - /// etag list is empty - EtagListEmpty, - - /// part size must be between 100k and 5G - OverflowPartSize, - - /// max size must be lt 48.8TB - OverflowMaxSize, -} - -impl ContentError { - fn new(kind: ContentErrorKind) -> Self { - Self { kind } - } -} - -impl Display for ContentError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.kind.fmt(f) - } -} - -impl Error for ContentError { - fn source(&self) -> Option<&(dyn Error + 'static)> { - self.kind.source() - } -} - -impl From for ContentError { - fn from(value: BuilderError) -> Self { - Self { - kind: ContentErrorKind::Builder(value), - } - } -} -impl From for ContentError { - fn from(value: reqwest::Error) -> Self { - Self { - kind: ContentErrorKind::Builder(BuilderError::from_reqwest(value)), - } - } -} -impl From for std::io::Error { - fn from(ContentError { kind }: ContentError) -> Self { - use std::io::ErrorKind::*; - use ContentErrorKind::*; - match kind { - Builder(e) => e.into(), - NoFoundUploadId => Self::new(NotFound, kind), - NoFoundEtag => Self::new(NotFound, kind), - OverflowMaxPartsCount => Self::new(InvalidInput, kind), - EtagListEmpty => Self::new(NotFound, kind), - OverflowPartSize => Self::new(Unsupported, kind), - OverflowMaxSize => Self::new(Unsupported, kind), - } - } -} - -impl Display for ContentErrorKind { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match *self { - Self::NoFoundUploadId => "not found upload id".fmt(f), - Self::Builder(_) => "builder request failed".fmt(f), - Self::NoFoundEtag => "not found etag".fmt(f), - Self::OverflowMaxPartsCount => "overflow max parts count".fmt(f), - Self::EtagListEmpty => "etag list is empty".fmt(f), - Self::OverflowPartSize => "part size must be between 100k and 5G".fmt(f), - Self::OverflowMaxSize => "max size must be lt 48.8TB".fmt(f), - } - } -} - -impl Error for ContentErrorKind { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - Self::Builder(e) => Some(e), - Self::NoFoundUploadId - | Self::NoFoundEtag - | Self::OverflowMaxPartsCount - | Self::EtagListEmpty - | Self::OverflowPartSize - | Self::OverflowMaxSize => None, - } - } -} - -#[cfg(test)] -mod tests { - use std::{ - io::{Read, Seek, Write}, - ops::Deref, - sync::Arc, - }; - - use super::{ - get_content_type, - test_suite::{AbortMulti, CompleteMulti, InitMulti, UploadMulti, UploadPart}, - Content, Inner, List, - }; - - use crate::{decode::RefineObject, object::InitObject, Client, ObjectPath}; - - #[test] - fn default() { - let inner = Inner::default(); - - assert_eq!(inner.path, ObjectPath::default()); - assert_eq!(inner.content_size, 0); - assert_eq!(inner.current_pos, 0); - assert_eq!(inner.content_part.len(), 0); - assert_eq!(inner.content_type, "application/octet-stream"); - assert_eq!(inner.upload_id, ""); - assert_eq!(inner.etag_list.len(), 0); - assert_eq!(inner.part_size, 200 * 1024 * 1024); - } - - #[test] - fn read() { - let client = Client::test_init(); - let mut con = Content::from_client(Arc::new(client)) - .path("aaa.txt") - .unwrap(); - - let mut buf = [0u8; 201]; - let err = con.read(&mut buf).unwrap_err(); - assert_eq!(err.to_string(), "max size must be lt 48.8TB"); - - let mut buf = [0u8; 10]; - let len = con.read(&mut buf).unwrap(); - assert_eq!(buf, [1u8, 2, 3, 4, 5, 0, 0, 0, 0, 0]); - assert_eq!(len, 5); - - let mut buf = [0u8; 3]; - let len = con.read(&mut buf).unwrap(); - assert_eq!(buf, [1u8, 2, 3]); - assert_eq!(len, 3); - - con.current_pos = 10; - let mut buf = [0u8; 3]; - let len = con.read(&mut buf).unwrap(); - assert_eq!(buf, [1u8, 2, 3]); - assert_eq!(len, 3); - } - - #[test] - fn init_object() { - let mut list = List::default(); - let client = Client::test_init(); - list.set_client(Arc::new(client.clone())); - - let con = list.init_object().unwrap(); - - assert_eq!(con.client.bucket, client.bucket); - assert_eq!(con.inner, Inner::default()); - } - - #[test] - fn from_client() { - let client = Client::test_init(); - - let con = Content::from_client(Arc::new(client.clone())); - - assert_eq!(con.client.bucket, client.bucket); - assert_eq!(con.inner, Inner::default()); - } - - #[test] - fn path() { - let con = Content::default().path("aaa.txt").unwrap(); - assert_eq!(con.path, "aaa.txt"); - } - - #[test] - fn part_canonicalized() { - let client = Client::test_init(); - let con = Content::from_client(Arc::new(client)) - .path("aaa.txt") - .unwrap(); - - let (url, can) = con.part_canonicalized("first=1&second=2"); - assert_eq!( - url.as_str(), - "https://bar.oss-cn-qingdao.aliyuncs.com/aaa.txt?first=1&second=2" - ); - assert_eq!(can.to_string(), "/bar/aaa.txt?first=1&second=2"); - } - - #[tokio::test] - async fn upload() { - let client = Client::test_init(); - let mut con = Content::from_client(Arc::new(client)) - .path("aaa.txt") - .unwrap(); - con.content_part.push(b"bbb".to_vec()); - con.upload().await.unwrap(); - } - - #[tokio::test] - #[should_panic] - async fn upload_panic() { - let client = Client::test_init(); - let mut con = Content::from_client(Arc::new(client)) - .path("aaa.txt") - .unwrap(); - con.content_part.push(b"bbb".to_vec()); - con.content_part.push(b"ccc".to_vec()); - let _ = con.upload().await; - } - - #[tokio::test] - async fn init_multi() { - let client = Client::test_init().middleware(Arc::new(InitMulti {})); - let mut con = Content::from_client(Arc::new(client)) - .path("aaa.txt") - .unwrap(); - - con.init_multi().await.unwrap(); - - assert_eq!(con.upload_id, "foo_upload_id"); - } - - #[tokio::test] - async fn upload_part() { - let client = Client::test_init().middleware(Arc::new(UploadPart {})); - let mut con = Content::from_client(Arc::new(client)) - .path("aaa.txt") - .unwrap(); - - let err = con.upload_part(1, b"bbb".to_vec()).await.unwrap_err(); - assert_eq!(err.to_string(), "not found upload id"); - - con.upload_id = "foo_upload_id".to_string(); - for _i in 0..10 { - con.etag_list.push((1, "a".parse().unwrap())); - } - let err = con.upload_part(1, b"bbb".to_vec()).await.unwrap_err(); - assert_eq!(err.to_string(), "overflow max parts count"); - con.etag_list.clear(); - - let err = con - .upload_part(1, b"012345678901234567890".to_vec()) - .await - .unwrap_err(); - assert_eq!(err.to_string(), "part size must be between 100k and 5G"); - - con.upload_part(2, b"bbb".to_vec()).await.unwrap(); - let (index, value) = con.etag_list.pop().unwrap(); - assert_eq!(index, 2); - assert_eq!(value.to_str().unwrap(), "foo_etag"); - } - - #[tokio::test] - async fn complete_multi() { - let client = Client::test_init().middleware(Arc::new(CompleteMulti {})); - let mut con = Content::from_client(Arc::new(client)) - .path("aaa.txt") - .unwrap(); - let err = con.complete_multi().await.unwrap_err(); - assert_eq!(err.to_string(), "not found upload id"); - - con.upload_id = "foo_upload_id".to_string(); - con.etag_list.push((1, "aaa".parse().unwrap())); - con.etag_list.push((2, "bbb".parse().unwrap())); - con.complete_multi().await.unwrap(); - assert!(con.etag_list.is_empty()); - assert!(con.upload_id.is_empty()); - } - - #[tokio::test] - async fn upload_multi() { - let client = Client::test_init().middleware(Arc::new(UploadMulti {})); - let mut con = Content::from_client(Arc::new(client)) - .path("aaa.txt") - .unwrap(); - - con.content_part.push(b"aaa".to_vec()); - con.content_part.push(b"bbb".to_vec()); - - con.upload_multi().await.unwrap(); - - assert_eq!(con.content_size, 6); - } - - #[tokio::test] - async fn abort_multi() { - let client = Client::test_init().middleware(Arc::new(AbortMulti {})); - let mut con = Content::from_client(Arc::new(client)) - .path("aaa.txt") - .unwrap(); - let err = con.complete_multi().await.unwrap_err(); - assert_eq!(err.to_string(), "not found upload id"); - - con.upload_id = "foo_upload_id".to_string(); - con.etag_list.push((1, "aaa".parse().unwrap())); - con.abort_multi().await.unwrap(); - assert!(con.etag_list.is_empty()); - assert!(con.upload_id.is_empty()); - } - - #[test] - fn seek() { - let mut inner = Inner::default(); - let pos = inner.seek(std::io::SeekFrom::Start(5)).unwrap(); - assert_eq!(pos, 5); - assert_eq!(inner.current_pos, 5); - - let err = inner.seek(std::io::SeekFrom::End(10)).unwrap_err(); - assert_eq!(err.to_string(), "content size is 0"); - - inner.content_size = 11; - let pos = inner.seek(std::io::SeekFrom::End(5)).unwrap(); - assert_eq!(pos, 6); - inner.current_pos = 6; - - let pos = inner.seek(std::io::SeekFrom::Current(-1)).unwrap(); - assert_eq!(pos, 5); - inner.current_pos = 5; - } - - #[test] - fn test_get_content_type() { - assert_eq!(get_content_type("aaa.jpg"), "image/jpeg"); - assert_eq!(get_content_type("aaa.pdf"), "application/pdf"); - assert_eq!(get_content_type("aaa.png"), "image/png"); - assert_eq!(get_content_type("aaa.gif"), "image/gif"); - assert_eq!(get_content_type("aaa.bmp"), "image/bmp"); - assert_eq!(get_content_type("aaa.zip"), "application/zip"); - assert_eq!(get_content_type("aaa.tar"), "application/x-tar"); - assert_eq!(get_content_type("aaa.gz"), "application/gzip"); - assert_eq!(get_content_type("aaa.txt"), "text/plain"); - assert_eq!(get_content_type("aaa.mp3"), "audio/mpeg"); - assert_eq!(get_content_type("aaa.wav"), "audio/wave"); - assert_eq!(get_content_type("aaa.mp4"), "video/mp4"); - assert_eq!(get_content_type("aaa.mov"), "video/quicktime"); - assert_eq!(get_content_type("aaa.avi"), "video/x-msvideo"); - assert_eq!(get_content_type("aaa.wmv"), "video/x-ms-wmv"); - assert_eq!(get_content_type("aaa.html"), "text/html"); - assert_eq!(get_content_type("aaa.js"), "application/javascript"); - assert_eq!(get_content_type("aaa.css"), "text/css"); - assert_eq!(get_content_type("aaa.php"), "application/x-httpd-php"); - assert_eq!(get_content_type("aaa.doc"), "application/octet-stream"); - assert_eq!(get_content_type("file"), "application/octet-stream"); - } - - #[test] - fn test_path() { - let mut inner = Inner::default(); - inner = inner.path("bbb.txt").unwrap(); - assert_eq!(inner.path.as_ref(), "bbb.txt"); - assert_eq!(inner.content_type, "text/plain"); - } - - #[test] - fn content_type_with_path() { - let mut inner = Inner::default(); - inner.path = "ccc.html".parse().unwrap(); - inner.content_type_with_path(); - assert_eq!(inner.content_type, "text/html"); - } - - #[test] - fn test_content_type() { - let mut inner = Inner::default(); - inner.content_type("bar"); - assert_eq!(inner.content_type, "bar"); - } - - #[test] - fn part_size() { - let mut inner = Inner::default(); - let err = inner.part_size(5).unwrap_err(); - assert_eq!(err.to_string(), "part size must be between 100k and 5G"); - - let err = inner.part_size(21).unwrap_err(); - assert_eq!(err.to_string(), "part size must be between 100k and 5G"); - - inner.part_size(11).unwrap(); - assert_eq!(inner.part_size, 11); - } - - #[test] - fn test_parse_upload_id() { - let mut content = Inner::default(); - - let xml = r#" - bucket_name - aaa - AC3251A13464411D8691F271CE33A300 - "#; - content.parse_upload_id(xml).unwrap(); - - assert_eq!(content.upload_id, "AC3251A13464411D8691F271CE33A300"); - - content.parse_upload_id("abc").unwrap_err(); - } - - #[test] - fn etag_list_xml() { - let mut inner = Inner::default(); - - let err = inner.etag_list_xml().unwrap_err(); - assert_eq!(err.to_string(), "etag list is empty"); - - inner.etag_list.push((1, "aaa".parse().unwrap())); - inner.etag_list.push((2, "bbb".parse().unwrap())); - - let xml = inner.etag_list_xml().unwrap(); - assert_eq!(xml, "1aaa2bbb"); - } - - #[test] - fn part_clear() { - let mut inner = Inner::default(); - assert_eq!(inner.content_part.len(), 0); - - inner.content_part.push(vec![1u8, 2, 3]); - inner.content_part.push(vec![1u8, 2, 3]); - inner.part_clear(); - assert_eq!(inner.content_part.len(), 0); - } - - #[test] - fn assert_impl() { - fn impl_fn, E: std::error::Error + 'static>(_: T) {} - - impl_fn(Content::default()); - - fn impl_deref>(_: T) {} - - impl_deref(Content::default()); - } - - #[test] - fn test_write() { - let mut content = Content::default(); - content.part_size = 5; - content.write_all(b"abcdefg").unwrap(); - - assert!(content.content_part.len() == 2); - assert_eq!(content.content_part[0], b"abcde"); - assert_eq!(content.content_part[1], b"fg"); - - content.part_clear(); - - content.write(b"mn").unwrap(); - assert!(content.content_part.len() == 1); - assert_eq!(content.content_part[0], b"mn"); - - content.part_clear(); - - let len = content.write(b"efghijklmn").unwrap(); - assert!(content.content_part.len() == 1); - assert_eq!(content.content_part[0], b"efghi"); - assert_eq!(len, 5); - - content.part_clear(); - - content.write(b"o").unwrap(); - content.write(b"p").unwrap(); - content.write(b"q").unwrap(); - assert!(content.content_part.len() == 1); - assert_eq!(content.content_part[0], b"opq"); - } -} diff --git a/src/object/content/blocking.rs b/src/object/content/blocking.rs deleted file mode 100644 index b5f0ac4..0000000 --- a/src/object/content/blocking.rs +++ /dev/null @@ -1,494 +0,0 @@ -//! 读写 object 内容 - -use std::{ - io::{Read, Result as IoResult, Seek, SeekFrom, Write}, - ops::{Deref, DerefMut}, - rc::Rc, -}; - -use http::{header::CONTENT_LENGTH, HeaderValue, Method}; -use url::Url; - -#[cfg(test)] -use super::mock::blocking::Files; -#[cfg(not(test))] -use crate::file::BlockingFiles; -use crate::{ - file::blocking::AlignBuilder, - object::InitObject, - types::{ - object::{InvalidObjectPath, SetObjectPath}, - CanonicalizedResource, - }, - ClientRc as Client, ObjectPath, -}; - -use super::{super::ObjectsBlocking, ContentError, ContentErrorKind, Inner}; - -/// # object 内容 -/// [OSS 分片上传文档](https://help.aliyun.com/zh/oss/user-guide/multipart-upload-12) -//#[derive(Debug)] -pub struct Content { - client: Rc, - inner: Inner, -} - -impl Write for Content { - // 写入缓冲区 - fn write(&mut self, buf: &[u8]) -> IoResult { - self.inner.write(buf) - } - - // 按分片数量选择上传 OSS 的方式 - fn flush(&mut self) -> IoResult<()> { - let len = self.content_part.len(); - - //println!("len: {}", len); - - if len == 0 { - return Ok(()); - } - if len == 1 { - return self.upload(); - } - - self.upload_multi() - } -} - -impl Read for Content { - fn read(&mut self, buf: &mut [u8]) -> IoResult { - let len = buf.len(); - if len as u64 > Inner::MAX_SIZE { - return Err(ContentError::new(ContentErrorKind::OverflowMaxSize).into()); - } - - let end = self.current_pos + len as u64; - let vec = self - .client - .get_object(self.path.clone(), self.current_pos..end - 1)?; - - let len = std::cmp::min(vec.len(), buf.len()); - buf[..len].copy_from_slice(&vec[..len]); - - Ok(len) - } -} - -impl Seek for Content { - fn seek(&mut self, pos: SeekFrom) -> IoResult { - self.inner.seek(pos) - } -} - -impl Default for Content { - fn default() -> Self { - Self { - client: Rc::default(), - inner: Inner::default(), - } - } -} - -impl Deref for Content { - type Target = Inner; - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -impl DerefMut for Content { - fn deref_mut(&mut self) -> &mut Inner { - &mut self.inner - } -} - -/// 带内容的 object 列表 -pub type List = ObjectsBlocking; - -impl InitObject for List { - fn init_object(&mut self) -> Option { - Some(Content { - client: self.client(), - ..Default::default() - }) - } -} - -impl Content { - /// 从 client 创建 - pub fn from_client(client: Rc) -> Content { - Content { - client, - ..Default::default() - } - } - /// 设置 ObjectPath - pub fn path

(mut self, path: P) -> Result - where - P: TryInto, - P::Error: Into, - { - self.path = path.try_into().map_err(Into::into)?; - self.content_type_with_path(); - Ok(self) - } - - fn part_canonicalized<'q>(&self, query: &'q str) -> (Url, CanonicalizedResource) { - let mut url = self.client.get_bucket_url(); - url.set_object_path(&self.path); - url.set_query(Some(query)); - - let bucket = self.client.get_bucket_name(); - ( - url, - CanonicalizedResource::new(format!("/{}/{}?{}", bucket, self.path.as_ref(), query)), - ) - } - - fn upload(&mut self) -> IoResult<()> { - assert!(self.content_part.len() == 1); - let content = self.content_part.pop().unwrap(); - self.client - .put_content_base(content, self.content_type, self.path.clone()) - .map(|_| ()) - .map_err(Into::into) - } - - fn upload_multi(&mut self) -> IoResult<()> { - self.init_multi()?; - - let mut i = 1; - let mut size: u64 = 0; - self.content_part.reverse(); - while let Some(item) = self.content_part.pop() { - size += item.len() as u64; - self.upload_part(i, item)?; - i += 1; - } - - self.complete_multi()?; - self.content_size = size; - - Ok(()) - } - - /// 初始化批量上传 - fn init_multi(&mut self) -> Result<(), ContentError> { - const UPLOADS: &str = "uploads"; - - let (url, resource) = self.part_canonicalized(UPLOADS); - let xml = self - .client - .builder(Method::POST, url, resource)? - .send_adjust_error()? - .text()?; - - self.parse_upload_id(&xml) - } - - /// 上传分块 - fn upload_part(&mut self, index: u16, buf: Vec) -> Result<(), ContentError> { - const ETAG: &str = "ETag"; - - if self.upload_id.is_empty() { - return Err(ContentError::new(ContentErrorKind::NoFoundUploadId)); - } - - if self.etag_list.len() >= Inner::MAX_PARTS_COUNT as usize { - return Err(ContentError::new(ContentErrorKind::OverflowMaxPartsCount)); - } - if buf.len() > Inner::PART_SIZE_MAX { - return Err(ContentError::new(ContentErrorKind::OverflowPartSize)); - } - - let query = format!("partNumber={}&uploadId={}", index, self.upload_id); - - let (url, resource) = self.part_canonicalized(&query); - - let content_length = buf.len().to_string(); - let headers = vec![( - CONTENT_LENGTH, - HeaderValue::from_str(&content_length).unwrap(), - )]; - - let resp = self - .client - .builder_with_header(Method::PUT, url, resource, headers)? - .body(buf) - .send_adjust_error()?; - - let etag = resp - .headers() - .get(ETAG) - .ok_or(ContentError::new(ContentErrorKind::NoFoundEtag))?; - - //println!("etag: {:?}", etag); - - // 59A2A10DD1686F679EE885FC1EBA5183 - //let etag = &(etag.to_str().unwrap())[1..33]; - - self.etag_list.push((index, etag.to_owned())); - - Ok(()) - } - - /// 完成分块上传 - fn complete_multi(&mut self) -> Result<(), ContentError> { - if self.upload_id.is_empty() { - return Err(ContentError::new(ContentErrorKind::NoFoundUploadId)); - } - - let xml = self.etag_list_xml()?; - - let query = format!("uploadId={}", self.upload_id); - - let (url, resource) = self.part_canonicalized(&query); - - let content_length = xml.len().to_string(); - let headers = vec![( - CONTENT_LENGTH, - HeaderValue::from_str(&content_length).unwrap(), - )]; - - let _resp = self - .client - .builder_with_header(Method::POST, url, resource, headers)? - .body(xml) - .send_adjust_error()?; - - //println!("resp: {}", resp); - self.etag_list.clear(); - self.upload_id = String::default(); - - Ok(()) - } - - /// 取消分块上传 - pub fn abort_multi(&mut self) -> Result<(), ContentError> { - if self.upload_id.is_empty() { - return Err(ContentError::new(ContentErrorKind::NoFoundUploadId)); - } - let query = format!("uploadId={}", self.upload_id); - - let (url, resource) = self.part_canonicalized(&query); - let _resp = self - .client - .builder(Method::DELETE, url, resource)? - .send_adjust_error()?; - - //println!("resp: {:?}", resp); - self.etag_list.clear(); - self.upload_id = String::default(); - - Ok(()) - } -} - -// impl Drop for Content { -// fn drop(&mut self) { -// if self.upload_id.is_empty() == false { -// self.abort_multi(); -// } -// } -// } - -impl From for Content { - fn from(value: Client) -> Self { - Content { - client: Rc::new(value), - ..Default::default() - } - } -} - -#[cfg(test)] -mod tests { - use crate::decode::RefineObject; - - use super::super::test_suite_block::{ - AbortMulti, CompleteMulti, InitMulti, UploadMulti, UploadPart, - }; - use super::*; - - #[test] - fn assert_impl() { - fn impl_fn, E: std::error::Error + 'static>(_: T) {} - - impl_fn(Content::default()); - - fn impl_deref>(_: T) {} - - impl_deref(Content::default()); - } - - #[test] - fn read() { - let client = Client::test_init(); - let mut con = Content::from_client(Rc::new(client)) - .path("aaa.txt") - .unwrap(); - - let mut buf = [0u8; 201]; - let err = con.read(&mut buf).unwrap_err(); - assert_eq!(err.to_string(), "max size must be lt 48.8TB"); - - let mut buf = [0u8; 10]; - let len = con.read(&mut buf).unwrap(); - assert_eq!(buf, [1u8, 2, 3, 4, 5, 0, 0, 0, 0, 0]); - assert_eq!(len, 5); - - let mut buf = [0u8; 3]; - let len = con.read(&mut buf).unwrap(); - assert_eq!(buf, [1u8, 2, 3]); - assert_eq!(len, 3); - - con.current_pos = 10; - let mut buf = [0u8; 3]; - let len = con.read(&mut buf).unwrap(); - assert_eq!(buf, [1u8, 2, 3]); - assert_eq!(len, 3); - } - - #[test] - fn init_object() { - let mut list = List::default(); - let client = Client::test_init(); - list.set_client(Rc::new(client.clone())); - - let con = list.init_object().unwrap(); - - assert_eq!(con.client.bucket, client.bucket); - assert_eq!(con.inner, Inner::default()); - } - - #[test] - fn from_client() { - let client = Client::test_init(); - - let con = Content::from_client(Rc::new(client.clone())); - - assert_eq!(con.client.bucket, client.bucket); - assert_eq!(con.inner, Inner::default()); - } - - #[test] - fn path() { - let con = Content::default().path("aaa.txt").unwrap(); - assert_eq!(con.path, "aaa.txt"); - } - - #[test] - fn part_canonicalized() { - let client = Client::test_init(); - let con = Content::from_client(Rc::new(client)) - .path("aaa.txt") - .unwrap(); - - let (url, can) = con.part_canonicalized("first=1&second=2"); - assert_eq!( - url.as_str(), - "https://bar.oss-cn-qingdao.aliyuncs.com/aaa.txt?first=1&second=2" - ); - assert_eq!(can.to_string(), "/bar/aaa.txt?first=1&second=2"); - } - - #[test] - fn upload() { - let client = Client::test_init(); - let mut con = Content::from_client(Rc::new(client)) - .path("aaa.txt") - .unwrap(); - con.content_part.push(b"bbb".to_vec()); - con.upload().unwrap(); - } - - #[test] - fn init_multi() { - let client = Client::test_init().middleware(Rc::new(InitMulti {})); - let mut con = Content::from_client(Rc::new(client)) - .path("aaa.txt") - .unwrap(); - - con.init_multi().unwrap(); - - assert_eq!(con.upload_id, "foo_upload_id"); - } - - #[test] - fn upload_part() { - let client = Client::test_init().middleware(Rc::new(UploadPart {})); - let mut con = Content::from_client(Rc::new(client)) - .path("aaa.txt") - .unwrap(); - - let err = con.upload_part(1, b"bbb".to_vec()).unwrap_err(); - assert_eq!(err.to_string(), "not found upload id"); - - con.upload_id = "foo_upload_id".to_string(); - for _i in 0..10 { - con.etag_list.push((1, "a".parse().unwrap())); - } - let err = con.upload_part(1, b"bbb".to_vec()).unwrap_err(); - assert_eq!(err.to_string(), "overflow max parts count"); - con.etag_list.clear(); - - let err = con - .upload_part(1, b"012345678901234567890".to_vec()) - .unwrap_err(); - assert_eq!(err.to_string(), "part size must be between 100k and 5G"); - - con.upload_part(2, b"bbb".to_vec()).unwrap(); - let (index, value) = con.etag_list.pop().unwrap(); - assert_eq!(index, 2); - assert_eq!(value.to_str().unwrap(), "foo_etag"); - } - - #[test] - fn complete_multi() { - let client = Client::test_init().middleware(Rc::new(CompleteMulti {})); - let mut con = Content::from_client(Rc::new(client)) - .path("aaa.txt") - .unwrap(); - let err = con.complete_multi().unwrap_err(); - assert_eq!(err.to_string(), "not found upload id"); - - con.upload_id = "foo_upload_id".to_string(); - con.etag_list.push((1, "aaa".parse().unwrap())); - con.etag_list.push((2, "bbb".parse().unwrap())); - con.complete_multi().unwrap(); - assert!(con.etag_list.is_empty()); - assert!(con.upload_id.is_empty()); - } - - #[test] - fn upload_multi() { - let client = Client::test_init().middleware(Rc::new(UploadMulti {})); - let mut con = Content::from_client(Rc::new(client)) - .path("aaa.txt") - .unwrap(); - - con.content_part.push(b"aaa".to_vec()); - con.content_part.push(b"bbb".to_vec()); - - con.upload_multi().unwrap(); - - assert_eq!(con.content_size, 6); - } - - #[test] - fn abort_multi() { - let client = Client::test_init().middleware(Rc::new(AbortMulti {})); - let mut con = Content::from_client(Rc::new(client)) - .path("aaa.txt") - .unwrap(); - let err = con.complete_multi().unwrap_err(); - assert_eq!(err.to_string(), "not found upload id"); - - con.upload_id = "foo_upload_id".to_string(); - con.etag_list.push((1, "aaa".parse().unwrap())); - con.abort_multi().unwrap(); - assert!(con.etag_list.is_empty()); - assert!(con.upload_id.is_empty()); - } -} diff --git a/src/object/content/mock.rs b/src/object/content/mock.rs deleted file mode 100644 index 84155f3..0000000 --- a/src/object/content/mock.rs +++ /dev/null @@ -1,167 +0,0 @@ -use async_trait::async_trait; -use http::HeaderValue; -use reqwest::Response; - -use crate::{file::FileError, types::ContentRange, Client, ObjectPath}; - -#[async_trait] -pub(super) trait Files { - async fn put_content_base( - &self, - content: Vec, - content_type: &str, - path: ObjectPath, - ) -> Result; - - async fn get_object(&self, path: ObjectPath, range: R) -> Result, FileError> - where - R: Into> + Send + Sync, - ContentRange: Into; -} - -static mut READ_FILE_NUM: u8 = 1; - -#[async_trait] -impl Files for Client { - async fn put_content_base( - &self, - content: Vec, - content_type: &str, - path: ObjectPath, - ) -> Result { - use http::response::Builder; - assert_eq!(content, b"bbb".to_vec()); - assert_eq!(content_type, "text/plain"); - assert_eq!(path.as_ref(), "aaa.txt"); - - let resp = Builder::new().status(200).body("").unwrap(); - - Ok(resp.into()) - } - async fn get_object(&self, path: ObjectPath, range: R) -> Result, FileError> - where - R: Into> + Send + Sync, - ContentRange: Into, - { - if unsafe { READ_FILE_NUM == 1 } { - unsafe { - READ_FILE_NUM += 1; - } - assert_eq!(path.as_ref(), "aaa.txt"); - let range: HeaderValue = range.into().into(); - assert_eq!(range.to_str().unwrap(), "bytes=0-9"); - - let vec = vec![1u8, 2, 3, 4, 5]; - - return Ok(vec); - } else if unsafe { READ_FILE_NUM == 2 } { - unsafe { - READ_FILE_NUM += 1; - } - assert_eq!(path.as_ref(), "aaa.txt"); - let range: HeaderValue = range.into().into(); - assert_eq!(range.to_str().unwrap(), "bytes=0-2"); - - let vec = vec![1u8, 2, 3, 4, 5]; - - return Ok(vec); - } else if unsafe { READ_FILE_NUM == 3 } { - unsafe { - READ_FILE_NUM += 1; - } - assert_eq!(path.as_ref(), "aaa.txt"); - let range: HeaderValue = range.into().into(); - assert_eq!(range.to_str().unwrap(), "bytes=10-12"); - - let vec = vec![1u8, 2, 3, 4, 5]; - - return Ok(vec); - } - - panic!("error"); - } -} - -#[cfg(feature = "blocking")] -pub(super) mod blocking { - use http::HeaderValue; - use reqwest::Response; - - use crate::{client::ClientRc as Client, file::FileError, types::ContentRange, ObjectPath}; - - pub trait Files { - fn put_content_base( - &self, - content: Vec, - content_type: &str, - path: ObjectPath, - ) -> Result; - /// # 获取文件内容 - fn get_object(&self, path: ObjectPath, range: R) -> Result, FileError> - where - R: Into>, - ContentRange: Into; - } - - static mut READ_FILE_NUM: u8 = 1; - - impl Files for Client { - fn put_content_base( - &self, - content: Vec, - content_type: &str, - path: ObjectPath, - ) -> Result { - use http::response::Builder; - assert_eq!(content, b"bbb".to_vec()); - assert_eq!(content_type, "text/plain"); - assert_eq!(path.as_ref(), "aaa.txt"); - - let resp = Builder::new().status(200).body("").unwrap(); - - Ok(resp.into()) - } - fn get_object(&self, path: ObjectPath, range: R) -> Result, FileError> - where - R: Into>, - ContentRange: Into, - { - if unsafe { READ_FILE_NUM == 1 } { - unsafe { - READ_FILE_NUM += 1; - } - assert_eq!(path.as_ref(), "aaa.txt"); - let range: HeaderValue = range.into().into(); - assert_eq!(range.to_str().unwrap(), "bytes=0-9"); - - let vec = vec![1u8, 2, 3, 4, 5]; - - return Ok(vec); - } else if unsafe { READ_FILE_NUM == 2 } { - unsafe { - READ_FILE_NUM += 1; - } - assert_eq!(path.as_ref(), "aaa.txt"); - let range: HeaderValue = range.into().into(); - assert_eq!(range.to_str().unwrap(), "bytes=0-2"); - - let vec = vec![1u8, 2, 3, 4, 5]; - - return Ok(vec); - } else if unsafe { READ_FILE_NUM == 3 } { - unsafe { - READ_FILE_NUM += 1; - } - assert_eq!(path.as_ref(), "aaa.txt"); - let range: HeaderValue = range.into().into(); - assert_eq!(range.to_str().unwrap(), "bytes=10-12"); - - let vec = vec![1u8, 2, 3, 4, 5]; - - return Ok(vec); - } - - panic!("error"); - } - } -} diff --git a/src/object/content/test_suite.rs b/src/object/content/test_suite.rs deleted file mode 100644 index 50ba4e2..0000000 --- a/src/object/content/test_suite.rs +++ /dev/null @@ -1,227 +0,0 @@ -use async_trait::async_trait; -use http::HeaderValue; -use reqwest::{Body, Request, Response}; - -use crate::builder::{BuilderError, Middleware}; - -#[derive(Debug)] -pub(super) struct InitMulti {} - -#[async_trait] -impl Middleware for InitMulti { - async fn handle(&self, request: Request) -> Result { - //println!("request {:?}", request); - assert_eq!(request.method(), "POST"); - assert_eq!( - request.url().as_str(), - "https://bar.oss-cn-qingdao.aliyuncs.com/aaa.txt?uploads" - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some(&HeaderValue::from_str("/bar/aaa.txt?uploads").unwrap()) - ); - use http::response::Builder; - let response = Builder::new() - .status(200) - .body( - r#" - bucket_name - aaa - foo_upload_id - "#, - ) - .unwrap(); - Ok(response.into()) - } -} - -#[derive(Debug)] -pub(super) struct UploadPart {} - -#[async_trait] -impl Middleware for UploadPart { - async fn handle(&self, request: Request) -> Result { - //println!("request {:?}", request); - assert_eq!(request.method(), "PUT"); - assert_eq!( - request.url().as_str(), - "https://bar.oss-cn-qingdao.aliyuncs.com/aaa.txt?partNumber=2&uploadId=foo_upload_id" - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some( - &HeaderValue::from_str("/bar/aaa.txt?partNumber=2&uploadId=foo_upload_id").unwrap() - ) - ); - let body = request.body().unwrap().clone(); - let xml = "bbb"; - let xml = Body::from(xml); - assert_eq!(body.as_bytes(), xml.as_bytes()); - use http::response::Builder; - let response = Builder::new() - .status(200) - .header("ETag", "foo_etag") - .body("") - .unwrap(); - Ok(response.into()) - } -} - -#[derive(Debug)] -pub(super) struct CompleteMulti {} - -#[async_trait] -impl Middleware for CompleteMulti { - async fn handle(&self, request: Request) -> Result { - //println!("request {:?}", request); - assert_eq!(request.method(), "POST"); - assert_eq!( - request.url().as_str(), - "https://bar.oss-cn-qingdao.aliyuncs.com/aaa.txt?uploadId=foo_upload_id" - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some(&HeaderValue::from_str("/bar/aaa.txt?uploadId=foo_upload_id").unwrap()) - ); - let body = request.body().unwrap().clone(); - let xml = "1aaa2bbb"; - let xml = Body::from(xml); - assert_eq!(body.as_bytes(), xml.as_bytes()); - use http::response::Builder; - let response = Builder::new().status(200).body("").unwrap(); - Ok(response.into()) - } -} - -#[derive(Debug)] -pub(super) struct AbortMulti {} - -#[async_trait] -impl Middleware for AbortMulti { - async fn handle(&self, request: Request) -> Result { - //println!("request {:?}", request); - assert_eq!(request.method(), "DELETE"); - assert_eq!( - request.url().as_str(), - "https://bar.oss-cn-qingdao.aliyuncs.com/aaa.txt?uploadId=foo_upload_id" - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some(&HeaderValue::from_str("/bar/aaa.txt?uploadId=foo_upload_id").unwrap()) - ); - use http::response::Builder; - let response = Builder::new().status(200).body("").unwrap(); - Ok(response.into()) - } -} - -#[derive(Debug)] -pub(super) struct UploadMulti {} - -static mut UPLOAD_MULTI_ORDER: i8 = 1; - -#[async_trait] -impl Middleware for UploadMulti { - async fn handle(&self, request: Request) -> Result { - if unsafe { UPLOAD_MULTI_ORDER == 1 } { - assert_eq!(request.method(), "POST"); - assert_eq!( - request.url().as_str(), - "https://bar.oss-cn-qingdao.aliyuncs.com/aaa.txt?uploads" - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some(&HeaderValue::from_str("/bar/aaa.txt?uploads").unwrap()) - ); - use http::response::Builder; - let response = Builder::new() - .status(200) - .body( - r#" - bucket_name - aaa - foo_upload_id2 - "#, - ) - .unwrap(); - unsafe { - UPLOAD_MULTI_ORDER += 1; - } - return Ok(response.into()); - } else if unsafe { UPLOAD_MULTI_ORDER == 2 } { - assert_eq!(request.method(), "PUT"); - assert_eq!( - request.url().as_str(), - "https://bar.oss-cn-qingdao.aliyuncs.com/aaa.txt?partNumber=1&uploadId=foo_upload_id2" - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some( - &HeaderValue::from_str("/bar/aaa.txt?partNumber=1&uploadId=foo_upload_id2") - .unwrap() - ) - ); - let body = request.body().unwrap().clone(); - let xml = "aaa"; - let xml = Body::from(xml); - assert_eq!(body.as_bytes(), xml.as_bytes()); - use http::response::Builder; - let response = Builder::new() - .status(200) - .header("ETag", "foo_etag1") - .body("") - .unwrap(); - unsafe { - UPLOAD_MULTI_ORDER += 1; - } - return Ok(response.into()); - } else if unsafe { UPLOAD_MULTI_ORDER == 3 } { - assert_eq!(request.method(), "PUT"); - assert_eq!( - request.url().as_str(), - "https://bar.oss-cn-qingdao.aliyuncs.com/aaa.txt?partNumber=2&uploadId=foo_upload_id2" - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some( - &HeaderValue::from_str("/bar/aaa.txt?partNumber=2&uploadId=foo_upload_id2") - .unwrap() - ) - ); - let body = request.body().unwrap().clone(); - let xml = "bbb"; - let xml = Body::from(xml); - assert_eq!(body.as_bytes(), xml.as_bytes()); - use http::response::Builder; - let response = Builder::new() - .status(200) - .header("ETag", "foo_etag2") - .body("") - .unwrap(); - unsafe { - UPLOAD_MULTI_ORDER += 1; - } - return Ok(response.into()); - } else if unsafe { UPLOAD_MULTI_ORDER == 4 } { - //unsafe {UPLOAD_MULTI_ORDER += 1;} - assert_eq!(request.method(), "POST"); - assert_eq!( - request.url().as_str(), - "https://bar.oss-cn-qingdao.aliyuncs.com/aaa.txt?uploadId=foo_upload_id2" - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some(&HeaderValue::from_str("/bar/aaa.txt?uploadId=foo_upload_id2").unwrap()) - ); - let body = request.body().unwrap().clone(); - let xml = "1foo_etag12foo_etag2"; - let xml = Body::from(xml); - assert_eq!(body.as_bytes(), xml.as_bytes()); - use http::response::Builder; - let response = Builder::new().status(200).body("").unwrap(); - return Ok(response.into()); - } - - panic!("error"); - } -} diff --git a/src/object/content/test_suite_block.rs b/src/object/content/test_suite_block.rs deleted file mode 100644 index 2e7256d..0000000 --- a/src/object/content/test_suite_block.rs +++ /dev/null @@ -1,221 +0,0 @@ -use http::HeaderValue; -use reqwest::blocking::{Body, Request, Response}; - -use crate::{blocking::builder::Middleware, builder::BuilderError}; - -#[derive(Debug)] -pub(super) struct InitMulti {} - -impl Middleware for InitMulti { - fn handle(&self, request: Request) -> Result { - //println!("request {:?}", request); - assert_eq!(request.method(), "POST"); - assert_eq!( - request.url().as_str(), - "https://bar.oss-cn-qingdao.aliyuncs.com/aaa.txt?uploads" - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some(&HeaderValue::from_str("/bar/aaa.txt?uploads").unwrap()) - ); - use http::response::Builder; - let response = Builder::new() - .status(200) - .body( - r#" - bucket_name - aaa - foo_upload_id - "#, - ) - .unwrap(); - Ok(response.into()) - } -} - -#[derive(Debug)] -pub(super) struct UploadPart {} - -impl Middleware for UploadPart { - fn handle(&self, request: Request) -> Result { - //println!("request {:?}", request); - assert_eq!(request.method(), "PUT"); - assert_eq!( - request.url().as_str(), - "https://bar.oss-cn-qingdao.aliyuncs.com/aaa.txt?partNumber=2&uploadId=foo_upload_id" - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some( - &HeaderValue::from_str("/bar/aaa.txt?partNumber=2&uploadId=foo_upload_id").unwrap() - ) - ); - let body = request.body().unwrap().clone(); - let xml = "bbb"; - let xml = Body::from(xml); - assert_eq!(body.as_bytes(), xml.as_bytes()); - use http::response::Builder; - let response = Builder::new() - .status(200) - .header("ETag", "foo_etag") - .body("") - .unwrap(); - Ok(response.into()) - } -} - -#[derive(Debug)] -pub(super) struct CompleteMulti {} - -impl Middleware for CompleteMulti { - fn handle(&self, request: Request) -> Result { - //println!("request {:?}", request); - assert_eq!(request.method(), "POST"); - assert_eq!( - request.url().as_str(), - "https://bar.oss-cn-qingdao.aliyuncs.com/aaa.txt?uploadId=foo_upload_id" - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some(&HeaderValue::from_str("/bar/aaa.txt?uploadId=foo_upload_id").unwrap()) - ); - let body = request.body().unwrap().clone(); - let xml = "1aaa2bbb"; - let xml = Body::from(xml); - assert_eq!(body.as_bytes(), xml.as_bytes()); - use http::response::Builder; - let response = Builder::new().status(200).body("").unwrap(); - Ok(response.into()) - } -} - -#[derive(Debug)] -pub(super) struct AbortMulti {} - -impl Middleware for AbortMulti { - fn handle(&self, request: Request) -> Result { - //println!("request {:?}", request); - assert_eq!(request.method(), "DELETE"); - assert_eq!( - request.url().as_str(), - "https://bar.oss-cn-qingdao.aliyuncs.com/aaa.txt?uploadId=foo_upload_id" - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some(&HeaderValue::from_str("/bar/aaa.txt?uploadId=foo_upload_id").unwrap()) - ); - use http::response::Builder; - let response = Builder::new().status(200).body("").unwrap(); - Ok(response.into()) - } -} - -#[derive(Debug)] -pub(super) struct UploadMulti {} - -static mut UPLOAD_MULTI_ORDER: i8 = 1; - -impl Middleware for UploadMulti { - fn handle(&self, request: Request) -> Result { - if unsafe { UPLOAD_MULTI_ORDER == 1 } { - assert_eq!(request.method(), "POST"); - assert_eq!( - request.url().as_str(), - "https://bar.oss-cn-qingdao.aliyuncs.com/aaa.txt?uploads" - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some(&HeaderValue::from_str("/bar/aaa.txt?uploads").unwrap()) - ); - use http::response::Builder; - let response = Builder::new() - .status(200) - .body( - r#" - bucket_name - aaa - foo_upload_id2 - "#, - ) - .unwrap(); - unsafe { - UPLOAD_MULTI_ORDER += 1; - } - return Ok(response.into()); - } else if unsafe { UPLOAD_MULTI_ORDER == 2 } { - assert_eq!(request.method(), "PUT"); - assert_eq!( - request.url().as_str(), - "https://bar.oss-cn-qingdao.aliyuncs.com/aaa.txt?partNumber=1&uploadId=foo_upload_id2" - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some( - &HeaderValue::from_str("/bar/aaa.txt?partNumber=1&uploadId=foo_upload_id2") - .unwrap() - ) - ); - let body = request.body().unwrap().clone(); - let xml = "aaa"; - let xml = Body::from(xml); - assert_eq!(body.as_bytes(), xml.as_bytes()); - use http::response::Builder; - let response = Builder::new() - .status(200) - .header("ETag", "foo_etag1") - .body("") - .unwrap(); - unsafe { - UPLOAD_MULTI_ORDER += 1; - } - return Ok(response.into()); - } else if unsafe { UPLOAD_MULTI_ORDER == 3 } { - assert_eq!(request.method(), "PUT"); - assert_eq!( - request.url().as_str(), - "https://bar.oss-cn-qingdao.aliyuncs.com/aaa.txt?partNumber=2&uploadId=foo_upload_id2" - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some( - &HeaderValue::from_str("/bar/aaa.txt?partNumber=2&uploadId=foo_upload_id2") - .unwrap() - ) - ); - let body = request.body().unwrap().clone(); - let xml = "bbb"; - let xml = Body::from(xml); - assert_eq!(body.as_bytes(), xml.as_bytes()); - use http::response::Builder; - let response = Builder::new() - .status(200) - .header("ETag", "foo_etag2") - .body("") - .unwrap(); - unsafe { - UPLOAD_MULTI_ORDER += 1; - } - return Ok(response.into()); - } else if unsafe { UPLOAD_MULTI_ORDER == 4 } { - //unsafe {UPLOAD_MULTI_ORDER += 1;} - assert_eq!(request.method(), "POST"); - assert_eq!( - request.url().as_str(), - "https://bar.oss-cn-qingdao.aliyuncs.com/aaa.txt?uploadId=foo_upload_id2" - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some(&HeaderValue::from_str("/bar/aaa.txt?uploadId=foo_upload_id2").unwrap()) - ); - let body = request.body().unwrap().clone(); - let xml = "1foo_etag12foo_etag2"; - let xml = Body::from(xml); - assert_eq!(body.as_bytes(), xml.as_bytes()); - use http::response::Builder; - let response = Builder::new().status(200).body("").unwrap(); - return Ok(response.into()); - } - - panic!("error"); - } -} diff --git a/src/object/test.rs b/src/object/test.rs deleted file mode 100644 index a788d1e..0000000 --- a/src/object/test.rs +++ /dev/null @@ -1,627 +0,0 @@ -use crate::{ - bucket::Bucket, - builder::ArcPointer, - config::BucketBase, - decode::RefineBucket, - object::{StorageClass, StorageClassKind}, - EndPoint, -}; - -use super::{Object, Objects}; - -#[cfg(test)] -mod tests { - use super::super::ObjectList; - use crate::{ - builder::ArcPointer, - config::BucketBase, - object::{Object, ObjectArc, StorageClass}, - types::{object::ObjectPath, QueryValue}, - Client, EndPoint, - }; - use chrono::{DateTime, NaiveDateTime, Utc}; - use std::sync::Arc; - - fn init_object_list(token: Option, list: Vec) -> ObjectList { - let client = Client::new( - "foo1".into(), - "foo2".into(), - "https://oss-cn-shanghai.aliyuncs.com".parse().unwrap(), - "foo4".parse().unwrap(), - ); - - let object_list = ObjectList::::new( - "abc.oss-cn-shanghai.aliyuncs.com".parse().unwrap(), - Some("foo2/".parse().unwrap()), - 100, - 200, - list, - token, - Arc::new(client), - vec![("key1".into(), "value1".into())], - ); - - object_list - } - - #[test] - fn test_object_list_fmt() { - let object_list = init_object_list(Some(String::from("foo3")), vec![]); - assert_eq!( - format!("{object_list:?}"), - "ObjectList { bucket: BucketBase { endpoint: EndPoint { kind: CnShanghai, is_internal: false }, name: BucketName(\"abc\") }, prefix: Some(ObjectDir(\"foo2/\")), max_keys: 100, key_count: 200, next_continuation_token: \"foo3\", common_prefixes: [], search_query: Query { inner: {InnerQueryKey { kind: Custom(\"key1\") }: InnerQueryValue(\"value1\")} } }" - ); - } - - #[test] - fn test_get_bucket() { - let object_list = init_object_list(Some(String::from("foo3")), vec![]); - - let bucket = object_list.bucket(); - - assert_eq!(bucket.name(), "abc"); - - assert!(object_list.prefix() == &Some("foo2/".parse().unwrap())); - - assert!(object_list.max_keys() == &100u32); - assert_eq!(object_list.max_keys().to_owned(), 100u32); - - assert_eq!(object_list.next_continuation_token_str(), "foo3"); - } - - #[test] - fn test_bucket_name() { - let object_list = init_object_list(Some(String::from("foo3")), vec![]); - let bucket_name = object_list.bucket_name(); - - assert!("abc" == bucket_name); - } - - #[test] - fn test_next_query() { - let object_list = init_object_list(Some(String::from("foo3")), vec![]); - - let query = object_list.next_query(); - - assert!(query.is_some()); - let inner_query = query.unwrap(); - assert_eq!( - inner_query.get("key1"), - Some(&QueryValue::from_static("value1")) - ); - assert_eq!( - inner_query.get("continuation-token"), - Some(&QueryValue::from_static("foo3")) - ); - - let object_list = init_object_list(None, vec![]); - let query = object_list.next_query(); - assert!(query.is_none()); - } - - #[test] - fn test_object_iter_in_list() { - let bucket = Arc::new("abc.oss-cn-shanghai.aliyuncs.com".parse().unwrap()); - let object_list = init_object_list( - None, - vec![ - Object::new( - Arc::clone(&bucket), - "key1".parse().unwrap(), - DateTime::::from_utc( - NaiveDateTime::from_timestamp_opt(123000, 0).unwrap(), - Utc, - ), - "foo3".into(), - "foo4".into(), - 100, - StorageClass::IA, - ), - Object::new( - Arc::clone(&bucket), - "key2".parse().unwrap(), - DateTime::::from_utc( - NaiveDateTime::from_timestamp_opt(123000, 0).unwrap(), - Utc, - ), - "foo3".into(), - "foo4".into(), - 100, - StorageClass::IA, - ), - ], - ); - - let mut iter = object_list.object_iter(); - let first = iter.next(); - assert!(first.is_some()); - assert_eq!(first.unwrap().base.path().as_ref(), "key1"); - - let second = iter.next(); - assert!(second.is_some()); - assert_eq!(second.unwrap().base.path().as_ref(), "key2"); - - let third = iter.next(); - assert!(third.is_none()); - } - - #[test] - fn test_common_prefixes() { - let mut object_list = init_object_list(None, vec![]); - let list = object_list.common_prefixes(); - assert!(list.len() == 0); - - object_list.set_common_prefixes(["abc/".parse().unwrap(), "cde/".parse().unwrap()]); - let list = object_list.common_prefixes(); - - assert!(list.len() == 2); - assert!(list[0] == "abc/"); - assert!(list[1] == "cde/"); - } - - #[test] - fn test_object_new() { - let bucket = Arc::new("abc.oss-cn-shanghai.aliyuncs.com".parse().unwrap()); - let object = Object::::new( - bucket, - "foo2".parse().unwrap(), - DateTime::::from_utc(NaiveDateTime::from_timestamp_opt(123000, 0).unwrap(), Utc), - "foo3".into(), - "foo4".into(), - 100, - StorageClass::IA, - ); - - assert_eq!(object.base.path().as_ref(), "foo2"); - assert_eq!(object.last_modified.to_string(), "1970-01-02 10:10:00 UTC"); - assert_eq!(object.etag, "foo3"); - assert_eq!(object._type, "foo4"); - assert_eq!(object.size, 100); - assert_eq!(object.storage_class, StorageClass::IA); - } - - #[test] - fn test_object_from_bucket() { - let bucket = Arc::new("abc.oss-cn-shanghai.aliyuncs.com".parse().unwrap()); - let object = Object::::from_bucket(bucket); - assert_eq!(object.base.bucket_name().as_ref(), "abc"); - } - - #[test] - fn test_object_builder() { - let bucket = Arc::new(BucketBase::new( - "bucket-name".parse().unwrap(), - EndPoint::CN_QINGDAO, - )); - let mut builder = ObjectArc::builder("abc".parse::().unwrap()); - - builder - .bucket(bucket) - .last_modified(DateTime::::from_utc( - NaiveDateTime::from_timestamp_opt(123000, 0).unwrap(), - Utc, - )) - .etag("foo1".to_owned()) - .set_type("foo2".to_owned()) - .size(123) - .storage_class(StorageClass::IA); - - let object = builder.build(); - - assert_eq!(object.base.path().as_ref(), "abc"); - assert_eq!(object.base.bucket_name().as_ref(), "bucket-name"); - assert_eq!(object.last_modified.to_string(), "1970-01-02 10:10:00 UTC"); - assert_eq!(object.etag, "foo1"); - assert_eq!(object._type, "foo2"); - assert_eq!(object.size, 123); - assert_eq!(object.storage_class, StorageClass::IA); - - let bucket_base = BucketBase::new("bucket-name2".parse().unwrap(), EndPoint::CN_QINGDAO); - let mut builder = ObjectArc::builder("abc".parse::().unwrap()); - builder.bucket_base(bucket_base); - let object2 = builder.build(); - assert_eq!(object2.base.bucket_name().as_ref(), "bucket-name2"); - } -} - -#[cfg(feature = "blocking")] -#[cfg(test)] -mod blocking_tests { - use std::rc::Rc; - - use chrono::{DateTime, NaiveDateTime, Utc}; - - use crate::builder::RcPointer; - - use super::super::{Object, StorageClass}; - - fn init_object( - bucket: &str, - path: &'static str, - last_modified: i64, - etag: &'static str, - _type: &'static str, - size: u64, - storage_class: StorageClass, - ) -> Object { - let bucket = Rc::new(bucket.parse().unwrap()); - Object::::new( - bucket, - path.parse().unwrap(), - DateTime::::from_utc( - NaiveDateTime::from_timestamp_opt(last_modified, 0).unwrap(), - Utc, - ), - etag.into(), - _type.into(), - size, - storage_class, - ) - } - - #[test] - fn test_object_eq() { - let object1 = init_object( - "abc.oss-cn-shanghai.aliyuncs.com", - "foo1", - 123000, - "efoo1", - "tyfoo1", - 12, - StorageClass::ARCHIVE, - ); - - let object2 = init_object( - "abc.oss-cn-shanghai.aliyuncs.com", - "foo1", - 123000, - "efoo1", - "tyfoo1", - 12, - StorageClass::ARCHIVE, - ); - - assert!(object1 == object2); - - let object3 = init_object( - "abc2.oss-cn-shanghai.aliyuncs.com", - "foo1", - 123000, - "efoo1", - "tyfoo1", - 12, - StorageClass::ARCHIVE, - ); - - assert!(object1 != object3); - - let object3 = init_object( - "abc.oss-cn-shanghai.aliyuncs.com", - "foo2", - 123000, - "efoo1", - "tyfoo1", - 12, - StorageClass::ARCHIVE, - ); - assert!(object1 != object3); - - let object3 = init_object( - "abc.oss-cn-shanghai.aliyuncs.com", - "foo1", - 123009, - "efoo1", - "tyfoo1", - 12, - StorageClass::ARCHIVE, - ); - assert!(object1 != object3); - - let object3 = init_object( - "abc.oss-cn-shanghai.aliyuncs.com", - "foo1", - 123000, - "efoo2", - "tyfoo1", - 12, - StorageClass::ARCHIVE, - ); - assert!(object1 != object3); - - let object3 = init_object( - "abc.oss-cn-shanghai.aliyuncs.com", - "foo1", - 123000, - "efoo1", - "tyfoo3", - 12, - StorageClass::ARCHIVE, - ); - assert!(object1 != object3); - - let object3 = init_object( - "abc.oss-cn-shanghai.aliyuncs.com", - "foo1", - 123000, - "efoo1", - "tyfoo1", - 256, - StorageClass::ARCHIVE, - ); - assert!(object1 != object3); - - let object3 = init_object( - "abc.oss-cn-shanghai.aliyuncs.com", - "foo1", - 123000, - "efoo1", - "tyfoo1", - 12, - StorageClass::IA, - ); - assert!(object1 != object3); - } -} - -mod item_error { - use std::error::Error; - - use crate::{ - builder::ArcPointer, - decode::RefineObject, - object::{BuildInItemError, Object}, - }; - - #[test] - fn size() { - let mut object = Object::::default(); - let err = RefineObject::::set_size(&mut object, "foo").unwrap_err(); - assert_eq!(format!("{err}"), "parse size failed, gived str: foo"); - assert_eq!( - format!("{}", err.source().unwrap()), - "invalid digit found in string" - ); - assert_eq!(format!("{err:?}"), "BuildInItemError { source: \"foo\", kind: Size(ParseIntError { kind: InvalidDigit }) }"); - } - #[test] - fn base_path() { - let mut object = Object::::default(); - let err = RefineObject::::set_key(&mut object, ".foo").unwrap_err(); - assert_eq!(format!("{err}"), "parse base-path failed, gived str: .foo"); - assert_eq!(format!("{}", err.source().unwrap()), "invalid object path"); - assert_eq!( - format!("{err:?}"), - "BuildInItemError { source: \".foo\", kind: BasePath(InvalidObjectPath) }" - ); - } - #[test] - fn last_modified() { - let mut object = Object::::default(); - let err = - RefineObject::::set_last_modified(&mut object, "foo").unwrap_err(); - assert_eq!( - format!("{err}"), - "parse last-modified failed, gived str: foo" - ); - assert_eq!( - format!("{}", err.source().unwrap()), - "input contains invalid characters" - ); - assert_eq!( - format!("{err:?}"), - "BuildInItemError { source: \"foo\", kind: LastModified(ParseError(Invalid)) }" - ); - } - - #[test] - fn storage_class() { - let mut object = Object::::default(); - - assert!(RefineObject::::set_storage_class(&mut object, "aaa").is_ok()); - let err = - RefineObject::::set_storage_class(&mut object, "xxx").unwrap_err(); - assert_eq!( - format!("{err}"), - "parse storage-class failed, gived str: xxx" - ); - assert!(err.source().is_none()); - assert_eq!( - format!("{err:?}"), - "BuildInItemError { source: \"xxx\", kind: InvalidStorageClass }" - ); - } -} - -#[test] -fn test_storage_class_new() { - let value = StorageClass::new("archive").unwrap(); - assert_eq!(value.kind, StorageClassKind::Archive); - let value = StorageClass::new("Archive").unwrap(); - assert_eq!(value.kind, StorageClassKind::Archive); - - let value = StorageClass::new("IA").unwrap(); - assert_eq!(value.kind, StorageClassKind::IA); - let value = StorageClass::new("ia").unwrap(); - assert_eq!(value.kind, StorageClassKind::IA); - - let value = StorageClass::new("standard").unwrap(); - assert_eq!(value.kind, StorageClassKind::Standard); - let value = StorageClass::new("Standard").unwrap(); - assert_eq!(value.kind, StorageClassKind::Standard); - - let value = StorageClass::new("cold_archive").unwrap(); - assert_eq!(value.kind, StorageClassKind::ColdArchive); - let value = StorageClass::new("ColdArchive").unwrap(); - assert_eq!(value.kind, StorageClassKind::ColdArchive); - - assert!(StorageClass::new("eeeeee").is_none()); -} - -mod list_error { - use std::{borrow::Cow, error::Error}; - - use crate::{ - builder::ArcPointer, - decode::RefineObjectList, - object::{BuildInItemError, Object, ObjectList}, - }; - - use super::super::ObjectListError; - - #[test] - fn key_count() { - let mut list = ObjectList::>::default(); - let err = RefineObjectList::, ObjectListError, BuildInItemError>::set_key_count(&mut list, "foo").unwrap_err(); - assert_eq!(format!("{err}"), "parse key-count failed, gived str: foo"); - assert_eq!( - format!("{}", err.source().unwrap()), - "invalid digit found in string" - ); - assert_eq!(format!("{err:?}"), "ObjectListError { source: \"foo\", kind: KeyCount(ParseIntError { kind: InvalidDigit }) }"); - } - - #[test] - fn max_keys() { - let mut list = ObjectList::>::default(); - let err = RefineObjectList::, ObjectListError, BuildInItemError>::set_max_keys(&mut list, "foo").unwrap_err(); - - assert_eq!(format!("{err}"), "parse max-keys failed, gived str: foo"); - assert_eq!( - format!("{}", err.source().unwrap()), - "invalid digit found in string" - ); - assert_eq!(format!("{err:?}"), "ObjectListError { source: \"foo\", kind: MaxKeys(ParseIntError { kind: InvalidDigit }) }"); - } - - #[test] - fn prefix() { - let mut list = ObjectList::>::default(); - let err = - RefineObjectList::, ObjectListError, BuildInItemError>::set_prefix( - &mut list, ".foo", - ) - .unwrap_err(); - - assert_eq!(format!("{err}"), "parse prefix failed, gived str: .foo"); - assert_eq!( - format!("{}", err.source().unwrap()), - "object-dir must end with `/`, and not start with `/`,`.`" - ); - assert_eq!( - format!("{err:?}"), - "ObjectListError { source: \".foo\", kind: Prefix(InvalidObjectDir) }" - ); - } - - #[test] - fn common_prefix() { - let mut list = ObjectList::>::default(); - - assert!(RefineObjectList::, ObjectListError, BuildInItemError>::set_common_prefix(&mut list, &[]).is_ok()); - - let item: Cow = "foo".into(); - let list_data = [item]; - let err = RefineObjectList::, ObjectListError, BuildInItemError>::set_common_prefix(&mut list, &list_data).unwrap_err(); - - assert_eq!( - format!("{err}"), - "parse common-prefix failed, gived str: foo" - ); - assert_eq!( - format!("{}", err.source().unwrap()), - "object-dir must end with `/`, and not start with `/`,`.`" - ); - assert_eq!( - format!("{err:?}"), - "ObjectListError { source: \"foo\", kind: CommonPrefix(InvalidObjectDir) }" - ); - } - - #[test] - fn next_token() { - let mut list = ObjectList::>::default(); - - assert!(RefineObjectList::, ObjectListError, BuildInItemError>::set_next_continuation_token_str(&mut list, "aaa").is_ok()); - - assert_eq!(list.next_continuation_token_str(), "aaa"); - } -} - -mod extract_list_error { - use std::error::Error; - - use crate::{ - builder::BuilderError, - decode::InnerListError, - object::{ExtractListError, ExtractListErrorKind}, - tests::reqwest_error, - }; - - #[test] - fn builder() { - let err = ExtractListError::from(BuilderError::bar()); - - assert_eq!(format!("{}", err), "builder error"); - assert_eq!(format!("{}", err.source().unwrap()), "bar"); - } - - #[tokio::test] - async fn reqwest() { - let err = ExtractListError::from(reqwest_error().await); - - assert_eq!(format!("{}", err), "reqwest error"); - assert_eq!( - format!("{}", err.source().unwrap()), - "error decoding response body: expected value at line 1 column 1" - ); - } - - #[test] - fn decode() { - let err = ExtractListError::from(InnerListError::from_xml()); - - assert_eq!(format!("{}", err), "decode xml failed"); - assert_eq!( - format!("{}", err.source().unwrap()), - "Cannot read text, expecting Event::Text" - ); - } - - #[test] - fn no_more_file() { - let err = ExtractListError { - kind: ExtractListErrorKind::NoMoreFile, - }; - assert_eq!(format!("{}", err), "no more file"); - assert!(err.source().is_none()); - } -} - -#[test] -fn test_from_bucket() { - use crate::Client; - use std::sync::Arc; - - let mut client = Client::default(); - client.endpoint = "shanghai".try_into().unwrap(); - let mut bucket = Bucket::default(); - bucket.set_name("aaa").unwrap(); - bucket.client = Arc::new(client); - - let objects = Objects::::from_bucket(&bucket, 10); - assert_eq!(objects.bucket.name(), "aaa"); - assert_eq!(objects.client.endpoint, EndPoint::CN_SHANGHAI); - assert!(objects.object_list.capacity() >= 10); -} - -#[test] -fn to_sign_url() { - let mut builder = Object::::builder("img1.png".parse().unwrap()); - builder.bucket_base(BucketBase::new( - "abc".parse().unwrap(), - EndPoint::CN_SHANGHAI, - )); - let object = builder.build(); - let url = object.to_sign_url(&"key".into(), &"secret".into(), 12345678); - assert_eq!(url.as_str(), "https://abc.oss-cn-shanghai.aliyuncs.com/img1.png?OSSAccessKeyId=key&Expires=12345678&Signature=v0HY%2FAKa4c8lnwzUvN9vWlMaem0%3D"); -} diff --git a/src/sts.rs b/src/sts.rs deleted file mode 100644 index 20efb96..0000000 --- a/src/sts.rs +++ /dev/null @@ -1,118 +0,0 @@ -//! # STS 临时访问权限管理服务 -//! 阿里云STS(Security Token Service)是阿里云提供的一种临时访问权限管理服务。RAM提供RAM用户和RAM角色两种身份。 -//! 其中,RAM角色不具备永久身份凭证,而只能通过STS获取可以自定义时效和访问权限的临时身份凭证,即安全令牌(STS Token)。 -//! @link [文档](https://help.aliyun.com/document_detail/28756.html) -//! -//! ## 用法 -//! -//! ``` -//! # async fn run() { -//! use aliyun_oss_client::{sts::STS, BucketName, Client, EndPoint}; -//! let client = Client::new_with_sts( -//! "STS.xxxxxxxx".into(), // KeyId -//! "EVd6dXew6xxxxxxxxxxxxxxxxxxxxxxxxxxx".into(), // KeySecret -//! EndPoint::SHANGHAI, -//! BucketName::new("yyyyyy").unwrap(), -//! "CAIS4gF1q6Ft5Bxxxxxxxxxxx".to_string(), // STS Token, type should be string, &str or more -//! ) -//! .unwrap(); -//! -//! let builder = client.get_bucket_list().await; -//! println!("{:?}", builder); -//! # } -//! ``` - -use http::{header::InvalidHeaderValue, HeaderValue}; - -use crate::{auth::AuthBuilder, client::Client, BucketName, EndPoint, KeyId, KeySecret}; - -/// 给 Client 增加 STS 能力 -pub trait STS: private::Sealed -where - Self: Sized, -{ - /// 用 STS 配置信息初始化 [`Client`] - /// - /// [`Client`]: crate::client::Client - fn new_with_sts( - access_key_id: KeyId, - access_key_secret: KeySecret, - endpoint: EndPoint, - bucket: BucketName, - security_token: ST, - ) -> Result - where - ST: TryInto, - ST::Error: Into; -} - -mod private { - pub trait Sealed {} -} - -impl private::Sealed for Client {} - -const SECURITY_TOKEN: &str = "x-oss-security-token"; - -impl STS for Client { - fn new_with_sts( - access_key_id: KeyId, - access_key_secret: KeySecret, - endpoint: EndPoint, - bucket: BucketName, - security_token: ST, - ) -> Result - where - ST: TryInto, - ST::Error: Into, - { - let mut auth_builder = AuthBuilder::default(); - auth_builder.key(access_key_id); - auth_builder.secret(access_key_secret); - auth_builder.header_insert(SECURITY_TOKEN, { - let mut token = security_token.try_into().map_err(|e| e.into())?; - token.set_sensitive(true); - token - }); - - Ok(Self::from_builder(auth_builder, endpoint, bucket)) - } -} - -#[cfg(test)] -mod tests { - use http::{HeaderValue, Method}; - - use crate::{file::AlignBuilder, types::CanonicalizedResource, BucketName, Client, EndPoint}; - - use super::STS; - - #[tokio::test] - async fn test_sts() { - let client = Client::new_with_sts( - "foo1".into(), - "foo2".into(), - EndPoint::CN_SHANGHAI, - BucketName::new("abc").unwrap(), - "bar", - ) - .unwrap(); - - let builder = client - .builder( - Method::GET, - "https://abc.oss-cn-shanghai.aliyuncs.com/" - .try_into() - .unwrap(), - CanonicalizedResource::default(), - ) - .unwrap(); - - let request = builder.build().unwrap(); - - let headers = request.headers(); - let sts_token = headers.get("x-oss-security-token"); - - assert_eq!(sts_token, Some(&HeaderValue::from_static("bar"))); - } -} diff --git a/src/tests/client.rs b/src/tests/client.rs deleted file mode 100644 index 4061f55..0000000 --- a/src/tests/client.rs +++ /dev/null @@ -1,259 +0,0 @@ -use http::header::CONTENT_TYPE; -use http::{HeaderValue, Method}; - -use crate::builder::ClientWithMiddleware; -use crate::file::AlignBuilder; -use crate::{client::Client, types::CanonicalizedResource, EndPoint}; - -#[test] -fn test_get_bucket_url() { - let client = Client::::new( - "foo1".into(), - "foo2".into(), - EndPoint::CN_QINGDAO, - "foo4".parse().unwrap(), - ); - let url = client.get_bucket_url(); - assert_eq!(url.as_str(), "https://foo4.oss-cn-qingdao.aliyuncs.com/"); -} - -#[test] -fn test_builder_with_header() { - let client = Client::::new( - "foo1".into(), - "foo2".into(), - EndPoint::CN_QINGDAO, - "foo4".parse().unwrap(), - ); - let url = "http://foo.example.net/foo".parse().unwrap(); - let resource = CanonicalizedResource::new("bar"); - let headers = vec![(CONTENT_TYPE, HeaderValue::from_static("application/json"))]; - let builder = client.builder_with_header(Method::POST, url, resource, headers); - - assert!(builder.is_ok()); - - let request = builder.unwrap().build().unwrap(); - - assert_eq!(request.method(), "POST"); - assert!(request.url().host().is_some()); - assert_eq!(request.url().path(), "/foo"); - assert_eq!( - request.headers().get("content-type"), - Some(&HeaderValue::from_str("application/json").unwrap()) - ); - assert_eq!( - request.headers().get("accesskeyid"), - Some(&HeaderValue::from_str("foo1").unwrap()) - ); - assert!(request.headers().get("secretaccesskey").is_none()); - assert_eq!( - request.headers().get("verb"), - Some(&HeaderValue::from_str("POST").unwrap()) - ); - assert_eq!( - request.headers().get("date"), - Some(&HeaderValue::from_str("Thu, 06 Oct 2022 20:40:00 GMT").unwrap()) - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some(&HeaderValue::from_str("bar").unwrap()) - ); - assert_eq!( - request.headers().get("authorization"), - Some(&HeaderValue::from_str("OSS foo1:FUrk4hgj2yIB8lJpnsSub+CTC9M=").unwrap()) - ); -} - -#[cfg(feature = "blocking")] -#[test] -fn test_blocking_builder_with_header() { - use crate::blocking::builder::ClientWithMiddleware; - use crate::client::Client; - use crate::file::blocking::AlignBuilder; - let client = Client::::new( - "foo1".into(), - "foo2".into(), - EndPoint::CN_QINGDAO, - "foo4".parse().unwrap(), - ); - let url = "http://foo.example.net/foo".parse().unwrap(); - let resource = CanonicalizedResource::new("bar"); - let headers = vec![(CONTENT_TYPE, HeaderValue::from_static("application/json"))]; - let builder = client.builder_with_header(Method::POST, url, resource, headers); - - assert!(builder.is_ok()); - - let request = builder.unwrap().build().unwrap(); - - assert_eq!(request.method(), "POST"); - assert!(request.url().host().is_some()); - assert_eq!(request.url().path(), "/foo"); - assert_eq!( - request.headers().get("content-type"), - Some(&HeaderValue::from_str("application/json").unwrap()) - ); - assert_eq!( - request.headers().get("accesskeyid"), - Some(&HeaderValue::from_str("foo1").unwrap()) - ); - assert!(request.headers().get("secretaccesskey").is_none()); - assert_eq!( - request.headers().get("verb"), - Some(&HeaderValue::from_str("POST").unwrap()) - ); - assert_eq!( - request.headers().get("date"), - Some(&HeaderValue::from_str("Thu, 06 Oct 2022 20:40:00 GMT").unwrap()) - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some(&HeaderValue::from_str("bar").unwrap()) - ); - assert_eq!( - request.headers().get("authorization"), - Some(&HeaderValue::from_str("OSS foo1:FUrk4hgj2yIB8lJpnsSub+CTC9M=").unwrap()) - ); -} - -mod handle_error { - use crate::builder::{check_http_status, BuilderError, BuilderErrorKind}; - use crate::errors::OssService; - use http::Response as HttpResponse; - use reqwest::Response; - - #[tokio::test] - async fn test_async_has_error() { - let http = HttpResponse::builder() - .status(302) - //.header("X-Custom-Foo", "Bar") - .body( - r#" - - foo_code - bar - 63145DB90BFD85303279D56B - honglei123.oss-cn-shanghai.aliyuncs.com - 900000 - 2022-09-04T07:11:33.000Z - 2022-09-04T08:11:37.000Z - - "#, - ) - .unwrap(); - let response: Response = http.into(); - - let res = check_http_status(response).await; - - let BuilderError { kind } = res.unwrap_err(); - match kind { - BuilderErrorKind::OssService(b) => { - let OssService { code, .. } = *b; - assert!(code == "foo_code"); - } - _ => unreachable!(), - } - - //mock.checkpoint(); - } - - #[tokio::test] - async fn test_async_ok() { - let http = HttpResponse::builder() - .status(200) - //.header("X-Custom-Foo", "Bar") - .body("body_abc") - .unwrap(); - let response: Response = http.into(); - - let res = check_http_status(response).await; - assert!(res.is_ok()); - let ok = res.unwrap(); - assert_eq!(ok.status(), 200); - assert_eq!(&ok.text().await.unwrap(), "body_abc"); - - let http = HttpResponse::builder() - .status(204) - //.header("X-Custom-Foo", "Bar") - .body("body_abc") - .unwrap(); - let response: Response = http.into(); - - let res = check_http_status(response).await; - assert!(res.is_ok()); - let ok = res.unwrap(); - assert_eq!(ok.status(), 204); - assert_eq!(&ok.text().await.unwrap(), "body_abc"); - } - - #[cfg(feature = "blocking")] - #[test] - fn test_blocking_has_error() { - use crate::blocking::builder::check_http_status as blocking_check_http_status; - use reqwest::blocking::Response as BlockingResponse; - - let http = HttpResponse::builder() - .status(302) - //.header("X-Custom-Foo", "Bar") - .body( - r#" - - foo_code - bar - 63145DB90BFD85303279D56B - honglei123.oss-cn-shanghai.aliyuncs.com - 900000 - 2022-09-04T07:11:33.000Z - 2022-09-04T08:11:37.000Z - - "#, - ) - .unwrap(); - let response: BlockingResponse = http.into(); - - let res = blocking_check_http_status(response); - - let BuilderError { kind } = res.unwrap_err(); - match kind { - BuilderErrorKind::OssService(b) => { - let OssService { code, .. } = *b; - assert!(code == "foo_code"); - } - _ => unreachable!(), - } - } - - #[cfg(feature = "blocking")] - #[test] - fn test_blocking_ok() { - use crate::blocking::builder::check_http_status as blocking_check_http_status; - use reqwest::blocking::Response as BlockingResponse; - - let http = HttpResponse::builder() - .status(200) - //.header("X-Custom-Foo", "Bar") - .body("body_abc") - .unwrap(); - let response: BlockingResponse = http.into(); - - let res = blocking_check_http_status(response); - assert!(res.is_ok()); - let ok = res.unwrap(); - assert_eq!(ok.status(), 200); - assert_eq!(&ok.text().unwrap(), "body_abc"); - - let http = HttpResponse::builder() - .status(204) - //.header("X-Custom-Foo", "Bar") - .body("body_abc") - .unwrap(); - let response: BlockingResponse = http.into(); - - let res = blocking_check_http_status(response); - assert!(res.is_ok()); - let ok = res.unwrap(); - assert_eq!(ok.status(), 204); - assert_eq!(&ok.text().unwrap(), "body_abc"); - } -} - -// blocking mock 有错误 diff --git a/src/tests/env.rs b/src/tests/env.rs deleted file mode 100644 index 03db9c5..0000000 --- a/src/tests/env.rs +++ /dev/null @@ -1,165 +0,0 @@ -use std::env::{remove_var, set_var}; - -use crate::{ - builder::ClientWithMiddleware, - client::Client, - config::{BucketBase, Config, InvalidConfigKind}, - types::EndPointKind, - EndPoint, -}; - -#[test] -fn client_from_env() { - set_var("ALIYUN_KEY_ID", "foo1"); - set_var("ALIYUN_KEY_SECRET", "foo2"); - set_var("ALIYUN_ENDPOINT", "qingdao"); - set_var("ALIYUN_BUCKET", "foo4"); - remove_var("ALIYUN_OSS_INTERNAL"); - let client = Client::::from_env().unwrap(); - let url = client.get_bucket_url(); - assert_eq!(url.as_str(), "https://foo4.oss-cn-qingdao.aliyuncs.com/"); - - set_var("ALIYUN_OSS_INTERNAL", "true"); - let client = Client::::from_env().unwrap(); - let url = client.get_bucket_url(); - assert_eq!( - url.as_str(), - "https://foo4.oss-cn-qingdao-internal.aliyuncs.com/" - ); - - set_var("ALIYUN_OSS_INTERNAL", "foo4"); - let client = Client::::from_env().unwrap(); - let url = client.get_bucket_url(); - assert_eq!(url.as_str(), "https://foo4.oss-cn-qingdao.aliyuncs.com/"); - - set_var("ALIYUN_OSS_INTERNAL", "1"); - let client = Client::::from_env().unwrap(); - let url = client.get_bucket_url(); - assert_eq!( - url.as_str(), - "https://foo4.oss-cn-qingdao-internal.aliyuncs.com/" - ); - - set_var("ALIYUN_OSS_INTERNAL", "yes"); - let client = Client::::from_env().unwrap(); - let url = client.get_bucket_url(); - assert_eq!( - url.as_str(), - "https://foo4.oss-cn-qingdao-internal.aliyuncs.com/" - ); - - set_var("ALIYUN_OSS_INTERNAL", "Y"); - let client = Client::::from_env().unwrap(); - let url = client.get_bucket_url(); - assert_eq!( - url.as_str(), - "https://foo4.oss-cn-qingdao-internal.aliyuncs.com/" - ); -} - -#[test] -fn config_from_env() { - set_var("ALIYUN_KEY_ID", "foo"); - set_var("ALIYUN_KEY_SECRET", "foo2"); - set_var("ALIYUN_ENDPOINT", "qingdao"); - set_var("ALIYUN_BUCKET", "foo3"); - remove_var("ALIYUN_OSS_INTERNAL"); - let config = Config::from_env().unwrap(); - assert_eq!(config.key.as_ref(), "foo"); - assert_eq!(config.secret.as_str(), "foo2"); - assert_eq!(&config.endpoint, &EndPoint::CN_QINGDAO); - assert_eq!(config.bucket.as_ref(), "foo3"); - - set_var("ALIYUN_ENDPOINT", "ossqd"); - let config = Config::from_env().unwrap_err(); - assert!(config.clone().get_source().len() == 0); - assert!(matches!(config.kind(), InvalidConfigKind::EndPoint(_))); - - set_var("ALIYUN_ENDPOINT", "hangzhou"); - set_var("ALIYUN_BUCKET", "foo3-"); - let config = Config::from_env().unwrap_err(); - assert!(config.clone().get_source().len() == 0); - assert!(matches!(config.kind(), InvalidConfigKind::BucketName(_))); -} - -#[test] -fn bucket_base_from_env() { - set_var("ALIYUN_ENDPOINT", "qingdao"); - set_var("ALIYUN_BUCKET", "foo1"); - - set_var("ALIYUN_OSS_INTERNAL", "0"); - let base = BucketBase::from_env().unwrap(); - assert!(!base.endpoint().is_internal()); - - set_var("ALIYUN_OSS_INTERNAL", "1"); - let base = BucketBase::from_env().unwrap(); - assert!(base.endpoint().is_internal()); - - set_var("ALIYUN_OSS_INTERNAL", "yes"); - let base = BucketBase::from_env().unwrap(); - assert!(base.endpoint().is_internal()); - - set_var("ALIYUN_OSS_INTERNAL", "Y"); - let base = BucketBase::from_env().unwrap(); - assert!(base.endpoint().is_internal()); - - remove_var("ALIYUN_OSS_INTERNAL"); - remove_var("ALIYUN_ENDPOINT"); - let base = BucketBase::from_env().unwrap_err(); - assert_eq!(base.clone().get_source(), "ALIYUN_ENDPOINT"); - assert!(matches!(base.kind(), InvalidConfigKind::VarError(_))); - - set_var("ALIYUN_ENDPOINT", "ossqd"); - let base = BucketBase::from_env().unwrap_err(); - assert_eq!(base.clone().get_source(), "ossqd"); - assert!(matches!(base.kind(), InvalidConfigKind::EndPoint(_))); - - set_var("ALIYUN_ENDPOINT", "qingdao"); - remove_var("ALIYUN_BUCKET"); - let base = BucketBase::from_env().unwrap_err(); - assert_eq!(base.clone().get_source(), "ALIYUN_BUCKET"); - assert!(matches!(base.kind(), InvalidConfigKind::VarError(_))); - - set_var("ALIYUN_BUCKET", "abc-"); - let base = BucketBase::from_env().unwrap_err(); - assert_eq!(base.clone().get_source(), "abc-"); - assert!(matches!(base.kind(), InvalidConfigKind::BucketName(_))); -} - -#[test] -fn end_point_from_env() { - remove_var("ALIYUN_ENDPOINT"); - let has_err = EndPoint::from_env(); - assert!(has_err.is_err()); - - set_var("ALIYUN_ENDPOINT", "ossaa"); - let has_err = EndPoint::from_env(); - assert!(has_err.is_err()); - - set_var("ALIYUN_ENDPOINT", "qingdao"); - remove_var("ALIYUN_OSS_INTERNAL"); - let endpoint = EndPoint::from_env().unwrap(); - assert_eq!(endpoint.kind, EndPointKind::CnQingdao); - assert!(!endpoint.is_internal); - - set_var("ALIYUN_OSS_INTERNAL", "true"); - let endpoint = EndPoint::from_env().unwrap(); - assert_eq!(endpoint.kind, EndPointKind::CnQingdao); - assert!(endpoint.is_internal); - - set_var("ALIYUN_OSS_INTERNAL", "0"); - let endpoint = EndPoint::from_env().unwrap(); - assert!(!endpoint.is_internal); - - set_var("ALIYUN_OSS_INTERNAL", "1"); - let endpoint = EndPoint::from_env().unwrap(); - assert!(endpoint.is_internal); - - set_var("ALIYUN_OSS_INTERNAL", "yes"); - let endpoint = EndPoint::from_env().unwrap(); - assert!(endpoint.is_internal); - - set_var("ALIYUN_OSS_INTERNAL", "Y"); - let endpoint = EndPoint::from_env().unwrap(); - assert!(endpoint.is_internal); -} diff --git a/src/tests/errors.rs b/src/tests/errors.rs deleted file mode 100644 index 3d930a4..0000000 --- a/src/tests/errors.rs +++ /dev/null @@ -1,317 +0,0 @@ -mod debug { - use std::error::Error as StdError; - use std::io::{self, ErrorKind}; - - use crate::auth::AuthError; - use crate::bucket::{BucketError, ExtractItemError, ExtractItemErrorKind}; - use crate::builder::{BuilderError, BuilderErrorKind}; - use crate::decode::InnerItemError; - use crate::file::FileError; - use crate::object::{ - BuildInItemError, ExtractListError, ExtractListErrorKind, ObjectListError, - }; - use crate::types::object::{InvalidObjectDir, InvalidObjectPath}; - use crate::types::{InvalidBucketName, InvalidEndPoint}; - use crate::Error; - - #[test] - fn test_io() { - let err = Error::from(io::Error::new(ErrorKind::Other, "foo")); - assert_eq!(format!("{}", err), "io error"); - assert_eq!(format!("{}", err.source().unwrap()), "foo"); - assert_eq!( - format!("{:?}", err), - "OssError { kind: Io(Custom { kind: Other, error: \"foo\" }) }" - ); - } - #[test] - fn test_dotenv() { - let err = Error::from(dotenv::Error::LineParse("abc".to_string(), 1)); - - assert_eq!(format!("{err}"), "dotenv error"); - assert_eq!( - format!("{}", err.source().unwrap()), - "Error parsing line: 'abc', error at line index: 1" - ); - - fn bar() -> Error { - dotenv::Error::LineParse("abc".to_string(), 1).into() - } - assert_eq!( - format!("{:?}", bar()), - "OssError { kind: Dotenv(LineParse(\"abc\", 1)) }" - ); - } - - #[test] - fn test_builder() { - let err = Error::from(BuilderError { - kind: BuilderErrorKind::Bar, - }); - assert_eq!(format!("{err}"), "builder error"); - assert_eq!(format!("{}", err.source().unwrap()), "bar"); - - fn bar() -> Error { - BuilderError { - kind: BuilderErrorKind::Bar, - } - .into() - } - assert_eq!( - format!("{:?}", bar()), - "OssError { kind: Builder(BuilderError { kind: Bar }) }" - ); - } - - #[test] - fn test_endpoint() { - let err = Error::from(InvalidEndPoint::new()); - assert_eq!(format!("{err}"), "invalid endpoint"); - assert_eq!( - format!("{}", err.source().unwrap()), - "endpoint must not with `-` prefix or `-` suffix or `oss-` prefix" - ); - - fn bar() -> Error { - InvalidEndPoint::new().into() - } - assert_eq!( - format!("{:?}", bar()), - "OssError { kind: EndPoint(InvalidEndPoint) }" - ); - } - - #[test] - fn test_bucket_name() { - let err = Error::from(InvalidBucketName::new()); - - assert_eq!(format!("{err}"), "invalid bucket name"); - assert_eq!( - format!("{}", err.source().unwrap()), - "bucket name only allow `alphabet, digit, -`, and must not with `-` prefix or `-` suffix" - ); - - fn bar() -> Error { - InvalidBucketName::new().into() - } - assert_eq!( - format!("{:?}", bar()), - "OssError { kind: BucketName(InvalidBucketName) }" - ); - } - - #[test] - fn test_config() { - use crate::config::InvalidConfig; - let err = Error::from(InvalidConfig::test_bucket()); - - assert_eq!(format!("{err}"), "invalid config"); - assert_eq!( - format!("{}", err.source().unwrap()), - "get config failed, source: bar" - ); - - fn bar() -> Error { - InvalidConfig::test_bucket().into() - } - assert_eq!( - format!("{:?}", bar()), - "OssError { kind: Config(InvalidConfig { source: \"bar\", kind: BucketName(InvalidBucketName) }) }" - ); - } - - #[test] - fn test_object_path() { - let err = Error::from(InvalidObjectPath::new()); - - assert_eq!(format!("{err}"), "invalid object path"); - assert_eq!(format!("{}", err.source().unwrap()), "invalid object path"); - - fn bar() -> Error { - InvalidObjectPath::new().into() - } - assert_eq!( - format!("{:?}", bar()), - "OssError { kind: ObjectPath(InvalidObjectPath) }" - ); - } - - #[test] - fn test_object_dir() { - let err = Error::from(InvalidObjectDir::new()); - - assert_eq!(format!("{err}"), "invalid object dir"); - assert_eq!( - format!("{}", err.source().unwrap()), - "object-dir must end with `/`, and not start with `/`,`.`" - ); - - fn bar() -> Error { - InvalidObjectDir::new().into() - } - assert_eq!( - format!("{:?}", bar()), - "OssError { kind: ObjectDir(InvalidObjectDir) }" - ); - } - - #[test] - #[cfg(feature = "decode")] - fn test_inner_item() { - use std::error::Error as StdError; - - use crate::decode::InnerItemError; - - let err = Error::from(InnerItemError::new()); - - assert_eq!(format!("{err}"), "decode into list error"); - assert_eq!(format!("{}", err.source().unwrap()), "demo"); - - fn bar() -> Error { - InnerItemError::new().into() - } - assert_eq!( - format!("{:?}", bar()), - "OssError { kind: InnerItem(InnerItemError(MyError)) }" - ); - } - #[test] - #[cfg(feature = "decode")] - fn test_inner_list() { - use crate::decode::InnerListError; - - let err = Error::from(InnerListError::from_xml()); - - assert_eq!(format!("{err}"), "decode into list error"); - assert_eq!( - format!("{}", err.source().unwrap()), - "Cannot read text, expecting Event::Text" - ); - - fn bar() -> Error { - InnerListError::from_xml().into() - } - assert_eq!( - format!("{:?}", bar()), - "OssError { kind: InnerList(InnerListError { kind: Xml(TextNotFound) }) }" - ); - } - - #[test] - fn test_build_in_iter_error() { - let err = Error::from(BuildInItemError::test_new()); - - assert_eq!(format!("{err}"), "build in item error"); - assert_eq!( - format!("{}", err.source().unwrap()), - "parse storage-class failed, gived str: foo" - ); - - fn bar() -> Error { - BuildInItemError::test_new().into() - } - assert_eq!( - format!("{:?}", bar()), - "OssError { kind: BuildInItemError(BuildInItemError { source: \"foo\", kind: InvalidStorageClass }) }" - ); - } - - #[test] - fn test_extract_list() { - let err = Error::from(ExtractListError { - kind: ExtractListErrorKind::NoMoreFile, - }); - - assert_eq!(format!("{err}"), "extract list error"); - assert_eq!(format!("{}", err.source().unwrap()), "no more file"); - - fn bar() -> Error { - ExtractListError { - kind: ExtractListErrorKind::NoMoreFile, - } - .into() - } - assert_eq!( - format!("{:?}", bar()), - "OssError { kind: ExtractList(ExtractListError { kind: NoMoreFile }) }" - ); - } - - #[test] - fn test_extract_item() { - let err = Error::from(ExtractItemError { - kind: ExtractItemErrorKind::Decode(InnerItemError::new()), - }); - - assert_eq!(format!("{err}"), "extract item error"); - assert_eq!(format!("{}", err.source().unwrap()), "decode xml failed"); - - fn bar() -> Error { - ExtractItemError { - kind: ExtractItemErrorKind::Decode(InnerItemError::new()), - } - .into() - } - assert_eq!( - format!("{:?}", bar()), - "OssError { kind: ExtractItem(ExtractItemError { kind: Decode(InnerItemError(MyError)) }) }" - ); - } - - #[test] - fn file() { - let err = Error::from(FileError::test_new()); - - assert_eq!(format!("{err}"), "file error"); - assert_eq!(format!("{}", err.source().unwrap()), "failed to get etag"); - assert_eq!( - format!("{err:?}"), - "OssError { kind: File(FileError { kind: EtagNotFound }) }" - ); - } - - #[test] - fn auth() { - let err = Error::from(AuthError::test_new()); - - assert_eq!(format!("{err}"), "auth error"); - assert_eq!( - format!("{}", err.source().unwrap()), - "invalid canonicalized-resource" - ); - assert_eq!( - format!("{err:?}"), - "OssError { kind: Auth(AuthError { kind: InvalidCanonicalizedResource }) }" - ); - } - - #[test] - fn bucket() { - let err = Error::from(BucketError::test_new()); - - assert_eq!(format!("{err}"), "bucket error"); - assert_eq!( - format!("{}", err.source().unwrap()), - "decode bucket xml faild, gived str: foo" - ); - assert_eq!( - format!("{err:?}"), - "OssError { kind: Bucket(BucketError { source: \"foo\", kind: InvalidStorageClass }) }" - ); - } - - #[test] - fn object_list() { - let err = Error::from(ObjectListError::test_new()); - - assert_eq!(format!("{err}"), "object list error"); - assert_eq!( - format!("{}", err.source().unwrap()), - "parse bar failed, gived str: foo" - ); - assert_eq!( - format!("{err:?}"), - "OssError { kind: ObjectList(ObjectListError { source: \"foo\", kind: Bar }) }" - ); - } -} diff --git a/src/tests/mod.rs b/src/tests/mod.rs deleted file mode 100644 index d7ec61c..0000000 --- a/src/tests/mod.rs +++ /dev/null @@ -1,25 +0,0 @@ -#[cfg(feature = "core")] -mod client; - -#[cfg(feature = "core")] -mod errors; - -#[cfg(feature = "core")] -pub mod object; - -#[cfg(feature = "env_test")] -mod env; - -pub async fn reqwest_error() -> reqwest::Error { - use http::response::Builder; - use reqwest::Response; - use serde::Deserialize; - - let response = Builder::new().status(200).body("aaaa").unwrap(); - - #[derive(Debug, Deserialize)] - struct Ip; - - let response = Response::from(response); - response.json::().await.unwrap_err() -} diff --git a/src/tests/object.rs b/src/tests/object.rs deleted file mode 100644 index d8860e2..0000000 --- a/src/tests/object.rs +++ /dev/null @@ -1,1065 +0,0 @@ -use crate::builder::ArcPointer; -#[cfg(feature = "blocking")] -use crate::builder::RcPointer; -use crate::builder::{BuilderError, ClientWithMiddleware, PointerFamily}; -use crate::file::Files; -use crate::object::ObjectList; -use crate::types::object::{CommonPrefixes, ObjectPath}; -use crate::{builder::Middleware, client::Client}; -use crate::{BucketName, EndPoint, Query, QueryKey}; -use async_trait::async_trait; -use http::HeaderValue; -use reqwest::{Request, Response}; -use std::error::Error; -use std::sync::Arc; - -pub(crate) fn assert_object_list( - list: ObjectList, - endpoint: EndPoint, - name: BucketName, - prefix: Option, - max_keys: u32, - key_count: u64, - next_continuation_token: String, - common_prefixes: CommonPrefixes, - search_query: Query, -) { - assert!(list.bucket().clone().endpoint() == endpoint); - assert!(list.bucket().clone().name() == name); - assert!(*list.prefix() == prefix); - assert!(*list.max_keys() == max_keys); - assert!(*list.key_count() == key_count); - assert!(*list.next_continuation_token_str() == next_continuation_token); - assert!(*list.common_prefixes() == common_prefixes); - assert!(*list.search_query() == search_query); -} - -#[cfg(feature = "blocking")] -#[test] -fn object_list_get_object_list() { - use crate::client::ClientRc; - use crate::{blocking::builder::Middleware, builder::RcPointer, object::ObjectList}; - use reqwest::blocking::{Request, Response}; - use std::rc::Rc; - - #[derive(Debug)] - struct MyMiddleware {} - - impl Middleware for MyMiddleware { - fn handle(&self, request: Request) -> Result { - //println!("request {:?}", request); - assert_eq!(request.method(), "GET"); - assert_eq!( - *request.url(), - "https://abc.oss-cn-shanghai.aliyuncs.com/?list-type=2&max-keys=5" - .parse() - .unwrap() - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some(&HeaderValue::from_str("/abc/").unwrap()) - ); - use http::response::Builder; - let response = Builder::new() - .status(200) - //.url(url.clone()) - .body( - r#" - - barname - foo2 - 100 - - false - - 9AB932LY.jpeg - 2022-06-26T09:53:21.000Z - "F75A15996D0857B16FA31A3B16624C26" - Normal - 18027 - Standard - - 23 - "#, - ) - .unwrap(); - let response = Response::from(response); - Ok(response) - } - } - - let client = ClientRc::new( - "foo1".into(), - "foo2".into(), - "https://oss-cn-shanghai.aliyuncs.com".parse().unwrap(), - "foo4".parse().unwrap(), - ) - .middleware(Rc::new(MyMiddleware {})); - - let object_list = ObjectList::::new( - "abc.oss-cn-shanghai.aliyuncs.com".parse().unwrap(), - Some("foo2/".parse().unwrap()), - 100, - 200, - Vec::new(), - None, - Rc::new(client), - vec![("max-keys".into(), 5u8.into())], - ); - - let res = object_list.get_object_list(); - - assert!(res.is_ok()); - let list = res.unwrap(); - assert_object_list::( - list, - EndPoint::CN_SHANGHAI, - "abc".parse().unwrap(), - Some("foo2/".parse().unwrap()), - 100, - 23, - String::default(), - CommonPrefixes::from_iter([]), - Query::from_iter([(QueryKey::MAX_KEYS, 5u16)]), - ); -} - -#[tokio::test] -async fn test_get_object_list() { - #[derive(Debug)] - struct MyMiddleware {} - - #[async_trait] - impl Middleware for MyMiddleware { - async fn handle(&self, request: Request) -> Result { - //println!("request {:?}", request); - assert_eq!(request.method(), "GET"); - assert_eq!( - *request.url(), - "https://foo4.oss-cn-shanghai.aliyuncs.com/?list-type=2&max-keys=5" - .parse() - .unwrap() - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some(&HeaderValue::from_str("/foo4/").unwrap()) - ); - use http::response::Builder; - let response = Builder::new() - .status(200) - //.url(url.clone()) - .body( - r#" - - barname - - 100 - - false - - 9AB932LY.jpeg - 2022-06-26T09:53:21.000Z - "F75A15996D0857B16FA31A3B16624C26" - Normal - 18027 - Standard - - 23 - "#, - ) - .unwrap(); - let response = Response::from(response); - Ok(response) - } - } - - let client = Client::::new( - "foo1".into(), - "foo2".into(), - "https://oss-cn-shanghai.aliyuncs.com".parse().unwrap(), - "foo4".parse().unwrap(), - ) - .middleware(Arc::new(MyMiddleware {})); - - let res = client - .get_object_list(vec![("max-keys".parse().unwrap(), "5".parse().unwrap())]) - .await; - - assert!(res.is_ok()); - let list = res.unwrap(); - assert_object_list::( - list, - EndPoint::CN_SHANGHAI, - "foo4".parse().unwrap(), - None, - 100, - 23, - String::default(), - CommonPrefixes::from_iter([]), - Query::from_iter([(QueryKey::MAX_KEYS, 5u16)]), - ); -} - -#[tokio::test] -async fn test_error_object_list() { - #[derive(Debug)] - struct MyMiddleware {} - - #[async_trait] - impl Middleware for MyMiddleware { - async fn handle(&self, request: Request) -> Result { - //println!("request {:?}", request); - assert_eq!(request.method(), "GET"); - assert_eq!( - *request.url(), - "https://foo4.oss-cn-shanghai.aliyuncs.com/?list-type=2&max-keys=5" - .parse() - .unwrap() - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some(&HeaderValue::from_str("/foo4/").unwrap()) - ); - use http::response::Builder; - let response = Builder::new() - .status(200) - //.url(url.clone()) - .body( - r#" - - barname - - 100 - - false - - 9AB932LY.jpeg - 2022-06-26T09:53:21.000Z - "F75A15996D0857B16FA31A3B16624C26" - Normal - 18027 - Standard - - foo - "#, - ) - .unwrap(); - let response = Response::from(response); - Ok(response) - } - } - - let client = Client::::new( - "foo1".into(), - "foo2".into(), - "https://oss-cn-shanghai.aliyuncs.com".parse().unwrap(), - "foo4".parse().unwrap(), - ) - .middleware(Arc::new(MyMiddleware {})); - - let res = client - .get_object_list(vec![("max-keys".parse().unwrap(), "5".parse().unwrap())]) - .await; - let err = res.unwrap_err(); - - assert_eq!(format!("{err}"), "decode xml failed"); - assert_eq!( - format!("{}", err.source().unwrap()), - "parse key-count failed, gived str: foo" - ); -} - -#[tokio::test] -async fn test_item_error_object_list() { - #[derive(Debug)] - struct MyMiddleware {} - - #[async_trait] - impl Middleware for MyMiddleware { - async fn handle(&self, request: Request) -> Result { - //println!("request {:?}", request); - assert_eq!(request.method(), "GET"); - assert_eq!( - *request.url(), - "https://foo4.oss-cn-shanghai.aliyuncs.com/?list-type=2&max-keys=5" - .parse() - .unwrap() - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some(&HeaderValue::from_str("/foo4/").unwrap()) - ); - use http::response::Builder; - let response = Builder::new() - .status(200) - //.url(url.clone()) - .body( - r#" - - barname - - 100 - - false - - 9AB932LY.jpeg - 2022-06-26T09:53:21.000Z - "F75A15996D0857B16FA31A3B16624C26" - Normal - aaa - Standard - - 23 - "#, - ) - .unwrap(); - let response = Response::from(response); - Ok(response) - } - } - - let client = Client::::new( - "foo1".into(), - "foo2".into(), - "https://oss-cn-shanghai.aliyuncs.com".parse().unwrap(), - "foo4".parse().unwrap(), - ) - .middleware(Arc::new(MyMiddleware {})); - - let res = client - .get_object_list(vec![("max-keys".parse().unwrap(), "5".parse().unwrap())]) - .await; - let err = res.unwrap_err(); - - assert_eq!(format!("{err}"), "decode xml failed"); - assert_eq!( - format!("{}", err.source().unwrap()), - "parse size failed, gived str: aaa" - ); -} - -#[cfg(feature = "blocking")] -#[test] -fn test_get_blocking_object_list() { - use crate::blocking::builder::Middleware; - use crate::client::ClientRc; - use reqwest::blocking::{Request, Response}; - use std::rc::Rc; - - #[derive(Debug)] - struct MyMiddleware {} - - impl Middleware for MyMiddleware { - fn handle(&self, request: Request) -> Result { - //println!("request {:?}", request); - assert_eq!(request.method(), "GET"); - assert_eq!( - *request.url(), - "https://foo4.oss-cn-shanghai.aliyuncs.com/?list-type=2&max-keys=5" - .parse() - .unwrap() - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some(&HeaderValue::from_str("/foo4/").unwrap()) - ); - use http::response::Builder; - let response = Builder::new() - .status(200) - //.url(url.clone()) - .body( - r#" - - barname - - 100 - - false - - 9AB932LY.jpeg - 2022-06-26T09:53:21.000Z - "F75A15996D0857B16FA31A3B16624C26" - Normal - 18027 - Standard - - 23 - "#, - ) - .unwrap(); - let response = Response::from(response); - Ok(response) - } - } - - let client = ClientRc::new( - "foo1".into(), - "foo2".into(), - "https://oss-cn-shanghai.aliyuncs.com".parse().unwrap(), - "foo4".parse().unwrap(), - ) - .middleware(Rc::new(MyMiddleware {})); - - let res = client.get_object_list([("max-keys".into(), "5".into())]); - - assert!(res.is_ok()); - let list = res.unwrap(); - assert_object_list::( - list, - EndPoint::CN_SHANGHAI, - "foo4".parse().unwrap(), - None, - 100, - 23, - String::default(), - CommonPrefixes::from_iter([]), - Query::from_iter([(QueryKey::MAX_KEYS, 5u16)]), - ); -} - -#[tokio::test] -async fn test_put_content_base() { - #[derive(Debug)] - struct MyMiddleware {} - - #[async_trait] - impl Middleware for MyMiddleware { - async fn handle(&self, request: Request) -> Result { - //println!("request {:?}", request); - assert_eq!(request.method(), "PUT"); - assert_eq!( - *request.url(), - "https://foo4.oss-cn-shanghai.aliyuncs.com/abc.text" - .parse() - .unwrap() - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some(&HeaderValue::from_str("/foo4/abc.text").unwrap()) - ); - use http::response::Builder; - let response = Builder::new() - .status(200) - //.url(url.clone()) - .body(r#"content bar"#) - .unwrap(); - let response = Response::from(response); - Ok(response) - } - } - - let client = Client::::new( - "foo1".into(), - "foo2".into(), - "https://oss-cn-shanghai.aliyuncs.com".parse().unwrap(), - "foo4".parse().unwrap(), - ) - .middleware(Arc::new(MyMiddleware {})); - - let content = String::from("Hello world"); - let content: Vec = content.into(); - - let res = client - .put_content_base( - content, - "application/text", - "abc.text".parse::().unwrap(), - ) - .await; - - //println!("{:?}", res); - assert!(res.is_ok()); -} - -#[cfg(feature = "blocking")] -#[test] -fn test_blocking_put_content_base() { - use crate::client::ClientRc; - use crate::{blocking::builder::Middleware, file::blocking::Files}; - use reqwest::blocking::{Request, Response}; - use std::rc::Rc; - - #[derive(Debug)] - struct MyMiddleware {} - - impl Middleware for MyMiddleware { - fn handle(&self, request: Request) -> Result { - //println!("request {:?}", request); - assert_eq!(request.method(), "PUT"); - assert_eq!( - *request.url(), - "https://foo4.oss-cn-shanghai.aliyuncs.com/abc.text" - .parse() - .unwrap() - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some(&HeaderValue::from_str("/foo4/abc.text").unwrap()) - ); - use http::response::Builder; - let response = Builder::new() - .status(200) - //.url(url.clone()) - .body(r#"content bar"#) - .unwrap(); - let response = Response::from(response); - Ok(response) - } - } - - let client = ClientRc::new( - "foo1".into(), - "foo2".into(), - "https://oss-cn-shanghai.aliyuncs.com".try_into().unwrap(), - "foo4".try_into().unwrap(), - ) - .middleware(Rc::new(MyMiddleware {})); - - let content = String::from("Hello world"); - let content: Vec = content.into(); - - let res = client.put_content_base(content, "application/text", "abc.text"); - - //println!("{:?}", res); - assert!(res.is_ok()); -} - -mod get_object { - use std::sync::Arc; - - use http::HeaderValue; - use reqwest::{Request, Response}; - - use crate::builder::{BuilderError, ClientWithMiddleware}; - use crate::file::Files; - use crate::types::object::ObjectPath; - use crate::{builder::Middleware, client::Client}; - use async_trait::async_trait; - - #[tokio::test] - async fn test_all_range() { - #[derive(Debug)] - struct MyMiddleware {} - - #[async_trait] - impl Middleware for MyMiddleware { - async fn handle(&self, request: Request) -> Result { - //println!("request {:?}", request); - assert_eq!(request.method(), "GET"); - assert_eq!( - *request.url(), - "https://foo4.oss-cn-shanghai.aliyuncs.com/foo.png" - .parse() - .unwrap() - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some(&HeaderValue::from_str("/foo4/foo.png").unwrap()) - ); - assert_eq!( - request.headers().get("Range"), - Some(&HeaderValue::from_str("bytes=0-").unwrap()) - ); - use http::response::Builder; - let response = Builder::new() - .status(200) - //.url(url.clone()) - .body(r#"content bar"#) - .unwrap(); - let response = Response::from(response); - Ok(response) - } - } - - let client = Client::::new( - "foo1".into(), - "foo2".into(), - "https://oss-cn-shanghai.aliyuncs.com".parse().unwrap(), - "foo4".parse().unwrap(), - ) - .middleware(Arc::new(MyMiddleware {})); - - let res = client - .get_object("foo.png".parse::().unwrap(), ..) - .await; - - //println!("{:?}", res); - assert!(res.is_ok()); - let res = res.unwrap(); - assert_eq!(res, String::from("content bar").into_bytes()) - } - - #[tokio::test] - async fn test_start_range() { - #[derive(Debug)] - struct MyMiddleware {} - - #[async_trait] - impl Middleware for MyMiddleware { - async fn handle(&self, request: Request) -> Result { - //println!("request {:?}", request); - assert_eq!(request.method(), "GET"); - assert_eq!( - *request.url(), - "https://foo4.oss-cn-shanghai.aliyuncs.com/foo.png" - .parse() - .unwrap() - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some(&HeaderValue::from_str("/foo4/foo.png").unwrap()) - ); - assert_eq!( - request.headers().get("Range"), - Some(&HeaderValue::from_str("bytes=1-").unwrap()) - ); - use http::response::Builder; - let response = Builder::new() - .status(206) - //.url(url.clone()) - .body(r#"content bar"#) - .unwrap(); - let response = Response::from(response); - Ok(response) - } - } - - let client = Client::::new( - "foo1".into(), - "foo2".into(), - "https://oss-cn-shanghai.aliyuncs.com".parse().unwrap(), - "foo4".parse().unwrap(), - ) - .middleware(Arc::new(MyMiddleware {})); - - let res = client - .get_object("foo.png".parse::().unwrap(), 1..) - .await; - - //println!("{:?}", res); - assert!(res.is_ok()); - let res = res.unwrap(); - assert_eq!(res, String::from("content bar").into_bytes()) - } - - #[tokio::test] - async fn test_end_range() { - #[derive(Debug)] - struct MyMiddleware {} - - #[async_trait] - impl Middleware for MyMiddleware { - async fn handle(&self, request: Request) -> Result { - //println!("request {:?}", request); - assert_eq!(request.method(), "GET"); - assert_eq!( - *request.url(), - "https://foo4.oss-cn-shanghai.aliyuncs.com/foo.png" - .parse() - .unwrap() - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some(&HeaderValue::from_str("/foo4/foo.png").unwrap()) - ); - assert_eq!( - request.headers().get("Range"), - Some(&HeaderValue::from_str("bytes=0-10").unwrap()) - ); - use http::response::Builder; - let response = Builder::new() - .status(206) - //.url(url.clone()) - .body(r#"content bar"#) - .unwrap(); - let response = Response::from(response); - Ok(response) - } - } - - let client = Client::::new( - "foo1".into(), - "foo2".into(), - "https://oss-cn-shanghai.aliyuncs.com".parse().unwrap(), - "foo4".parse().unwrap(), - ) - .middleware(Arc::new(MyMiddleware {})); - - let res = client - .get_object("foo.png".parse::().unwrap(), ..10) - .await; - - //println!("{:?}", res); - assert!(res.is_ok()); - let res = res.unwrap(); - assert_eq!(res, String::from("content bar").into_bytes()) - } - - #[tokio::test] - async fn test_start_end_range() { - #[derive(Debug)] - struct MyMiddleware {} - - #[async_trait] - impl Middleware for MyMiddleware { - async fn handle(&self, request: Request) -> Result { - //println!("request {:?}", request); - assert_eq!(request.method(), "GET"); - assert_eq!( - *request.url(), - "https://foo4.oss-cn-shanghai.aliyuncs.com/foo.png" - .parse() - .unwrap() - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some(&HeaderValue::from_str("/foo4/foo.png").unwrap()) - ); - assert_eq!( - request.headers().get("Range"), - Some(&HeaderValue::from_str("bytes=2-10").unwrap()) - ); - use http::response::Builder; - let response = Builder::new() - .status(206) - //.url(url.clone()) - .body(r#"content bar"#) - .unwrap(); - let response = Response::from(response); - Ok(response) - } - } - - let client = Client::::new( - "foo1".into(), - "foo2".into(), - "https://oss-cn-shanghai.aliyuncs.com".parse().unwrap(), - "foo4".parse().unwrap(), - ) - .middleware(Arc::new(MyMiddleware {})); - - let res = client - .get_object("foo.png".parse::().unwrap(), 2..10) - .await; - - //println!("{:?}", res); - assert!(res.is_ok()); - let res = res.unwrap(); - assert_eq!(res, String::from("content bar").into_bytes()) - } -} - -#[cfg(feature = "blocking")] -mod blocking_get_object { - use std::rc::Rc; - - use http::HeaderValue; - use reqwest::blocking::{Request, Response}; - - use crate::blocking::builder::ClientWithMiddleware; - use crate::builder::BuilderError; - use crate::file::blocking::Files; - use crate::{blocking::builder::Middleware, client::Client}; - - #[test] - fn test_all_range() { - #[derive(Debug)] - struct MyMiddleware {} - - impl Middleware for MyMiddleware { - fn handle(&self, request: Request) -> Result { - //println!("request {:?}", request); - assert_eq!(request.method(), "GET"); - assert_eq!( - *request.url(), - "https://foo4.oss-cn-shanghai.aliyuncs.com/foo.png" - .parse() - .unwrap() - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some(&HeaderValue::from_str("/foo4/foo.png").unwrap()) - ); - assert_eq!( - request.headers().get("Range"), - Some(&HeaderValue::from_str("bytes=0-").unwrap()) - ); - use http::response::Builder; - let response = Builder::new() - .status(200) - //.url(url.clone()) - .body(r#"content bar"#) - .unwrap(); - let response = Response::from(response); - Ok(response) - } - } - - let client = Client::::new( - "foo1".into(), - "foo2".into(), - "https://oss-cn-shanghai.aliyuncs.com".parse().unwrap(), - "foo4".parse().unwrap(), - ) - .middleware(Rc::new(MyMiddleware {})); - - let res = client.get_object("foo.png", ..); - - //println!("{:?}", res); - assert!(res.is_ok()); - let res = res.unwrap(); - assert_eq!(res, String::from("content bar").into_bytes()) - } - - #[test] - fn test_start_range() { - #[derive(Debug)] - struct MyMiddleware {} - - impl Middleware for MyMiddleware { - fn handle(&self, request: Request) -> Result { - //println!("request {:?}", request); - assert_eq!(request.method(), "GET"); - assert_eq!( - *request.url(), - "https://foo4.oss-cn-shanghai.aliyuncs.com/foo.png" - .parse() - .unwrap() - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some(&HeaderValue::from_str("/foo4/foo.png").unwrap()) - ); - assert_eq!( - request.headers().get("Range"), - Some(&HeaderValue::from_str("bytes=1-").unwrap()) - ); - use http::response::Builder; - let response = Builder::new() - .status(206) - //.url(url.clone()) - .body(r#"content bar"#) - .unwrap(); - let response = Response::from(response); - Ok(response) - } - } - - let client = Client::::new( - "foo1".into(), - "foo2".into(), - "https://oss-cn-shanghai.aliyuncs.com".parse().unwrap(), - "foo4".parse().unwrap(), - ) - .middleware(Rc::new(MyMiddleware {})); - - let res = client.get_object("foo.png", 1..); - - //println!("{:?}", res); - assert!(res.is_ok()); - let res = res.unwrap(); - assert_eq!(res, String::from("content bar").into_bytes()) - } - - #[test] - fn test_end_range() { - #[derive(Debug)] - struct MyMiddleware {} - - impl Middleware for MyMiddleware { - fn handle(&self, request: Request) -> Result { - //println!("request {:?}", request); - assert_eq!(request.method(), "GET"); - assert_eq!( - *request.url(), - "https://foo4.oss-cn-shanghai.aliyuncs.com/foo.png" - .parse() - .unwrap() - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some(&HeaderValue::from_str("/foo4/foo.png").unwrap()) - ); - assert_eq!( - request.headers().get("Range"), - Some(&HeaderValue::from_str("bytes=0-10").unwrap()) - ); - use http::response::Builder; - let response = Builder::new() - .status(206) - //.url(url.clone()) - .body(r#"content bar"#) - .unwrap(); - let response = Response::from(response); - Ok(response) - } - } - - let client = Client::::new( - "foo1".into(), - "foo2".into(), - "https://oss-cn-shanghai.aliyuncs.com".parse().unwrap(), - "foo4".parse().unwrap(), - ) - .middleware(Rc::new(MyMiddleware {})); - - let res = client.get_object("foo.png", ..10); - - //println!("{:?}", res); - assert!(res.is_ok()); - let res = res.unwrap(); - assert_eq!(res, String::from("content bar").into_bytes()) - } - - #[test] - fn test_start_end_range() { - #[derive(Debug)] - struct MyMiddleware {} - - impl Middleware for MyMiddleware { - fn handle(&self, request: Request) -> Result { - //println!("request {:?}", request); - assert_eq!(request.method(), "GET"); - assert_eq!( - *request.url(), - "https://foo4.oss-cn-shanghai.aliyuncs.com/foo.png" - .parse() - .unwrap() - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some(&HeaderValue::from_str("/foo4/foo.png").unwrap()) - ); - assert_eq!( - request.headers().get("Range"), - Some(&HeaderValue::from_str("bytes=2-10").unwrap()) - ); - use http::response::Builder; - let response = Builder::new() - .status(206) - //.url(url.clone()) - .body(r#"content bar"#) - .unwrap(); - let response = Response::from(response); - Ok(response) - } - } - - let client = Client::::new( - "foo1".into(), - "foo2".into(), - "https://oss-cn-shanghai.aliyuncs.com".parse().unwrap(), - "foo4".parse().unwrap(), - ) - .middleware(Rc::new(MyMiddleware {})); - - let res = client.get_object("foo.png", 2..10); - - //println!("{:?}", res); - assert!(res.is_ok()); - let res = res.unwrap(); - assert_eq!(res, String::from("content bar").into_bytes()) - } -} - -#[tokio::test] -async fn test_delete_object() { - #[derive(Debug)] - struct MyMiddleware {} - - #[async_trait] - impl Middleware for MyMiddleware { - async fn handle(&self, request: Request) -> Result { - //println!("request {:?}", request); - assert_eq!(request.method(), "DELETE"); - assert_eq!( - *request.url(), - "https://foo4.oss-cn-shanghai.aliyuncs.com/abc.png" - .parse() - .unwrap() - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some(&HeaderValue::from_str("/foo4/abc.png").unwrap()) - ); - use http::response::Builder; - let response = Builder::new() - .status(200) - //.url(url.clone()) - .body( - r#" - "#, - ) - .unwrap(); - let response = Response::from(response); - Ok(response) - } - } - - let client = Client::::new( - "foo1".into(), - "foo2".into(), - "https://oss-cn-shanghai.aliyuncs.com".parse().unwrap(), - "foo4".parse().unwrap(), - ) - .middleware(Arc::new(MyMiddleware {})); - - let res = client - .delete_object("abc.png".parse::().unwrap()) - .await; - //println!("{:?}", res); - assert!(res.is_ok()); -} - -#[cfg(feature = "blocking")] -#[test] -fn test_blocking_delete_object() { - use crate::client::ClientRc; - use crate::{blocking::builder::Middleware, file::BlockingFiles}; - use reqwest::blocking::{Request, Response}; - use std::rc::Rc; - - #[derive(Debug)] - struct MyMiddleware {} - - #[async_trait] - impl Middleware for MyMiddleware { - fn handle(&self, request: Request) -> Result { - //println!("request {:?}", request); - assert_eq!(request.method(), "DELETE"); - assert_eq!( - *request.url(), - "https://foo4.oss-cn-shanghai.aliyuncs.com/abc.png" - .parse() - .unwrap() - ); - assert_eq!( - request.headers().get("canonicalizedresource"), - Some(&HeaderValue::from_str("/foo4/abc.png").unwrap()) - ); - use http::response::Builder; - let response = Builder::new() - .status(200) - //.url(url.clone()) - .body( - r#" - "#, - ) - .unwrap(); - let response = Response::from(response); - Ok(response) - } - } - - let client = ClientRc::new( - "foo1".into(), - "foo2".into(), - "https://oss-cn-shanghai.aliyuncs.com".parse().unwrap(), - "foo4".parse().unwrap(), - ) - .middleware(Rc::new(MyMiddleware {})); - - let res = client.delete_object("abc.png"); - //println!("{:?}", res); - assert!(res.is_ok()); -} diff --git a/src/types.rs b/src/types.rs index 98b2e6c..c659cf7 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,167 +1,28 @@ -//! lib 内置类型的定义模块 +use std::collections::HashMap; -use std::{ - borrow::Cow, - error::Error, - fmt::{self, Debug, Display, Formatter}, - str::FromStr, -}; - -use chrono::{DateTime, TimeZone}; -use http::header::{HeaderValue, InvalidHeaderValue, ToStrError}; use url::Url; -#[cfg(feature = "core")] -pub mod core; - -pub mod object; -#[cfg(test)] -mod test; - -use crate::consts::{TRUE1, TRUE2, TRUE3, TRUE4}; - -#[cfg(feature = "core")] -pub use self::core::{ContentRange, Query, QueryKey, QueryValue, SetOssQuery}; -use self::object::{ObjectPathInner, SetObjectPath}; +use crate::bucket::Bucket; -const OSS_DOMAIN_PREFIX: &str = "https://oss-"; -#[allow(dead_code)] -const OSS_INTERNAL: &str = "-internal"; -const OSS_DOMAIN_MAIN: &str = ".aliyuncs.com"; -const OSS_HYPHEN: &str = "oss-"; +pub struct Key(String); -const HANGZHOU: &str = "cn-hangzhou"; -const SHANGHAI: &str = "cn-shanghai"; -const QINGDAO: &str = "cn-qingdao"; -const BEIJING: &str = "cn-beijing"; -const ZHANGJIAKOU: &str = "cn-zhangjiakou"; -const HONGKONG: &str = "cn-hongkong"; -const SHENZHEN: &str = "cn-shenzhen"; -const US_WEST1: &str = "us-west-1"; -const US_EAST1: &str = "us-east-1"; -const AP_SOUTH_EAST1: &str = "ap-southeast-1"; - -const HANGZHOU_L: &str = "hangzhou"; -const SHANGHAI_L: &str = "shanghai"; -const QINGDAO_L: &str = "qingdao"; -const BEIJING_L: &str = "beijing"; -const ZHANGJIAKOU_L: &str = "zhangjiakou"; -const HONGKONG_L: &str = "hongkong"; -const SHENZHEN_L: &str = "shenzhen"; - -const COM: &str = "com"; -const ALIYUNCS: &str = "aliyuncs"; - -/// 阿里云 OSS 的签名 key -#[derive(Clone, Debug, PartialEq, Eq, Default, Hash)] -pub struct InnerKeyId<'a>(Cow<'a, str>); - -/// 静态作用域的 InnerKeyId -pub type KeyId = InnerKeyId<'static>; - -impl AsRef for InnerKeyId<'_> { - fn as_ref(&self) -> &str { - &self.0 +impl Key { + pub fn new>(key: K) -> Key { + Key(key.into()) } -} - -impl Display for InnerKeyId<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) + pub fn as_str(&self) -> &str { + self.0.as_str() } } -impl TryInto for InnerKeyId<'_> { - type Error = InvalidHeaderValue; - fn try_into(self) -> Result { - HeaderValue::from_str(self.as_ref()) - } -} - -impl From for KeyId { - fn from(s: String) -> KeyId { - Self(Cow::Owned(s)) - } -} +pub struct Secret(pub(crate) String); -impl<'a: 'b, 'b> From<&'a str> for InnerKeyId<'b> { - fn from(key_id: &'a str) -> Self { - Self(Cow::Borrowed(key_id)) +impl Secret { + pub fn new>(secret: S) -> Secret { + Secret(secret.into()) } -} - -impl<'a> InnerKeyId<'a> { - /// Creates a new `KeyId` from the given string. - pub fn new(key_id: impl Into>) -> Self { - Self(key_id.into()) - } - - /// Const function that creates a new `KeyId` from a static str. - pub const fn from_static(key_id: &'static str) -> Self { - Self(Cow::Borrowed(key_id)) - } -} - -//=================================================================================================== - -/// 阿里云 OSS 的签名 secret -#[derive(Clone, PartialEq, Eq, Default, Hash)] -pub struct InnerKeySecret<'a>(Cow<'a, str>); - -/// 静态作用域的 InnerKeySecret -pub type KeySecret = InnerKeySecret<'static>; - -impl Display for InnerKeySecret<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "******secret******") - } -} - -impl Debug for InnerKeySecret<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_tuple("KeySecret").finish() - } -} - -impl From for KeySecret { - fn from(s: String) -> Self { - Self(Cow::Owned(s)) - } -} - -impl<'a: 'b, 'b> From<&'a str> for InnerKeySecret<'b> { - fn from(secret: &'a str) -> Self { - Self(Cow::Borrowed(secret)) - } -} - -impl<'a> InnerKeySecret<'a> { - /// Creates a new `KeySecret` from the given string. - pub fn new(secret: impl Into>) -> Self { - Self(secret.into()) - } - - /// Const function that creates a new `KeySecret` from a static str. - pub const fn from_static(secret: &'static str) -> Self { - Self(Cow::Borrowed(secret)) - } - - #[cfg(test)] - pub(crate) fn as_str(&self) -> &str { - &self.0 - } - - /// 加密 String 数据 - #[inline] - pub fn encryption_string( - &self, - string: String, - ) -> Result { - self.encryption(string.as_bytes()) - } - /// # 加密数据 - /// 这种加密方式可保证秘钥明文只会存在于 `InnerKeySecret` 类型内,不会被读取或复制 + /// 这种加密方式可保证秘钥明文只会存在于 `Secret` 类型内,不会被读取或复制 pub fn encryption( &self, data: &[u8], @@ -184,8 +45,6 @@ impl<'a> InnerKeySecret<'a> { } } -//=================================================================================================== - /// # OSS 的可用区 /// [aliyun docs](https://help.aliyun.com/document_detail/31837.htm) #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] @@ -196,6 +55,28 @@ pub struct EndPoint { pub(crate) is_internal: bool, } +const OSS_INTERNAL: &str = "-internal"; +const OSS_DOMAIN_MAIN: &str = ".aliyuncs.com"; + +const HANGZHOU: &str = "cn-hangzhou"; +const SHANGHAI: &str = "cn-shanghai"; +const QINGDAO: &str = "cn-qingdao"; +const BEIJING: &str = "cn-beijing"; +const ZHANGJIAKOU: &str = "cn-zhangjiakou"; +const HONGKONG: &str = "cn-hongkong"; +const SHENZHEN: &str = "cn-shenzhen"; +const US_WEST1: &str = "us-west-1"; +const US_EAST1: &str = "us-east-1"; +const AP_SOUTH_EAST1: &str = "ap-southeast-1"; + +// const HANGZHOU_L: &str = "hangzhou"; +// const SHANGHAI_L: &str = "shanghai"; +// const QINGDAO_L: &str = "qingdao"; +// const BEIJING_L: &str = "beijing"; +// const ZHANGJIAKOU_L: &str = "zhangjiakou"; +// const HONGKONG_L: &str = "hongkong"; +// const SHENZHEN_L: &str = "shenzhen"; + impl EndPoint { /// 杭州 pub const CN_HANGZHOU: Self = Self { @@ -270,263 +151,6 @@ impl EndPoint { kind: EndPointKind::ApSouthEast1, is_internal: false, }; -} - -/// # OSS 的可用区种类 enum -#[derive(Clone, Debug, PartialEq, Eq, Default, Hash)] -#[non_exhaustive] -pub(crate) enum EndPointKind { - /// 杭州可用区 - #[default] - CnHangzhou, - /// 上海可用区 - CnShanghai, - /// 青岛可用区 - CnQingdao, - /// 北京可用区 - CnBeijing, - /// 张家口可用区 - CnZhangjiakou, // 张家口 lenght=13 - /// 香港 - CnHongkong, - /// 深圳 - CnShenzhen, - /// 美国西部 - UsWest1, - /// 美国东部 - UsEast1, - /// 新加坡 - ApSouthEast1, - /// 其他可用区 fuzhou,ap-southeast-6 等 - Other(Cow<'static, str>), -} - -impl AsRef for EndPoint { - /// ``` - /// # use aliyun_oss_client::types::EndPoint; - /// - /// assert_eq!(EndPoint::HANGZHOU.as_ref(), "cn-hangzhou"); - /// assert_eq!(EndPoint::SHANGHAI.as_ref(), "cn-shanghai"); - /// assert_eq!(EndPoint::QINGDAO.as_ref(), "cn-qingdao"); - /// assert_eq!(EndPoint::BEIJING.as_ref(), "cn-beijing"); - /// assert_eq!(EndPoint::ZHANGJIAKOU.as_ref(), "cn-zhangjiakou"); - /// assert_eq!(EndPoint::HONGKONG.as_ref(), "cn-hongkong"); - /// assert_eq!(EndPoint::SHENZHEN.as_ref(), "cn-shenzhen"); - /// assert_eq!(EndPoint::US_WEST_1.as_ref(), "us-west-1"); - /// assert_eq!(EndPoint::US_EAST_1.as_ref(), "us-east-1"); - /// assert_eq!(EndPoint::AP_SOUTH_EAST_1.as_ref(), "ap-southeast-1"); - /// ``` - fn as_ref(&self) -> &str { - use EndPointKind::*; - match &self.kind { - CnHangzhou => HANGZHOU, - CnShanghai => SHANGHAI, - CnQingdao => QINGDAO, - CnBeijing => BEIJING, - CnZhangjiakou => ZHANGJIAKOU, - CnHongkong => HONGKONG, - CnShenzhen => SHENZHEN, - UsWest1 => US_WEST1, - UsEast1 => US_EAST1, - ApSouthEast1 => AP_SOUTH_EAST1, - Other(str) => str, - } - } -} - -impl Display for EndPoint { - /// ``` - /// # use aliyun_oss_client::types::EndPoint; - /// let mut endpoint = EndPoint::HANGZHOU; - /// assert_eq!(format!("{}", endpoint), "cn-hangzhou"); - /// endpoint.set_internal(true); - /// assert_eq!(format!("{}", endpoint), "cn-hangzhou-internal"); - /// ``` - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!( - f, - "{}{}", - self.as_ref(), - if self.is_internal { OSS_INTERNAL } else { "" } - ) - } -} - -impl TryFrom for EndPoint { - type Error = InvalidEndPoint; - /// 字符串转 endpoint - /// - /// 举例 - /// ``` - /// # use aliyun_oss_client::types::EndPoint; - /// let e: EndPoint = String::from("qingdao").try_into().unwrap(); - /// assert_eq!(e, EndPoint::QINGDAO); - /// ``` - fn try_from(url: String) -> Result { - Self::new(&url) - } -} - -impl<'a> TryFrom<&'a str> for EndPoint { - type Error = InvalidEndPoint; - /// 字符串字面量转 endpoint - /// - /// 举例 - /// ``` - /// # use aliyun_oss_client::types::EndPoint; - /// let e: EndPoint = "qingdao".try_into().unwrap(); - /// assert_eq!(e, EndPoint::QINGDAO); - /// ``` - fn try_from(url: &'a str) -> Result { - Self::new(url) - } -} - -impl FromStr for EndPoint { - type Err = InvalidEndPoint; - fn from_str(url: &str) -> Result { - Self::new(url) - } -} - -impl TryFrom for EndPoint { - type Error = InvalidEndPoint; - fn try_from(url: Url) -> Result { - use url::Host; - let domain = if let Some(Host::Domain(domain)) = url.host() { - domain - } else { - return Err(InvalidEndPoint::new()); - }; - let mut url_pieces = domain.rsplit('.'); - - match (url_pieces.next(), url_pieces.next()) { - (Some(COM), Some(ALIYUNCS)) => (), - _ => return Err(InvalidEndPoint::new()), - } - - match url_pieces.next() { - Some(endpoint) => match EndPoint::from_host_piece(endpoint) { - Ok(end) => Ok(end), - _ => Err(InvalidEndPoint::new()), - }, - _ => Err(InvalidEndPoint::new()), - } - } -} - -impl<'a> EndPoint { - /// 通过字符串字面值初始化 endpoint - /// - /// 例如 - /// ``` - /// # use aliyun_oss_client::types::EndPoint; - /// EndPoint::from_static("qingdao"); - /// ``` - pub fn from_static(url: &'a str) -> Self { - Self::new(url).unwrap_or_else(|_| panic!("Unknown Endpoint :{}", url)) - } - - /// # Safety - /// 用于静态定义其他可用区 - pub const unsafe fn from_static2(url: &'static str) -> Self { - Self { - kind: EndPointKind::Other(Cow::Borrowed(url)), - is_internal: false, - } - } - - /// 初始化 endpoint enum - /// ```rust - /// # use aliyun_oss_client::types::EndPoint; - /// # use std::borrow::Cow; - /// assert!(matches!( - /// EndPoint::new("shanghai"), - /// Ok(EndPoint::SHANGHAI) - /// )); - /// - /// assert!(EndPoint::new("abc-").is_err()); - /// assert!(EndPoint::new("-abc").is_err()); - /// assert!(EndPoint::new("abc-def234ab").is_ok()); - /// assert!(EndPoint::new("abc-def*#$%^ab").is_err()); - /// assert!(EndPoint::new("cn-jinan").is_ok()); - /// assert!(EndPoint::new("cn-jinan").is_ok()); - /// assert!(EndPoint::new("oss-cn-jinan").is_err()); - /// ``` - pub fn new(url: &'a str) -> Result { - const OSS_STR: &str = "oss"; - use EndPointKind::*; - if url.is_empty() { - return Err(InvalidEndPoint::new()); - } - // 是否是内网 - let is_internal = url.ends_with(OSS_INTERNAL); - let url = if is_internal { - let len = url.len(); - &url[..len - 9] - } else { - url - }; - - let kind = if url.contains(SHANGHAI_L) { - Ok(CnShanghai) - } else if url.contains(HANGZHOU_L) { - Ok(CnHangzhou) - } else if url.contains(QINGDAO_L) { - Ok(CnQingdao) - } else if url.contains(BEIJING_L) { - Ok(CnBeijing) - } else if url.contains(ZHANGJIAKOU_L) { - Ok(CnZhangjiakou) - } else if url.contains(HONGKONG_L) { - Ok(CnHongkong) - } else if url.contains(SHENZHEN_L) { - Ok(CnShenzhen) - } else if url.contains(US_WEST1) { - Ok(UsWest1) - } else if url.contains(US_EAST1) { - Ok(UsEast1) - } else if url.contains(AP_SOUTH_EAST1) { - Ok(ApSouthEast1) - } else { - if url.starts_with('-') || url.ends_with('-') || url.starts_with(OSS_STR) { - return Err(InvalidEndPoint::new()); - } - - if !url.chars().all(valid_oss_character) { - return Err(InvalidEndPoint::new()); - } - - Ok(Other(Cow::Owned(url.to_owned()))) - }; - - kind.map(|kind| Self { kind, is_internal }) - } - - /// 从 oss 域名中提取 Endpoint 信息 - pub(crate) fn from_host_piece(url: &'a str) -> Result { - if !url.starts_with(OSS_HYPHEN) { - return Err(InvalidEndPoint::new()); - } - Self::new(&url[4..]) - } - - /// use env init Endpoint - pub fn from_env() -> Result { - let endpoint = std::env::var("ALIYUN_ENDPOINT").map_err(|_| InvalidEndPoint::new())?; - let mut endpoint: EndPoint = endpoint.parse().map_err(|_| InvalidEndPoint::new())?; - if let Ok(is_internal) = std::env::var("ALIYUN_OSS_INTERNAL") { - if is_internal == TRUE1 - || is_internal == TRUE2 - || is_internal == TRUE3 - || is_internal == TRUE4 - { - endpoint.set_internal(true); - } - } - - Ok(endpoint) - } /// # 调整 API 指向是否为内网 /// @@ -544,7 +168,7 @@ impl<'a> EndPoint { /// ``` /// # use aliyun_oss_client::types::EndPoint; /// use reqwest::Url; - /// let mut endpoint = EndPoint::new("shanghai").unwrap(); + /// let mut endpoint = EndPoint::CN_SHANGHAI;; /// assert_eq!( /// endpoint.to_url(), /// Url::parse("https://oss-cn-shanghai.aliyuncs.com").unwrap() @@ -557,6 +181,7 @@ impl<'a> EndPoint { /// ); /// ``` pub fn to_url(&self) -> Url { + const OSS_DOMAIN_PREFIX: &str = "https://oss-"; let mut url = String::from(OSS_DOMAIN_PREFIX); url.push_str(self.as_ref()); @@ -570,653 +195,197 @@ impl<'a> EndPoint { } } -/// 无效的可用区 -#[derive(PartialEq, Eq, Hash, Clone)] -#[non_exhaustive] -pub struct InvalidEndPoint { - _priv: (), -} - -impl InvalidEndPoint { - pub(crate) fn new() -> InvalidEndPoint { - InvalidEndPoint { _priv: () } - } -} - -impl Debug for InvalidEndPoint { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_struct("InvalidEndPoint").finish() - } -} - -impl Error for InvalidEndPoint {} - -impl fmt::Display for InvalidEndPoint { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "endpoint must not with `-` prefix or `-` suffix or `oss-` prefix" - ) - } -} - -impl PartialEq<&str> for EndPoint { - /// # 相等比较 - /// ``` - /// # use aliyun_oss_client::types::EndPoint; - /// let e: EndPoint = String::from("qingdao").try_into().unwrap(); - /// assert!(e == "cn-qingdao"); - /// ``` - #[inline] - fn eq(&self, other: &&str) -> bool { - &self.as_ref() == other - } -} - -impl PartialEq for &str { - /// # 相等比较 - /// ``` - /// # use aliyun_oss_client::types::EndPoint; - /// let e: EndPoint = String::from("qingdao").try_into().unwrap(); - /// assert!("cn-qingdao" == e); - /// ``` - #[inline] - fn eq(&self, other: &EndPoint) -> bool { - self == &other.as_ref() - } -} - -impl PartialEq for EndPoint { - /// # 相等比较 +impl AsRef for EndPoint { /// ``` /// # use aliyun_oss_client::types::EndPoint; - /// use reqwest::Url; - /// let endpoint = EndPoint::new("shanghai").unwrap(); - /// assert!(endpoint == Url::parse("https://oss-cn-shanghai.aliyuncs.com").unwrap()); - /// ``` - #[inline] - fn eq(&self, other: &Url) -> bool { - &self.to_url() == other - } -} - -//=================================================================================================== - -/// 存储 bucket 名字的类型 -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct BucketName(Cow<'static, str>); - -impl AsRef for BucketName { - fn as_ref(&self) -> &str { - &self.0 - } -} - -impl Display for BucketName { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} - -impl Default for BucketName { - #![allow(clippy::unwrap_used)] - fn default() -> BucketName { - BucketName::new("a").unwrap() - } -} - -// impl TryInto for BucketName { -// type Error = InvalidHeaderValue; -// fn try_into(self) -> Result { -// HeaderValue::from_str(self.as_ref()) -// } -// } -impl TryFrom for BucketName { - type Error = InvalidBucketName; - /// ``` - /// # use aliyun_oss_client::types::BucketName; - /// let b: BucketName = String::from("abc").try_into().unwrap(); - /// assert_eq!(b, BucketName::new("abc").unwrap()); - /// ``` - fn try_from(s: String) -> Result { - Self::new(s) - } -} - -impl<'a> TryFrom<&'a str> for BucketName { - type Error = InvalidBucketName; - /// ``` - /// # use aliyun_oss_client::types::BucketName; - /// let b: BucketName = "abc".try_into().unwrap(); - /// assert_eq!(b, BucketName::new("abc").unwrap()); - /// ``` - fn try_from(bucket: &'a str) -> Result { - Self::from_static(bucket) - } -} - -impl FromStr for BucketName { - type Err = InvalidBucketName; - /// ``` - /// # use aliyun_oss_client::types::BucketName; - /// let b: BucketName = "abc".parse().unwrap(); - /// assert_eq!(b, BucketName::new("abc").unwrap()); - /// ``` - fn from_str(s: &str) -> Result { - Self::from_static(s) - } -} - -impl<'a> BucketName { - /// Creates a new `BucketName` from the given string. - /// 只允许小写字母、数字、短横线(-),且不能以短横线开头或结尾 - /// - /// ``` - /// # use aliyun_oss_client::types::BucketName; /// - /// assert!(BucketName::new("").is_err()); - /// assert!(BucketName::new("abc").is_ok()); - /// assert!(BucketName::new("abc-").is_err()); - /// assert!(BucketName::new("-abc").is_err()); - /// assert!(BucketName::new("abc-def234ab").is_ok()); - /// assert!(BucketName::new("abc-def*#$%^ab").is_err()); - /// ``` - pub fn new(bucket: impl Into>) -> Result { - let bucket = bucket.into(); - - if bucket.is_empty() || bucket.starts_with('-') || bucket.ends_with('-') { - return Err(InvalidBucketName::new()); - } - - if !bucket.chars().all(valid_oss_character) { - return Err(InvalidBucketName::new()); - } - - Ok(Self(bucket)) - } - - /// use env init BucketName - pub fn from_env() -> Result { - let string = std::env::var("ALIYUN_BUCKET").map_err(|_| InvalidBucketName::new())?; - - string.parse() - } - - /// Const function that creates a new `BucketName` from a static str. - /// ``` - /// # use aliyun_oss_client::types::BucketName; - /// - /// assert!(BucketName::from_static("").is_err()); - /// assert!(BucketName::from_static("abc").is_ok()); - /// assert!(BucketName::from_static("abc-").is_err()); - /// assert!(BucketName::from_static("-abc").is_err()); - /// assert!(BucketName::from_static("abc-def234ab").is_ok()); - /// assert!(BucketName::from_static("abc-def*#$%^ab").is_err()); + /// assert_eq!(EndPoint::HANGZHOU.as_ref(), "cn-hangzhou"); + /// assert_eq!(EndPoint::SHANGHAI.as_ref(), "cn-shanghai"); + /// assert_eq!(EndPoint::QINGDAO.as_ref(), "cn-qingdao"); + /// assert_eq!(EndPoint::BEIJING.as_ref(), "cn-beijing"); + /// assert_eq!(EndPoint::ZHANGJIAKOU.as_ref(), "cn-zhangjiakou"); + /// assert_eq!(EndPoint::HONGKONG.as_ref(), "cn-hongkong"); + /// assert_eq!(EndPoint::SHENZHEN.as_ref(), "cn-shenzhen"); + /// assert_eq!(EndPoint::US_WEST_1.as_ref(), "us-west-1"); + /// assert_eq!(EndPoint::US_EAST_1.as_ref(), "us-east-1"); + /// assert_eq!(EndPoint::AP_SOUTH_EAST_1.as_ref(), "ap-southeast-1"); /// ``` - pub fn from_static(bucket: &'a str) -> Result { - if bucket.is_empty() || bucket.starts_with('-') || bucket.ends_with('-') { - return Err(InvalidBucketName::new()); - } - - if !bucket.chars().all(valid_oss_character) { - return Err(InvalidBucketName::new()); + fn as_ref(&self) -> &str { + use EndPointKind::*; + match &self.kind { + CnHangzhou => HANGZHOU, + CnShanghai => SHANGHAI, + CnQingdao => QINGDAO, + CnBeijing => BEIJING, + CnZhangjiakou => ZHANGJIAKOU, + CnHongkong => HONGKONG, + CnShenzhen => SHENZHEN, + UsWest1 => US_WEST1, + UsEast1 => US_EAST1, + ApSouthEast1 => AP_SOUTH_EAST1, + Other(str) => str, } - - Ok(Self(Cow::Owned(bucket.to_owned()))) - } - - /// # Safety - pub const unsafe fn from_static2(bucket: &'static str) -> Self { - Self(Cow::Borrowed(bucket)) - } -} - -fn valid_oss_character(c: char) -> bool { - match c { - _ if c.is_ascii_lowercase() => true, - _ if c.is_numeric() => true, - '-' => true, - _ => false, - } -} - -impl PartialEq<&str> for BucketName { - /// 相等比较 - /// ``` - /// # use aliyun_oss_client::types::BucketName; - /// let path = BucketName::new("abc").unwrap(); - /// assert!(path == "abc"); - /// ``` - #[inline] - fn eq(&self, other: &&str) -> bool { - &self.0 == other - } -} - -impl PartialEq for &str { - /// 相等比较 - /// ``` - /// # use aliyun_oss_client::types::BucketName; - /// let path = BucketName::new("abc").unwrap(); - /// assert!("abc" == path); - /// ``` - #[inline] - fn eq(&self, other: &BucketName) -> bool { - self == &other.0 - } -} - -/// 无效的 bucket 名称 -#[derive(PartialEq, Clone)] -#[non_exhaustive] -pub struct InvalidBucketName { - _priv: (), -} - -impl InvalidBucketName { - pub(crate) fn new() -> InvalidBucketName { - InvalidBucketName { _priv: () } - } -} - -impl Debug for InvalidBucketName { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_struct("InvalidBucketName").finish() - } -} - -impl Error for InvalidBucketName {} - -impl fmt::Display for InvalidBucketName { - /// ``` - /// # use aliyun_oss_client::types::BucketName; - /// - /// let err = BucketName::from_static("").unwrap_err(); - /// assert_eq!( - /// format!("{}", err), - /// "bucket name only allow `alphabet, digit, -`, and must not with `-` prefix or `-` suffix" - /// ); - /// ``` - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "bucket name only allow `alphabet, digit, -`, and must not with `-` prefix or `-` suffix" - ) } } -//=================================================================================================== - -/// aliyun OSS 的配置 ContentMd5 +/// # OSS 的可用区种类 enum #[derive(Clone, Debug, PartialEq, Eq, Default, Hash)] -pub struct InnerContentMd5<'a>(Cow<'a, str>); -/// 静态作用域的 InnerContentMd5 -pub type ContentMd5 = InnerContentMd5<'static>; - -impl AsRef for InnerContentMd5<'_> { - fn as_ref(&self) -> &str { - &self.0 - } -} - -impl Display for InnerContentMd5<'_> { - /// ``` - /// # use aliyun_oss_client::types::ContentMd5; - /// let md5 = ContentMd5::new("abc"); - /// assert_eq!(format!("{md5}"), "abc"); - /// ``` - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} - -impl TryInto for InnerContentMd5<'_> { - type Error = InvalidHeaderValue; - fn try_into(self) -> Result { - HeaderValue::from_str(self.as_ref()) - } -} - -impl TryInto for &InnerContentMd5<'_> { - type Error = InvalidHeaderValue; - fn try_into(self) -> Result { - HeaderValue::from_str(self.as_ref()) - } -} -impl From for ContentMd5 { - /// ``` - /// # use aliyun_oss_client::types::ContentMd5; - /// let md5: ContentMd5 = String::from("abc").into(); - /// assert_eq!(format!("{md5}"), "abc"); - /// ``` - fn from(s: String) -> Self { - Self(Cow::Owned(s)) - } -} - -impl<'a: 'b, 'b> From<&'a str> for InnerContentMd5<'b> { - fn from(value: &'a str) -> Self { - Self(Cow::Borrowed(value)) - } -} - -impl<'a> InnerContentMd5<'a> { - /// Creates a new `ContentMd5` from the given string. - pub fn new(val: impl Into>) -> Self { - Self(val.into()) - } - - /// Const function that creates a new `ContentMd5` from a static str. - pub const fn from_static(val: &'static str) -> Self { - Self(Cow::Borrowed(val)) - } -} - -//=================================================================================================== - -/// aliyun OSS 的配置 ContentType -#[derive(Clone, Debug, PartialEq, Eq, Default)] -pub struct ContentType(Cow<'static, str>); - -impl AsRef for ContentType { - fn as_ref(&self) -> &str { - &self.0 - } +#[non_exhaustive] +pub(crate) enum EndPointKind { + /// 杭州可用区 + #[default] + CnHangzhou, + /// 上海可用区 + CnShanghai, + /// 青岛可用区 + CnQingdao, + /// 北京可用区 + CnBeijing, + /// 张家口可用区 + CnZhangjiakou, // 张家口 lenght=13 + /// 香港 + CnHongkong, + /// 深圳 + CnShenzhen, + /// 美国西部 + UsWest1, + /// 美国东部 + UsEast1, + /// 新加坡 + ApSouthEast1, + /// 其他可用区 fuzhou,ap-southeast-6 等 + #[allow(dead_code)] + Other(String), } -impl Display for ContentType { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} +pub struct CanonicalizedResource(String); -impl TryInto for ContentType { - type Error = InvalidHeaderValue; - fn try_into(self) -> Result { - HeaderValue::from_str(self.as_ref()) - } -} -impl TryFrom for ContentType { - type Error = ToStrError; - fn try_from(value: HeaderValue) -> Result { - Ok(Self(Cow::Owned(value.to_str()?.to_owned()))) - } -} -impl From for ContentType { - fn from(s: String) -> Self { - Self(Cow::Owned(s)) +impl Default for CanonicalizedResource { + fn default() -> Self { + CanonicalizedResource("/".to_owned()) } } -impl ContentType { - /// Creates a new `ContentMd5` from the given string. - pub fn new(val: impl Into>) -> Self { - Self(val.into()) +impl CanonicalizedResource { + pub fn new(str: String) -> CanonicalizedResource { + CanonicalizedResource(str) } - - /// Const function that creates a new `ContentMd5` from a static str. - pub const fn from_static(val: &'static str) -> Self { - Self(Cow::Borrowed(val)) - } -} - -//=================================================================================================== - -/// 用于计算签名的 Date -#[derive(Clone, Debug, PartialEq, Eq, Default, Hash)] -pub struct InnerDate<'a>(Cow<'a, str>); -/// 静态作用域的 InnerDate -pub type Date = InnerDate<'static>; - -impl AsRef for InnerDate<'_> { - fn as_ref(&self) -> &str { + pub fn as_str(&self) -> &str { &self.0 } -} -impl Display for InnerDate<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) + pub fn from_bucket_info(bucket: &Bucket) -> CanonicalizedResource { + Self(format!("/{}/?bucketInfo", bucket.as_str())) } -} -impl TryInto for InnerDate<'_> { - type Error = InvalidHeaderValue; - fn try_into(self) -> Result { - HeaderValue::from_str(self.as_ref()) - } -} - -impl From> for Date -where - Tz::Offset: fmt::Display, -{ - fn from(d: DateTime) -> Self { - Self(Cow::Owned(d.format("%a, %d %b %Y %T GMT").to_string())) - } -} - -impl<'a> InnerDate<'a> { - /// # Safety - /// Const function that creates a new `Date` from a static str. - pub const unsafe fn from_static(val: &'static str) -> Self { - Self(Cow::Borrowed(val)) + pub fn from_object_list( + bucket: &Bucket, + continuation_token: Option<&String>, + ) -> CanonicalizedResource { + match continuation_token { + Some(token) => Self(format!( + "/{}/?continuation-token={}", + bucket.as_str(), + token + )), + None => Self(format!("/{}/", bucket.as_str())), + } } } -//=================================================================================================== - -/// 计算方式,参考 [aliyun 文档](https://help.aliyun.com/document_detail/31951.htm) -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct InnerCanonicalizedResource<'a>(Cow<'a, str>); -/// 静态作用域的 InnerCanonicalizedResource -pub type CanonicalizedResource = InnerCanonicalizedResource<'static>; - -impl AsRef for InnerCanonicalizedResource<'_> { - fn as_ref(&self) -> &str { - &self.0 - } +/// 存储类型 +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)] +#[non_exhaustive] +pub struct StorageClass { + kind: StorageClassKind, } -impl Display for InnerCanonicalizedResource<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)] +#[non_exhaustive] +enum StorageClassKind { + /// Standard 默认 + #[default] + Standard, + /// IA + IA, + /// Archive + Archive, + /// ColdArchive + ColdArchive, +} + +impl StorageClass { + /// Archive + pub const ARCHIVE: Self = Self { + kind: StorageClassKind::Archive, + }; + /// IA + pub const IA: Self = Self { + kind: StorageClassKind::IA, + }; + /// Standard + pub const STANDARD: Self = Self { + kind: StorageClassKind::Standard, + }; + /// ColdArchive + pub const COLD_ARCHIVE: Self = Self { + kind: StorageClassKind::ColdArchive, + }; -impl TryInto for InnerCanonicalizedResource<'_> { - type Error = InvalidHeaderValue; - fn try_into(self) -> Result { - HeaderValue::from_str(self.as_ref()) - } -} -impl From for CanonicalizedResource { - fn from(s: String) -> Self { - Self(Cow::Owned(s)) - } -} + /// init StorageClass + pub fn new(s: &str) -> Option { + let start_char = s.chars().next()?; -impl<'a: 'b, 'b> From<&'a str> for InnerCanonicalizedResource<'b> { - fn from(value: &'a str) -> Self { - Self(Cow::Borrowed(value)) + let kind = match start_char { + 'a' | 'A' => StorageClassKind::Archive, + 'i' | 'I' => StorageClassKind::IA, + 's' | 'S' => StorageClassKind::Standard, + 'c' | 'C' => StorageClassKind::ColdArchive, + _ => return None, + }; + Some(Self { kind }) } } -impl Default for InnerCanonicalizedResource<'_> { - fn default() -> Self { - InnerCanonicalizedResource(Cow::Owned("/".to_owned())) - } +#[derive(Debug, Default, Clone)] +pub struct ObjectQuery { + map: HashMap, } -#[cfg(any(feature = "core", feature = "auth"))] -pub(crate) const CONTINUATION_TOKEN: &str = "continuation-token"; -#[cfg(any(feature = "core", feature = "auth"))] -pub(crate) const BUCKET_INFO: &str = "bucketInfo"; -#[cfg(any(feature = "core", feature = "auth"))] -const QUERY_KEYWORD: [&str; 2] = ["acl", BUCKET_INFO]; - -impl<'a> InnerCanonicalizedResource<'a> { - /// Creates a new `CanonicalizedResource` from the given string. - pub fn new(val: impl Into>) -> Self { - Self(val.into()) - } - - /// 只有 endpoint ,而没有 bucket 的时候 - #[inline(always)] - pub fn from_endpoint() -> Self { - Self::default() - } - - /// Const function that creates a new `CanonicalizedResource` from a static str. - pub const fn from_static(val: &'static str) -> Self { - Self(Cow::Borrowed(val)) - } - - /// 获取 bucket 的签名参数 - #[cfg(feature = "core")] - pub fn from_bucket>(bucket: B, query: Option<&str>) -> Self { - match query { - Some(q) => { - if QUERY_KEYWORD.iter().any(|&str| str == q) { - return Self::new(format!("/{}/?{}", bucket.as_ref().as_ref(), q)); - } - - Self::new(format!("/{}/", bucket.as_ref().as_ref())) - } - None => Self::default(), +impl ObjectQuery { + pub const DELIMITER: &'static str = "delimiter"; + pub const START_AFTER: &'static str = "start-after"; + pub const CONTINUATION_TOKEN: &'static str = "continuation-token"; + pub const MAX_KEYS: &'static str = "max-keys"; + pub const PREFIX: &'static str = "prefix"; + pub const ENCODING_TYPE: &'static str = "encoding-type"; + pub const FETCH_OWNER: &'static str = "fetch-owner"; + pub fn new() -> Self { + Self { + map: HashMap::new(), } } - - /// 获取 bucket 的签名参数 - #[cfg(feature = "auth")] - pub fn from_bucket_name(bucket: &BucketName, query: Option<&str>) -> Self { - match query { - Some(q) => { - if QUERY_KEYWORD.iter().any(|&str| str == q) { - return Self::new(format!("/{}/?{}", bucket.as_ref(), q)); - } - - Self::new(format!("/{}/", bucket.as_ref())) - } - None => Self::default(), - } + pub fn insert, V: Into>(&mut self, key: K, value: V) -> Option { + self.map.insert(key.into(), value.into()) } - /// 获取 bucket 的签名参数 - /// 带查询条件的 - /// - /// 如果查询条件中有翻页的话,则忽略掉其他字段 - #[cfg(feature = "core")] - #[inline] - pub fn from_bucket_query>(bucket: B, query: &Query) -> Self { - Self::from_bucket_query2(bucket.as_ref(), query) + pub(crate) fn get_next_token(&self) -> Option<&String> { + self.map.get(Self::CONTINUATION_TOKEN) } - #[cfg(feature = "core")] - #[doc(hidden)] - pub fn from_bucket_query2(bucket: &BucketName, query: &Query) -> Self { - match query.get(QueryKey::CONTINUATION_TOKEN) { - Some(v) => Self::new(format!( - "/{}/?continuation-token={}", - bucket.as_ref(), - v.as_ref() - )), - None => Self::new(format!("/{}/", bucket.as_ref())), + pub(crate) fn to_oss_query(&self) -> String { + const LIST_TYPE2: &str = "list-type=2"; + let mut query_str = String::from(LIST_TYPE2); + for (key, value) in self.map.iter() { + query_str += "&"; + query_str += key; + query_str += "="; + query_str += value; } + query_str } - /// 根据 OSS 存储对象(Object)查询签名参数 - #[cfg(feature = "core")] - pub(crate) fn from_object< - Q: IntoIterator, - B: AsRef, - P: AsRef, - >( - (bucket, path): (B, P), - query: Q, - ) -> Self { - let query = Query::from_iter(query); - if query.is_empty() { - Self::new(format!("/{}/{}", bucket.as_ref(), path.as_ref())) - } else { - Self::new(format!( - "/{}/{}?{}", - bucket.as_ref(), - path.as_ref(), - query.to_url_query() - )) - } - } - - pub(crate) fn from_object_str(bucket: &str, path: &str) -> Self { - Self::new(format!("/{}/{}", bucket, path)) - } - - #[cfg(feature = "auth")] - pub(crate) fn from_object_without_query, P: AsRef>( - bucket: B, - path: P, - ) -> Self { - Self::new(format!("/{}/{}", bucket.as_ref(), path.as_ref())) - } -} - -impl PartialEq<&str> for InnerCanonicalizedResource<'_> { - /// # 相等比较 - /// ``` - /// # use aliyun_oss_client::types::CanonicalizedResource; - /// let res = CanonicalizedResource::new("abc"); - /// assert!(res == "abc"); - /// ``` - #[inline] - fn eq(&self, other: &&str) -> bool { - &self.0 == other - } -} - -impl PartialEq> for &str { - /// # 相等比较 - /// ``` - /// # use aliyun_oss_client::types::CanonicalizedResource; - /// let res = CanonicalizedResource::new("abc"); - /// assert!("abc" == res); - /// ``` - #[inline] - fn eq(&self, other: &InnerCanonicalizedResource<'_>) -> bool { - self == &other.0 + pub fn insert_next_token(&mut self, token: String) -> Option { + self.map.insert(Self::CONTINUATION_TOKEN.into(), token) } } - -/// 根据 endpoint, bucket, path 获取接口信息 -pub fn get_url_resource( - endpoint: &EndPoint, - bucket: &BucketName, - path: &ObjectPathInner, -) -> (Url, CanonicalizedResource) { - let mut url = url_from_bucket(endpoint, bucket); - url.set_object_path(path); - - let resource = CanonicalizedResource::from_object_str(bucket.as_ref(), path.as_ref()); - - (url, resource) -} - -/// 根据 endpoint, bucket, path 获取接口信息 -pub fn get_url_resource2, B: AsRef>( - endpoint: E, - bucket: B, - path: &ObjectPathInner, -) -> (Url, CanonicalizedResource) { - get_url_resource(endpoint.as_ref(), bucket.as_ref(), path) -} - -pub(crate) fn url_from_bucket(endpoint: &EndPoint, bucket: &BucketName) -> Url { - let url = format!( - "https://{}.oss-{}.aliyuncs.com", - bucket.as_ref(), - endpoint.to_string(), - ); - url.parse().unwrap_or_else(|_| { - unreachable!("covert to url failed, bucket: {bucket}, endpoint: {endpoint}") - }) -} diff --git a/src/types/core.rs b/src/types/core.rs deleted file mode 100644 index 3fd9ffc..0000000 --- a/src/types/core.rs +++ /dev/null @@ -1,905 +0,0 @@ -//! 核心功能用到的类型 Query ContentRange 等 - -use std::borrow::Cow; -use std::collections::HashMap; - -const DELIMITER: &str = "delimiter"; -const START_AFTER: &str = "start-after"; -const CONTINUATION_TOKEN: &str = "continuation-token"; -const MAX_KEYS: &str = "max-keys"; -const PREFIX: &str = "prefix"; -const ENCODING_TYPE: &str = "encoding-type"; -const FETCH_OWNER: &str = "fetch-owner"; -const DEFAULT_MAX_KEYS: usize = 100; - -//=================================================================================================== -/// 查询条件 -/// -/// ``` -/// use aliyun_oss_client::types::Query; -/// -/// let query: Query = [("abc", "def")].into_iter().collect(); -/// assert_eq!(query.len(), 1); -/// -/// let value = query.get("abc"); -/// assert!(value.is_some()); -/// let value = value.unwrap(); -/// assert_eq!(value.as_ref(), "def"); -/// -/// let str = query.to_oss_string(); -/// assert_eq!(str.as_str(), "list-type=2&abc=def"); -/// let str = query.to_url_query(); -/// assert_eq!(str.as_str(), "abc=def"); -/// ``` -#[derive(Clone, Debug, Default)] -pub struct Query { - inner: HashMap, -} - -impl AsMut> for Query { - fn as_mut(&mut self) -> &mut HashMap { - &mut self.inner - } -} - -impl AsRef> for Query { - fn as_ref(&self) -> &HashMap { - &self.inner - } -} - -impl Query { - /// Creates an empty `Query`. - /// - /// The hash map is initially created with a capacity of 0, so it will not allocate until it - /// is first inserted into. - pub fn new() -> Self { - Self { - inner: HashMap::new(), - } - } - - /// Creates an empty `Query` with at least the specified capacity. - pub fn with_capacity(capacity: usize) -> Self { - Self { - inner: HashMap::with_capacity(capacity), - } - } - - /// Inserts a key-value pair into the map. - pub fn insert( - &mut self, - key: impl Into, - value: impl Into, - ) -> Option { - self.as_mut().insert(key.into(), value.into()) - } - - /// Returns a reference to the value corresponding to the key. - pub fn get(&self, key: impl Into) -> Option<&QueryValue> { - self.as_ref().get(&key.into()) - } - - /// Returns the number of elements in the map. - pub fn len(&self) -> usize { - self.as_ref().len() - } - - /// Returns `true` if the map contains no elements. - pub fn is_empty(&self) -> bool { - self.as_ref().is_empty() - } - - /// Removes a key from the map, returning the value at the key if the key - /// was previously in the map. - pub fn remove(&mut self, key: impl Into) -> Option { - self.as_mut().remove(&key.into()) - } - - /// 将查询参数拼成 aliyun 接口需要的格式 - pub fn to_oss_string(&self) -> String { - const LIST_TYPE2: &str = "list-type=2"; - let mut query_str = String::from(LIST_TYPE2); - for (key, value) in self.as_ref().iter() { - query_str += "&"; - query_str += key.as_ref(); - query_str += "="; - query_str += value.as_ref(); - } - query_str - } - - /// 转化成 url 参数的形式 - /// a=foo&b=bar - pub fn to_url_query(&self) -> String { - self.as_ref() - .iter() - .map(|(k, v)| { - let mut res = String::with_capacity(k.as_ref().len() + v.as_ref().len() + 1); - res.push_str(k.as_ref()); - res.push('='); - res.push_str(v.as_ref()); - res - }) - .collect::>() - .join("&") - } - - pub(crate) fn get_max_keys(&self) -> usize { - match self.get(QueryKey::MAX_KEYS) { - Some(capacity) => capacity.try_into().unwrap_or(DEFAULT_MAX_KEYS), - None => DEFAULT_MAX_KEYS, - } - } -} - -#[cfg(test)] -mod test_query { - use super::*; - #[test] - fn get_max_keys() { - let query = Query::new(); - assert_eq!(query.get_max_keys(), 100); - - let mut query = Query::new(); - query.insert(QueryKey::MAX_KEYS, "10"); - assert_eq!(query.get_max_keys(), 10); - - let mut query = Query::new(); - query.insert(QueryKey::MAX_KEYS, "str"); - assert_eq!(query.get_max_keys(), 100); - } -} - -impl Index for Query { - type Output = QueryValue; - - fn index(&self, index: QueryKey) -> &Self::Output { - self.get(index).expect("no found query key") - } -} - -impl IntoIterator for Query { - type Item = (QueryKey, QueryValue); - type IntoIter = std::vec::IntoIter; - /// # 使用 Vec 转 Query - fn into_iter(self) -> Self::IntoIter { - self.inner.into_iter().collect::>().into_iter() - } -} - -impl FromIterator<(QueryKey, QueryValue)> for Query { - fn from_iter(iter: I) -> Self - where - I: IntoIterator, - { - let mut map = Query::default(); - map.as_mut().extend(iter); - map - } -} - -impl<'a> FromIterator<(&'a str, &'a str)> for Query { - /// 转化例子 - /// ``` - /// # use aliyun_oss_client::Query; - /// # use aliyun_oss_client::QueryKey; - /// let query: Query = [("max-keys", "123")].into_iter().collect(); - /// assert_eq!(query.get(QueryKey::MAX_KEYS), Some(&123u8.into())); - /// assert_eq!(query.get(QueryKey::MAX_KEYS), Some(&123u16.into())); - /// ``` - fn from_iter(iter: I) -> Self - where - I: IntoIterator, - { - let inner = iter.into_iter().map(|(k, v)| { - ( - k.parse().expect("invalid QueryKey"), - v.parse().expect("invalid QueryValue"), - ) - }); - - let mut map = Query::default(); - map.as_mut().extend(inner); - map - } -} - -impl<'a> FromIterator<(Cow<'a, str>, Cow<'a, str>)> for Query { - /// 转化例子 - /// ``` - /// # use aliyun_oss_client::Query; - /// # use aliyun_oss_client::QueryKey; - /// let query: Query = [("max-keys", "123")].into_iter().collect(); - /// assert_eq!(query.get(QueryKey::MAX_KEYS), Some(&123u8.into())); - /// assert_eq!(query.get(QueryKey::MAX_KEYS), Some(&123u16.into())); - /// ``` - fn from_iter(iter: I) -> Self - where - I: IntoIterator, Cow<'a, str>)>, - { - let inner = iter.into_iter().map(|(k, v)| { - ( - k.as_ref().parse().expect("invalid QueryKey"), - v.as_ref().parse().expect("invalid QueryValue"), - ) - }); - - let mut map = Query::default(); - map.as_mut().extend(inner); - map - } -} - -impl<'a> FromIterator<(&'a str, u8)> for Query { - /// 转化例子 - /// ``` - /// # use aliyun_oss_client::Query; - /// # use aliyun_oss_client::QueryKey; - /// let query = Query::from_iter([("max-keys", 123u8)]); - /// assert_eq!(query.get(QueryKey::MAX_KEYS), Some(&123u8.into())); - /// assert_eq!(query.get(QueryKey::MAX_KEYS), Some(&123u16.into())); - /// ``` - fn from_iter(iter: I) -> Self - where - I: IntoIterator, - { - let inner = iter - .into_iter() - .map(|(k, v)| (k.parse().expect("invalid QueryKey"), v.into())); - - let mut map = Query::default(); - map.as_mut().extend(inner); - map - } -} - -impl<'a> FromIterator<(&'a str, u16)> for Query { - /// 转化例子 - /// ``` - /// # use aliyun_oss_client::Query; - /// # use aliyun_oss_client::QueryKey; - /// let query = Query::from_iter([("max-keys", 123u16)]); - /// assert_eq!(query.get(QueryKey::MAX_KEYS), Some(&123u8.into())); - /// assert_eq!(query.get(QueryKey::MAX_KEYS), Some(&123u16.into())); - /// ``` - fn from_iter(iter: I) -> Self - where - I: IntoIterator, - { - let inner = iter - .into_iter() - .map(|(k, v)| (k.parse().expect("invalid QueryKey"), v.into())); - - let mut map = Query::default(); - map.as_mut().extend(inner); - map - } -} - -impl<'a> FromIterator<(QueryKey, &'a str)> for Query { - /// 转化例子 - /// ``` - /// # use aliyun_oss_client::Query; - /// # use aliyun_oss_client::QueryKey; - /// let query = Query::from_iter([(QueryKey::MAX_KEYS, "123")]); - /// assert_eq!(query.get(QueryKey::MAX_KEYS), Some(&123u8.into())); - /// assert_eq!(query.get(QueryKey::MAX_KEYS), Some(&123u16.into())); - /// ``` - fn from_iter(iter: I) -> Self - where - I: IntoIterator, - { - let inner = iter - .into_iter() - .map(|(k, v)| (k, v.parse().expect("invalid QueryValue"))); - - let mut map = Query::default(); - map.as_mut().extend(inner); - map - } -} - -macro_rules! impl_from_iter { - ($key:ty, $val:ty, $convert:expr) => { - impl FromIterator<($key, $val)> for Query { - fn from_iter(iter: I) -> Self - where - I: IntoIterator, - { - let inner = iter.into_iter().map($convert); - - let mut map = Query::default(); - map.as_mut().extend(inner); - map - } - } - }; -} - -impl_from_iter!(QueryKey, u8, |(k, v)| (k, v.into())); -impl_from_iter!(QueryKey, u16, |(k, v)| (k, v.into())); - -#[cfg(test)] -mod tests_query_from_iter { - use super::*; - #[test] - fn test() { - let query = Query::from_iter([(QueryKey::MAX_KEYS, 123u8)]); - assert_eq!(query.get(QueryKey::MAX_KEYS), Some(&123u8.into())); - assert_eq!(query.get(QueryKey::MAX_KEYS), Some(&123u16.into())); - - let query = Query::from_iter([(QueryKey::MAX_KEYS, 123u16)]); - assert_eq!(query.get(QueryKey::MAX_KEYS), Some(&123u8.into())); - assert_eq!(query.get(QueryKey::MAX_KEYS), Some(&123u16.into())); - } -} - -impl PartialEq for Query { - fn eq(&self, other: &Query) -> bool { - self.as_ref() == other.as_ref() - } -} - -/// 为 Url 拼接 [`Query`] 数据 -/// [`Query`]: crate::types::Query -pub trait SetOssQuery: private::Sealed { - /// 给 Url 结构体增加 `set_search_query` 方法 - fn set_oss_query(&mut self, query: &Query); -} - -mod private { - pub trait Sealed {} -} - -impl private::Sealed for Url {} - -impl SetOssQuery for Url { - /// 将查询参数拼接到 API 的 Url 上 - /// - /// # 例子 - /// ``` - /// use aliyun_oss_client::types::Query; - /// use aliyun_oss_client::types::SetOssQuery; - /// use reqwest::Url; - /// - /// let query = Query::from_iter([("abc", "def")]); - /// let mut url = Url::parse("https://exapmle.com").unwrap(); - /// url.set_oss_query(&query); - /// assert_eq!(url.as_str(), "https://exapmle.com/?list-type=2&abc=def"); - /// assert_eq!(url.query(), Some("list-type=2&abc=def")); - /// ``` - fn set_oss_query(&mut self, query: &Query) { - self.set_query(Some(&query.to_oss_string())); - } -} - -/// 查询条件的键 -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -#[non_exhaustive] -pub struct InnerQueryKey<'a> { - kind: QueryKeyEnum<'a>, -} - -impl InnerQueryKey<'_> { - /// 对Object名字进行分组的字符。所有Object名字包含指定的前缀,第一次出现delimiter字符之间的Object作为一组元素(即CommonPrefixes) - /// 示例值 `/` - pub const DELIMITER: Self = Self { - kind: QueryKeyEnum::Delimiter, - }; - - /// 设定从start-after之后按字母排序开始返回Object。 - /// start-after用来实现分页显示效果,参数的长度必须小于1024字节。 - /// 做条件查询时,即使start-after在列表中不存在,也会从符合start-after字母排序的下一个开始打印。 - pub const START_AFTER: Self = Self { - kind: QueryKeyEnum::StartAfter, - }; - - /// 指定List操作需要从此token开始。您可从ListObjectsV2(GetBucketV2)结果中的NextContinuationToken获取此token。 - /// 用于分页,返回下一页的数据 - pub const CONTINUATION_TOKEN: Self = Self { - kind: QueryKeyEnum::ContinuationToken, - }; - - /// 指定返回Object的最大数。 - /// 取值:大于0小于等于1000 - pub const MAX_KEYS: Self = Self { - kind: QueryKeyEnum::MaxKeys, - }; - - /// # 限定返回文件的Key必须以prefix作为前缀。 - /// 如果把prefix设为某个文件夹名,则列举以此prefix开头的文件,即该文件夹下递归的所有文件和子文件夹。 - /// - /// 在设置prefix的基础上,将delimiter设置为正斜线(/)时,返回值就只列举该文件夹下的文件,文件夹下的子文件夹名返回在CommonPrefixes中, - /// 子文件夹下递归的所有文件和文件夹不显示。 - /// - /// 例如,一个Bucket中有三个Object,分别为fun/test.jpg、fun/movie/001.avi和fun/movie/007.avi。如果设定prefix为fun/, - /// 则返回三个Object;如果在prefix设置为fun/的基础上,将delimiter设置为正斜线(/),则返回fun/test.jpg和fun/movie/。 - /// ## 要求 - /// - 参数的长度必须小于1024字节。 - /// - 设置prefix参数时,不能以正斜线(/)开头。如果prefix参数置空,则默认列举Bucket内的所有Object。 - /// - 使用prefix查询时,返回的Key中仍会包含prefix。 - pub const PREFIX: Self = Self { - kind: QueryKeyEnum::Prefix, - }; - - /// 对返回的内容进行编码并指定编码的类型。 - pub const ENCODING_TYPE: Self = Self { - kind: QueryKeyEnum::EncodingType, - }; - - /// 指定是否在返回结果中包含owner信息。 - pub const FETCH_OWNER: Self = Self { - kind: QueryKeyEnum::FetchOwner, - }; -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -#[non_exhaustive] -enum QueryKeyEnum<'a> { - /// 对Object名字进行分组的字符。所有Object名字包含指定的前缀,第一次出现delimiter字符之间的Object作为一组元素(即CommonPrefixes) - /// 示例值 `/` - Delimiter, - - /// 设定从start-after之后按字母排序开始返回Object。 - /// start-after用来实现分页显示效果,参数的长度必须小于1024字节。 - /// 做条件查询时,即使start-after在列表中不存在,也会从符合start-after字母排序的下一个开始打印。 - StartAfter, - - /// 指定List操作需要从此token开始。您可从ListObjectsV2(GetBucketV2)结果中的NextContinuationToken获取此token。 - /// 用于分页,返回下一页的数据 - ContinuationToken, - - /// 指定返回Object的最大数。 - /// 取值:大于0小于等于1000 - MaxKeys, - - /// # 限定返回文件的Key必须以prefix作为前缀。 - /// 如果把prefix设为某个文件夹名,则列举以此prefix开头的文件,即该文件夹下递归的所有文件和子文件夹。 - /// - /// 在设置prefix的基础上,将delimiter设置为正斜线(/)时,返回值就只列举该文件夹下的文件,文件夹下的子文件夹名返回在CommonPrefixes中, - /// 子文件夹下递归的所有文件和文件夹不显示。 - /// - /// 例如,一个Bucket中有三个Object,分别为fun/test.jpg、fun/movie/001.avi和fun/movie/007.avi。如果设定prefix为fun/, - /// 则返回三个Object;如果在prefix设置为fun/的基础上,将delimiter设置为正斜线(/),则返回fun/test.jpg和fun/movie/。 - /// ## 要求 - /// - 参数的长度必须小于1024字节。 - /// - 设置prefix参数时,不能以正斜线(/)开头。如果prefix参数置空,则默认列举Bucket内的所有Object。 - /// - 使用prefix查询时,返回的Key中仍会包含prefix。 - Prefix, - - /// 对返回的内容进行编码并指定编码的类型。 - EncodingType, - - /// 指定是否在返回结果中包含owner信息。 - FetchOwner, - - /// 自定义 - Custom(Cow<'a, str>), -} - -/// 查询条件的键 -pub type QueryKey = InnerQueryKey<'static>; - -impl AsRef for InnerQueryKey<'_> { - /// ``` - /// # use aliyun_oss_client::QueryKey; - /// # use std::borrow::Cow; - /// assert_eq!(QueryKey::DELIMITER.as_ref(), "delimiter"); - /// assert_eq!(QueryKey::START_AFTER.as_ref(), "start-after"); - /// assert_eq!(QueryKey::CONTINUATION_TOKEN.as_ref(), "continuation-token"); - /// assert_eq!(QueryKey::MAX_KEYS.as_ref(), "max-keys"); - /// assert_eq!(QueryKey::PREFIX.as_ref(), "prefix"); - /// assert_eq!(QueryKey::ENCODING_TYPE.as_ref(), "encoding-type"); - /// assert_eq!(QueryKey::new("abc").as_ref(), "abc"); - /// ``` - fn as_ref(&self) -> &str { - use QueryKeyEnum::*; - - match &self.kind { - Delimiter => "delimiter", - StartAfter => "start-after", - ContinuationToken => "continuation-token", - MaxKeys => "max-keys", - Prefix => "prefix", - EncodingType => "encoding-type", - // TODO - FetchOwner => unimplemented!("parse xml not support fetch owner"), - Custom(str) => str.as_ref(), - } - } -} - -impl Display for InnerQueryKey<'_> { - /// ``` - /// # use aliyun_oss_client::QueryKey; - /// assert_eq!(format!("{}", QueryKey::DELIMITER), "delimiter"); - /// ``` - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.as_ref()) - } -} - -impl From for InnerQueryKey<'_> { - fn from(s: String) -> Self { - Self::new(s) - } -} -impl<'a: 'b, 'b> From<&'a str> for InnerQueryKey<'b> { - fn from(date: &'a str) -> Self { - Self::new(date) - } -} - -impl FromStr for QueryKey { - type Err = InvalidQueryKey; - /// 示例 - /// ``` - /// # use aliyun_oss_client::types::QueryKey; - /// let value: QueryKey = "abc".into(); - /// assert!(value == QueryKey::from_static("abc")); - /// ``` - fn from_str(s: &str) -> Result { - Ok(Self::from_static(s)) - } -} - -/// 异常的查询条件键 -#[derive(Debug)] -pub struct InvalidQueryKey { - _priv: (), -} - -impl Error for InvalidQueryKey {} - -impl Display for InvalidQueryKey { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "invalid query key") - } -} - -impl<'a> InnerQueryKey<'a> { - /// # Examples - /// ``` - /// # use aliyun_oss_client::QueryKey; - /// let key = QueryKey::new("delimiter"); - /// assert!(key == QueryKey::DELIMITER); - /// assert!(QueryKey::new("start-after") == QueryKey::START_AFTER); - /// assert!(QueryKey::new("continuation-token") == QueryKey::CONTINUATION_TOKEN); - /// assert!(QueryKey::new("max-keys") == QueryKey::MAX_KEYS); - /// assert!(QueryKey::new("prefix") == QueryKey::PREFIX); - /// assert!(QueryKey::new("encoding-type") == QueryKey::ENCODING_TYPE); - /// ``` - /// *`fetch-owner` 功能未实现,特殊说明* - pub fn new(val: impl Into>) -> Self { - use QueryKeyEnum::*; - - let val = val.into(); - let kind = if val.contains(DELIMITER) { - Delimiter - } else if val.contains(START_AFTER) { - StartAfter - } else if val.contains(CONTINUATION_TOKEN) { - ContinuationToken - } else if val.contains(MAX_KEYS) { - MaxKeys - } else if val.contains(PREFIX) { - Prefix - } else if val.contains(ENCODING_TYPE) { - EncodingType - } else if val.contains(FETCH_OWNER) { - unimplemented!("parse xml not support fetch owner"); - } else { - Custom(val) - }; - Self { kind } - } - - /// # Examples - /// ``` - /// # use aliyun_oss_client::QueryKey; - /// let key = QueryKey::from_static("delimiter"); - /// assert!(key == QueryKey::DELIMITER); - /// assert!(QueryKey::from_static("start-after") == QueryKey::START_AFTER); - /// assert!(QueryKey::from_static("continuation-token") == QueryKey::CONTINUATION_TOKEN); - /// assert!(QueryKey::from_static("max-keys") == QueryKey::MAX_KEYS); - /// assert!(QueryKey::from_static("prefix") == QueryKey::PREFIX); - /// assert!(QueryKey::from_static("encoding-type") == QueryKey::ENCODING_TYPE); - /// ``` - /// *`fetch-owner` 功能未实现,特殊说明* - pub fn from_static(val: &str) -> Self { - use QueryKeyEnum::*; - - let kind = if val.contains(DELIMITER) { - Delimiter - } else if val.contains(START_AFTER) { - StartAfter - } else if val.contains(CONTINUATION_TOKEN) { - ContinuationToken - } else if val.contains(MAX_KEYS) { - MaxKeys - } else if val.contains(PREFIX) { - Prefix - } else if val.contains(ENCODING_TYPE) { - EncodingType - } else if val.contains(FETCH_OWNER) { - unimplemented!("parse xml not support fetch owner"); - } else { - Custom(Cow::Owned(val.to_owned())) - }; - Self { kind } - } -} - -#[cfg(test)] -mod test_query_key { - use super::*; - - #[test] - #[should_panic] - fn test_fetch_owner() { - QueryKey::new("fetch-owner"); - } - - #[test] - fn test_custom() { - let key = QueryKey::new("abc"); - assert!(matches!(key.kind, QueryKeyEnum::Custom(_))); - } - - #[test] - fn test_into_iter() { - let query = Query::from_iter(vec![("foo", "bar")]); - let list: Vec<_> = query.into_iter().collect(); - assert_eq!(list.len(), 1); - assert!(matches!(&list[0].0.kind, &QueryKeyEnum::Custom(_))); - let value: QueryValue = "bar".parse().unwrap(); - assert_eq!(list[0].1, value); - } - - #[test] - fn test_from_static() { - let key = QueryKey::from_static("abc"); - assert!(matches!(key.kind, QueryKeyEnum::Custom(_))); - } -} - -/// 查询条件的值 -#[derive(Clone, Debug, PartialEq, Eq, Default, Hash)] -pub struct InnerQueryValue<'a>(Cow<'a, str>); -/// 查询条件的值 -pub type QueryValue = InnerQueryValue<'static>; - -impl AsRef for InnerQueryValue<'_> { - fn as_ref(&self) -> &str { - &self.0 - } -} - -impl Display for InnerQueryValue<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} - -impl From for InnerQueryValue<'_> { - fn from(s: String) -> Self { - Self(Cow::Owned(s)) - } -} -impl<'a: 'b, 'b> From<&'a str> for InnerQueryValue<'b> { - fn from(date: &'a str) -> Self { - Self::new(date) - } -} - -impl PartialEq<&str> for InnerQueryValue<'_> { - #[inline] - fn eq(&self, other: &&str) -> bool { - &self.0 == other - } -} - -impl From for InnerQueryValue<'_> { - /// 数字转 Query 值 - /// - /// ``` - /// # use aliyun_oss_client::Query; - /// # use aliyun_oss_client::QueryKey; - /// let query = Query::from_iter([("max_keys", 100u8)]); - /// let query = Query::from_iter([(QueryKey::MAX_KEYS, 100u8)]); - /// ``` - fn from(num: u8) -> Self { - Self(Cow::Owned(num.to_string())) - } -} - -impl PartialEq for InnerQueryValue<'_> { - #[inline] - fn eq(&self, other: &u8) -> bool { - self.to_string() == other.to_string() - } -} - -impl From for InnerQueryValue<'_> { - /// 数字转 Query 值 - /// - /// ``` - /// use aliyun_oss_client::Query; - /// let query = Query::from_iter([("max_keys", 100u16)]); - /// ``` - fn from(num: u16) -> Self { - Self(Cow::Owned(num.to_string())) - } -} - -impl PartialEq for InnerQueryValue<'_> { - #[inline] - fn eq(&self, other: &u16) -> bool { - self.to_string() == other.to_string() - } -} - -impl From for QueryValue { - /// bool 转 Query 值 - /// - /// ``` - /// use aliyun_oss_client::Query; - /// let query = Query::from_iter([("abc", "false")]); - /// ``` - fn from(b: bool) -> Self { - if b { - Self::from_static("true") - } else { - Self::from_static("false") - } - } -} - -impl FromStr for InnerQueryValue<'_> { - type Err = InvalidQueryValue; - /// 示例 - /// ``` - /// # use aliyun_oss_client::types::QueryValue; - /// let value: QueryValue = "abc".parse().unwrap(); - /// assert!(value == "abc"); - /// ``` - fn from_str(s: &str) -> Result { - Ok(Self::from_static2(s)) - } -} - -impl TryFrom<&InnerQueryValue<'_>> for usize { - type Error = ParseIntError; - fn try_from(value: &InnerQueryValue<'_>) -> Result { - value.0.parse() - } -} - -/// 异常的查询值 -#[derive(Debug)] -pub struct InvalidQueryValue { - _priv: (), -} - -impl Error for InvalidQueryValue {} - -impl Display for InvalidQueryValue { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "invalid query value") - } -} - -impl<'a> InnerQueryValue<'a> { - /// Creates a new `QueryValue` from the given string. - pub fn new(val: impl Into>) -> Self { - Self(val.into()) - } - - /// Const function that creates a new `QueryValue` from a static str. - pub const fn from_static(val: &'static str) -> Self { - Self(Cow::Borrowed(val)) - } - - /// Const function that creates a new `QueryValue` from a static str. - pub fn from_static2(val: &str) -> Self { - Self(Cow::Owned(val.to_owned())) - } -} - -use std::error::Error; -use std::fmt::{Display, Formatter}; -use std::num::ParseIntError; -use std::ops::{Index, Range, RangeFrom, RangeFull, RangeTo}; -use std::str::FromStr; - -use http::HeaderValue; -use reqwest::Url; - -/// 用于指定返回内容的区域的 type -pub struct ContentRange { - start: Option, - end: Option, -} - -unsafe impl Send for ContentRange {} -unsafe impl Sync for ContentRange {} - -impl From> for ContentRange { - fn from(r: Range) -> Self { - Self { - start: Some(r.start), - end: Some(r.end), - } - } -} - -impl From for ContentRange { - fn from(_: RangeFull) -> Self { - Self { - start: None, - end: None, - } - } -} - -impl From> for ContentRange { - fn from(f: RangeFrom) -> Self { - Self { - start: Some(f.start), - end: None, - } - } -} - -impl From> for ContentRange { - fn from(t: RangeTo) -> Self { - Self { - start: None, - end: Some(t.end), - } - } -} - -macro_rules! generate_range { - ($($t:ty)*) => ($( - impl From> for HeaderValue { - /// # 转化成 OSS 需要的格式 - /// @link [OSS 文档](https://help.aliyun.com/document_detail/31980.html) - fn from(con: ContentRange<$t>) -> HeaderValue { - let string = match (con.start, con.end) { - (Some(ref start), Some(ref end)) => format!("bytes={}-{}", start, end), - (Some(ref start), None) => format!("bytes={}-", start), - (None, Some(ref end)) => format!("bytes=0-{}", end), - (None, None) => "bytes=0-".to_string(), - }; - - HeaderValue::from_str(&string).unwrap_or_else(|_| { - panic!( - "content-range into header-value failed, content-range is : {}", - string - ) - }) - } - } - )*) -} - -generate_range!(u8 u16 u32 u64 u128 i8 i16 i32 i64 i128); - -#[cfg(test)] -mod test_range { - #[test] - fn test() { - use super::ContentRange; - use reqwest::header::HeaderValue; - fn abc>>(range: R) -> HeaderValue { - range.into().into() - } - - assert_eq!(abc(..), HeaderValue::from_str("bytes=0-").unwrap()); - assert_eq!(abc(1..), HeaderValue::from_str("bytes=1-").unwrap()); - assert_eq!(abc(10..20), HeaderValue::from_str("bytes=10-20").unwrap()); - assert_eq!(abc(..20), HeaderValue::from_str("bytes=0-20").unwrap()); - } -} diff --git a/src/types/object.rs b/src/types/object.rs deleted file mode 100644 index 5a7fe7f..0000000 --- a/src/types/object.rs +++ /dev/null @@ -1,583 +0,0 @@ -//! object 相关的类型 - -use url::Url; - -use std::{ - borrow::Cow, - convert::Infallible, - fmt::{self, Debug, Display}, - ops::{Add, AddAssign}, - path::Path, - str::FromStr, -}; - -use crate::{BucketName, EndPoint}; - -#[cfg(test)] -mod test; - -#[cfg(feature = "core")] -pub mod base; -#[cfg(feature = "core")] -pub use base::{InvalidObjectBase, ObjectBase}; - -/// OSS Object 存储对象的路径 -/// 不带前缀 `/` -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct ObjectPathInner<'a>(Cow<'a, str>); - -/// OSS Object 存储对象的路径 -/// 不带前缀 `/` -pub type ObjectPath = ObjectPathInner<'static>; - -impl AsRef for ObjectPathInner<'_> { - fn as_ref(&self) -> &str { - &self.0 - } -} - -impl fmt::Display for ObjectPathInner<'_> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -impl Default for ObjectPathInner<'_> { - /// 默认值 - /// ``` - /// # use aliyun_oss_client::types::object::ObjectPath; - /// let path = ObjectPath::default(); - /// assert!(path == ""); - /// ``` - fn default() -> Self { - Self(Cow::Borrowed("")) - } -} - -impl PartialEq<&str> for ObjectPathInner<'_> { - /// 相等比较 - /// ``` - /// # use aliyun_oss_client::types::object::ObjectPath; - /// let path = ObjectPath::new("abc").unwrap(); - /// assert!(path == "abc"); - /// ``` - #[inline] - fn eq(&self, other: &&str) -> bool { - &self.0 == other - } -} - -impl PartialEq> for &str { - /// 相等比较 - /// ``` - /// # use aliyun_oss_client::types::object::ObjectPath; - /// let path = ObjectPath::new("abc").unwrap(); - /// assert!("abc" == path); - /// ``` - #[inline] - fn eq(&self, other: &ObjectPathInner) -> bool { - self == &other.0 - } -} - -impl PartialEq for ObjectPathInner<'_> { - /// 相等比较 - /// ``` - /// # use aliyun_oss_client::types::object::ObjectPath; - /// let path = ObjectPath::new("abc").unwrap(); - /// assert!(path == "abc".to_string()); - /// ``` - #[inline] - fn eq(&self, other: &String) -> bool { - &self.0 == other - } -} - -impl PartialEq> for String { - /// 相等比较 - /// ``` - /// # use aliyun_oss_client::types::object::ObjectPath; - /// let path = ObjectPath::new("abc").unwrap(); - /// assert!("abc".to_string() == path); - /// ``` - #[inline] - fn eq(&self, other: &ObjectPathInner) -> bool { - self == &other.0 - } -} - -impl<'a> ObjectPathInner<'a> { - /// Creates a new `ObjectPath` from the given string. - /// ``` - /// # use aliyun_oss_client::types::object::ObjectPath; - /// assert!(ObjectPath::new("abc.jpg").is_ok()); - /// assert!(ObjectPath::new("abc/def.jpg").is_ok()); - /// assert!(ObjectPath::new("/").is_err()); - /// assert!(ObjectPath::new("/abc").is_err()); - /// assert!(ObjectPath::new("abc/").is_err()); - /// assert!(ObjectPath::new(".abc").is_err()); - /// assert!(ObjectPath::new("../abc").is_err()); - /// assert!(ObjectPath::new(r"aaa\abc").is_err()); - /// ``` - pub fn new(val: impl Into>) -> Result { - let val = val.into(); - if val.starts_with('/') || val.starts_with('.') || val.ends_with('/') { - return Err(InvalidObjectPath::new()); - } - if !val.chars().all(|c| c != '\\') { - return Err(InvalidObjectPath::new()); - } - Ok(Self(val)) - } - - /// # Safety - /// - /// Const function that creates a new `ObjectPath` from a static str. - /// ``` - /// # use aliyun_oss_client::types::object::ObjectPath; - /// let path = unsafe { ObjectPath::from_static("abc") }; - /// assert!(path == "abc"); - /// ``` - pub const unsafe fn from_static(secret: &'static str) -> Self { - Self(Cow::Borrowed(secret)) - } -} - -impl TryFrom for ObjectPathInner<'_> { - type Error = InvalidObjectPath; - /// ``` - /// # use aliyun_oss_client::types::object::ObjectPath; - /// let path: ObjectPath = String::from("abc").try_into().unwrap(); - /// assert!(path == "abc"); - /// ``` - fn try_from(val: String) -> Result { - Self::new(val) - } -} - -impl TryFrom<&String> for ObjectPathInner<'_> { - type Error = InvalidObjectPath; - /// ``` - /// # use aliyun_oss_client::types::object::ObjectPath; - /// let str = String::from("abc"); - /// let str_p = &str; - /// let path: ObjectPath = str_p.try_into().unwrap(); - /// assert!(path == "abc"); - /// ``` - fn try_from(val: &String) -> Result { - Self::new(val.to_owned()) - } -} - -impl TryFrom> for ObjectPathInner<'_> { - type Error = InvalidObjectPath; - fn try_from(val: Box) -> Result { - Self::new(*val) - } -} - -impl<'a: 'b, 'b> TryFrom<&'a str> for ObjectPathInner<'b> { - type Error = InvalidObjectPath; - fn try_from(val: &'a str) -> Result { - Self::new(val) - } -} - -impl FromStr for ObjectPathInner<'_> { - type Err = InvalidObjectPath; - /// ``` - /// # use aliyun_oss_client::types::object::ObjectPath; - /// use std::str::FromStr; - /// let path: ObjectPath = "img1.jpg".parse().unwrap(); - /// assert!(path == "img1.jpg"); - /// assert!(ObjectPath::from_str("abc.jpg").is_ok()); - /// assert!(ObjectPath::from_str("abc/def.jpg").is_ok()); - /// assert!(ObjectPath::from_str("/").is_err()); - /// assert!(ObjectPath::from_str("/abc").is_err()); - /// assert!(ObjectPath::from_str("abc/").is_err()); - /// assert!(ObjectPath::from_str(".abc").is_err()); - /// assert!(ObjectPath::from_str("../abc").is_err()); - /// assert!(ObjectPath::from_str(r"aaa\abc").is_err()); - /// ``` - fn from_str(s: &str) -> Result { - if s.starts_with('/') || s.starts_with('.') || s.ends_with('/') { - return Err(InvalidObjectPath::new()); - } - - if !s.chars().all(|c| c != '\\') { - return Err(InvalidObjectPath::new()); - } - Ok(Self(Cow::Owned(s.to_owned()))) - } -} - -impl TryFrom<&[u8]> for ObjectPathInner<'_> { - type Error = InvalidObjectPath; - fn try_from(value: &[u8]) -> Result { - use std::str; - - str::from_utf8(value) - .map_err(|_| InvalidObjectPath::new()) - .and_then(str::parse) - } -} - -impl TryFrom<&Path> for ObjectPathInner<'_> { - type Error = InvalidObjectPath; - fn try_from(value: &Path) -> Result { - let val = value.to_str().ok_or(InvalidObjectPath::new())?; - if std::path::MAIN_SEPARATOR != '/' { - val.replace(std::path::MAIN_SEPARATOR, "/").parse() - } else { - val.parse() - } - } -} - -/// 不合法的文件路径 -pub struct InvalidObjectPath { - _priv: (), -} - -impl InvalidObjectPath { - pub(crate) fn new() -> Self { - Self { _priv: () } - } -} - -impl Display for InvalidObjectPath { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "invalid object path") - } -} - -impl Debug for InvalidObjectPath { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("InvalidObjectPath").finish() - } -} - -impl std::error::Error for InvalidObjectPath {} - -impl From for InvalidObjectPath { - fn from(_: Infallible) -> Self { - Self { _priv: () } - } -} - -/// 将 object 的路径拼接到 Url 上去 -pub trait SetObjectPath: private::Sealed { - /// 为 Url 添加方法 - fn set_object_path(&mut self, path: &ObjectPathInner); -} - -mod private { - pub trait Sealed {} -} - -impl private::Sealed for Url {} - -impl SetObjectPath for Url { - fn set_object_path(&mut self, path: &ObjectPathInner) { - self.set_path(path.as_ref()); - } -} - -/// OSS Object 对象路径的前缀目录 -/// 不带前缀 `/`, 且必须以 `/` 结尾 -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct ObjectDir<'a>(Cow<'a, str>); - -impl AsRef for ObjectDir<'_> { - fn as_ref(&self) -> &str { - &self.0 - } -} - -impl AsMut for ObjectDir<'_> { - fn as_mut(&mut self) -> &mut String { - self.0.to_mut() - } -} - -impl fmt::Display for ObjectDir<'_> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -// impl Default for ObjectDir<'_> { -// /// 默认值 -// /// ``` -// /// # use aliyun_oss_client::types::object::ObjectDir; -// /// let path = ObjectDir::default(); -// /// assert!(path == "default/"); -// /// ``` -// fn default() -> Self { -// Self(Cow::Borrowed("default/")) -// } -// } - -impl PartialEq<&str> for ObjectDir<'_> { - /// 相等比较 - /// ``` - /// # use aliyun_oss_client::types::object::ObjectDir; - /// let path = ObjectDir::new("abc/").unwrap(); - /// assert!(path == "abc/"); - /// ``` - #[inline] - fn eq(&self, other: &&str) -> bool { - &self.0 == other - } -} - -impl PartialEq> for &str { - /// 相等比较 - /// ``` - /// # use aliyun_oss_client::types::object::ObjectDir; - /// let path = ObjectDir::new("abc/").unwrap(); - /// assert!("abc/" == path); - /// ``` - #[inline] - fn eq(&self, other: &ObjectDir<'_>) -> bool { - self == &other.0 - } -} - -impl PartialEq for ObjectDir<'_> { - /// 相等比较 - /// ``` - /// # use aliyun_oss_client::types::object::ObjectDir; - /// let path = ObjectDir::new("abc/").unwrap(); - /// assert!(path == "abc/".to_string()); - /// ``` - #[inline] - fn eq(&self, other: &String) -> bool { - &self.0 == other - } -} - -impl PartialEq> for String { - /// 相等比较 - /// ``` - /// # use aliyun_oss_client::types::object::ObjectDir; - /// let path = ObjectDir::new("abc/").unwrap(); - /// assert!("abc/".to_string() == path); - /// ``` - #[inline] - fn eq(&self, other: &ObjectDir) -> bool { - self == &other.0 - } -} - -impl<'dir1, 'dir2: 'dir1> Add> for ObjectDir<'dir1> { - type Output = ObjectDir<'dir1>; - - /// # 支持 ObjectDir 相加运算 - /// ``` - /// # use aliyun_oss_client::types::object::ObjectDir; - /// let dir1 = ObjectDir::new("dir1/").unwrap(); - /// let dir2 = ObjectDir::new("dir2/").unwrap(); - /// let full_dir = ObjectDir::new("dir1/dir2/").unwrap(); - /// - /// assert_eq!(dir1 + dir2, full_dir); - /// ``` - fn add(self, rhs: ObjectDir<'dir2>) -> Self::Output { - let mut string = self.0; - - string += rhs.0; - ObjectDir(string) - } -} - -impl<'dir1, 'dir2: 'dir1> AddAssign> for ObjectDir<'dir1> { - /// # 支持 ObjectDir 相加运算 - /// ``` - /// # use aliyun_oss_client::types::object::ObjectDir; - /// let mut dir1 = ObjectDir::new("dir1/").unwrap(); - /// let dir2 = ObjectDir::new("dir2/").unwrap(); - /// let full_dir = ObjectDir::new("dir1/dir2/").unwrap(); - /// - /// dir1 += dir2; - /// assert_eq!(dir1, full_dir); - /// ``` - fn add_assign(&mut self, rhs: ObjectDir<'dir2>) { - *self.as_mut() += rhs.as_ref(); - } -} - -impl<'file, 'dir: 'file> Add> for ObjectDir<'dir> { - type Output = ObjectPathInner<'file>; - - /// # 支持 ObjectDir 与 ObjectPath 相加运算 - /// ``` - /// # use aliyun_oss_client::types::object::{ObjectDir, ObjectPath}; - /// let dir1 = ObjectDir::new("dir1/").unwrap(); - /// let file1 = ObjectPath::new("img1.png").unwrap(); - /// let full_file = ObjectPath::new("dir1/img1.png").unwrap(); - /// - /// assert_eq!(dir1 + file1, full_file); - /// ``` - fn add(self, rhs: ObjectPathInner<'file>) -> Self::Output { - let mut string = self.0; - - string += rhs.0; - ObjectPathInner(string) - } -} - -impl<'a> ObjectDir<'a> { - /// Creates a new `ObjectPath` from the given string. - /// ``` - /// # use aliyun_oss_client::types::object::ObjectDir; - /// assert!(ObjectDir::new("abc/").is_ok()); - /// assert!(ObjectDir::new("abc/def/").is_ok()); - /// assert!(ObjectDir::new("/").is_err()); - /// assert!(ObjectDir::new("/abc/").is_err()); - /// assert!(ObjectDir::new(".abc/").is_err()); - /// assert!(ObjectDir::new("../abc/").is_err()); - /// assert!(ObjectDir::new(r"aaa\abc/").is_err()); - /// ``` - pub fn new<'b: 'a>(val: impl Into>) -> Result { - let val = val.into(); - if val.starts_with('/') || val.starts_with('.') || !val.ends_with('/') { - return Err(InvalidObjectDir::new()); - } - if !val.chars().all(|c| c != '\\') { - return Err(InvalidObjectDir::new()); - } - Ok(Self(val)) - } - - /// # Safety - /// - /// Const function that creates a new `ObjectPath` from a static str. - /// ``` - /// # use aliyun_oss_client::types::object::ObjectDir; - /// let path = unsafe { ObjectDir::from_static("abc/") }; - /// assert!(path == "abc/"); - /// ``` - pub const unsafe fn from_static(secret: &'a str) -> Self { - Self(Cow::Borrowed(secret)) - } -} - -impl TryFrom for ObjectDir<'_> { - type Error = InvalidObjectDir; - /// ``` - /// # use aliyun_oss_client::types::object::ObjectDir; - /// let path: ObjectDir = String::from("abc/").try_into().unwrap(); - /// assert!(path == "abc/"); - /// ``` - fn try_from(val: String) -> Result { - Self::new(val) - } -} - -impl<'a: 'b, 'b> TryFrom<&'a str> for ObjectDir<'b> { - type Error = InvalidObjectDir; - fn try_from(val: &'a str) -> Result { - Self::new(val) - } -} - -impl FromStr for ObjectDir<'_> { - type Err = InvalidObjectDir; - /// ``` - /// # use aliyun_oss_client::types::object::ObjectDir; - /// use std::str::FromStr; - /// let path: ObjectDir = "path1/".parse().unwrap(); - /// assert!(path == "path1/"); - /// assert!(ObjectDir::from_str("abc/").is_ok()); - /// assert!(ObjectDir::from_str("abc/def/").is_ok()); - /// assert!(ObjectDir::from_str("/").is_err()); - /// assert!(ObjectDir::from_str("/abc/").is_err()); - /// assert!(ObjectDir::from_str(".abc/").is_err()); - /// assert!(ObjectDir::from_str("../abc/").is_err()); - /// assert!(ObjectDir::from_str(r"aaa\abc/").is_err()); - /// ``` - fn from_str(s: &str) -> Result { - if s.starts_with('/') || s.starts_with('.') || !s.ends_with('/') { - return Err(InvalidObjectDir::new()); - } - - if !s.chars().all(|c| c != '\\') { - return Err(InvalidObjectDir::new()); - } - Ok(Self(Cow::Owned(s.to_owned()))) - } -} - -impl TryFrom<&Path> for ObjectDir<'_> { - type Error = InvalidObjectDir; - fn try_from(value: &Path) -> Result { - let val = value.to_str().ok_or(InvalidObjectDir::new())?; - if std::path::MAIN_SEPARATOR != '/' { - val.replace(std::path::MAIN_SEPARATOR, "/").parse() - } else { - val.parse() - } - } -} - -/// 不合法的文件目录路径 -pub struct InvalidObjectDir { - _priv: (), -} - -impl InvalidObjectDir { - pub(crate) fn new() -> InvalidObjectDir { - InvalidObjectDir { _priv: () } - } -} - -impl Display for InvalidObjectDir { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "object-dir must end with `/`, and not start with `/`,`.`" - ) - } -} - -impl Debug for InvalidObjectDir { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("InvalidObjectDir").finish() - } -} - -impl std::error::Error for InvalidObjectDir {} - -/// 根据 OSS 的配置信息初始化外部类型 -pub trait FromOss { - /// 根据配置信息,计算外部类型的具体实现 - fn from_oss(endpoint: &EndPoint, bucket: &BucketName, path: &ObjectPath) -> Self; -} - -/// 给 Url 设置一个初始化方法,根据 OSS 的配置信息,返回文件的完整 OSS Url -impl FromOss for Url { - /// 根据配置信息,计算完整的 Url - fn from_oss(endpoint: &EndPoint, bucket: &BucketName, path: &ObjectPath) -> Self { - let mut end_url = endpoint.to_url(); - - let host = end_url.host_str(); - - let mut name_str = bucket.to_string() + "."; - - let new_host = host.map(|h| { - name_str.push_str(h); - &*name_str - }); - // 因为 endpoint 都是已知字符组成,bucket 也有格式要求,所以 unwrap 是安全的 - end_url - .set_host(new_host) - .unwrap_or_else(|_| panic!("set host failed: host: {}", new_host.unwrap_or("none"))); - - end_url.set_object_path(path); - - end_url - } -} - -/// 文件夹下的子文件夹名,子文件夹下递归的所有文件和文件夹不包含在这里。 -pub type CommonPrefixes = Vec>; diff --git a/src/types/object/base.rs b/src/types/object/base.rs deleted file mode 100644 index 51ce9cd..0000000 --- a/src/types/object/base.rs +++ /dev/null @@ -1,316 +0,0 @@ -//! ObjectBase 定义 - -use oss_derive::oss_gen_rc; -use url::Url; - -use super::{ - super::{CanonicalizedResource, InvalidBucketName}, - InvalidObjectPath, ObjectPath, SetObjectPath, -}; -use crate::{ - auth::query::QueryAuth, - builder::{ArcPointer, PointerFamily}, - EndPoint, KeyId, KeySecret, -}; -use crate::{config::BucketBase, BucketName, QueryKey, QueryValue}; - -#[cfg(feature = "blocking")] -use crate::builder::RcPointer; -#[cfg(feature = "blocking")] -use std::rc::Rc; -use std::sync::Arc; - -/// # Object 元信息 -/// 包含所属 bucket endpoint 以及文件路径 -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct ObjectBase { - pub(super) bucket: PointerSel::Bucket, - pub(crate) path: ObjectPath, -} - -impl Default for ObjectBase { - fn default() -> Self { - Self { - bucket: T::Bucket::default(), - path: ObjectPath::default(), - } - } -} - -impl AsRef for ObjectBase { - fn as_ref(&self) -> &ObjectPath { - &self.path - } -} - -impl ObjectBase { - /// 初始化 Object 元信息 - #[cfg(test)] - pub(crate) fn new

(bucket: T::Bucket, path: P) -> Result - where - P: TryInto, - P::Error: Into, - { - let path = path.try_into().map_err(|e| e.into())?; - - Ok(Self { bucket, path }) - } - - #[inline] - pub(crate) fn new2(bucket: T::Bucket, path: ObjectPath) -> Self { - Self { bucket, path } - } - - /// 为 Object 元信息设置 bucket - pub fn set_bucket(&mut self, bucket: T::Bucket) { - self.bucket = bucket; - } - - pub(crate) fn init_with_bucket(bucket: T::Bucket) -> Self { - Self { - bucket, - ..Default::default() - } - } - - /// 为 Object 元信息设置文件路径 - pub fn set_path

(&mut self, path: P) -> Result<(), InvalidObjectPath> - where - P: TryInto, - P::Error: Into, - { - self.path = path.try_into().map_err(|e| e.into())?; - - Ok(()) - } - - /// 返回 Object 元信息的文件路径 - pub fn path(&self) -> ObjectPath { - self.path.to_owned() - } -} - -#[oss_gen_rc] -impl ObjectBase { - #[doc(hidden)] - #[inline] - pub fn from_bucket

(bucket: BucketBase, path: P) -> Result - where - P: TryInto, - P::Error: Into, - { - Ok(Self { - bucket: Arc::new(bucket), - path: path.try_into().map_err(|e| e.into())?, - }) - } - - #[doc(hidden)] - #[inline] - #[cfg(test)] - #[allow(dead_code)] - pub(crate) fn try_from_bucket(bucket: B, path: &str) -> Result - where - B: TryInto, - B::Error: Into, - { - Ok(Self { - bucket: Arc::new(bucket.try_into().map_err(|e| e.into())?), - path: path - .parse() - .map_err(|e| InvalidObjectBase::from_path(path, e))?, - }) - } - - #[doc(hidden)] - #[inline] - pub fn from_ref_bucket

(bucket: Arc, path: P) -> Result - where - P: TryInto, - P::Error: Into, - { - Ok(Self { - bucket, - path: path.try_into().map_err(|e| e.into())?, - }) - } - - #[inline] - #[allow(dead_code)] - pub(crate) fn from_bucket_name( - bucket: &str, - endpoint: &str, - path: &str, - ) -> Result { - let bucket = BucketBase::new( - bucket - .parse() - .map_err(|e: InvalidBucketName| InvalidObjectBase::from_bucket_name(bucket, e))?, - endpoint - .parse() - .map_err(|e| InvalidObjectBase::from_endpoint(endpoint, e))?, - ); - Ok(Self { - bucket: Arc::new(bucket), - path: path - .parse() - .map_err(|e| InvalidObjectBase::from_path(path, e))?, - }) - } - - #[doc(hidden)] - #[inline] - pub fn bucket_name(&self) -> &BucketName { - self.bucket.get_name() - } - - /// 获取 EndPoint 引用 - pub fn endpoint(&self) -> &EndPoint { - self.bucket.endpoint_ref() - } - - /// 根据提供的查询参数信息,获取当前 object 对应的接口请求参数( url 和 CanonicalizedResource) - #[inline] - pub fn get_url_resource>( - &self, - query: Q, - ) -> (Url, CanonicalizedResource) { - let mut url = self.bucket.to_url(); - url.set_object_path(&self.path); - - let resource = - CanonicalizedResource::from_object((self.bucket.name(), self.path.as_ref()), query); - - (url, resource) - } - - /// 带签名的 Url 链接 - pub fn to_sign_url(&self, key: &KeyId, secret: &KeySecret, expires: i64) -> Url { - let auth = QueryAuth::new_with_bucket(key, secret, &self.bucket); - auth.to_url(&self.path, expires) - } -} - -#[oss_gen_rc] -impl PartialEq> for ObjectBase { - #[inline] - fn eq(&self, other: &ObjectBase) -> bool { - *self.bucket == *other.bucket && self.path == other.path - } -} - -impl PartialEq<&str> for ObjectBase { - /// 相等比较 - /// ``` - /// # use aliyun_oss_client::types::object::ObjectBase; - /// # use aliyun_oss_client::config::BucketBase; - /// # use aliyun_oss_client::builder::ArcPointer; - /// # use std::sync::Arc; - /// use aliyun_oss_client::types::BucketName; - /// let mut path = ObjectBase::::default(); - /// path.set_path("abc"); - /// assert!(path == "abc"); - /// - /// let mut bucket = BucketBase::default(); - /// bucket.set_name("def".parse::().unwrap()); - /// bucket.try_set_endpoint("shanghai").unwrap(); - /// path.set_bucket(Arc::new(bucket)); - /// assert!(path == "abc"); - /// ``` - #[inline] - fn eq(&self, other: &&str) -> bool { - &self.path == other - } -} - -pub use invalid::InvalidObjectBase; - -/// 定义 InvalidObjectBase -pub mod invalid { - use std::{ - error::Error, - fmt::{self, Display}, - }; - - use crate::{ - config::InvalidBucketBase, - types::{InvalidBucketName, InvalidEndPoint}, - }; - - use super::InvalidObjectPath; - - /// Object 元信息的错误集 - #[derive(Debug)] - #[non_exhaustive] - pub struct InvalidObjectBase { - pub(crate) source: String, - pub(crate) kind: InvalidObjectBaseKind, - } - - impl InvalidObjectBase { - pub(super) fn from_bucket_name(name: &str, err: InvalidBucketName) -> Self { - InvalidObjectBase { - source: name.to_string(), - kind: InvalidObjectBaseKind::BucketName(err), - } - } - pub(super) fn from_endpoint(name: &str, err: InvalidEndPoint) -> Self { - InvalidObjectBase { - source: name.to_string(), - kind: InvalidObjectBaseKind::EndPoint(err), - } - } - - pub(super) fn from_path(name: &str, err: InvalidObjectPath) -> Self { - InvalidObjectBase { - source: name.to_string(), - kind: InvalidObjectBaseKind::Path(err), - } - } - } - - /// Object 元信息的错误集 - #[derive(Debug)] - #[non_exhaustive] - pub(crate) enum InvalidObjectBaseKind { - #[doc(hidden)] - Bucket(InvalidBucketBase), - #[doc(hidden)] - BucketName(InvalidBucketName), - #[doc(hidden)] - EndPoint(InvalidEndPoint), - #[doc(hidden)] - Path(InvalidObjectPath), - #[cfg(test)] - Bar, - } - - impl Display for InvalidObjectBase { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "get object base faild, source: {}", self.source) - } - } - - impl Error for InvalidObjectBase { - fn source(&self) -> Option<&(dyn Error + 'static)> { - use InvalidObjectBaseKind::*; - match &self.kind { - Bucket(b) => Some(b), - BucketName(e) => Some(e), - EndPoint(e) => Some(e), - Path(p) => Some(p), - #[cfg(test)] - Bar => None, - } - } - } - - impl From for InvalidObjectBase { - fn from(value: InvalidBucketBase) -> Self { - Self { - source: value.clone().source_string(), - kind: InvalidObjectBaseKind::Bucket(value), - } - } - } -} diff --git a/src/types/object/test.rs b/src/types/object/test.rs deleted file mode 100644 index 5da0885..0000000 --- a/src/types/object/test.rs +++ /dev/null @@ -1,227 +0,0 @@ -#[cfg(feature = "core")] -mod test_core { - use std::error::Error; - use std::path::Path; - - use std::sync::Arc; - - use reqwest::Url; - - use crate::builder::ArcPointer; - use crate::config::BucketBase; - use crate::types::object::base::invalid::InvalidObjectBaseKind; - use crate::types::object::{FromOss, InvalidObjectDir, ObjectPathInner}; - use crate::types::object::{InvalidObjectBase, InvalidObjectPath, ObjectBase}; - use crate::{BucketName, EndPoint, ObjectDir, ObjectPath}; - - #[test] - fn object_from_ref_bucket() { - use std::env::set_var; - set_var("ALIYUN_ENDPOINT", "qingdao"); - set_var("ALIYUN_BUCKET", "foo1"); - let object = ObjectBase::::from_ref_bucket( - Arc::new(BucketBase::from_env().unwrap()), - "img1.jpg", - ) - .unwrap(); - - assert_eq!(object.path(), "img1.jpg"); - } - - #[test] - fn object_from_bucket_name() { - let object = - ObjectBase::::from_bucket_name("foo1", "qingdao", "img1.jpg").unwrap(); - - assert_eq!(object.path(), "img1.jpg"); - - let object = - ObjectBase::::from_bucket_name("-foo1", "qingdao", "img1.jpg").unwrap_err(); - assert!(matches!(object.kind, InvalidObjectBaseKind::BucketName(_))); - - let object = - ObjectBase::::from_bucket_name("foo1", "-q-", "img1.jpg").unwrap_err(); - assert!(matches!(object.kind, InvalidObjectBaseKind::EndPoint(_))); - } - - #[test] - fn init_with_bucket() { - let bucket = Arc::new("abc.qingdao".parse().unwrap()); - let base = ObjectBase::::init_with_bucket(bucket); - - assert!(base.bucket.get_name().as_ref() == "abc"); - } - - #[test] - fn object_base_debug() { - let object = ObjectBase::::default(); - assert_eq!(format!("{object:?}"), "ObjectBase { bucket: BucketBase { endpoint: EndPoint { kind: CnHangzhou, is_internal: false }, name: BucketName(\"a\") }, path: ObjectPathInner(\"\") }"); - } - - #[test] - fn test_invalid_obj_base() { - let bucket = InvalidObjectBase { - source: "aaa".to_string(), - kind: InvalidObjectBaseKind::Bar, - }; - - assert_eq!(format!("{bucket}"), "get object base faild, source: aaa"); - assert!(bucket.source().is_none()); - - let path = InvalidObjectBase { - source: "aaa".to_string(), - kind: InvalidObjectBaseKind::Path(InvalidObjectPath::new()), - }; - assert_eq!(format!("{}", path.source().unwrap()), "invalid object path"); - assert_eq!( - format!("{path:?}"), - "InvalidObjectBase { source: \"aaa\", kind: Path(InvalidObjectPath) }" - ); - - let base = ObjectBase::::try_from_bucket("-a", "path1").unwrap_err(); - assert!(matches!(base.kind, InvalidObjectBaseKind::Bucket(_))); - - let base = ObjectBase::::try_from_bucket("abc.qingdao", "/path1").unwrap_err(); - assert!(matches!(base.kind, InvalidObjectBaseKind::Path(_))); - } - - #[test] - fn test_path2object_path() { - use std::str::FromStr; - - let path = Path::new("path2/file_name"); - let obj_path = ObjectPath::try_from(path).unwrap(); - assert_eq!(obj_path, "path2/file_name"); - - let str = Box::new(String::from_str("abc").unwrap()); - let path = ObjectPath::try_from(str).unwrap(); - assert_eq!(path, "abc"); - - let buff = "abc".as_bytes(); - let path = ObjectPathInner::try_from(buff).unwrap(); - assert_eq!(path, "abc"); - } - - #[test] - fn test_obj_dir_display() { - let dir = ObjectDir::new("path/").unwrap(); - - assert_eq!(format!("{dir}"), "path/"); - } - - #[test] - fn test_obj_dir_eq() { - assert!("path/" == ObjectDir::new("path/").unwrap()); - assert!("path/".to_string() == ObjectDir::new("path/").unwrap()); - assert!(ObjectDir::new("path/").unwrap() == "path/".to_string()); - } - - #[test] - fn test_dir_from_str() { - let dir = ObjectDir::try_from("path/").unwrap(); - assert_eq!(dir, ObjectDir::new("path/").unwrap()); - - let dir = ObjectDir::try_from("path").unwrap_err(); - assert!(matches!(dir, InvalidObjectDir { .. })); - } - #[test] - fn test_dir_from_path() { - use std::path::Path; - let path = Path::new("path/"); - let obj_path = ObjectDir::try_from(path).unwrap(); - assert_eq!(obj_path, ObjectDir::new("path/").unwrap()); - } - - #[test] - fn test_invalid_dir_debug() { - let err = InvalidObjectDir::new(); - assert_eq!( - format!("{err}"), - "object-dir must end with `/`, and not start with `/`,`.`" - ); - assert_eq!(format!("{err:?}"), "InvalidObjectDir"); - } - - #[test] - fn test_object_path_display() { - let path = ObjectPath::new("path1").unwrap(); - assert_eq!(format!("{path}"), "path1"); - } - #[test] - fn test_object_path_eq() { - assert!(ObjectPath::new("path1").unwrap() == "path1"); - assert!(ObjectPath::new("path1").unwrap() == "path1".to_string()); - assert!("path1" == ObjectPath::new("path1").unwrap()); - assert!("path1".to_string() == ObjectPath::new("path1").unwrap()); - } - - #[test] - fn test_invalid_path_debug() { - let err = InvalidObjectPath::new(); - assert_eq!(format!("{err}"), "invalid object path"); - assert_eq!(format!("{err:?}"), "InvalidObjectPath"); - } - - #[test] - fn test_url_from_oss() { - use crate::EndPoint; - let endpoint = EndPoint::CN_QINGDAO; - let bucket = BucketName::new("foo").unwrap(); - let path = ObjectPath::new("file1").unwrap(); - - let url = Url::from_oss(&endpoint, &bucket, &path); - - assert_eq!( - url, - Url::parse("https://foo.oss-cn-qingdao.aliyuncs.com/file1").unwrap() - ); - } - - #[test] - fn to_sign_url() { - let object = ObjectBase::::from_bucket( - BucketBase::new("abc".parse().unwrap(), EndPoint::CN_BEIJING), - "path1.png", - ) - .unwrap(); - - let url = object.to_sign_url(&"key".into(), &"secret".into(), 1234567890); - assert_eq!(url.as_str(), "https://abc.oss-cn-beijing.aliyuncs.com/path1.png?OSSAccessKeyId=key&Expires=1234567890&Signature=Kpqvd4gWgHNlkCfcYzRiHmDO%2Fvw%3D"); - } -} - -#[cfg(feature = "blocking")] -mod blocking_tests { - use crate::builder::RcPointer; - use crate::types::object::ObjectBase; - use crate::EndPoint; - - fn crate_object_base(bucket: &'static str, path: &'static str) -> ObjectBase { - use std::rc::Rc; - - let bucket = bucket.parse().unwrap(); - - let object = ObjectBase::::new2(Rc::new(bucket), path.try_into().unwrap()); - object - } - - #[test] - fn test_get_object_info() { - let object = crate_object_base("abc.oss-cn-shanghai.aliyuncs.com", "bar"); - - assert_eq!(object.bucket_name(), &"abc"); - assert_eq!(object.endpoint(), &EndPoint::CN_SHANGHAI); - assert_eq!(object.path(), "bar"); - } - - #[test] - fn test_object_base_eq() { - let object1 = crate_object_base("abc.oss-cn-shanghai.aliyuncs.com", "bar"); - let object2 = crate_object_base("abc.oss-cn-shanghai.aliyuncs.com", "bar"); - let object3 = crate_object_base("abc.oss-cn-qingdao.aliyuncs.com", "bar"); - let object4 = crate_object_base("abc.oss-cn-shanghai.aliyuncs.com", "ba2"); - assert!(object1 == object2); - assert!(object1 != object3); - assert!(object1 != object4); - } -} diff --git a/src/types/test.rs b/src/types/test.rs deleted file mode 100644 index 48924f3..0000000 --- a/src/types/test.rs +++ /dev/null @@ -1,361 +0,0 @@ -use std::env::{remove_var, set_var}; - -use chrono::{TimeZone, Utc}; -use http::HeaderValue; -use reqwest::Url; - -use crate::{ - types::{CanonicalizedResource, EndPointKind, InvalidBucketName}, - BucketName, EndPoint, KeyId, KeySecret, -}; - -use super::{ContentMd5, ContentType, Date, InvalidEndPoint}; - -#[test] -fn key_id() { - let key = KeyId::new("aaa"); - assert_eq!(format!("{key}"), "aaa"); - - assert!(TryInto::::try_into(key).is_ok()); - - let key2 = KeyId::from_static("aaa"); - assert_eq!(format!("{key2}"), "aaa"); -} - -#[test] -fn secret() { - let secret = KeySecret::new("aaa"); - assert_eq!(format!("{secret}"), "******secret******"); - assert_eq!(secret.as_str(), "aaa"); - - let key2 = KeySecret::from_static("bbb"); - assert_eq!(format!("{key2}"), "******secret******"); - assert_eq!(key2.as_str(), "bbb"); - - assert_eq!(format!("{key2:?}"), "KeySecret"); -} - -#[test] -fn encryption() { - let secret = KeySecret::new("aaa"); - let result = secret.encryption(b"bbb").unwrap(); - assert_eq!(result, "nGG1u/pDDcCCgw9xP87bPPJxWTY="); - - let secret = KeySecret::new("aaa"); - let result = secret.encryption_string("bbb".into()).unwrap(); - assert_eq!(result, "nGG1u/pDDcCCgw9xP87bPPJxWTY="); -} - -#[test] -fn endpoint() { - let end = unsafe { EndPoint::from_static2("aaa") }; - - assert_eq!(end.as_ref(), "aaa"); - - let end1 = EndPoint { - kind: EndPointKind::Other("aaa".into()), - is_internal: false, - }; - assert_eq!(end1.as_ref(), "aaa"); - - assert!(EndPoint::new("").is_err()); - - assert!(end == end1); - assert!(end == "aaa"); - assert!("aaa" == end); - - let endpoint = EndPoint::new("shanghai").unwrap(); - assert!(endpoint == Url::parse("https://oss-cn-shanghai.aliyuncs.com").unwrap()); -} - -mod test_endpoint { - use std::borrow::Cow; - - use super::*; - - #[test] - #[should_panic] - fn test_endpoint_painc() { - EndPoint::from_static("-weifang"); - } - - #[test] - fn test_new() { - assert!(matches!( - EndPoint::new("hangzhou"), - Ok(EndPoint::CN_HANGZHOU) - )); - - assert!(matches!(EndPoint::new("qingdao"), Ok(EndPoint::CN_QINGDAO))); - - assert!(matches!(EndPoint::new("beijing"), Ok(EndPoint::CN_BEIJING))); - - assert!(matches!( - EndPoint::new("zhangjiakou"), - Ok(EndPoint::CN_ZHANGJIAKOU) - )); - - assert!(matches!( - EndPoint::new("hongkong"), - Ok(EndPoint::CN_HONGKONG) - )); - - assert!(matches!( - EndPoint::new("shenzhen"), - Ok(EndPoint::CN_SHENZHEN) - )); - - assert!(matches!( - EndPoint::new("us-west-1"), - Ok(EndPoint::US_WEST_1) - )); - - assert!(matches!( - EndPoint::new("us-east-1"), - Ok(EndPoint::US_EAST_1) - )); - - assert!(matches!( - EndPoint::new("ap-southeast-1"), - Ok(EndPoint::AP_SOUTH_EAST_1) - )); - - assert!(matches!( - EndPoint::new("weifang"), - Ok(EndPoint { - kind: EndPointKind::Other(Cow::Owned(_)), - .. - }) - )); - - assert!(matches!( - EndPoint::new("https://oss-cn-qingdao-internal.aliyuncs.com"), - Ok(EndPoint { - kind: EndPointKind::CnQingdao, - is_internal: false, - }) - )); - assert!(matches!( - EndPoint::new("https://oss-cn-qingdao.aliyuncs.com"), - Ok(EndPoint { - kind: EndPointKind::CnQingdao, - is_internal: false, - }) - )); - - let res = EndPoint::new("abc-internal").unwrap(); - assert_eq!(res.is_internal, true); - assert_eq!(res.as_ref(), "abc"); - } - - #[test] - fn test_from_host_piece() { - assert!(EndPoint::from_host_piece("qingdao").is_err()); - - assert_eq!( - EndPoint::from_host_piece("oss-cn-qingdao"), - Ok(EndPoint::CN_QINGDAO) - ); - assert_eq!( - EndPoint::from_host_piece("oss-qingdao"), - Ok(EndPoint { - kind: EndPointKind::CnQingdao, - is_internal: false, - }) - ); - assert_eq!( - EndPoint::from_host_piece("oss-qingdao-internal"), - Ok(EndPoint { - kind: EndPointKind::CnQingdao, - is_internal: true, - }) - ); - } - - #[test] - fn test_from_url() { - let url = Url::parse("https://oss-cn-qingdao.aliyuncs.com/").unwrap(); - let endpoint = EndPoint::try_from(url).unwrap(); - - assert!(matches!(endpoint.kind, EndPointKind::CnQingdao)); - assert_eq!(endpoint.is_internal, false); - - let url = Url::parse("https://oss-cn-qingdao-internal.aliyuncs.com/").unwrap(); - let endpoint = EndPoint::try_from(url).unwrap(); - - assert!(matches!(endpoint.kind, EndPointKind::CnQingdao)); - assert_eq!(endpoint.is_internal, true); - - let url = Url::parse("https://192.168.3.1/").unwrap(); - assert!(EndPoint::try_from(url).is_err()); - - let url = Url::parse("https://oss-cn-qingdao-internal.aliyuncs.cn/").unwrap(); - assert!(EndPoint::try_from(url).is_err()); - - let url = Url::parse("https://oss-cn-qingdao-internal.aliyun.com/").unwrap(); - assert!(EndPoint::try_from(url).is_err()); - - let url = Url::parse("https://aliyuncs.com/").unwrap(); - assert!(EndPoint::try_from(url).is_err()); - - let url = Url::parse("https://-cn-qingdao.aliyuncs.com/").unwrap(); - assert!(EndPoint::try_from(url).is_err()); - } - - #[test] - fn internal() { - let mut endpoint = EndPoint::CN_BEIJING; - - assert!(!endpoint.is_internal()); - endpoint.set_internal(true); - assert!(endpoint.is_internal()); - } -} - -#[test] -fn invalid_endpoint() { - let err1 = InvalidEndPoint::new(); - let err2 = InvalidEndPoint::new(); - - assert!(err1 == err2); -} - -#[test] -fn bucket_name() { - let name = unsafe { BucketName::from_static2("aaa") }; - assert_eq!(format!("{name}"), "aaa"); - - let invalid = InvalidBucketName::new(); - let invalid2 = InvalidBucketName::new(); - assert!(invalid == invalid2); -} - -#[test] -fn bucket_name_from_env() { - remove_var("ALIYUN_BUCKET"); - let bucket = BucketName::from_env(); - assert!(bucket.is_err()); - - set_var("ALIYUN_BUCKET", "abc"); - let name = BucketName::from_env().unwrap(); - assert_eq!(name, "abc"); -} - -#[test] -fn content_md5() { - let value: crate::types::InnerContentMd5 = ContentMd5::from_static("aaa"); - assert!(TryInto::::try_into(value).is_ok()); -} - -#[test] -fn content_type() { - let content = ContentType::from("aaa".to_string()); - assert_eq!(format!("{content}"), "aaa"); - assert!(TryInto::::try_into(content).is_ok()); - - let content = ContentType::from_static("aaa"); - assert_eq!(format!("{content}"), "aaa"); -} - -#[test] -fn date() { - let date = unsafe { Date::from_static("Sat, 01 Jan 2022 18:01:01 GMT") }; - - assert_eq!(format!("{date}"), "Sat, 01 Jan 2022 18:01:01 GMT"); - - let utc = Utc.with_ymd_and_hms(2022, 1, 1, 18, 1, 1).unwrap(); - - let date = Date::from(utc); - - assert_eq!(date.as_ref(), "Sat, 01 Jan 2022 18:01:01 GMT"); - - assert!(TryInto::::try_into(date).is_ok()); -} - -#[test] -fn canonicalized_resource() { - let value = CanonicalizedResource::from_static("aaa"); - assert_eq!(format!("{value}"), "aaa"); - - assert!(TryInto::::try_into(value).is_ok()); - - let value = CanonicalizedResource::from("aaa".to_string()); - assert_eq!(format!("{value}"), "aaa"); -} - -mod tests_canonicalized_resource { - - #[cfg(feature = "auth")] - #[test] - fn canonicalized_from_bucket_name() { - use crate::{types::CanonicalizedResource, BucketName}; - - let name = BucketName::new("aaa").unwrap(); - let value = CanonicalizedResource::from_bucket_name(&name, Some("bucketInfo")); - assert!(value == "/aaa/?bucketInfo"); - - let value = CanonicalizedResource::from_bucket_name(&name, Some("bbb")); - assert!(value == "/aaa/"); - - let value = CanonicalizedResource::from_bucket_name(&name, None); - assert!("/" == value); - } - - #[cfg(feature = "core")] - #[test] - fn test_from_bucket() { - use crate::{config::BucketBase, types::CanonicalizedResource}; - - let base: BucketBase = "abc.jinan".parse().unwrap(); - let resource = CanonicalizedResource::from_bucket(&base, Some("bucketInfo")); - assert_eq!(resource, "/abc/?bucketInfo"); - - let base: BucketBase = "abc.jinan".parse().unwrap(); - let resource = CanonicalizedResource::from_bucket(&base, Some("bar")); - assert_eq!(resource, "/abc/"); - - let base: BucketBase = "abc.jinan".parse().unwrap(); - let resource = CanonicalizedResource::from_bucket(&base, None); - assert_eq!(resource, "/"); - } - - #[cfg(feature = "core")] - #[test] - fn test_from_bucket_query2() { - use crate::{types::CanonicalizedResource, BucketName, Query, QueryKey}; - - let bucket = BucketName::new("abc").unwrap(); - let query = Query::new(); - let resource = CanonicalizedResource::from_bucket_query2(&bucket, &query); - assert_eq!(resource, CanonicalizedResource::new("/abc/")); - - let mut query = Query::new(); - query.insert("list-type", "2"); - query.insert(QueryKey::CONTINUATION_TOKEN, "foo"); - let resource = CanonicalizedResource::from_bucket_query2(&bucket, &query); - assert_eq!( - resource, - CanonicalizedResource::new("/abc/?continuation-token=foo") - ); - } - - #[cfg(feature = "core")] - #[test] - fn test_from_object() { - use super::CanonicalizedResource; - - let resource = CanonicalizedResource::from_object(("foo", "bar"), []); - assert!(resource == "/foo/bar"); - - let resource = - CanonicalizedResource::from_object(("foo", "bar"), [("foo2".into(), "bar2".into())]); - assert!(resource == "/foo/bar?foo2=bar2"); - } - - #[test] - fn test_from_object_str() { - use super::CanonicalizedResource; - let resource = CanonicalizedResource::from_object_str("foo", "bar"); - assert!(resource.as_ref() == "/foo/bar"); - } -} diff --git a/tests/integration_test.rs b/tests/integration_test.rs deleted file mode 100644 index 1b79273..0000000 --- a/tests/integration_test.rs +++ /dev/null @@ -1,236 +0,0 @@ -#[cfg(all(feature = "core", not(tarpaulin)))] -mod test_async { - use aliyun_oss_client::{Client, Method}; - use assert_matches::assert_matches; - use dotenv::dotenv; - - #[tokio::test] - async fn test_get_bucket_list() { - dotenv().ok(); - - let client = Client::from_env().unwrap(); - - let bucket_list = client.get_bucket_list().await; - - assert_matches!(bucket_list, Ok(_)); - } - - #[tokio::test] - async fn test_get_bucket_info() { - dotenv().ok(); - - let client = Client::from_env().unwrap(); - - let bucket_list = client.get_bucket_info().await; - - assert_matches!(bucket_list, Ok(_)); - } - - #[tokio::test] - async fn test_get_bucket_info_with_trait() { - use aliyun_oss_client::auth::RequestWithOSS; - use std::env; - dotenv().ok(); - - let client = reqwest::Client::default(); - - let key_id = env::var("ALIYUN_KEY_ID").unwrap(); - let key_secret = env::var("ALIYUN_KEY_SECRET").unwrap(); - let bucket = env::var("ALIYUN_BUCKET").unwrap(); - - let mut request = client - .request( - Method::GET, - format!("https://{bucket}.oss-cn-shanghai.aliyuncs.com/?bucketInfo"), - ) - .build() - .unwrap(); - - request.with_oss(key_id.into(), key_secret.into()).unwrap(); - - let response = client.execute(request).await; - - assert!(response.is_ok()); - assert!(response.unwrap().status().is_success()); - } - - #[tokio::test] - async fn get_object_by_bucket_struct() { - dotenv().ok(); - - let client = Client::from_env().unwrap(); - - let bucket_list = client.get_bucket_list().await.unwrap(); - - let query = [ - ("max-keys".parse().unwrap(), "5".parse().unwrap()), - ("prefix".parse().unwrap(), "babel".parse().unwrap()), - ]; - - let buckets = bucket_list.to_vec(); - let the_bucket = &buckets[0]; - let object_list = the_bucket.get_object_list(query).await; - assert_matches!(object_list, Ok(_)); - } - - #[tokio::test] - async fn test_get_object_list() { - dotenv().ok(); - - let client = Client::from_env().unwrap(); - - let object_list = client.get_object_list([]).await; - - assert_matches!(object_list, Ok(_)); - } - - #[cfg(feature = "put_file")] - #[tokio::test] - async fn test_put_get_and_delete_file() { - dotenv().ok(); - use aliyun_oss_client::{file::Files, types::object::ObjectPath}; - - let client = Client::from_env().unwrap(); - - let object_list = client - .put_file("examples/bg2015071010.png", "examples/bg2015071010.png") - .await; - - assert_matches!(object_list, Ok(_)); - - let object = client.get_object("examples/bg2015071010.png", ..10).await; - assert_matches!(object, Ok(_)); - - let object = client - .get_object(ObjectPath::new("examples/bg2015071010.png").unwrap(), ..10) - .await; - assert_matches!(object, Ok(_)); - - let result = client.delete_object("examples/bg2015071010.png").await; - - assert_matches!(result, Ok(_)); - } - - // #[tokio::test] - // #[cfg(feature = "bench")] - // #[bench] - // async fn bench_get_object(b: &mut Bencher) { - // dotenv().ok(); - - // let client = Client::from_env().unwrap(); - // b.iter(|| { - // client.get_object_list().await; - // }); - // } -} - -#[cfg(feature = "blocking")] -mod test_blocking { - - use aliyun_oss_client::ClientRc; - use assert_matches::assert_matches; - use dotenv::dotenv; - - #[test] - fn test_get_bucket_list() { - dotenv().ok(); - - let client = ClientRc::from_env().unwrap(); - - let bucket_list = client.get_bucket_list(); - - assert_matches!(bucket_list, Ok(_)); - } - - #[test] - fn test_get_bucket_info() { - dotenv().ok(); - - let client = ClientRc::from_env().unwrap(); - - let bucket_list = client.get_bucket_info(); - - assert_matches!(bucket_list, Ok(_)); - } - - #[test] - fn get_object_by_bucket_struct() { - dotenv().ok(); - - let client = ClientRc::from_env().unwrap(); - - let bucket_list = client.get_bucket_list().unwrap(); - - let buckets = bucket_list.to_vec(); - let the_bucket = &buckets[0]; - let object_list = the_bucket.get_object_list(vec![("max-keys".into(), "2".into())]); - assert_matches!(object_list, Ok(_)); - let mut object_list = object_list.unwrap(); - assert_matches!(object_list.next(), Some(_)); - } - - #[test] - fn test_get_object() { - dotenv().ok(); - - let client = ClientRc::from_env().unwrap(); - - let object_list = client.get_object_list([]); - - assert_matches!(object_list, Ok(_)); - } - - #[test] - fn test_get_object_next() { - dotenv().ok(); - - let client = ClientRc::from_env().unwrap(); - let query = vec![("max-keys".into(), 2u8.into())]; - let mut object_list = client.get_object_list(query).unwrap(); - - assert_matches!(object_list.next(), Some(_)); - assert_matches!(object_list.next(), Some(_)); - } - - #[cfg(feature = "put_file")] - #[test] - fn test_put_and_delete_file() { - use aliyun_oss_client::file::BlockingFiles; - dotenv().ok(); - - let client = ClientRc::from_env().unwrap(); - - // 第一种读取文件路径的方式 - let object_list = client.put_file("examples/bg2015071010.png", "examples/bg2015071010.png"); - - assert_matches!(object_list, Ok(_)); - - let result = client.delete_object("examples/bg2015071010.png"); - - assert_matches!(result, Ok(_)); - - // 第二种读取文件路径的方式 - let object_list = client.put_file("examples/bg2015071010.png", "examples/bg2015071010.png"); - - assert_matches!(object_list, Ok(_)); - - let result = client.delete_object("examples/bg2015071010.png"); - - assert_matches!(result, Ok(_)); - } - - // #[bench] - // fn bench_get_object(b: &mut Bencher){ - // dotenv().ok(); - - // let key_id = env::var("ALIYUN_KEY_ID").unwrap(); - // let key_secret = env::var("ALIYUN_KEY_SECRET").unwrap(); - // let endpoint = env::var("ALIYUN_ENDPOINT").unwrap(); - // let bucket = env::var("ALIYUN_BUCKET").unwrap(); - - // let client = client::Client::new(key_id,key_secret, endpoint, bucket); - // b.iter(|| { - // client.get_object_list(); - // }); - // } -} diff --git a/tests/query_auth.rs b/tests/query_auth.rs deleted file mode 100644 index b8da0c1..0000000 --- a/tests/query_auth.rs +++ /dev/null @@ -1,49 +0,0 @@ -use std::{io::Write, sync::Arc}; - -use aliyun_oss_client::{auth::query::QueryAuth, config::Config, object::content::Content}; -use chrono::Utc; -use dotenv::dotenv; -use reqwest::Client; - -#[tokio::test] -async fn run() { - dotenv().ok(); - let config = Config::from_env().unwrap(); - - let auth = QueryAuth::from(&config); - - let time = Utc::now().timestamp() + 3600; - let url = auth.to_url(&"babel.config.js".parse().unwrap(), time); - - let client = Client::new().get(url).send().await.unwrap(); - assert!(client.status().is_success()); -} - -#[test] -fn test_multi_upload() { - dotenv::dotenv().ok(); - - let client = aliyun_oss_client::Client::from_env().unwrap(); - - let mut objcet = Content::from_client(Arc::new(client)) - .path("aaabbb3.txt") - .unwrap(); - - //objcet.part_size(100 * 1024).unwrap(); - - { - objcet - .write_all(b"abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789") - .unwrap(); - objcet.flush().unwrap() - } - - println!("finish"); - - // let mut buf = [0u8; 11]; - // objcet.set_size("72").unwrap(); - // objcet.seek(std::io::SeekFrom::Start(26)).unwrap(); - // objcet.read(&mut buf).unwrap(); - - // println!("buf: {:?}", buf); -}