This is page 52 of 55. Use http://codebase.md/apache/opendal?page={x} to view the full context.
# Directory Structure
```
├── .asf.yaml
├── .config
│ └── nextest.toml
├── .devcontainer
│ ├── devcontainer.json
│ └── post_create.sh
├── .editorconfig
├── .env.example
├── .gitattributes
├── .github
│ ├── actions
│ │ ├── fuzz_test
│ │ │ └── action.yaml
│ │ ├── setup
│ │ │ └── action.yaml
│ │ ├── setup-hadoop
│ │ │ └── action.yaml
│ │ ├── setup-ocaml
│ │ │ └── action.yaml
│ │ ├── test_behavior_binding_c
│ │ │ └── action.yaml
│ │ ├── test_behavior_binding_cpp
│ │ │ └── action.yaml
│ │ ├── test_behavior_binding_go
│ │ │ └── action.yaml
│ │ ├── test_behavior_binding_java
│ │ │ └── action.yaml
│ │ ├── test_behavior_binding_nodejs
│ │ │ └── action.yaml
│ │ ├── test_behavior_binding_python
│ │ │ └── action.yaml
│ │ ├── test_behavior_core
│ │ │ └── action.yaml
│ │ └── test_behavior_integration_object_store
│ │ └── action.yml
│ ├── CODEOWNERS
│ ├── dependabot.yml
│ ├── ISSUE_TEMPLATE
│ │ ├── 1-bug-report.yml
│ │ ├── 2-feature-request.yml
│ │ ├── 3-new-release.md
│ │ └── config.yml
│ ├── pull_request_template.md
│ ├── release.yml
│ ├── scripts
│ │ ├── test_behavior
│ │ │ ├── __init__.py
│ │ │ ├── plan.py
│ │ │ └── test_plan.py
│ │ ├── test_go_binding
│ │ │ ├── generate_test_scheme.py
│ │ │ └── matrix.yaml
│ │ └── weekly_update
│ │ ├── .gitignore
│ │ ├── .python-version
│ │ ├── main.py
│ │ ├── pyproject.toml
│ │ ├── README.md
│ │ └── uv.lock
│ ├── services
│ │ ├── aliyun_drive
│ │ │ └── aliyun_drive
│ │ │ └── disable_action.yml
│ │ ├── alluxio
│ │ │ └── alluxio
│ │ │ └── action.yml
│ │ ├── azblob
│ │ │ ├── azure_azblob
│ │ │ │ └── action.yml
│ │ │ └── azurite_azblob
│ │ │ └── action.yml
│ │ ├── azdls
│ │ │ └── azdls
│ │ │ └── action.yml
│ │ ├── azfile
│ │ │ └── azfile
│ │ │ └── action.yml
│ │ ├── b2
│ │ │ └── b2
│ │ │ └── action.yml
│ │ ├── cacache
│ │ │ └── cacache
│ │ │ └── action.yml
│ │ ├── compfs
│ │ │ └── compfs
│ │ │ └── action.yml
│ │ ├── cos
│ │ │ └── cos
│ │ │ └── action.yml
│ │ ├── dashmap
│ │ │ └── dashmap
│ │ │ └── action.yml
│ │ ├── dropbox
│ │ │ └── dropbox
│ │ │ └── disable_action.yml
│ │ ├── etcd
│ │ │ ├── etcd
│ │ │ │ └── action.yml
│ │ │ ├── etcd-cluster
│ │ │ │ └── action.yml
│ │ │ └── etcd-tls
│ │ │ └── action.yml
│ │ ├── fs
│ │ │ └── local_fs
│ │ │ └── action.yml
│ │ ├── ftp
│ │ │ └── vsftpd
│ │ │ └── disable_action.yml
│ │ ├── gcs
│ │ │ ├── gcs
│ │ │ │ └── action.yml
│ │ │ └── gcs_with_default_storage_class
│ │ │ └── action.yml
│ │ ├── gdrive
│ │ │ └── gdrive
│ │ │ └── action.yml
│ │ ├── gridfs
│ │ │ ├── gridfs
│ │ │ │ └── action.yml
│ │ │ └── gridfs_with_basic_auth
│ │ │ └── action.yml
│ │ ├── hdfs
│ │ │ ├── hdfs_cluster
│ │ │ │ └── action.yml
│ │ │ ├── hdfs_cluster_with_atomic_write_dir
│ │ │ │ └── action.yml
│ │ │ ├── hdfs_default
│ │ │ │ └── action.yml
│ │ │ ├── hdfs_default_gcs
│ │ │ │ └── action.yml
│ │ │ ├── hdfs_default_on_azurite_azblob
│ │ │ │ └── action.yml
│ │ │ ├── hdfs_default_on_minio_s3
│ │ │ │ └── action.yml
│ │ │ └── hdfs_default_with_atomic_write_dir
│ │ │ └── action.yml
│ │ ├── hdfs_native
│ │ │ └── hdfs_native_cluster
│ │ │ └── action.yml
│ │ ├── http
│ │ │ ├── caddy
│ │ │ │ └── action.yml
│ │ │ └── nginx
│ │ │ └── action.yml
│ │ ├── huggingface
│ │ │ └── huggingface
│ │ │ └── action.yml
│ │ ├── koofr
│ │ │ └── koofr
│ │ │ └── disable_action.yml
│ │ ├── memcached
│ │ │ ├── memcached
│ │ │ │ └── action.yml
│ │ │ └── memcached_with_auth
│ │ │ └── action.yml
│ │ ├── memory
│ │ │ └── memory
│ │ │ └── action.yml
│ │ ├── mini_moka
│ │ │ └── mini_moka
│ │ │ └── action.yml
│ │ ├── moka
│ │ │ └── moka
│ │ │ └── action.yml
│ │ ├── mongodb
│ │ │ ├── mongodb_with_basic_auth
│ │ │ │ └── action.yml
│ │ │ └── mongodb_with_no_auth
│ │ │ └── action.yml
│ │ ├── monoiofs
│ │ │ └── monoiofs
│ │ │ └── action.yml
│ │ ├── mysql
│ │ │ └── mysql
│ │ │ └── action.yml
│ │ ├── oss
│ │ │ ├── oss
│ │ │ │ └── action.yml
│ │ │ └── oss_with_versioning
│ │ │ └── action.yml
│ │ ├── persy
│ │ │ └── persy
│ │ │ └── action.yml
│ │ ├── postgresql
│ │ │ └── postgresql
│ │ │ └── action.yml
│ │ ├── redb
│ │ │ └── redb
│ │ │ └── action.yml
│ │ ├── redis
│ │ │ ├── dragonfly
│ │ │ │ └── action.yml
│ │ │ ├── kvrocks
│ │ │ │ └── action.yml
│ │ │ ├── redis
│ │ │ │ └── action.yml
│ │ │ ├── redis_tls
│ │ │ │ └── action.yml
│ │ │ ├── redis_with_cluster
│ │ │ │ └── action.yml
│ │ │ └── redis_with_cluster_tls
│ │ │ └── action.yml
│ │ ├── rocksdb
│ │ │ └── rocksdb
│ │ │ └── action.yml
│ │ ├── s3
│ │ │ ├── 0_minio_s3
│ │ │ │ └── action.yml
│ │ │ ├── aws_s3
│ │ │ │ └── action.yml
│ │ │ ├── aws_s3_with_list_objects_v1
│ │ │ │ └── action.yml
│ │ │ ├── aws_s3_with_sse_c
│ │ │ │ └── action.yml
│ │ │ ├── aws_s3_with_versioning
│ │ │ │ └── action.yml
│ │ │ ├── aws_s3_with_virtual_host
│ │ │ │ └── action.yml
│ │ │ ├── ceph_radios_s3_with_versioning
│ │ │ │ └── disable_action.yml
│ │ │ ├── ceph_rados_s3
│ │ │ │ └── disable_action.yml
│ │ │ ├── minio_s3_with_anonymous
│ │ │ │ └── action.yml
│ │ │ ├── minio_s3_with_list_objects_v1
│ │ │ │ └── action.yml
│ │ │ ├── minio_s3_with_versioning
│ │ │ │ └── action.yml
│ │ │ └── r2
│ │ │ └── disabled_action.yml
│ │ ├── seafile
│ │ │ └── seafile
│ │ │ └── action.yml
│ │ ├── sftp
│ │ │ ├── sftp
│ │ │ │ └── action.yml
│ │ │ └── sftp_with_default_root
│ │ │ └── action.yml
│ │ ├── sled
│ │ │ ├── sled
│ │ │ │ └── action.yml
│ │ │ └── sled_with_tree
│ │ │ └── action.yml
│ │ ├── sqlite
│ │ │ └── sqlite
│ │ │ └── action.yml
│ │ ├── swift
│ │ │ ├── ceph_rados_swift
│ │ │ │ └── action.yml
│ │ │ └── swift
│ │ │ └── action.yml
│ │ ├── tikv
│ │ │ └── tikv
│ │ │ └── disable_action.yml
│ │ ├── webdav
│ │ │ ├── 0_nginx
│ │ │ │ └── action.yml
│ │ │ ├── jfrog
│ │ │ │ └── disabled_action.yml
│ │ │ ├── nextcloud
│ │ │ │ └── action.yml
│ │ │ ├── nginx_with_empty_password
│ │ │ │ └── action.yml
│ │ │ ├── nginx_with_password
│ │ │ │ └── action.yml
│ │ │ ├── nginx_with_redirect
│ │ │ │ └── action.yml
│ │ │ └── owncloud
│ │ │ └── action.yml
│ │ └── webhdfs
│ │ ├── webhdfs
│ │ │ └── action.yml
│ │ ├── webhdfs_with_list_batch_disabled
│ │ │ └── action.yml
│ │ └── webhdfs_with_user_name
│ │ └── action.yml
│ └── workflows
│ ├── ci_bindings_c.yml
│ ├── ci_bindings_cpp.yml
│ ├── ci_bindings_d.yml
│ ├── ci_bindings_dart.yml
│ ├── ci_bindings_dotnet.yml
│ ├── ci_bindings_go.yml
│ ├── ci_bindings_haskell.yml
│ ├── ci_bindings_java.yml
│ ├── ci_bindings_lua.yml
│ ├── ci_bindings_nodejs.yml
│ ├── ci_bindings_ocaml.yml
│ ├── ci_bindings_php.yml
│ ├── ci_bindings_python.yml
│ ├── ci_bindings_ruby.yml
│ ├── ci_bindings_swift.yml
│ ├── ci_bindings_zig.yml
│ ├── ci_check.yml
│ ├── ci_core.yml
│ ├── ci_integration_dav_server.yml
│ ├── ci_integration_object_store.yml
│ ├── ci_integration_parquet.yml
│ ├── ci_integration_spring.yml
│ ├── ci_integration_unftp_sbe.yml
│ ├── ci_odev.yml
│ ├── ci_weekly_update.yml
│ ├── discussion-thread-link.yml
│ ├── docs.yml
│ ├── full-ci-promote.yml
│ ├── release_dart.yml
│ ├── release_java.yml
│ ├── release_nodejs.yml
│ ├── release_python.yml
│ ├── release_ruby.yml
│ ├── release_rust.yml
│ ├── service_test_ghac.yml
│ ├── test_behavior_binding_c.yml
│ ├── test_behavior_binding_cpp.yml
│ ├── test_behavior_binding_go.yml
│ ├── test_behavior_binding_java.yml
│ ├── test_behavior_binding_nodejs.yml
│ ├── test_behavior_binding_python.yml
│ ├── test_behavior_core.yml
│ ├── test_behavior_integration_object_store.yml
│ ├── test_behavior.yml
│ ├── test_edge.yml
│ └── test_fuzz.yml
├── .gitignore
├── .taplo.toml
├── .typos.toml
├── .vscode
│ └── settings.json
├── .yamlfmt
├── AGENTS.md
├── bindings
│ ├── java
│ │ ├── .cargo
│ │ │ └── config.toml
│ │ ├── .gitignore
│ │ ├── .mvn
│ │ │ └── wrapper
│ │ │ └── maven-wrapper.properties
│ │ ├── Cargo.toml
│ │ ├── DEPENDENCIES.md
│ │ ├── DEPENDENCIES.rust.tsv
│ │ ├── mvnw
│ │ ├── mvnw.cmd
│ │ ├── pom.xml
│ │ ├── README.md
│ │ ├── src
│ │ │ ├── async_operator.rs
│ │ │ ├── convert.rs
│ │ │ ├── error.rs
│ │ │ ├── executor.rs
│ │ │ ├── layer.rs
│ │ │ ├── lib.rs
│ │ │ ├── main
│ │ │ │ ├── java
│ │ │ │ │ └── org
│ │ │ │ │ └── apache
│ │ │ │ │ └── opendal
│ │ │ │ │ ├── AsyncExecutor.java
│ │ │ │ │ ├── AsyncOperator.java
│ │ │ │ │ ├── Capability.java
│ │ │ │ │ ├── Entry.java
│ │ │ │ │ ├── Environment.java
│ │ │ │ │ ├── layer
│ │ │ │ │ │ ├── ConcurrentLimitLayer.java
│ │ │ │ │ │ ├── package-info.java
│ │ │ │ │ │ └── RetryLayer.java
│ │ │ │ │ ├── Layer.java
│ │ │ │ │ ├── ListOptions.java
│ │ │ │ │ ├── Metadata.java
│ │ │ │ │ ├── NativeLibrary.java
│ │ │ │ │ ├── NativeObject.java
│ │ │ │ │ ├── OpenDAL.java
│ │ │ │ │ ├── OpenDALException.java
│ │ │ │ │ ├── Operator.java
│ │ │ │ │ ├── OperatorInfo.java
│ │ │ │ │ ├── OperatorInputStream.java
│ │ │ │ │ ├── OperatorOutputStream.java
│ │ │ │ │ ├── package-info.java
│ │ │ │ │ ├── PresignedRequest.java
│ │ │ │ │ ├── ReadOptions.java
│ │ │ │ │ ├── ServiceConfig.java
│ │ │ │ │ ├── StatOptions.java
│ │ │ │ │ └── WriteOptions.java
│ │ │ │ └── resources
│ │ │ │ ├── bindings.properties
│ │ │ │ └── META-INF
│ │ │ │ └── NOTICE
│ │ │ ├── operator_input_stream.rs
│ │ │ ├── operator_output_stream.rs
│ │ │ ├── operator.rs
│ │ │ ├── test
│ │ │ │ └── java
│ │ │ │ └── org
│ │ │ │ └── apache
│ │ │ │ └── opendal
│ │ │ │ └── test
│ │ │ │ ├── AsyncExecutorTest.java
│ │ │ │ ├── behavior
│ │ │ │ │ ├── AsyncCopyTest.java
│ │ │ │ │ ├── AsyncCreateDirTest.java
│ │ │ │ │ ├── AsyncListTest.java
│ │ │ │ │ ├── AsyncPresignTest.java
│ │ │ │ │ ├── AsyncReadOnlyTest.java
│ │ │ │ │ ├── AsyncRenameTest.java
│ │ │ │ │ ├── AsyncStatOptionsTest.java
│ │ │ │ │ ├── AsyncWriteOptionsTest.java
│ │ │ │ │ ├── AsyncWriteTest.java
│ │ │ │ │ ├── BehaviorExtension.java
│ │ │ │ │ ├── BehaviorTestBase.java
│ │ │ │ │ ├── BlockingCopyTest.java
│ │ │ │ │ ├── BlockingCreateDirTest.java
│ │ │ │ │ ├── BlockingListTest.java
│ │ │ │ │ ├── BlockingReadOnlyTest.java
│ │ │ │ │ ├── BlockingRenameTest.java
│ │ │ │ │ ├── BlockingStatOptionsTest.java
│ │ │ │ │ ├── BlockingWriteOptionTest.java
│ │ │ │ │ ├── BlockingWriteTest.java
│ │ │ │ │ └── RegressionTest.java
│ │ │ │ ├── condition
│ │ │ │ │ └── OpenDALExceptionCondition.java
│ │ │ │ ├── LayerTest.java
│ │ │ │ ├── MetadataTest.java
│ │ │ │ ├── OperatorDuplicateTest.java
│ │ │ │ ├── OperatorInfoTest.java
│ │ │ │ ├── OperatorInputOutputStreamTest.java
│ │ │ │ ├── OperatorUtf8DecodeTest.java
│ │ │ │ └── UtilityTest.java
│ │ │ └── utility.rs
│ │ ├── tools
│ │ │ └── build.py
│ │ ├── upgrade.md
│ │ └── users.md
│ ├── nodejs
│ │ ├── .cargo
│ │ │ └── config.toml
│ │ ├── .gitignore
│ │ ├── .node-version
│ │ ├── .npmignore
│ │ ├── .npmrc
│ │ ├── .prettierignore
│ │ ├── benchmark
│ │ │ ├── deno.ts
│ │ │ ├── node.js
│ │ │ └── README.md
│ │ ├── build.rs
│ │ ├── Cargo.toml
│ │ ├── CONTRIBUTING.md
│ │ ├── DEPENDENCIES.md
│ │ ├── DEPENDENCIES.rust.tsv
│ │ ├── devbox.json
│ │ ├── devbox.lock
│ │ ├── generated.d.ts
│ │ ├── generated.js
│ │ ├── index.cjs
│ │ ├── index.d.ts
│ │ ├── index.mjs
│ │ ├── npm
│ │ │ ├── darwin-arm64
│ │ │ │ ├── package.json
│ │ │ │ └── README.md
│ │ │ ├── darwin-x64
│ │ │ │ ├── package.json
│ │ │ │ └── README.md
│ │ │ ├── linux-arm64-gnu
│ │ │ │ ├── package.json
│ │ │ │ └── README.md
│ │ │ ├── linux-arm64-musl
│ │ │ │ ├── package.json
│ │ │ │ └── README.md
│ │ │ ├── linux-x64-gnu
│ │ │ │ ├── package.json
│ │ │ │ └── README.md
│ │ │ ├── linux-x64-musl
│ │ │ │ ├── package.json
│ │ │ │ └── README.md
│ │ │ ├── win32-arm64-msvc
│ │ │ │ ├── package.json
│ │ │ │ └── README.md
│ │ │ └── win32-x64-msvc
│ │ │ ├── package.json
│ │ │ └── README.md
│ │ ├── package.json
│ │ ├── pnpm-lock.yaml
│ │ ├── README.md
│ │ ├── scripts
│ │ │ └── header.mjs
│ │ ├── src
│ │ │ ├── capability.rs
│ │ │ ├── layer.rs
│ │ │ ├── lib.rs
│ │ │ └── options.rs
│ │ ├── tests
│ │ │ ├── service.test.mjs
│ │ │ ├── suites
│ │ │ │ ├── async.suite.mjs
│ │ │ │ ├── asyncDeleteOptions.suite.mjs
│ │ │ │ ├── asyncLister.suite.mjs
│ │ │ │ ├── asyncListOptions.suite.mjs
│ │ │ │ ├── asyncReadOptions.suite.mjs
│ │ │ │ ├── asyncStatOptions.suite.mjs
│ │ │ │ ├── asyncWriteOptions.suite.mjs
│ │ │ │ ├── index.mjs
│ │ │ │ ├── layer.suite.mjs
│ │ │ │ ├── services.suite.mjs
│ │ │ │ ├── sync.suite.mjs
│ │ │ │ ├── syncDeleteOptions.suite.mjs
│ │ │ │ ├── syncLister.suite.mjs
│ │ │ │ ├── syncListOptions.suite.mjs
│ │ │ │ ├── syncReadOptions.suite.mjs
│ │ │ │ ├── syncStatOptions.suite.mjs
│ │ │ │ └── syncWriteOptions.suite.mjs
│ │ │ └── utils.mjs
│ │ ├── theme
│ │ │ ├── index.tsx
│ │ │ └── package.json
│ │ ├── tsconfig.json
│ │ ├── tsconfig.theme.json
│ │ ├── typedoc.json
│ │ ├── upgrade.md
│ │ └── vitest.config.mjs
│ ├── python
│ │ ├── .gitignore
│ │ ├── benchmark
│ │ │ ├── async_opendal_benchmark.py
│ │ │ ├── async_origin_s3_benchmark_with_gevent.py
│ │ │ └── README.md
│ │ ├── Cargo.toml
│ │ ├── CONTRIBUTING.md
│ │ ├── DEPENDENCIES.md
│ │ ├── DEPENDENCIES.rust.tsv
│ │ ├── docs
│ │ │ ├── api
│ │ │ │ ├── async_file.md
│ │ │ │ ├── async_operator.md
│ │ │ │ ├── capability.md
│ │ │ │ ├── exceptions.md
│ │ │ │ ├── file.md
│ │ │ │ ├── layers.md
│ │ │ │ ├── operator.md
│ │ │ │ └── types.md
│ │ │ └── index.md
│ │ ├── justfile
│ │ ├── mkdocs.yml
│ │ ├── pyproject.toml
│ │ ├── pyrightconfig.json
│ │ ├── python
│ │ │ └── opendal
│ │ │ ├── __init__.py
│ │ │ ├── capability.pyi
│ │ │ ├── exceptions.pyi
│ │ │ ├── file.pyi
│ │ │ ├── layers.pyi
│ │ │ ├── operator.pyi
│ │ │ ├── py.typed
│ │ │ ├── services.pyi
│ │ │ └── types.pyi
│ │ ├── README.md
│ │ ├── ruff.toml
│ │ ├── src
│ │ │ ├── capability.rs
│ │ │ ├── errors.rs
│ │ │ ├── file.rs
│ │ │ ├── layers.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ ├── metadata.rs
│ │ │ ├── operator.rs
│ │ │ ├── options.rs
│ │ │ ├── services.rs
│ │ │ └── utils.rs
│ │ ├── template
│ │ │ └── module.html.jinja2
│ │ ├── tests
│ │ │ ├── conftest.py
│ │ │ ├── test_async_check.py
│ │ │ ├── test_async_copy.py
│ │ │ ├── test_async_delete.py
│ │ │ ├── test_async_exists.py
│ │ │ ├── test_async_list.py
│ │ │ ├── test_async_pickle_types.py
│ │ │ ├── test_async_rename.py
│ │ │ ├── test_capability.py
│ │ │ ├── test_exceptions.py
│ │ │ ├── test_pickle_rw.py
│ │ │ ├── test_read.py
│ │ │ ├── test_sync_check.py
│ │ │ ├── test_sync_copy.py
│ │ │ ├── test_sync_delete.py
│ │ │ ├── test_sync_exists.py
│ │ │ ├── test_sync_list.py
│ │ │ ├── test_sync_pickle_types.py
│ │ │ ├── test_sync_rename.py
│ │ │ └── test_write.py
│ │ ├── upgrade.md
│ │ ├── users.md
│ │ └── uv.lock
│ └── README.md
├── CHANGELOG.md
├── CITATION.cff
├── CLAUDE.md
├── CONTRIBUTING.md
├── core
│ ├── benches
│ │ ├── ops
│ │ │ ├── main.rs
│ │ │ ├── read.rs
│ │ │ ├── README.md
│ │ │ ├── utils.rs
│ │ │ └── write.rs
│ │ ├── README.md
│ │ ├── types
│ │ │ ├── buffer.rs
│ │ │ ├── main.rs
│ │ │ ├── README.md
│ │ │ └── tasks.rs
│ │ ├── vs_fs
│ │ │ ├── Cargo.toml
│ │ │ ├── README.md
│ │ │ └── src
│ │ │ └── main.rs
│ │ └── vs_s3
│ │ ├── Cargo.toml
│ │ ├── README.md
│ │ └── src
│ │ └── main.rs
│ ├── Cargo.lock
│ ├── Cargo.toml
│ ├── CHANGELOG.md
│ ├── CONTRIBUTING.md
│ ├── core
│ │ ├── Cargo.toml
│ │ └── src
│ │ ├── blocking
│ │ │ ├── delete.rs
│ │ │ ├── list.rs
│ │ │ ├── mod.rs
│ │ │ ├── operator.rs
│ │ │ ├── read
│ │ │ │ ├── buffer_iterator.rs
│ │ │ │ ├── mod.rs
│ │ │ │ ├── reader.rs
│ │ │ │ ├── std_bytes_iterator.rs
│ │ │ │ └── std_reader.rs
│ │ │ └── write
│ │ │ ├── mod.rs
│ │ │ ├── std_writer.rs
│ │ │ └── writer.rs
│ │ ├── docs
│ │ │ ├── comparisons
│ │ │ │ ├── mod.rs
│ │ │ │ └── vs_object_store.md
│ │ │ ├── concepts.rs
│ │ │ ├── internals
│ │ │ │ ├── accessor.rs
│ │ │ │ ├── layer.rs
│ │ │ │ └── mod.rs
│ │ │ ├── mod.rs
│ │ │ ├── performance
│ │ │ │ ├── concurrent_write.md
│ │ │ │ ├── http_optimization.md
│ │ │ │ └── mod.rs
│ │ │ ├── rfcs
│ │ │ │ ├── 0000_example.md
│ │ │ │ ├── 0041_object_native_api.md
│ │ │ │ ├── 0044_error_handle.md
│ │ │ │ ├── 0057_auto_region.md
│ │ │ │ ├── 0069_object_stream.md
│ │ │ │ ├── 0090_limited_reader.md
│ │ │ │ ├── 0112_path_normalization.md
│ │ │ │ ├── 0191_async_streaming_io.md
│ │ │ │ ├── 0203_remove_credential.md
│ │ │ │ ├── 0221_create_dir.md
│ │ │ │ ├── 0247_retryable_error.md
│ │ │ │ ├── 0293_object_id.md
│ │ │ │ ├── 0337_dir_entry.md
│ │ │ │ ├── 0409_accessor_capabilities.md
│ │ │ │ ├── 0413_presign.md
│ │ │ │ ├── 0423_command_line_interface.md
│ │ │ │ ├── 0429_init_from_iter.md
│ │ │ │ ├── 0438_multipart.md
│ │ │ │ ├── 0443_gateway.md
│ │ │ │ ├── 0501_new_builder.md
│ │ │ │ ├── 0554_write_refactor.md
│ │ │ │ ├── 0561_list_metadata_reuse.md
│ │ │ │ ├── 0599_blocking_api.md
│ │ │ │ ├── 0623_redis_service.md
│ │ │ │ ├── 0627_split_capabilities.md
│ │ │ │ ├── 0661_path_in_accessor.md
│ │ │ │ ├── 0793_generic_kv_services.md
│ │ │ │ ├── 0926_object_reader.md
│ │ │ │ ├── 0977_refactor_error.md
│ │ │ │ ├── 1085_object_handler.md
│ │ │ │ ├── 1391_object_metadataer.md
│ │ │ │ ├── 1398_query_based_metadata.md
│ │ │ │ ├── 1420_object_writer.md
│ │ │ │ ├── 1477_remove_object_concept.md
│ │ │ │ ├── 1735_operation_extension.md
│ │ │ │ ├── 2083_writer_sink_api.md
│ │ │ │ ├── 2133_append_api.md
│ │ │ │ ├── 2299_chain_based_operator_api.md
│ │ │ │ ├── 2602_object_versioning.md
│ │ │ │ ├── 2758_merge_append_into_write.md
│ │ │ │ ├── 2774_lister_api.md
│ │ │ │ ├── 2779_list_with_metakey.md
│ │ │ │ ├── 2852_native_capability.md
│ │ │ │ ├── 2884_merge_range_read_into_read.md
│ │ │ │ ├── 3017_remove_write_copy_from.md
│ │ │ │ ├── 3197_config.md
│ │ │ │ ├── 3232_align_list_api.md
│ │ │ │ ├── 3243_list_prefix.md
│ │ │ │ ├── 3356_lazy_reader.md
│ │ │ │ ├── 3526_list_recursive.md
│ │ │ │ ├── 3574_concurrent_stat_in_list.md
│ │ │ │ ├── 3734_buffered_reader.md
│ │ │ │ ├── 3898_concurrent_writer.md
│ │ │ │ ├── 3911_deleter_api.md
│ │ │ │ ├── 4382_range_based_read.md
│ │ │ │ ├── 4638_executor.md
│ │ │ │ ├── 5314_remove_metakey.md
│ │ │ │ ├── 5444_operator_from_uri.md
│ │ │ │ ├── 5479_context.md
│ │ │ │ ├── 5485_conditional_reader.md
│ │ │ │ ├── 5495_list_with_deleted.md
│ │ │ │ ├── 5556_write_returns_metadata.md
│ │ │ │ ├── 5871_read_returns_metadata.md
│ │ │ │ ├── 6189_remove_native_blocking.md
│ │ │ │ ├── 6209_glob_support.md
│ │ │ │ ├── 6213_options_api.md
│ │ │ │ ├── 6370_foyer_integration.md
│ │ │ │ ├── 6678_simulate_layer.md
│ │ │ │ ├── 6707_capability_override_layer.md
│ │ │ │ ├── 6817_checksum.md
│ │ │ │ ├── 6828_core.md
│ │ │ │ ├── 7130_route_layer.md
│ │ │ │ ├── mod.rs
│ │ │ │ └── README.md
│ │ │ └── upgrade.md
│ │ ├── layers
│ │ │ ├── complete.rs
│ │ │ ├── correctness_check.rs
│ │ │ ├── error_context.rs
│ │ │ ├── http_client.rs
│ │ │ ├── mod.rs
│ │ │ ├── simulate.rs
│ │ │ └── type_eraser.rs
│ │ ├── lib.rs
│ │ ├── raw
│ │ │ ├── accessor.rs
│ │ │ ├── atomic_util.rs
│ │ │ ├── enum_utils.rs
│ │ │ ├── futures_util.rs
│ │ │ ├── http_util
│ │ │ │ ├── body.rs
│ │ │ │ ├── bytes_content_range.rs
│ │ │ │ ├── bytes_range.rs
│ │ │ │ ├── client.rs
│ │ │ │ ├── error.rs
│ │ │ │ ├── header.rs
│ │ │ │ ├── mod.rs
│ │ │ │ ├── multipart.rs
│ │ │ │ └── uri.rs
│ │ │ ├── layer.rs
│ │ │ ├── mod.rs
│ │ │ ├── oio
│ │ │ │ ├── buf
│ │ │ │ │ ├── flex_buf.rs
│ │ │ │ │ ├── mod.rs
│ │ │ │ │ ├── pooled_buf.rs
│ │ │ │ │ └── queue_buf.rs
│ │ │ │ ├── delete
│ │ │ │ │ ├── api.rs
│ │ │ │ │ ├── batch_delete.rs
│ │ │ │ │ ├── mod.rs
│ │ │ │ │ └── one_shot_delete.rs
│ │ │ │ ├── entry.rs
│ │ │ │ ├── list
│ │ │ │ │ ├── api.rs
│ │ │ │ │ ├── flat_list.rs
│ │ │ │ │ ├── hierarchy_list.rs
│ │ │ │ │ ├── mod.rs
│ │ │ │ │ ├── page_list.rs
│ │ │ │ │ └── prefix_list.rs
│ │ │ │ ├── mod.rs
│ │ │ │ ├── read
│ │ │ │ │ ├── api.rs
│ │ │ │ │ └── mod.rs
│ │ │ │ └── write
│ │ │ │ ├── api.rs
│ │ │ │ ├── append_write.rs
│ │ │ │ ├── block_write.rs
│ │ │ │ ├── mod.rs
│ │ │ │ ├── multipart_write.rs
│ │ │ │ ├── one_shot_write.rs
│ │ │ │ └── position_write.rs
│ │ │ ├── operation.rs
│ │ │ ├── ops.rs
│ │ │ ├── path_cache.rs
│ │ │ ├── path.rs
│ │ │ ├── rps.rs
│ │ │ ├── serde_util.rs
│ │ │ ├── std_io_util.rs
│ │ │ ├── time.rs
│ │ │ ├── tokio_util.rs
│ │ │ └── version.rs
│ │ ├── services
│ │ │ ├── memory
│ │ │ │ ├── backend.rs
│ │ │ │ ├── config.rs
│ │ │ │ ├── core.rs
│ │ │ │ ├── deleter.rs
│ │ │ │ ├── docs.md
│ │ │ │ ├── lister.rs
│ │ │ │ ├── mod.rs
│ │ │ │ └── writer.rs
│ │ │ └── mod.rs
│ │ └── types
│ │ ├── buffer.rs
│ │ ├── builder.rs
│ │ ├── capability.rs
│ │ ├── context
│ │ │ ├── mod.rs
│ │ │ ├── read.rs
│ │ │ └── write.rs
│ │ ├── delete
│ │ │ ├── deleter.rs
│ │ │ ├── futures_delete_sink.rs
│ │ │ ├── input.rs
│ │ │ └── mod.rs
│ │ ├── entry.rs
│ │ ├── error.rs
│ │ ├── execute
│ │ │ ├── api.rs
│ │ │ ├── executor.rs
│ │ │ ├── executors
│ │ │ │ ├── mod.rs
│ │ │ │ └── tokio_executor.rs
│ │ │ └── mod.rs
│ │ ├── list.rs
│ │ ├── metadata.rs
│ │ ├── mod.rs
│ │ ├── mode.rs
│ │ ├── operator
│ │ │ ├── builder.rs
│ │ │ ├── info.rs
│ │ │ ├── mod.rs
│ │ │ ├── operator_futures.rs
│ │ │ ├── operator.rs
│ │ │ ├── registry.rs
│ │ │ └── uri.rs
│ │ ├── options.rs
│ │ ├── read
│ │ │ ├── buffer_stream.rs
│ │ │ ├── futures_async_reader.rs
│ │ │ ├── futures_bytes_stream.rs
│ │ │ ├── mod.rs
│ │ │ └── reader.rs
│ │ └── write
│ │ ├── buffer_sink.rs
│ │ ├── futures_async_writer.rs
│ │ ├── futures_bytes_sink.rs
│ │ ├── mod.rs
│ │ └── writer.rs
│ ├── DEPENDENCIES.md
│ ├── DEPENDENCIES.rust.tsv
│ ├── edge
│ │ ├── file_write_on_full_disk
│ │ │ ├── Cargo.toml
│ │ │ ├── README.md
│ │ │ └── src
│ │ │ └── main.rs
│ │ ├── README.md
│ │ ├── s3_aws_assume_role_with_web_identity
│ │ │ ├── Cargo.toml
│ │ │ ├── README.md
│ │ │ └── src
│ │ │ └── main.rs
│ │ └── s3_read_on_wasm
│ │ ├── .gitignore
│ │ ├── Cargo.toml
│ │ ├── README.md
│ │ ├── src
│ │ │ └── lib.rs
│ │ └── webdriver.json
│ ├── fuzz
│ │ ├── .gitignore
│ │ ├── Cargo.toml
│ │ ├── fuzz_reader.rs
│ │ ├── fuzz_writer.rs
│ │ └── README.md
│ ├── layers
│ │ ├── async-backtrace
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── await-tree
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── capability-check
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── chaos
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── concurrent-limit
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── dtrace
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── fastmetrics
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── fastrace
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── foyer
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── hotpath
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── immutable-index
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── logging
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── metrics
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── mime-guess
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── observe-metrics-common
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── otelmetrics
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── oteltrace
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── prometheus
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── prometheus-client
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── retry
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── route
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── tail-cut
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── throttle
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── timeout
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ └── tracing
│ │ ├── Cargo.toml
│ │ └── src
│ │ └── lib.rs
│ ├── LICENSE
│ ├── README.md
│ ├── services
│ │ ├── aliyun-drive
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── alluxio
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── azblob
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── azdls
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── azfile
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── azure-common
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ └── lib.rs
│ │ ├── b2
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── cacache
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── lib.rs
│ │ │ └── writer.rs
│ │ ├── cloudflare-kv
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ ├── model.rs
│ │ │ └── writer.rs
│ │ ├── compfs
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ ├── reader.rs
│ │ │ └── writer.rs
│ │ ├── cos
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── d1
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── model.rs
│ │ │ └── writer.rs
│ │ ├── dashmap
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── dbfs
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── dropbox
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── builder.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── etcd
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── foundationdb
│ │ │ ├── build.rs
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── lib.rs
│ │ │ └── writer.rs
│ │ ├── fs
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ ├── reader.rs
│ │ │ └── writer.rs
│ │ ├── ftp
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── err.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ ├── reader.rs
│ │ │ └── writer.rs
│ │ ├── gcs
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ ├── uri.rs
│ │ │ └── writer.rs
│ │ ├── gdrive
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── builder.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── ghac
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ └── writer.rs
│ │ ├── github
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ ├── mod.rs
│ │ │ └── writer.rs
│ │ ├── gridfs
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── lib.rs
│ │ │ └── writer.rs
│ │ ├── hdfs
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ ├── reader.rs
│ │ │ └── writer.rs
│ │ ├── hdfs-native
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ ├── reader.rs
│ │ │ └── writer.rs
│ │ ├── http
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ └── lib.rs
│ │ ├── huggingface
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ └── lister.rs
│ │ ├── ipfs
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── ipld.rs
│ │ │ └── lib.rs
│ │ ├── ipmfs
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── builder.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── koofr
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── lakefs
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── memcached
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── binary.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── lib.rs
│ │ │ └── writer.rs
│ │ ├── mini_moka
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── moka
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── mongodb
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── lib.rs
│ │ │ └── writer.rs
│ │ ├── monoiofs
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── lib.rs
│ │ │ ├── reader.rs
│ │ │ └── writer.rs
│ │ ├── mysql
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── lib.rs
│ │ │ └── writer.rs
│ │ ├── obs
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── onedrive
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── builder.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── graph_model.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── opfs
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ └── utils.rs
│ │ ├── oss
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── pcloud
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── persy
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── lib.rs
│ │ │ └── writer.rs
│ │ ├── postgresql
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── lib.rs
│ │ │ └── writer.rs
│ │ ├── redb
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── lib.rs
│ │ │ └── writer.rs
│ │ ├── redis
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── delete.rs
│ │ │ ├── docs.md
│ │ │ ├── lib.rs
│ │ │ └── writer.rs
│ │ ├── rocksdb
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── s3
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── compatible_services.md
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ ├── mod.rs
│ │ │ └── writer.rs
│ │ ├── seafile
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── sftp
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ ├── reader.rs
│ │ │ ├── utils.rs
│ │ │ └── writer.rs
│ │ ├── sled
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── sqlite
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── lib.rs
│ │ │ └── writer.rs
│ │ ├── surrealdb
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── lib.rs
│ │ │ └── writer.rs
│ │ ├── swift
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── compatible_services.md
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── tikv
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── lib.rs
│ │ │ └── writer.rs
│ │ ├── upyun
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── vercel-artifacts
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── builder.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ └── writer.rs
│ │ ├── vercel-blob
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── webdav
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ └── writer.rs
│ │ ├── webhdfs
│ │ │ ├── Cargo.toml
│ │ │ └── src
│ │ │ ├── backend.rs
│ │ │ ├── config.rs
│ │ │ ├── core.rs
│ │ │ ├── deleter.rs
│ │ │ ├── docs.md
│ │ │ ├── error.rs
│ │ │ ├── lib.rs
│ │ │ ├── lister.rs
│ │ │ ├── message.rs
│ │ │ └── writer.rs
│ │ └── yandex-disk
│ │ ├── Cargo.toml
│ │ └── src
│ │ ├── backend.rs
│ │ ├── config.rs
│ │ ├── core.rs
│ │ ├── deleter.rs
│ │ ├── docs.md
│ │ ├── error.rs
│ │ ├── lib.rs
│ │ ├── lister.rs
│ │ └── writer.rs
│ ├── src
│ │ └── lib.rs
│ ├── testkit
│ │ ├── Cargo.toml
│ │ └── src
│ │ ├── lib.rs
│ │ ├── read.rs
│ │ ├── utils.rs
│ │ └── write.rs
│ ├── tests
│ │ ├── behavior
│ │ │ ├── async_copy.rs
│ │ │ ├── async_create_dir.rs
│ │ │ ├── async_delete.rs
│ │ │ ├── async_list.rs
│ │ │ ├── async_presign.rs
│ │ │ ├── async_read.rs
│ │ │ ├── async_rename.rs
│ │ │ ├── async_stat.rs
│ │ │ ├── async_write.rs
│ │ │ ├── main.rs
│ │ │ ├── README.md
│ │ │ └── utils.rs
│ │ └── data
│ │ ├── normal_dir
│ │ │ └── .gitkeep
│ │ ├── normal_file.txt
│ │ ├── special_dir !@#$%^&()_+-=;',
│ │ │ └── .gitkeep
│ │ └── special_file !@#$%^&()_+-=;',.txt
│ ├── upgrade.md
│ └── users.md
├── deny.toml
├── DEPENDENCIES.md
├── dev
│ ├── Cargo.lock
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ ├── generate
│ │ ├── java.j2
│ │ ├── java.rs
│ │ ├── mod.rs
│ │ ├── parser.rs
│ │ ├── python.j2
│ │ └── python.rs
│ ├── main.rs
│ └── release
│ ├── mod.rs
│ └── package.rs
├── doap.rdf
├── fixtures
│ ├── alluxio
│ │ └── docker-compose-alluxio.yml
│ ├── azblob
│ │ └── docker-compose-azurite.yml
│ ├── data
│ │ ├── normal_dir
│ │ │ └── .gitkeep
│ │ ├── normal_file.txt
│ │ ├── special_dir !@#$%^&()_+-=;',
│ │ │ └── .gitkeep
│ │ └── special_file !@#$%^&()_+-=;',.txt
│ ├── etcd
│ │ ├── ca-key.pem
│ │ ├── ca.pem
│ │ ├── client-key.pem
│ │ ├── client.pem
│ │ ├── docker-compose-cluster.yml
│ │ ├── docker-compose-standalone-tls.yml
│ │ ├── docker-compose-standalone.yml
│ │ ├── server-key.pem
│ │ └── server.pem
│ ├── ftp
│ │ └── docker-compose-vsftpd.yml
│ ├── hdfs
│ │ ├── azurite-azblob-core-site.xml
│ │ ├── docker-compose-hdfs-cluster.yml
│ │ ├── gcs-core-site.xml
│ │ ├── hdfs-site.xml
│ │ └── minio-s3-core-site.xml
│ ├── http
│ │ ├── Caddyfile
│ │ ├── docker-compose-caddy.yml
│ │ ├── docker-compose-nginx.yml
│ │ └── nginx.conf
│ ├── libsql
│ │ ├── docker-compose-auth.yml
│ │ └── docker-compose.yml
│ ├── memcached
│ │ ├── docker-compose-memcached-with-auth.yml
│ │ └── docker-compose-memcached.yml
│ ├── mongodb
│ │ ├── docker-compose-basic-auth.yml
│ │ └── docker-compose-no-auth.yml
│ ├── mysql
│ │ ├── docker-compose.yml
│ │ └── init.sql
│ ├── postgresql
│ │ ├── docker-compose.yml
│ │ └── init.sql
│ ├── redis
│ │ ├── docker-compose-dragonfly.yml
│ │ ├── docker-compose-kvrocks.yml
│ │ ├── docker-compose-redis-cluster-tls.yml
│ │ ├── docker-compose-redis-cluster.yml
│ │ ├── docker-compose-redis-tls.yml
│ │ ├── docker-compose-redis.yml
│ │ └── ssl
│ │ ├── .gitignore
│ │ ├── ca.crt
│ │ ├── ca.key
│ │ ├── ca.srl
│ │ ├── README.md
│ │ ├── redis.crt
│ │ ├── redis.key
│ │ └── req.conf
│ ├── s3
│ │ ├── docker-compose-ceph-rados.yml
│ │ └── docker-compose-minio.yml
│ ├── seafile
│ │ └── docker-compose-seafile.yml
│ ├── sftp
│ │ ├── change_root_dir.sh
│ │ ├── docker-compose-sftp-with-default-root.yml
│ │ ├── docker-compose-sftp.yml
│ │ ├── health-check.sh
│ │ ├── test_ssh_key
│ │ └── test_ssh_key.pub
│ ├── sqlite
│ │ └── data.sql
│ ├── swift
│ │ ├── docker-compose-ceph-rados.yml
│ │ └── docker-compose-swift.yml
│ ├── tikv
│ │ ├── gen_cert.sh
│ │ ├── pd-tls.toml
│ │ ├── pd.toml
│ │ ├── ssl
│ │ │ ├── ca-key.pem
│ │ │ ├── ca.pem
│ │ │ ├── client-key.pem
│ │ │ ├── client.pem
│ │ │ ├── pd-server-key.pem
│ │ │ ├── pd-server.pem
│ │ │ ├── tikv-server-key.pem
│ │ │ └── tikv-server.pem
│ │ ├── tikv-tls.toml
│ │ └── tikv.toml
│ ├── webdav
│ │ ├── config
│ │ │ └── nginx
│ │ │ └── http.conf
│ │ ├── docker-compose-webdav-jfrog.yml
│ │ ├── docker-compose-webdav-nextcloud.yml
│ │ ├── docker-compose-webdav-owncloud.yml
│ │ ├── docker-compose-webdav-with-auth.yml
│ │ ├── docker-compose-webdav-with-empty-passwd.yml
│ │ ├── docker-compose-webdav.yml
│ │ └── health-check-nextcloud.sh
│ └── webhdfs
│ └── docker-compose-webhdfs.yml
├── justfile
├── LICENSE
├── licenserc.toml
├── NOTICE
├── README.md
├── rust-toolchain.toml
├── rustfmt.toml
└── scripts
├── constants.py
├── dependencies.py
├── merge_local_staging.py
├── README.md
├── verify.py
└── workspace.py
```
# Files
--------------------------------------------------------------------------------
/core/core/src/types/operator/operator.rs:
--------------------------------------------------------------------------------
```rust
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use std::future::Future;
use futures::Stream;
use futures::StreamExt;
use futures::TryStreamExt;
use crate::operator_futures::*;
use crate::raw::oio::DeleteDyn;
use crate::raw::*;
use crate::types::delete::Deleter;
use crate::*;
/// The `Operator` serves as the entry point for all public asynchronous APIs.
///
/// For more details about the `Operator`, refer to the [`concepts`][crate::docs::concepts] section.
///
/// All cloned `Operator` instances share the same internal state, such as
/// `HttpClient` and `Runtime`. Some layers may modify the internal state of
/// the `Operator` too like inject logging and metrics for `HttpClient`.
///
/// ## Build
///
/// Users can initialize an `Operator` through the following methods:
///
/// - [`Operator::new`]: Creates an operator using a [`services`] builder, such as [`services::Memory`].
/// - [`Operator::from_config`]: Creates an operator using a [`services`] configuration, such as [`services::MemoryConfig`].
/// - [`Operator::from_iter`]: Creates an operator from an iterator of configuration key-value pairs.
///
/// ```
/// # use anyhow::Result;
/// use opendal_core::services::Memory;
/// use opendal_core::Operator;
/// async fn test() -> Result<()> {
/// // Build an `Operator` to start operating the storage.
/// let _: Operator = Operator::new(Memory::default())?.finish();
///
/// Ok(())
/// }
/// ```
///
/// ## Layer
///
/// After the operator is built, users can add the layers they need on top of it.
///
/// OpenDAL offers various layers for users to choose from. Visit [`layers`] for further details.
///
/// Please note that `Layer` can modify internal contexts such as `HttpClient`
/// and `Runtime` for all clones of given operator. Therefore, it is recommended
/// to add layers before interacting with the storage. Adding or duplicating
/// layers after accessing the storage may result in unexpected behavior.
///
/// ```
/// use opendal_core::layers::HttpClientLayer;
/// use opendal_core::raw::HttpClient;
/// use opendal_core::services::Memory;
/// use opendal_core::Operator;
/// use opendal_core::Result;
///
/// async fn test() -> Result<()> {
/// let op: Operator = Operator::new(Memory::default())?.finish();
///
/// // OpenDAL will replace the default HTTP client now.
/// let client = HttpClient::new()?;
/// let op = op.layer(HttpClientLayer::new(client));
///
/// Ok(())
/// }
/// ```
///
/// ## Operate
///
/// After the operator is built and the layers are added, users can start operating the storage.
///
/// The operator is `Send`, `Sync`, and `Clone`. It has no internal state, and all APIs only take
/// a `&self` reference, making it safe to share the operator across threads.
///
/// Operator provides a consistent API pattern for data operations. For reading operations, it exposes:
///
/// - [`Operator::read`]: Executes a read operation.
/// - [`Operator::read_with`]: Executes a read operation with additional options using the builder pattern.
/// - [`Operator::read_options`]: Executes a read operation with extra options provided via a [`options::ReadOptions`] struct.
/// - [`Operator::reader`]: Creates a reader for streaming data, allowing for flexible access.
/// - [`Operator::reader_with`]: Creates a reader with advanced options using the builder pattern.
/// - [`Operator::reader_options`]: Creates a reader with extra options provided via a [`options::ReadOptions`] struct.
///
/// The [`Reader`] created by [`Operator`] supports custom read control methods and can be converted
/// into [`futures::AsyncRead`] or [`futures::Stream`] for broader ecosystem compatibility.
///
/// ```no_run
/// use opendal_core::options;
/// use opendal_core::services;
/// use opendal_core::Operator;
/// use opendal_core::Result;
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// // Pick a builder and configure it.
/// let builder = services::Memory::default();
///
/// // Init an operator
/// let op = Operator::new(builder)?.finish();
///
/// // Fetch this file's metadata
/// let meta = op.stat("hello.txt").await?;
/// let length = meta.content_length();
///
/// // Read data from `hello.txt` with options.
/// let bs = op
/// .read_with("hello.txt")
/// .range(0..8 * 1024 * 1024)
/// .chunk(1024 * 1024)
/// .concurrent(4)
/// .await?;
///
/// // The same to:
/// let bs = op
/// .read_options("hello.txt", options::ReadOptions {
/// range: (0..8 * 1024 * 1024).into(),
/// chunk: Some(1024 * 1024),
/// concurrent: 4,
/// ..Default::default()
/// })
/// .await?;
///
/// Ok(())
/// }
/// ```
#[derive(Clone, Debug)]
pub struct Operator {
// accessor is what Operator delegates for
accessor: Accessor,
}
/// # Operator basic API.
impl Operator {
/// Fetch the internal accessor.
pub fn inner(&self) -> &Accessor {
&self.accessor
}
/// Convert inner accessor into operator.
pub fn from_inner(accessor: Accessor) -> Self {
Self { accessor }
}
/// Convert operator into inner accessor.
pub fn into_inner(self) -> Accessor {
self.accessor
}
/// Get information of underlying accessor.
///
/// # Examples
///
/// ```
/// # use std::sync::Arc;
/// # use anyhow::Result;
/// use opendal_core::Operator;
///
/// # async fn test(op: Operator) -> Result<()> {
/// let info = op.info();
/// # Ok(())
/// # }
/// ```
pub fn info(&self) -> OperatorInfo {
OperatorInfo::new(self.accessor.info())
}
/// Get the executor used by current operator.
pub fn executor(&self) -> Executor {
self.accessor.info().executor()
}
/// Update executor for the context.
///
/// All cloned `Operator` instances share the same internal state, such as
/// `HttpClient` and `Runtime`. Some layers may modify the internal state of
/// the `Operator` too like inject logging and metrics for `HttpClient`.
///
/// # Note
///
/// Tasks must be forwarded to the old executor after the update. Otherwise, features such as retry, timeout, and metrics may not function properly.
pub fn update_executor(&self, f: impl FnOnce(Executor) -> Executor) {
self.accessor.info().update_executor(f);
}
}
/// # Operator async API.
impl Operator {
/// Check if this operator can work correctly.
///
/// We will send a `list` request to path and return any errors we met.
///
/// ```
/// # use std::sync::Arc;
/// # use anyhow::Result;
/// use opendal_core::Operator;
///
/// # async fn test(op: Operator) -> Result<()> {
/// op.check().await?;
/// # Ok(())
/// # }
/// ```
pub async fn check(&self) -> Result<()> {
let mut ds = self.lister_with("/").limit(1).await?;
match ds.next().await {
Some(Err(e)) if e.kind() != ErrorKind::NotFound => Err(e),
_ => Ok(()),
}
}
/// Retrieve the metadata for the specified path.
///
/// # Notes
///
/// ## Extra Options
///
/// [`Operator::stat`] is a wrapper around [`Operator::stat_with`] that uses no additional options.
/// To specify extra options such as `if_match` and `if_none_match`, please use [`Operator::stat_with`] instead.
///
/// # Examples
///
/// ## Check if file exists
///
/// ```
/// # use anyhow::Result;
/// # use futures::io;
/// # use opendal_core::Operator;
/// use opendal_core::ErrorKind;
/// #
/// # async fn test(op: Operator) -> Result<()> {
/// if let Err(e) = op.stat("test").await {
/// if e.kind() == ErrorKind::NotFound {
/// println!("file not exist")
/// }
/// }
/// # Ok(())
/// # }
/// ```
pub async fn stat(&self, path: &str) -> Result<Metadata> {
self.stat_with(path).await
}
/// Retrieve the metadata of the specified path with additional options.
///
/// # Options
///
/// Check [`options::StatOptions`] for all available options.
///
/// # Examples
///
/// ## Get metadata while `ETag` matches
///
/// `stat_with` will
///
/// - return `Ok(metadata)` if `ETag` matches
/// - return `Err(error)` and `error.kind() == ErrorKind::ConditionNotMatch` if file exists but
/// `ETag` mismatch
/// - return `Err(err)` if other errors occur, for example, `NotFound`.
///
/// ```
/// # use anyhow::Result;
/// # use futures::io;
/// # use opendal_core::Operator;
/// use opendal_core::ErrorKind;
/// #
/// # async fn test(op: Operator) -> Result<()> {
/// if let Err(e) = op.stat_with("test").if_match("<etag>").await {
/// if e.kind() == ErrorKind::ConditionNotMatch {
/// println!("file exists, but etag mismatch")
/// }
/// if e.kind() == ErrorKind::NotFound {
/// println!("file not exist")
/// }
/// }
/// # Ok(())
/// # }
/// ```
pub fn stat_with(&self, path: &str) -> FutureStat<impl Future<Output = Result<Metadata>>> {
let path = normalize_path(path);
OperatorFuture::new(
self.inner().clone(),
path,
options::StatOptions::default(),
Self::stat_inner,
)
}
/// Retrieve the metadata of the specified path with additional options.
///
/// # Examples
///
/// ## Get metadata while `ETag` matches
///
/// `stat_with` will
///
/// - return `Ok(metadata)` if `ETag` matches
/// - return `Err(error)` and `error.kind() == ErrorKind::ConditionNotMatch` if file exists but
/// `ETag` mismatch
/// - return `Err(err)` if other errors occur, for example, `NotFound`.
///
/// ```
/// # use anyhow::Result;
/// # use futures::io;
/// # use opendal_core::Operator;
/// use opendal_core::options;
/// use opendal_core::ErrorKind;
/// #
/// # async fn test(op: Operator) -> Result<()> {
/// let res = op
/// .stat_options("test", options::StatOptions {
/// if_match: Some("<etag>".to_string()),
/// ..Default::default()
/// })
/// .await;
/// if let Err(e) = res {
/// if e.kind() == ErrorKind::ConditionNotMatch {
/// println!("file exists, but etag mismatch")
/// }
/// if e.kind() == ErrorKind::NotFound {
/// println!("file not exist")
/// }
/// }
/// # Ok(())
/// # }
/// ```
pub async fn stat_options(&self, path: &str, opts: options::StatOptions) -> Result<Metadata> {
let path = normalize_path(path);
Self::stat_inner(self.accessor.clone(), path, opts).await
}
#[inline]
async fn stat_inner(
acc: Accessor,
path: String,
opts: options::StatOptions,
) -> Result<Metadata> {
let rp = acc.stat(&path, opts.into()).await?;
Ok(rp.into_metadata())
}
/// Check whether this path exists.
///
/// # Example
///
/// ```
/// use anyhow::Result;
/// use futures::io;
/// use opendal_core::Operator;
///
/// async fn test(op: Operator) -> Result<()> {
/// let _ = op.exists("test").await?;
///
/// Ok(())
/// }
/// ```
pub async fn exists(&self, path: &str) -> Result<bool> {
let r = self.stat(path).await;
match r {
Ok(_) => Ok(true),
Err(err) if err.kind() == ErrorKind::NotFound => Ok(false),
Err(err) => Err(err),
}
}
/// Create a directory at the specified path.
///
/// # Notes
///
/// To specify that a path is a directory, you must include a trailing slash (/).
/// Omitting the trailing slash may cause OpenDAL to return a `NotADirectory` error.
///
/// # Behavior
///
/// - Creating a directory that already exists will succeed.
/// - Directory creation is always recursive, functioning like `mkdir -p`.
///
/// # Examples
///
/// ```
/// # use opendal_core::Result;
/// # use opendal_core::Operator;
/// # async fn test(op: Operator) -> Result<()> {
/// op.create_dir("path/to/dir/").await?;
/// # Ok(())
/// # }
/// ```
pub async fn create_dir(&self, path: &str) -> Result<()> {
let path = normalize_path(path);
if !validate_path(&path, EntryMode::DIR) {
return Err(Error::new(
ErrorKind::NotADirectory,
"the path trying to create should end with `/`",
)
.with_operation("create_dir")
.with_context("service", self.inner().info().scheme())
.with_context("path", &path));
}
self.inner().create_dir(&path, OpCreateDir::new()).await?;
Ok(())
}
/// Read the entire file into bytes from given path.
///
/// # Notes
///
/// ## Additional Options
///
/// [`Operator::read`] is a simplified method that does not support additional options. To access features like `range` and `if_match`, please use [`Operator::read_with`] or [`Operator::read_options`] instead.
///
/// ## Streaming Read
///
/// This function reads all content into memory at once. For more precise memory management or to read big file lazily, please use [`Operator::reader`].
///
/// # Examples
///
/// ```
/// # use opendal_core::Result;
/// # use opendal_core::Operator;
/// # use futures::TryStreamExt;
/// # async fn test(op: Operator) -> Result<()> {
/// let bs = op.read("path/to/file").await?;
/// # Ok(())
/// # }
/// ```
pub async fn read(&self, path: &str) -> Result<Buffer> {
self.read_options(path, options::ReadOptions::default())
.await
}
/// Read the entire file into bytes from given path with additional options.
///
/// # Notes
///
/// ## Streaming Read
///
/// This function reads all content into memory at once. For more precise memory management or to read big file lazily, please use [`Operator::reader`].
///
/// # Options
///
/// Visit [`options::ReadOptions`] for all available options.
///
/// # Examples
///
/// Read the first 10 bytes of a file:
///
/// ```
/// # use opendal_core::Result;
/// # use opendal_core::Operator;
/// # async fn test(op: Operator) -> Result<()> {
/// let bs = op.read_with("path/to/file").range(0..10).await?;
/// # Ok(())
/// # }
/// ```
pub fn read_with(&self, path: &str) -> FutureRead<impl Future<Output = Result<Buffer>>> {
let path = normalize_path(path);
OperatorFuture::new(
self.inner().clone(),
path,
options::ReadOptions::default(),
Self::read_inner,
)
}
/// Read the entire file into bytes from given path with additional options.
///
/// # Notes
///
/// ## Streaming Read
///
/// This function reads all content into memory at once. For more precise memory management or to read big file lazily, please use [`Operator::reader`].
///
/// # Examples
///
/// Read the first 10 bytes of a file:
///
/// ```
/// # use opendal_core::Result;
/// # use opendal_core::Operator;
/// use opendal_core::options;
/// # async fn test(op: Operator) -> Result<()> {
/// let bs = op
/// .read_options("path/to/file", options::ReadOptions {
/// range: (0..10).into(),
/// ..Default::default()
/// })
/// .await?;
/// # Ok(())
/// # }
/// ```
pub async fn read_options(&self, path: &str, opts: options::ReadOptions) -> Result<Buffer> {
let path = normalize_path(path);
Self::read_inner(self.inner().clone(), path, opts).await
}
#[inline]
async fn read_inner(acc: Accessor, path: String, opts: options::ReadOptions) -> Result<Buffer> {
if !validate_path(&path, EntryMode::FILE) {
return Err(
Error::new(ErrorKind::IsADirectory, "read path is a directory")
.with_operation("read")
.with_context("service", acc.info().scheme())
.with_context("path", &path),
);
}
let (args, opts) = opts.into();
let range = args.range();
let context = ReadContext::new(acc, path, args, opts);
let r = Reader::new(context);
let buf = r.read(range.to_range()).await?;
Ok(buf)
}
/// Create a new reader of given path.
///
/// # Notes
///
/// ## Extra Options
///
/// [`Operator::reader`] is a simplified method without any options. To use additional options such as `concurrent` or `if_match`, please use [`Operator::reader_with`] or [`Operator::reader_options`] instead.
///
/// # Examples
///
/// ```
/// # use opendal_core::Result;
/// # use opendal_core::Operator;
/// # use futures::TryStreamExt;
/// # async fn test(op: Operator) -> Result<()> {
/// let r = op.reader("path/to/file").await?;
/// // Read the first 10 bytes of the file
/// let data = r.read(0..10).await?;
/// # Ok(())
/// # }
/// ```
pub async fn reader(&self, path: &str) -> Result<Reader> {
self.reader_with(path).await
}
/// Create a new reader of given path with additional options.
///
/// # Options
///
/// Visit [`options::ReaderOptions`] for all available options.
///
/// # Examples
///
/// Create a reader with a specific version ID:
///
/// ```
/// # use opendal_core::Result;
/// # use opendal_core::Operator;
/// # async fn test(op: Operator) -> Result<()> {
/// let r = op.reader_with("path/to/file").version("version_id").await?;
/// // Read the first 10 bytes of the file
/// let data = r.read(0..10).await?;
/// # Ok(())
/// # }
/// ```
pub fn reader_with(&self, path: &str) -> FutureReader<impl Future<Output = Result<Reader>>> {
let path = normalize_path(path);
OperatorFuture::new(
self.inner().clone(),
path,
options::ReaderOptions::default(),
Self::reader_inner,
)
}
/// Create a new reader of given path with additional options.
///
/// # Examples
///
/// Create a reader with a specific version ID:
///
/// ```
/// # use opendal_core::Result;
/// # use opendal_core::Operator;
/// use opendal_core::options;
/// # async fn test(op: Operator) -> Result<()> {
/// let r = op
/// .reader_options("path/to/file", options::ReaderOptions {
/// version: Some("version_id".to_string()),
/// ..Default::default()
/// })
/// .await?;
/// // Read the first 10 bytes of the file
/// let data = r.read(0..10).await?;
/// # Ok(())
/// # }
/// ```
pub async fn reader_options(&self, path: &str, opts: options::ReaderOptions) -> Result<Reader> {
let path = normalize_path(path);
Self::reader_inner(self.inner().clone(), path, opts).await
}
/// Allow this unused async since we don't want
/// to change our public API.
#[allow(clippy::unused_async)]
#[inline]
async fn reader_inner(
acc: Accessor,
path: String,
options: options::ReaderOptions,
) -> Result<Reader> {
if !validate_path(&path, EntryMode::FILE) {
return Err(
Error::new(ErrorKind::IsADirectory, "read path is a directory")
.with_operation("Operator::reader")
.with_context("service", acc.info().scheme())
.with_context("path", path),
);
}
let (args, opts) = options.into();
let context = ReadContext::new(acc, path, args, opts);
Ok(Reader::new(context))
}
/// Write all data to the specified path at once.
///
/// # Notes
///
/// Visit [`performance::concurrent_write`][crate::docs::performance::concurrent_write] for more details on concurrent writes.
///
/// ## Extra Options
///
/// [`Operator::write`] is a simplified method that does not include additional options.
/// For advanced features such as `chunk` and `concurrent`, use [`Operator::write_with`] or [`Operator::write_options`] instead.
///
/// ## Streaming Write
///
/// This method executes a single bulk write operation. For more precise memory management
/// or to write data in a streaming fashion, use [`Operator::writer`] instead.
///
/// ## Multipart Uploads
///
/// OpenDAL offers multipart upload capabilities through the [`Writer`] abstraction,
/// automatically managing all upload details for you. You can fine-tune the upload process
/// by adjusting the `chunk` size and the number of `concurrent` operations using [`Operator::writer_with`].
///
/// # Examples
///
/// ```
/// # use opendal_core::Result;
/// # use opendal_core::Operator;
/// # use futures::StreamExt;
/// # use futures::SinkExt;
/// use bytes::Bytes;
///
/// # async fn test(op: Operator) -> Result<()> {
/// op.write("path/to/file", vec![0; 4096]).await?;
/// # Ok(())
/// # }
/// ```
pub async fn write(&self, path: &str, bs: impl Into<Buffer>) -> Result<Metadata> {
let bs = bs.into();
self.write_with(path, bs).await
}
/// Write all data to the specified path at once with additional options.
///
/// # Notes
///
/// Visit [`performance::concurrent_write`][crate::docs::performance::concurrent_write] for more details on concurrent writes.
///
/// ## Streaming Write
///
/// This method executes a single bulk write operation. For more precise memory management
/// or to write data in a streaming fashion, use [`Operator::writer`] instead.
///
/// ## Multipart Uploads
///
/// OpenDAL offers multipart upload capabilities through the [`Writer`] abstraction,
/// automatically managing all upload details for you. You can fine-tune the upload process
/// by adjusting the `chunk` size and the number of `concurrent` operations using [`Operator::writer_with`].
///
/// # Options
///
/// Visit [`options::WriteOptions`] for all available options.
///
/// # Examples
///
/// Write data to a file only when it does not already exist:
///
/// ```
/// # use opendal_core::Result;
/// # use opendal_core::Operator;
/// use bytes::Bytes;
///
/// # async fn test(op: Operator) -> Result<()> {
/// let _ = op
/// .write_with("path/to/file", vec![0; 4096])
/// .if_not_exists(true)
/// .await?;
/// # Ok(())
/// # }
/// ```
pub fn write_with(
&self,
path: &str,
bs: impl Into<Buffer>,
) -> FutureWrite<impl Future<Output = Result<Metadata>>> {
let path = normalize_path(path);
let bs = bs.into();
OperatorFuture::new(
self.inner().clone(),
path,
(options::WriteOptions::default(), bs),
|inner, path, (opts, bs)| Self::write_inner(inner, path, bs, opts),
)
}
/// Write all data to the specified path at once with additional options.
///
/// # Notes
///
/// Visit [`performance::concurrent_write`][crate::docs::performance::concurrent_write] for more details on concurrent writes.
///
/// ## Streaming Write
///
/// This method executes a single bulk write operation. For more precise memory management
/// or to write data in a streaming fashion, use [`Operator::writer`] instead.
///
/// ## Multipart Uploads
///
/// OpenDAL offers multipart upload capabilities through the [`Writer`] abstraction,
/// automatically managing all upload details for you. You can fine-tune the upload process
/// by adjusting the `chunk` size and the number of `concurrent` operations using [`Operator::writer_with`].
///
/// # Examples
///
/// Write data to a file only when it does not already exist:
///
/// ```
/// # use opendal_core::Result;
/// # use opendal_core::Operator;
/// use opendal_core::options;
///
/// # async fn test(op: Operator) -> Result<()> {
/// let _ = op
/// .write_options("path/to/file", vec![0; 4096], options::WriteOptions {
/// if_not_exists: true,
/// ..Default::default()
/// })
/// .await?;
/// # Ok(())
/// # }
/// ```
pub async fn write_options(
&self,
path: &str,
bs: impl Into<Buffer>,
opts: options::WriteOptions,
) -> Result<Metadata> {
let path = normalize_path(path);
Self::write_inner(self.inner().clone(), path, bs.into(), opts).await
}
#[inline]
async fn write_inner(
acc: Accessor,
path: String,
bs: Buffer,
opts: options::WriteOptions,
) -> Result<Metadata> {
if !validate_path(&path, EntryMode::FILE) {
return Err(
Error::new(ErrorKind::IsADirectory, "write path is a directory")
.with_operation("Operator::write")
.with_context("service", acc.info().scheme())
.with_context("path", &path),
);
}
let (args, opts) = opts.into();
let context = WriteContext::new(acc, path, args, opts);
let mut w = Writer::new(context).await?;
w.write(bs).await?;
w.close().await
}
/// Create a new writer of given path.
///
/// # Notes
///
/// ## Writer Features
///
/// The writer provides several powerful capabilities:
/// - Streaming writes for continuous data transfer
/// - Automatic multipart upload handling
/// - Memory-efficient chunk-based writing
///
/// ## Extra Options
///
/// [`Operator::writer`] is a simplified version that does not include additional options.
/// For advanced features such as `chunk` and `concurrent`, use [`Operator::writer_with`] or [`Operator::writer_options`] instead.
///
/// # Examples
///
/// ```
/// # use opendal_core::Result;
/// # use opendal_core::Operator;
/// use bytes::Bytes;
///
/// # async fn test(op: Operator) -> Result<()> {
/// let mut w = op.writer("path/to/file").await?;
/// w.write(vec![0; 4096]).await?;
/// w.write(vec![1; 4096]).await?;
/// w.close().await?;
/// # Ok(())
/// # }
/// ```
pub async fn writer(&self, path: &str) -> Result<Writer> {
self.writer_with(path).await
}
/// Create a new writer of given path with additional options.
///
/// # Notes
///
/// ## Writer Features
///
/// The writer provides several powerful capabilities:
/// - Streaming writes for continuous data transfer
/// - Automatic multipart upload handling
/// - Memory-efficient chunk-based writing
///
/// ## Chunk Size Handling
///
/// Storage services often have specific requirements for chunk sizes:
/// - Services like `s3` may return `EntityTooSmall` errors for undersized chunks
/// - Using small chunks in cloud storage services can lead to increased costs
///
/// OpenDAL automatically determines optimal chunk sizes based on the service's
/// [Capability](crate::types::Capability). However, you can override this by explicitly
/// setting the `chunk` parameter.
///
/// Visit [`performance::concurrent_write`][crate::docs::performance::concurrent_write] for more details on concurrent writes.
///
/// # Examples
///
/// ```
/// # use opendal_core::Result;
/// # use opendal_core::Operator;
/// use bytes::Bytes;
///
/// # async fn test(op: Operator) -> Result<()> {
/// let mut w = op
/// .writer_with("path/to/file")
/// .chunk(4 * 1024 * 1024)
/// .concurrent(8)
/// .await?;
/// w.write(vec![0; 4096]).await?;
/// w.write(vec![1; 4096]).await?;
/// w.close().await?;
/// # Ok(())
/// # }
/// ```
pub fn writer_with(&self, path: &str) -> FutureWriter<impl Future<Output = Result<Writer>>> {
let path = normalize_path(path);
OperatorFuture::new(
self.inner().clone(),
path,
options::WriteOptions::default(),
Self::writer_inner,
)
}
/// Create a new writer of given path with additional options.
///
/// # Notes
///
/// ## Writer Features
///
/// The writer provides several powerful capabilities:
/// - Streaming writes for continuous data transfer
/// - Automatic multipart upload handling
/// - Memory-efficient chunk-based writing
///
/// ## Chunk Size Handling
///
/// Storage services often have specific requirements for chunk sizes:
/// - Services like `s3` may return `EntityTooSmall` errors for undersized chunks
/// - Using small chunks in cloud storage services can lead to increased costs
///
/// OpenDAL automatically determines optimal chunk sizes based on the service's
/// [Capability](crate::types::Capability). However, you can override this by explicitly
/// setting the `chunk` parameter.
///
/// Visit [`performance::concurrent_write`][crate::docs::performance::concurrent_write] for more details on concurrent writes.
///
/// # Examples
///
/// Write data to a file in 4MiB chunk size and at 8 concurrency:
///
/// ```
/// # use opendal_core::Result;
/// # use opendal_core::Operator;
/// use bytes::Bytes;
///
/// # async fn test(op: Operator) -> Result<()> {
/// let mut w = op
/// .writer_with("path/to/file")
/// .chunk(4 * 1024 * 1024)
/// .concurrent(8)
/// .await?;
/// w.write(vec![0; 4096]).await?;
/// w.write(vec![1; 4096]).await?;
/// w.close().await?;
/// # Ok(())
/// # }
/// ```
pub async fn writer_options(&self, path: &str, opts: options::WriteOptions) -> Result<Writer> {
let path = normalize_path(path);
Self::writer_inner(self.inner().clone(), path, opts).await
}
#[inline]
async fn writer_inner(
acc: Accessor,
path: String,
opts: options::WriteOptions,
) -> Result<Writer> {
if !validate_path(&path, EntryMode::FILE) {
return Err(
Error::new(ErrorKind::IsADirectory, "write path is a directory")
.with_operation("Operator::writer")
.with_context("service", acc.info().scheme())
.with_context("path", &path),
);
}
let (args, opts) = opts.into();
let context = WriteContext::new(acc, path, args, opts);
let w = Writer::new(context).await?;
Ok(w)
}
/// Copy a file from `from` to `to`.
///
/// # Notes
///
/// - `from` and `to` must be a file.
/// - `to` will be overwritten if it exists.
/// - If `from` and `to` are the same, an `IsSameFile` error will occur.
/// - `copy` is idempotent. For same `from` and `to` input, the result will be the same.
///
/// # Examples
///
/// ```
/// # use opendal_core::Result;
/// # use opendal_core::Operator;
///
/// # async fn test(op: Operator) -> Result<()> {
/// op.copy("path/to/file", "path/to/file2").await?;
/// # Ok(())
/// # }
/// ```
pub async fn copy(&self, from: &str, to: &str) -> Result<()> {
let from = normalize_path(from);
if !validate_path(&from, EntryMode::FILE) {
return Err(
Error::new(ErrorKind::IsADirectory, "from path is a directory")
.with_operation("Operator::copy")
.with_context("service", self.info().scheme())
.with_context("from", from),
);
}
let to = normalize_path(to);
if !validate_path(&to, EntryMode::FILE) {
return Err(
Error::new(ErrorKind::IsADirectory, "to path is a directory")
.with_operation("Operator::copy")
.with_context("service", self.info().scheme())
.with_context("to", to),
);
}
if from == to {
return Err(
Error::new(ErrorKind::IsSameFile, "from and to paths are same")
.with_operation("Operator::copy")
.with_context("service", self.info().scheme())
.with_context("from", from)
.with_context("to", to),
);
}
self.inner().copy(&from, &to, OpCopy::new()).await?;
Ok(())
}
/// Copy a file from `from` to `to` with additional options.
///
/// # Notes
///
/// - `from` and `to` must be a file.
/// - If `from` and `to` are the same, an `IsSameFile` error will occur.
/// - `copy` is idempotent. For same `from` and `to` input, the result will be the same.
///
/// # Options
///
/// Visit [`options::CopyOptions`] for all available options.
///
/// # Examples
///
/// Copy a file only if the destination doesn't exist:
///
/// ```
/// # use opendal_core::Result;
/// # use opendal_core::Operator;
///
/// # async fn test(op: Operator) -> Result<()> {
/// op.copy_with("path/to/file", "path/to/file2")
/// .if_not_exists(true)
/// .await?;
/// # Ok(())
/// # }
/// ```
pub fn copy_with(&self, from: &str, to: &str) -> FutureCopy<impl Future<Output = Result<()>>> {
let from = normalize_path(from);
let to = normalize_path(to);
OperatorFuture::new(
self.inner().clone(),
from,
(options::CopyOptions::default(), to),
Self::copy_inner,
)
}
/// Copy a file from `from` to `to` with additional options.
///
/// # Notes
///
/// - `from` and `to` must be a file.
/// - If `from` and `to` are the same, an `IsSameFile` error will occur.
/// - `copy` is idempotent. For same `from` and `to` input, the result will be the same.
///
/// # Options
///
/// Check [`options::CopyOptions`] for all available options.
///
/// # Examples
///
/// Copy a file only if the destination doesn't exist:
///
/// ```
/// # use opendal_core::Result;
/// # use opendal_core::Operator;
/// # use opendal_core::options::CopyOptions;
///
/// # async fn test(op: Operator) -> Result<()> {
/// let mut opts = CopyOptions::default();
/// opts.if_not_exists = true;
/// op.copy_options("path/to/file", "path/to/file2", opts).await?;
/// # Ok(())
/// # }
/// ```
pub async fn copy_options(
&self,
from: &str,
to: &str,
opts: impl Into<options::CopyOptions>,
) -> Result<()> {
let from = normalize_path(from);
let to = normalize_path(to);
let opts = opts.into();
Self::copy_inner(self.inner().clone(), from, (opts, to)).await
}
async fn copy_inner(
acc: Accessor,
from: String,
(opts, to): (options::CopyOptions, String),
) -> Result<()> {
if !validate_path(&from, EntryMode::FILE) {
return Err(
Error::new(ErrorKind::IsADirectory, "from path is a directory")
.with_operation("Operator::copy")
.with_context("service", acc.info().scheme())
.with_context("from", from),
);
}
if !validate_path(&to, EntryMode::FILE) {
return Err(
Error::new(ErrorKind::IsADirectory, "to path is a directory")
.with_operation("Operator::copy")
.with_context("service", acc.info().scheme())
.with_context("to", to),
);
}
if from == to {
return Err(
Error::new(ErrorKind::IsSameFile, "from and to paths are same")
.with_operation("Operator::copy")
.with_context("service", acc.info().scheme())
.with_context("from", &from)
.with_context("to", &to),
);
}
let mut op = OpCopy::new();
if opts.if_not_exists {
op = op.with_if_not_exists(true);
}
acc.copy(&from, &to, op).await.map(|_| ())
}
/// Rename a file from `from` to `to`.
///
/// # Notes
///
/// - `from` and `to` must be a file.
/// - `to` will be overwritten if it exists.
/// - If `from` and `to` are the same, an `IsSameFile` error will occur.
///
/// # Examples
///
/// ```
/// # use opendal_core::Result;
/// # use opendal_core::Operator;
///
/// # async fn test(op: Operator) -> Result<()> {
/// op.rename("path/to/file", "path/to/file2").await?;
/// # Ok(())
/// # }
/// ```
pub async fn rename(&self, from: &str, to: &str) -> Result<()> {
let from = normalize_path(from);
if !validate_path(&from, EntryMode::FILE) {
return Err(
Error::new(ErrorKind::IsADirectory, "from path is a directory")
.with_operation("Operator::move_")
.with_context("service", self.info().scheme())
.with_context("from", from),
);
}
let to = normalize_path(to);
if !validate_path(&to, EntryMode::FILE) {
return Err(
Error::new(ErrorKind::IsADirectory, "to path is a directory")
.with_operation("Operator::move_")
.with_context("service", self.info().scheme())
.with_context("to", to),
);
}
if from == to {
return Err(
Error::new(ErrorKind::IsSameFile, "from and to paths are same")
.with_operation("Operator::move_")
.with_context("service", self.info().scheme())
.with_context("from", from)
.with_context("to", to),
);
}
self.inner().rename(&from, &to, OpRename::new()).await?;
Ok(())
}
/// Delete the given path.
///
/// # Notes
///
/// - Deleting a file that does not exist won't return errors.
///
/// # Examples
///
/// ```
/// # use anyhow::Result;
/// # use futures::io;
/// # use opendal_core::Operator;
/// # async fn test(op: Operator) -> Result<()> {
/// op.delete("test").await?;
/// # Ok(())
/// # }
/// ```
pub async fn delete(&self, path: &str) -> Result<()> {
self.delete_with(path).await
}
/// Delete the given path with additional options.
///
/// # Notes
///
/// - Deleting a file that does not exist won't return errors.
///
/// # Options
///
/// Visit [`options::DeleteOptions`] for all available options.
///
/// # Examples
///
/// Delete a specific version of a file:
///
/// ```
/// # use opendal_core::Result;
/// # use opendal_core::Operator;
///
/// # async fn test(op: Operator, version: &str) -> Result<()> {
/// op.delete_with("path/to/file").version(version).await?;
/// # Ok(())
/// # }
/// ```
pub fn delete_with(&self, path: &str) -> FutureDelete<impl Future<Output = Result<()>>> {
let path = normalize_path(path);
OperatorFuture::new(
self.inner().clone(),
path,
options::DeleteOptions::default(),
Self::delete_inner,
)
}
/// Delete the given path with additional options.
///
/// # Notes
///
/// - Deleting a file that does not exist won't return errors.
///
/// # Examples
///
/// Delete a specific version of a file:
///
/// ```
/// # use opendal_core::Result;
/// # use opendal_core::Operator;
/// use opendal_core::options;
///
/// # async fn test(op: Operator, version: &str) -> Result<()> {
/// op.delete_options("path/to/file", options::DeleteOptions {
/// version: Some(version.to_string()),
/// ..Default::default()
/// })
/// .await?;
/// # Ok(())
/// # }
/// ```
pub async fn delete_options(&self, path: &str, opts: options::DeleteOptions) -> Result<()> {
let path = normalize_path(path);
Self::delete_inner(self.inner().clone(), path, opts).await
}
async fn delete_inner(acc: Accessor, path: String, opts: options::DeleteOptions) -> Result<()> {
let (_, mut deleter) = acc.delete_dyn().await?;
let args = opts.into();
deleter.delete_dyn(&path, args).await?;
deleter.close_dyn().await?;
Ok(())
}
/// Delete an infallible iterator of paths.
///
/// Also see:
///
/// - [`Operator::delete_try_iter`]: delete a fallible iterator of paths.
/// - [`Operator::delete_stream`]: delete an infallible stream of paths.
/// - [`Operator::delete_try_stream`]: delete a fallible stream of paths.
pub async fn delete_iter<I, D>(&self, iter: I) -> Result<()>
where
I: IntoIterator<Item = D>,
D: IntoDeleteInput,
{
let mut deleter = self.deleter().await?;
deleter.delete_iter(iter).await?;
deleter.close().await?;
Ok(())
}
/// Delete a fallible iterator of paths.
///
/// Also see:
///
/// - [`Operator::delete_iter`]: delete an infallible iterator of paths.
/// - [`Operator::delete_stream`]: delete an infallible stream of paths.
/// - [`Operator::delete_try_stream`]: delete a fallible stream of paths.
pub async fn delete_try_iter<I, D>(&self, try_iter: I) -> Result<()>
where
I: IntoIterator<Item = Result<D>>,
D: IntoDeleteInput,
{
let mut deleter = self.deleter().await?;
deleter.delete_try_iter(try_iter).await?;
deleter.close().await?;
Ok(())
}
/// Delete an infallible stream of paths.
///
/// Also see:
///
/// - [`Operator::delete_iter`]: delete an infallible iterator of paths.
/// - [`Operator::delete_try_iter`]: delete a fallible iterator of paths.
/// - [`Operator::delete_try_stream`]: delete a fallible stream of paths.
pub async fn delete_stream<S, D>(&self, stream: S) -> Result<()>
where
S: Stream<Item = D>,
D: IntoDeleteInput,
{
let mut deleter = self.deleter().await?;
deleter.delete_stream(stream).await?;
deleter.close().await?;
Ok(())
}
/// Delete a fallible stream of paths.
///
/// Also see:
///
/// - [`Operator::delete_iter`]: delete an infallible iterator of paths.
/// - [`Operator::delete_try_iter`]: delete a fallible iterator of paths.
/// - [`Operator::delete_stream`]: delete an infallible stream of paths.
pub async fn delete_try_stream<S, D>(&self, try_stream: S) -> Result<()>
where
S: Stream<Item = Result<D>>,
D: IntoDeleteInput,
{
let mut deleter = self.deleter().await?;
deleter.delete_try_stream(try_stream).await?;
deleter.close().await?;
Ok(())
}
/// Create a [`Deleter`] to continuously remove content from storage.
///
/// It leverages batch deletion capabilities provided by storage services for efficient removal.
///
/// Users can have more control over the deletion process by using [`Deleter`] directly.
pub async fn deleter(&self) -> Result<Deleter> {
Deleter::create(self.inner().clone()).await
}
/// Remove the path and all nested dirs and files recursively.
///
/// # Deprecated
///
/// This method is deprecated since v0.55.0. Use [`Operator::delete_with`] with
/// `recursive(true)` instead.
///
/// ## Migration Example
///
/// Instead of:
/// ```ignore
/// op.remove_all("path/to/dir").await?;
/// ```
///
/// Use:
/// ```ignore
/// op.delete_with("path/to/dir").recursive(true).await?;
/// ```
///
/// # Notes
///
/// If underlying services support delete in batch, we will use batch
/// delete instead.
///
/// # Examples
///
/// ```
/// # use anyhow::Result;
/// # use futures::io;
/// # use opendal_core::Operator;
/// #
/// # async fn test(op: Operator) -> Result<()> {
/// op.remove_all("path/to/dir").await?;
/// # Ok(())
/// # }
/// ```
#[deprecated(
since = "0.55.0",
note = "Use `delete_with` with `recursive(true)` instead"
)]
pub async fn remove_all(&self, path: &str) -> Result<()> {
self.delete_with(path).recursive(true).await
}
/// List entries whose paths start with the given prefix `path`.
///
/// # Semantics
///
/// - Listing is **prefix-based**. It does not require the parent directory to exist.
/// - If `path` itself exists (file or dir), it will be returned as an entry in addition to any prefixed children.
/// - If `path` is absent but deeper objects exist (e.g. `path/child`), the list succeeds and returns those prefixed entries instead of an error.
///
/// ## Streaming List
///
/// This function materializes the entire list into memory. For large listings, prefer [`Operator::lister`] to stream entries.
///
/// # Examples
///
/// This example will list all entries under the dir `path/to/dir/`.
///
/// ```
/// # use anyhow::Result;
/// use opendal_core::EntryMode;
/// use opendal_core::Operator;
/// # async fn test(op: Operator) -> Result<()> {
/// let mut entries = op.list("path/to/dir/").await?;
/// for entry in entries {
/// match entry.metadata().mode() {
/// EntryMode::FILE => {
/// println!("Handling file")
/// }
/// EntryMode::DIR => {
/// println!("Handling dir {}", entry.path())
/// }
/// EntryMode::Unknown => continue,
/// }
/// }
/// # Ok(())
/// # }
/// ```
pub async fn list(&self, path: &str) -> Result<Vec<Entry>> {
self.list_with(path).await
}
/// List entries whose paths start with the given prefix `path` with additional options.
///
/// # Semantics
///
/// Inherits the prefix semantics described in [`Operator::list`]: returns `path` itself if it exists and tolerates missing parents when prefixed objects exist.
///
/// # Notes
///
/// ## Streaming List
///
/// This function materializes the entire list into memory. For large listings, prefer [`Operator::lister`] to stream entries.
///
/// ## Options
///
/// See [`options::ListOptions`] for the full set. Common knobs:
/// - Traversal: `recursive` (default `false`) toggles depth-first listing under the prefix.
/// - Pagination: `limit` and `start_after` tune page size and resume positions (backend dependent).
/// - Versioning: `versions` / `deleted` ask versioned backends to return extra entries.
///
/// # Examples
///
/// This example will list all entries recursively under the prefix `path/to/prefix`.
///
/// ```
/// # use anyhow::Result;
/// use opendal_core::EntryMode;
/// use opendal_core::Operator;
/// # async fn test(op: Operator) -> Result<()> {
/// let mut entries = op.list_with("path/to/prefix").recursive(true).await?;
/// for entry in entries {
/// match entry.metadata().mode() {
/// EntryMode::FILE => {
/// println!("Handling file")
/// }
/// EntryMode::DIR => {
/// println!("Handling dir like start a new list via meta.path()")
/// }
/// EntryMode::Unknown => continue,
/// }
/// }
/// # Ok(())
/// # }
/// ```
pub fn list_with(&self, path: &str) -> FutureList<impl Future<Output = Result<Vec<Entry>>>> {
let path = normalize_path(path);
OperatorFuture::new(
self.inner().clone(),
path,
options::ListOptions::default(),
Self::list_inner,
)
}
/// List entries whose paths start with the given prefix `path` using explicit options.
///
/// # Semantics
///
/// Same prefix behavior as [`Operator::list`]: returns `path` itself if present and tolerates missing parents when prefixed objects exist.
///
/// # Options
///
/// Accepts [`options::ListOptions`] (see field docs for meaning).
///
/// ## Streaming List
///
/// Materializes the entire list; use [`Operator::lister`] to stream large result sets.
///
/// # Examples
///
/// This example will list all entries recursively under the prefix `path/to/prefix`.
///
/// ```
/// # use anyhow::Result;
/// use opendal_core::options;
/// use opendal_core::EntryMode;
/// use opendal_core::Operator;
/// # async fn test(op: Operator) -> Result<()> {
/// let mut entries = op
/// .list_options("path/to/prefix", options::ListOptions {
/// recursive: true,
/// ..Default::default()
/// })
/// .await?;
/// for entry in entries {
/// match entry.metadata().mode() {
/// EntryMode::FILE => {
/// println!("Handling file")
/// }
/// EntryMode::DIR => {
/// println!("Handling dir like start a new list via meta.path()")
/// }
/// EntryMode::Unknown => continue,
/// }
/// }
/// # Ok(())
/// # }
/// ```
pub async fn list_options(&self, path: &str, opts: options::ListOptions) -> Result<Vec<Entry>> {
let path = normalize_path(path);
Self::list_inner(self.inner().clone(), path, opts).await
}
#[inline]
async fn list_inner(
acc: Accessor,
path: String,
opts: options::ListOptions,
) -> Result<Vec<Entry>> {
let args = opts.into();
let lister = Lister::create(acc, &path, args).await?;
lister.try_collect().await
}
/// Create a streaming lister for entries whose paths start with the given prefix `path`.
///
/// # Semantics
///
/// Shares the same prefix semantics as [`Operator::list`]: the parent directory is not required to exist; the entry for `path` is yielded if present; missing parents with deeper objects are accepted.
///
/// # Options
///
/// Takes the same [`options::ListOptions`] as [`list_with`](Operator::list_with): traversal (`recursive`), pagination (`limit`, `start_after`), and versioning (`versions`, `deleted`).
///
/// # Examples
///
/// ```
/// # use anyhow::Result;
/// # use futures::io;
/// use futures::TryStreamExt;
/// use opendal_core::EntryMode;
/// use opendal_core::Operator;
/// # async fn test(op: Operator) -> Result<()> {
/// let mut ds = op.lister("path/to/dir/").await?;
/// while let Some(mut de) = ds.try_next().await? {
/// match de.metadata().mode() {
/// EntryMode::FILE => {
/// println!("Handling file")
/// }
/// EntryMode::DIR => {
/// println!("Handling dir like start a new list via meta.path()")
/// }
/// EntryMode::Unknown => continue,
/// }
/// }
/// # Ok(())
/// # }
/// ```
pub async fn lister(&self, path: &str) -> Result<Lister> {
self.lister_with(path).await
}
/// Create a new lister to list entries that start with the given prefix `path` using additional options.
///
/// # Options
///
/// Same as [`lister_with`](Operator::lister_with); see [`options::ListOptions`] for traversal, pagination, and versioning knobs.
///
/// # Examples
///
/// ## List all files recursively
///
/// ```
/// # use anyhow::Result;
/// use futures::TryStreamExt;
/// use opendal_core::EntryMode;
/// use opendal_core::Operator;
/// # async fn test(op: Operator) -> Result<()> {
/// let mut lister = op.lister_with("path/to/dir/").recursive(true).await?;
/// while let Some(mut entry) = lister.try_next().await? {
/// match entry.metadata().mode() {
/// EntryMode::FILE => {
/// println!("Handling file {}", entry.path())
/// }
/// EntryMode::DIR => {
/// println!("Handling dir {}", entry.path())
/// }
/// EntryMode::Unknown => continue,
/// }
/// }
/// # Ok(())
/// # }
/// ```
pub fn lister_with(&self, path: &str) -> FutureLister<impl Future<Output = Result<Lister>>> {
let path = normalize_path(path);
OperatorFuture::new(
self.inner().clone(),
path,
options::ListOptions::default(),
Self::lister_inner,
)
}
/// Create a new lister to list entries that start with the given prefix `path` using additional options.
///
/// # Semantics
///
/// Inherits the prefix behavior of [`Operator::lister_with`].
///
/// # Options
///
/// Uses [`options::ListOptions`] to control traversal, pagination, and versioning.
///
/// # Examples
///
/// ## List all files recursively
///
/// ```
/// # use anyhow::Result;
/// use futures::TryStreamExt;
/// use opendal_core::options;
/// use opendal_core::EntryMode;
/// use opendal_core::Operator;
/// # async fn test(op: Operator) -> Result<()> {
/// let mut lister = op
/// .lister_options("path/to/dir/", options::ListOptions {
/// recursive: true,
/// ..Default::default()
/// })
/// .await?;
/// while let Some(mut entry) = lister.try_next().await? {
/// match entry.metadata().mode() {
/// EntryMode::FILE => {
/// println!("Handling file {}", entry.path())
/// }
/// EntryMode::DIR => {
/// println!("Handling dir {}", entry.path())
/// }
/// EntryMode::Unknown => continue,
/// }
/// }
/// # Ok(())
/// # }
/// ```
pub async fn lister_options(&self, path: &str, opts: options::ListOptions) -> Result<Lister> {
let path = normalize_path(path);
Self::lister_inner(self.inner().clone(), path, opts).await
}
#[inline]
async fn lister_inner(
acc: Accessor,
path: String,
opts: options::ListOptions,
) -> Result<Lister> {
let args = opts.into();
let lister = Lister::create(acc, &path, args).await?;
Ok(lister)
}
}
/// Operator presign API.
impl Operator {
/// Presign an operation for stat(head).
///
/// # Example
///
/// ```
/// use anyhow::Result;
/// use futures::io;
/// use opendal_core::Operator;
/// use std::time::Duration;
///
/// async fn test(op: Operator) -> Result<()> {
/// let signed_req = op.presign_stat("test",Duration::from_secs(3600)).await?;
/// let req = http::Request::builder()
/// .method(signed_req.method())
/// .uri(signed_req.uri())
/// .body(())?;
///
/// # Ok(())
/// # }
/// ```
pub async fn presign_stat(&self, path: &str, expire: Duration) -> Result<PresignedRequest> {
let path = normalize_path(path);
let op = OpPresign::new(OpStat::new(), expire);
let rp = self.inner().presign(&path, op).await?;
Ok(rp.into_presigned_request())
}
/// Presign an operation for stat(head).
///
/// # Example
///
/// ```
/// use anyhow::Result;
/// use futures::io;
/// use opendal_core::Operator;
/// use std::time::Duration;
///
/// async fn test(op: Operator) -> Result<()> {
/// let signed_req = op.presign_stat_with("test",Duration::from_secs(3600)).override_content_disposition("attachment; filename=\"othertext.txt\"").await?;
/// # Ok(())
/// # }
/// ```
pub fn presign_stat_with(
&self,
path: &str,
expire: Duration,
) -> FuturePresignStat<impl Future<Output = Result<PresignedRequest>>> {
let path = normalize_path(path);
OperatorFuture::new(
self.inner().clone(),
path,
(options::StatOptions::default(), expire),
Self::presign_stat_inner,
)
}
/// Presign an operation for stat(head) with additional options.
///
/// # Options
///
/// Visit [`options::StatOptions`] for all available options.
///
/// # Example
///
/// ```
/// use anyhow::Result;
/// use opendal_core::Operator;
/// use opendal_core::options;
/// use std::time::Duration;
///
/// async fn test(op: Operator) -> Result<()> {
/// let signed_req = op.presign_stat_options(
/// "test",
/// Duration::from_secs(3600),
/// options::StatOptions {
/// if_match: Some("<etag>".to_string()),
/// ..Default::default()
/// }
/// ).await?;
/// let req = http::Request::builder()
/// .method(signed_req.method())
/// .uri(signed_req.uri())
/// .body(())?;
///
/// # Ok(())
/// # }
/// ```
pub async fn presign_stat_options(
&self,
path: &str,
expire: Duration,
opts: options::StatOptions,
) -> Result<PresignedRequest> {
let path = normalize_path(path);
Self::presign_stat_inner(self.inner().clone(), path, (opts, expire)).await
}
#[inline]
async fn presign_stat_inner(
acc: Accessor,
path: String,
(opts, expire): (options::StatOptions, Duration),
) -> Result<PresignedRequest> {
let op = OpPresign::new(OpStat::from(opts), expire);
let rp = acc.presign(&path, op).await?;
Ok(rp.into_presigned_request())
}
/// Presign an operation for read.
///
/// # Notes
///
/// ## Extra Options
///
/// `presign_read` is a wrapper of [`Self::presign_read_with`] without any options. To use
/// extra options like `override_content_disposition`, please use [`Self::presign_read_with`] or
/// [`Self::presign_read_options] instead.
///
/// # Example
///
/// ```
/// use anyhow::Result;
/// use futures::io;
/// use opendal_core::Operator;
/// use std::time::Duration;
///
/// async fn test(op: Operator) -> Result<()> {
/// let signed_req = op.presign_read("test.txt", Duration::from_secs(3600)).await?;
/// # Ok(())
/// # }
/// ```
///
/// - `signed_req.method()`: `GET`
/// - `signed_req.uri()`: `https://s3.amazonaws.com/examplebucket/test.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=access_key_id/20130721/us-east-1/s3/aws4_request&X-Amz-Date=20130721T201207Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=<signature-value>`
/// - `signed_req.headers()`: `{ "host": "s3.amazonaws.com" }`
///
/// We can download this file via `curl` or other tools without credentials:
///
/// ```shell
/// curl "https://s3.amazonaws.com/examplebucket/test.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=access_key_id/20130721/us-east-1/s3/aws4_request&X-Amz-Date=20130721T201207Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=<signature-value>" -O /tmp/test.txt
/// ```
pub async fn presign_read(&self, path: &str, expire: Duration) -> Result<PresignedRequest> {
let path = normalize_path(path);
let op = OpPresign::new(OpRead::new(), expire);
let rp = self.inner().presign(&path, op).await?;
Ok(rp.into_presigned_request())
}
/// Presign an operation for read with extra options.
///
/// # Options
///
/// Visit [`options::ReadOptions`] for all available options.
///
/// # Example
///
/// ```
/// use std::time::Duration;
///
/// use anyhow::Result;
/// use futures::io;
/// use opendal_core::Operator;
///
/// async fn test(op: Operator) -> Result<()> {
/// let signed_req = op
/// .presign_read_with("test.txt", Duration::from_secs(3600))
/// .override_content_type("text/plain")
/// .await?;
/// Ok(())
/// }
/// ```
pub fn presign_read_with(
&self,
path: &str,
expire: Duration,
) -> FuturePresignRead<impl Future<Output = Result<PresignedRequest>>> {
let path = normalize_path(path);
OperatorFuture::new(
self.inner().clone(),
path,
(options::ReadOptions::default(), expire),
Self::presign_read_inner,
)
}
/// Presign an operation for read with additional options.
///
/// # Options
///
/// Visit [`options::ReadOptions`] for all available options.
///
/// # Example
///
/// ```
/// use anyhow::Result;
/// use opendal_core::Operator;
/// use opendal_core::options;
/// use std::time::Duration;
///
/// async fn test(op: Operator) -> Result<()> {
/// let signed_req = op.presign_read_options(
/// "file",
/// Duration::from_secs(3600),
/// options::ReadOptions {
/// override_content_disposition: Some("attachment; filename=\"othertext.txt\"".to_string()),
/// ..Default::default()
/// }
/// ).await?;
/// let req = http::Request::builder()
/// .method(signed_req.method())
/// .uri(signed_req.uri())
/// .body(())?;
///
/// # Ok(())
/// # }
/// ```
pub async fn presign_read_options(
&self,
path: &str,
expire: Duration,
opts: options::ReadOptions,
) -> Result<PresignedRequest> {
let path = normalize_path(path);
Self::presign_read_inner(self.inner().clone(), path, (opts, expire)).await
}
#[inline]
async fn presign_read_inner(
acc: Accessor,
path: String,
(opts, expire): (options::ReadOptions, Duration),
) -> Result<PresignedRequest> {
let (op_read, _) = opts.into();
let op = OpPresign::new(op_read, expire);
let rp = acc.presign(&path, op).await?;
Ok(rp.into_presigned_request())
}
/// Presign an operation for write.
///
/// # Notes
///
/// ## Extra Options
///
/// `presign_write` is a wrapper of [`Self::presign_write_with`] without any options. To use
/// extra options like `content_type`, please use [`Self::presign_write_with`] or
/// [`Self::presign_write_options`] instead.
///
/// # Example
///
/// ```
/// use std::time::Duration;
///
/// use anyhow::Result;
/// use opendal_core::Operator;
///
/// async fn test(op: Operator) -> Result<()> {
/// let signed_req = op
/// .presign_write("test.txt", Duration::from_secs(3600))
/// .await?;
/// Ok(())
/// }
/// ```
///
/// - `signed_req.method()`: `PUT`
/// - `signed_req.uri()`: `https://s3.amazonaws.com/examplebucket/test.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=access_key_id/20130721/us-east-1/s3/aws4_request&X-Amz-Date=20130721T201207Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=<signature-value>`
/// - `signed_req.headers()`: `{ "host": "s3.amazonaws.com" }`
///
/// We can upload file as this file via `curl` or other tools without credential:
///
/// ```shell
/// curl -X PUT "https://s3.amazonaws.com/examplebucket/test.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=access_key_id/20130721/us-east-1/s3/aws4_request&X-Amz-Date=20130721T201207Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=<signature-value>" -d "Hello, World!"
/// ```
pub async fn presign_write(&self, path: &str, expire: Duration) -> Result<PresignedRequest> {
self.presign_write_with(path, expire).await
}
/// Presign an operation for write with extra options.
///
/// # Options
///
/// Visit [`options::WriteOptions`] for all available options.
///
/// # Example
///
/// ```
/// use std::time::Duration;
///
/// use anyhow::Result;
/// use opendal_core::Operator;
///
/// async fn test(op: Operator) -> Result<()> {
/// let signed_req = op
/// .presign_write_with("test", Duration::from_secs(3600))
/// .cache_control("no-store")
/// .await?;
/// let req = http::Request::builder()
/// .method(signed_req.method())
/// .uri(signed_req.uri())
/// .body(())?;
///
/// Ok(())
/// }
/// ```
pub fn presign_write_with(
&self,
path: &str,
expire: Duration,
) -> FuturePresignWrite<impl Future<Output = Result<PresignedRequest>>> {
let path = normalize_path(path);
OperatorFuture::new(
self.inner().clone(),
path,
(options::WriteOptions::default(), expire),
Self::presign_write_inner,
)
}
/// Presign an operation for write with additional options.
///
/// # Options
///
/// Check [`options::WriteOptions`] for all available options.
///
/// # Example
///
/// ```
/// use anyhow::Result;
/// use opendal_core::Operator;
/// use opendal_core::options;
/// use std::time::Duration;
///
/// async fn test(op: Operator) -> Result<()> {
/// let signed_req = op.presign_write_options(
/// "file",
/// Duration::from_secs(3600),
/// options::WriteOptions {
/// content_type: Some("application/json".to_string()),
/// cache_control: Some("max-age=3600".to_string()),
/// if_not_exists: true,
/// ..Default::default()
/// }
/// ).await?;
/// let req = http::Request::builder()
/// .method(signed_req.method())
/// .uri(signed_req.uri())
/// .body(())?;
///
/// # Ok(())
/// # }
/// ```
pub async fn presign_write_options(
&self,
path: &str,
expire: Duration,
opts: options::WriteOptions,
) -> Result<PresignedRequest> {
let path = normalize_path(path);
Self::presign_write_inner(self.inner().clone(), path, (opts, expire)).await
}
#[inline]
async fn presign_write_inner(
acc: Accessor,
path: String,
(opts, expire): (options::WriteOptions, Duration),
) -> Result<PresignedRequest> {
let (op_write, _) = opts.into();
let op = OpPresign::new(op_write, expire);
let rp = acc.presign(&path, op).await?;
Ok(rp.into_presigned_request())
}
/// Presign an operation for delete.
///
/// # Notes
///
/// ## Extra Options
///
/// `presign_delete` is a wrapper of [`Self::presign_delete_with`] without any options.
///
/// # Example
///
/// ```
/// use std::time::Duration;
///
/// use anyhow::Result;
/// use opendal_core::Operator;
///
/// async fn test(op: Operator) -> Result<()> {
/// let signed_req = op
/// .presign_delete("test.txt", Duration::from_secs(3600))
/// .await?;
/// Ok(())
/// }
/// ```
///
/// - `signed_req.method()`: `DELETE`
/// - `signed_req.uri()`: `https://s3.amazonaws.com/examplebucket/test.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=access_key_id/20130721/us-east-1/s3/aws4_request&X-Amz-Date=20130721T201207Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=<signature-value>`
/// - `signed_req.headers()`: `{ "host": "s3.amazonaws.com" }`
///
/// We can delete file as this file via `curl` or other tools without credential:
///
/// ```shell
/// curl -X DELETE "https://s3.amazonaws.com/examplebucket/test.txt?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=access_key_id/20130721/us-east-1/s3/aws4_request&X-Amz-Date=20130721T201207Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=<signature-value>"
/// ```
pub async fn presign_delete(&self, path: &str, expire: Duration) -> Result<PresignedRequest> {
self.presign_delete_with(path, expire).await
}
/// Presign an operation for delete without extra options.
pub fn presign_delete_with(
&self,
path: &str,
expire: Duration,
) -> FuturePresignDelete<impl Future<Output = Result<PresignedRequest>>> {
let path = normalize_path(path);
OperatorFuture::new(
self.inner().clone(),
path,
(options::DeleteOptions::default(), expire),
Self::presign_delete_inner,
)
}
/// Presign an operation for delete with additional options.
///
/// # Options
///
/// Visit [`options::DeleteOptions`] for all available options.
///
/// # Example
///
/// ```
/// use anyhow::Result;
/// use opendal_core::Operator;
/// use opendal_core::options;
/// use std::time::Duration;
///
/// async fn test(op: Operator) -> Result<()> {
/// let signed_req = op.presign_delete_options(
/// "path/to/file",
/// Duration::from_secs(3600),
/// options::DeleteOptions {
/// ..Default::default()
/// }
/// ).await?;
/// let req = http::Request::builder()
/// .method(signed_req.method())
/// .uri(signed_req.uri())
/// .body(())?;
///
/// # Ok(())
/// # }
/// ```
pub async fn presign_delete_options(
&self,
path: &str,
expire: Duration,
opts: options::DeleteOptions,
) -> Result<PresignedRequest> {
let path = normalize_path(path);
Self::presign_delete_inner(self.inner().clone(), path, (opts, expire)).await
}
#[inline]
async fn presign_delete_inner(
acc: Accessor,
path: String,
(opts, expire): (options::DeleteOptions, Duration),
) -> Result<PresignedRequest> {
let op = OpPresign::new(OpDelete::from(opts), expire);
let rp = acc.presign(&path, op).await?;
Ok(rp.into_presigned_request())
}
}
```