opendal_core/services/webdav/
backend.rs1use std::fmt::Debug;
19use std::str::FromStr;
20use std::sync::Arc;
21
22use http::Response;
23use http::StatusCode;
24use log::debug;
25
26use super::WEBDAV_SCHEME;
27use super::config::WebdavConfig;
28use super::core::*;
29use super::deleter::WebdavDeleter;
30use super::error::parse_error;
31use super::lister::WebdavLister;
32use super::writer::WebdavWriter;
33use crate::raw::*;
34use crate::*;
35
36#[doc = include_str!("docs.md")]
38#[derive(Default)]
39pub struct WebdavBuilder {
40 pub(super) config: WebdavConfig,
41}
42
43impl Debug for WebdavBuilder {
44 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45 f.debug_struct("WebdavBuilder")
46 .field("config", &self.config)
47 .finish_non_exhaustive()
48 }
49}
50
51impl WebdavBuilder {
52 pub fn endpoint(mut self, endpoint: &str) -> Self {
56 self.config.endpoint = if endpoint.is_empty() {
57 None
58 } else {
59 Some(endpoint.to_string())
60 };
61
62 self
63 }
64
65 pub fn username(mut self, username: &str) -> Self {
69 if !username.is_empty() {
70 self.config.username = Some(username.to_owned());
71 }
72 self
73 }
74
75 pub fn password(mut self, password: &str) -> Self {
79 if !password.is_empty() {
80 self.config.password = Some(password.to_owned());
81 }
82 self
83 }
84
85 pub fn token(mut self, token: &str) -> Self {
89 if !token.is_empty() {
90 self.config.token = Some(token.to_string());
91 }
92 self
93 }
94
95 pub fn root(mut self, root: &str) -> Self {
97 self.config.root = if root.is_empty() {
98 None
99 } else {
100 Some(root.to_string())
101 };
102
103 self
104 }
105}
106
107impl Builder for WebdavBuilder {
108 type Config = WebdavConfig;
109
110 fn build(self) -> Result<impl Access> {
111 debug!("backend build started: {:?}", &self);
112
113 let endpoint = match &self.config.endpoint {
114 Some(v) => v,
115 None => {
116 return Err(Error::new(ErrorKind::ConfigInvalid, "endpoint is empty")
117 .with_context("service", WEBDAV_SCHEME));
118 }
119 };
120 let server_path = http::Uri::from_str(endpoint)
122 .map_err(|err| {
123 Error::new(ErrorKind::ConfigInvalid, "endpoint is invalid")
124 .with_context("service", WEBDAV_SCHEME)
125 .set_source(err)
126 })?
127 .path()
128 .trim_end_matches('/')
129 .to_string();
130
131 let root = normalize_root(&self.config.root.clone().unwrap_or_default());
132 debug!("backend use root {root}");
133
134 let mut authorization = None;
135 if let Some(username) = &self.config.username {
136 authorization = Some(format_authorization_by_basic(
137 username,
138 self.config.password.as_deref().unwrap_or_default(),
139 )?);
140 }
141 if let Some(token) = &self.config.token {
142 authorization = Some(format_authorization_by_bearer(token)?)
143 }
144
145 let core = Arc::new(WebdavCore {
146 info: {
147 let am = AccessorInfo::default();
148 am.set_scheme(WEBDAV_SCHEME)
149 .set_root(&root)
150 .set_native_capability(Capability {
151 stat: true,
152
153 read: true,
154
155 write: true,
156 write_can_empty: true,
157
158 create_dir: true,
159 delete: true,
160
161 copy: !self.config.disable_copy,
162
163 rename: true,
164
165 list: true,
166
167 shared: true,
170
171 ..Default::default()
172 });
173
174 am.into()
175 },
176 endpoint: endpoint.to_string(),
177 server_path,
178 authorization,
179 root,
180 });
181 Ok(WebdavBackend { core })
182 }
183}
184
185#[derive(Clone, Debug)]
187pub struct WebdavBackend {
188 core: Arc<WebdavCore>,
189}
190
191impl Access for WebdavBackend {
192 type Reader = HttpBody;
193 type Writer = oio::OneShotWriter<WebdavWriter>;
194 type Lister = oio::PageLister<WebdavLister>;
195 type Deleter = oio::OneShotDeleter<WebdavDeleter>;
196
197 fn info(&self) -> Arc<AccessorInfo> {
198 self.core.info.clone()
199 }
200
201 async fn create_dir(&self, path: &str, _: OpCreateDir) -> Result<RpCreateDir> {
202 self.core.webdav_mkcol(path).await?;
203 Ok(RpCreateDir::default())
204 }
205
206 async fn stat(&self, path: &str, _: OpStat) -> Result<RpStat> {
207 let metadata = self.core.webdav_stat(path).await?;
208 Ok(RpStat::new(metadata))
209 }
210
211 async fn read(&self, path: &str, args: OpRead) -> Result<(RpRead, Self::Reader)> {
212 let resp = self.core.webdav_get(path, args.range(), &args).await?;
213
214 let status = resp.status();
215
216 match status {
217 StatusCode::OK | StatusCode::PARTIAL_CONTENT => {
218 Ok((RpRead::default(), resp.into_body()))
219 }
220 _ => {
221 let (part, mut body) = resp.into_parts();
222 let buf = body.to_buffer().await?;
223 Err(parse_error(Response::from_parts(part, buf)))
224 }
225 }
226 }
227
228 async fn write(&self, path: &str, args: OpWrite) -> Result<(RpWrite, Self::Writer)> {
229 self.core.webdav_mkcol(get_parent(path)).await?;
231
232 Ok((
233 RpWrite::default(),
234 oio::OneShotWriter::new(WebdavWriter::new(self.core.clone(), args, path.to_string())),
235 ))
236 }
237
238 async fn delete(&self) -> Result<(RpDelete, Self::Deleter)> {
239 Ok((
240 RpDelete::default(),
241 oio::OneShotDeleter::new(WebdavDeleter::new(self.core.clone())),
242 ))
243 }
244
245 async fn list(&self, path: &str, args: OpList) -> Result<(RpList, Self::Lister)> {
246 Ok((
247 RpList::default(),
248 oio::PageLister::new(WebdavLister::new(self.core.clone(), path, args)),
249 ))
250 }
251
252 async fn copy(&self, from: &str, to: &str, _args: OpCopy) -> Result<RpCopy> {
253 let resp = self.core.webdav_copy(from, to).await?;
254
255 let status = resp.status();
256
257 match status {
258 StatusCode::CREATED | StatusCode::NO_CONTENT => Ok(RpCopy::default()),
259 _ => Err(parse_error(resp)),
260 }
261 }
262
263 async fn rename(&self, from: &str, to: &str, _args: OpRename) -> Result<RpRename> {
264 let resp = self.core.webdav_move(from, to).await?;
265
266 let status = resp.status();
267 match status {
268 StatusCode::CREATED | StatusCode::NO_CONTENT | StatusCode::OK => {
269 Ok(RpRename::default())
270 }
271 _ => Err(parse_error(resp)),
272 }
273 }
274}