You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@opendal.apache.org by xu...@apache.org on 2026/10/21 10:00:36 UTC
(opendal) branch main updated: feat(services/webdav): support rename with if_not_exists (#8089)
This is an automated email from the ASF dual-hosted git repository.
Xuanwo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/opendal.git
The following commit(s) were added to refs/heads/main by this push:
new 6be95285d feat(services/webdav): support rename with if_not_exists (#8089)
6be95285d is described below
commit 6be95285da1ef595e84c9ce8e04732bd78b02726
Author: ZIHAN DAI <99...@users.noreply.github.com>
AuthorDate: Wed Oct 21 21:00:30 2026 +1100
feat(services/webdav): support rename with if_not_exists (#8089)
Implements the webdav box of #7828 (RFC-7818), which the RFC calls a
strong candidate: RFC 4918 defines Overwrite for MOVE, and section 10.6
makes "F" mean the server must not perform the move if the destination
maps to a resource.
webdav_move hardcoded Overwrite: T, so OpRename was ignored -- the
backend took it as _args. It now threads the flag through and sends F
when if_not_exists is set.
Nothing was needed on the error side. RFC 4918 section 9.9.4 has the
server answer 412 when the precondition fails, and webdav already maps
PRECONDITION_FAILED to ErrorKind::ConditionNotMatch, which is what the
behavior test expects.
The mkcol on the destination's parent stays. test_rename_with_if_not_
exists_nested renames onto a three-level path that does not exist yet,
so the parent creation is what makes it pass. The consequence is that a
412 can leave an empty parent behind; the destination itself is
untouched, so it does not violate the RFC.
Verified against the nginx fixture from .github/services/webdav rather
than by reading:
all three if_not_exists behavior tests pass
all 10 rename behavior tests pass
the whole webdav behavior suite, 124 tests pass
test_rename_overwrite is in that set, so the default path still sends T.
Hardcoding T back while keeping the capability bit fails exactly one
test -- the one asserting ConditionNotMatch -- and leaves the other nine
green, which is what tells you the header rather than the bit is doing
the work.
Only webdav here. hdfs_native can follow, also docker-verifiable.
azfile and azdls need 1Password secrets for their CI, so a fork PR gets
no behavior coverage on them and I would rather not advertise an
atomicity-gated capability on a code reading alone.
---
core/services/webdav/src/backend.rs | 8 ++++++--
core/services/webdav/src/core.rs | 3 ++-
2 files changed, 8 insertions(+), 3 deletions(-)
diff --git a/core/services/webdav/src/backend.rs b/core/services/webdav/src/backend.rs
index 1807aec5e..0868a991b 100644
--- a/core/services/webdav/src/backend.rs
+++ b/core/services/webdav/src/backend.rs
@@ -224,6 +224,7 @@ impl Builder for WebdavBuilder {
copy: true,
rename: true,
+ rename_with_if_not_exists: true,
list: true,
@@ -388,9 +389,12 @@ impl Service for WebdavBackend {
ctx: &OperationContext,
from: &str,
to: &str,
- _args: OpRename,
+ args: OpRename,
) -> Result<RpRename> {
- let resp = self.core.webdav_move(ctx, from, to).await?;
+ let resp = self
+ .core
+ .webdav_move(ctx, from, to, args.if_not_exists())
+ .await?;
let status = resp.status();
match status {
diff --git a/core/services/webdav/src/core.rs b/core/services/webdav/src/core.rs
index 3efc23dfb..6bb5c6723 100644
--- a/core/services/webdav/src/core.rs
+++ b/core/services/webdav/src/core.rs
@@ -359,6 +359,7 @@ impl WebdavCore {
ctx: &OperationContext,
from: &str,
to: &str,
+ if_not_exists: bool,
) -> Result<Response<Buffer>> {
// Check if source file exists.
let _ = self.webdav_stat(ctx, from).await?;
@@ -378,7 +379,7 @@ impl WebdavCore {
}
req = req.header(HEADER_DESTINATION, target_uri);
- req = req.header(HEADER_OVERWRITE, "T");
+ req = req.header(HEADER_OVERWRITE, if if_not_exists { "F" } else { "T" });
let req = req
.extension(Operation::Rename)