Skip to main content

opendal_service_webdav/
config.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use std::fmt::Debug;
19
20use serde::Deserialize;
21use serde::Serialize;
22
23use super::backend::WebdavBuilder;
24
25/// Config for [WebDAV](https://datatracker.ietf.org/doc/html/rfc4918) backend support.
26#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
27#[serde(default)]
28#[non_exhaustive]
29pub struct WebdavConfig {
30    /// endpoint of this backend
31    pub endpoint: Option<String>,
32    /// username of this backend
33    pub username: Option<String>,
34    /// password of this backend
35    pub password: Option<String>,
36    /// token of this backend
37    pub token: Option<String>,
38    /// root of this backend
39    pub root: Option<String>,
40    /// Deprecated: WebDAV copy capability is enabled by default.
41    #[deprecated(
42        since = "0.57.0",
43        note = "WebDAV copy capability is enabled by default and this option is no longer needed."
44    )]
45    pub disable_copy: bool,
46    /// Disable automatic parent directory creation before write operations.
47    ///
48    /// By default, OpenDAL creates parent directories using MKCOL before writing files.
49    /// This requires PROPFIND support to check directory existence.
50    ///
51    /// Some WebDAV-compatible servers (e.g., bazel-remote) don't support PROPFIND
52    /// or don't require explicit directory creation. Enable this option to skip
53    /// the MKCOL calls and write files directly.
54    ///
55    /// Default: false
56    pub disable_create_dir: bool,
57    /// Deprecated: WebDAV user metadata capability is enabled by default.
58    #[deprecated(
59        since = "0.57.0",
60        note = "WebDAV user metadata capability is enabled by default. Use CapabilityOverrideLayer to override write_with_user_metadata for endpoints without PROPPATCH support."
61    )]
62    pub enable_user_metadata: bool,
63    /// The XML namespace prefix for user metadata properties.
64    ///
65    /// This prefix is used in PROPPATCH/PROPFIND XML requests.
66    /// Different servers may require different prefixes.
67    ///
68    /// Default: "opendal"
69    pub user_metadata_prefix: Option<String>,
70    /// The XML namespace URI for user metadata properties.
71    ///
72    /// This URI uniquely identifies the namespace for custom properties.
73    /// Different servers may require different namespace URIs.
74    /// For example, Nextcloud might work better with its own namespace.
75    ///
76    /// Default: `https://opendal.apache.org/ns`
77    pub user_metadata_uri: Option<String>,
78    /// Enable conditional read support.
79    ///
80    /// When enabled (the default), OpenDAL forwards the RFC 7232 headers
81    /// `If-Match`, `If-None-Match`, `If-Modified-Since` and
82    /// `If-Unmodified-Since` to the server when callers provide them.
83    ///
84    /// Some WebDAV-compatible servers (e.g., nginx-dav) don't return ETags
85    /// in PROPFIND or don't honor these headers on GET. Setting this to
86    /// `false` drops the four `read_with_if_*` capabilities, so calls like
87    /// `reader_with(path).if_match(...)` return `ErrorKind::Unsupported`
88    /// locally instead of being silently ignored by the server.
89    ///
90    /// Default: true
91    pub enable_conditional_read: bool,
92}
93
94#[allow(deprecated)]
95impl Default for WebdavConfig {
96    fn default() -> Self {
97        Self {
98            endpoint: None,
99            username: None,
100            password: None,
101            token: None,
102            root: None,
103            disable_copy: false,
104            disable_create_dir: false,
105            enable_user_metadata: false,
106            user_metadata_prefix: None,
107            user_metadata_uri: None,
108            enable_conditional_read: true,
109        }
110    }
111}
112
113impl Debug for WebdavConfig {
114    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115        f.debug_struct("WebdavConfig")
116            .field("endpoint", &self.endpoint)
117            .field("username", &self.username)
118            .field("root", &self.root)
119            .field("disable_create_dir", &self.disable_create_dir)
120            .field("user_metadata_prefix", &self.user_metadata_prefix)
121            .field("user_metadata_uri", &self.user_metadata_uri)
122            .field("enable_conditional_read", &self.enable_conditional_read)
123            .finish_non_exhaustive()
124    }
125}
126
127impl opendal_core::Configurator for WebdavConfig {
128    type Builder = WebdavBuilder;
129
130    fn from_uri(uri: &opendal_core::OperatorUri) -> opendal_core::Result<Self> {
131        let mut map = uri.options().clone();
132        if let Some(authority) = uri.authority() {
133            map.insert("endpoint".to_string(), format!("https://{authority}"));
134        }
135
136        if let Some(root) = uri.root() {
137            map.insert("root".to_string(), root.to_string());
138        }
139
140        Self::from_iter(map)
141    }
142
143    fn into_builder(self) -> Self::Builder {
144        WebdavBuilder { config: self }
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151    use opendal_core::Configurator;
152    use opendal_core::OperatorUri;
153
154    #[test]
155    fn from_uri_sets_endpoint_and_root() {
156        let uri = OperatorUri::new(
157            "webdav://webdav.example.com/remote.php/webdav",
158            Vec::<(String, String)>::new(),
159        )
160        .unwrap();
161
162        let cfg = WebdavConfig::from_uri(&uri).unwrap();
163        assert_eq!(cfg.endpoint.as_deref(), Some("https://webdav.example.com"));
164        assert_eq!(cfg.root.as_deref(), Some("remote.php/webdav"));
165    }
166
167    #[test]
168    fn from_uri_ignores_endpoint_override() {
169        let uri = OperatorUri::new(
170            "webdav://dav.internal/data",
171            vec![(
172                "endpoint".to_string(),
173                "http://dav.internal:8080".to_string(),
174            )],
175        )
176        .unwrap();
177
178        let cfg = WebdavConfig::from_uri(&uri).unwrap();
179        assert_eq!(cfg.endpoint.as_deref(), Some("https://dav.internal"));
180    }
181
182    #[test]
183    #[allow(deprecated)]
184    fn from_uri_accepts_deprecated_disable_copy() {
185        let uri = OperatorUri::new(
186            "webdav://dav.example.com",
187            vec![("disable_copy".to_string(), "true".to_string())],
188        )
189        .unwrap();
190
191        let cfg = WebdavConfig::from_uri(&uri).unwrap();
192        assert!(cfg.disable_copy);
193    }
194
195    #[test]
196    fn from_uri_propagates_disable_create_dir() {
197        let uri = OperatorUri::new(
198            "webdav://dav.example.com",
199            vec![("disable_create_dir".to_string(), "true".to_string())],
200        )
201        .unwrap();
202
203        let cfg = WebdavConfig::from_uri(&uri).unwrap();
204        assert!(cfg.disable_create_dir);
205    }
206}