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)