1#![doc = include_str!("../README.md")]
19#![cfg_attr(docsrs, feature(doc_cfg))]
20#![cfg_attr(docsrs, doc(auto_cfg))]
21#![deny(missing_docs)]
22use std::sync::Arc;
23
24use opendal_core::raw::*;
25use opendal_core::*;
26
27#[derive(Clone, Debug, Default)]
58#[non_exhaustive]
59pub struct CapabilityCheckLayer {}
60
61impl CapabilityCheckLayer {
62 pub fn new() -> Self {
64 Self::default()
65 }
66}
67
68impl Layer for CapabilityCheckLayer {
69 fn apply_service(&self, inner: Servicer) -> Servicer {
70 Arc::new(self.layer(inner))
71 }
72}
73
74impl CapabilityCheckLayer {
75 fn layer(&self, inner: Servicer) -> CapabilityCheckService {
76 CapabilityCheckService { inner }
77 }
78}
79
80#[doc(hidden)]
81#[derive(Debug)]
82pub struct CapabilityCheckService {
83 inner: Servicer,
84}
85
86fn new_unsupported_error(info: &ServiceInfo, op: Operation, args: &str) -> Error {
87 let scheme = info.scheme();
88 let op = op.into_static();
89
90 Error::new(
91 ErrorKind::Unsupported,
92 format!("The service {scheme} does not support the operation {op} with the arguments {args}. Please verify if the relevant flags have been enabled, or submit an issue if you believe this is incorrect."),
93 )
94 .with_operation(op)
95}
96
97impl Service for CapabilityCheckService {
98 type Reader = oio::Reader;
99 type Writer = oio::Writer;
100 type Lister = oio::Lister;
101 type Deleter = oio::Deleter;
102 type Copier = oio::Copier;
103 type Composer = oio::Composer;
104
105 fn info(&self) -> ServiceInfo {
106 self.inner.info()
107 }
108
109 fn capability(&self) -> Capability {
110 self.inner.capability()
111 }
112
113 async fn create_dir(
114 &self,
115 ctx: &OperationContext,
116 path: &str,
117 args: OpCreateDir,
118 ) -> Result<RpCreateDir> {
119 self.inner.create_dir(ctx, path, args).await
120 }
121
122 async fn stat(&self, ctx: &OperationContext, path: &str, args: OpStat) -> Result<RpStat> {
123 self.inner.stat(ctx, path, args).await
124 }
125
126 fn read(&self, ctx: &OperationContext, path: &str, args: OpRead) -> Result<Self::Reader> {
127 self.inner.read(ctx, path, args)
128 }
129
130 fn write(&self, ctx: &OperationContext, path: &str, args: OpWrite) -> Result<Self::Writer> {
131 let capability = self.capability();
132 let info = self.info();
133 if !capability.write_with_content_type && args.content_type().is_some() {
134 return Err(new_unsupported_error(
135 &info,
136 Operation::Write,
137 "content_type",
138 ));
139 }
140 if !capability.write_with_cache_control && args.cache_control().is_some() {
141 return Err(new_unsupported_error(
142 &info,
143 Operation::Write,
144 "cache_control",
145 ));
146 }
147 if !capability.write_with_content_disposition && args.content_disposition().is_some() {
148 return Err(new_unsupported_error(
149 &info,
150 Operation::Write,
151 "content_disposition",
152 ));
153 }
154
155 self.inner.write(ctx, path, args)
156 }
157
158 fn copy(
159 &self,
160 ctx: &OperationContext,
161 from: &str,
162 to: &str,
163 args: OpCopy,
164 ) -> Result<Self::Copier> {
165 let capability = self.capability();
166 let info = self.info();
167 if args.if_not_exists() && !capability.copy_with_if_not_exists {
168 return Err(new_unsupported_error(
169 &info,
170 Operation::Copy,
171 "if_not_exists",
172 ));
173 }
174 if args.if_match().is_some() && !capability.copy_with_if_match {
175 return Err(new_unsupported_error(&info, Operation::Copy, "if_match"));
176 }
177 if args.source_version().is_some() && !capability.copy_with_source_version {
178 return Err(new_unsupported_error(
179 &info,
180 Operation::Copy,
181 "source_version",
182 ));
183 }
184
185 self.inner.copy(ctx, from, to, args)
186 }
187
188 fn compose(&self, ctx: &OperationContext, to: &str, args: OpCompose) -> Result<Self::Composer> {
189 let capability = self.capability();
190 let info = self.info();
191 let checks = [
192 (
193 args.content_type().is_some(),
194 capability.compose_with_content_type,
195 "content_type",
196 ),
197 (
198 args.content_disposition().is_some(),
199 capability.compose_with_content_disposition,
200 "content_disposition",
201 ),
202 (
203 args.content_encoding().is_some(),
204 capability.compose_with_content_encoding,
205 "content_encoding",
206 ),
207 (
208 args.cache_control().is_some(),
209 capability.compose_with_cache_control,
210 "cache_control",
211 ),
212 (
213 args.user_metadata().is_some(),
214 capability.compose_with_user_metadata,
215 "user_metadata",
216 ),
217 (
218 args.if_match().is_some(),
219 capability.compose_with_if_match,
220 "if_match",
221 ),
222 (
223 args.if_none_match().is_some(),
224 capability.compose_with_if_none_match,
225 "if_none_match",
226 ),
227 (
228 args.if_version_match().is_some(),
229 capability.compose_with_if_version_match,
230 "if_version_match",
231 ),
232 (
233 args.if_version_not_match().is_some(),
234 capability.compose_with_if_version_not_match,
235 "if_version_not_match",
236 ),
237 (
238 args.if_not_exists(),
239 capability.compose_with_if_not_exists,
240 "if_not_exists",
241 ),
242 ];
243
244 if !capability.compose {
245 return Err(new_unsupported_error(&info, Operation::Compose, ""));
246 }
247 if let Some((_, _, name)) = checks
248 .iter()
249 .find(|(used, supported, _)| *used && !supported)
250 {
251 return Err(new_unsupported_error(&info, Operation::Compose, name));
252 }
253
254 self.inner.compose(ctx, to, args)
255 }
256
257 fn delete(&self, ctx: &OperationContext) -> Result<Self::Deleter> {
258 self.inner.delete(ctx)
259 }
260
261 fn list(&self, ctx: &OperationContext, path: &str, args: OpList) -> Result<Self::Lister> {
262 let capability = self.capability();
263 if !capability.list_with_versions && args.versions() {
264 let info = self.info();
265 return Err(new_unsupported_error(&info, Operation::List, "version"));
266 }
267
268 self.inner.list(ctx, path, args)
269 }
270
271 async fn rename(
272 &self,
273 ctx: &OperationContext,
274 from: &str,
275 to: &str,
276 args: OpRename,
277 ) -> Result<RpRename> {
278 self.inner.rename(ctx, from, to, args).await
279 }
280
281 async fn restore(
282 &self,
283 ctx: &OperationContext,
284 path: &str,
285 args: OpRestore,
286 ) -> Result<RpRestore> {
287 let capability = self.capability();
288 let info = self.info();
289 if !capability.restore {
290 return Err(new_unsupported_error(&info, Operation::Restore, ""));
291 }
292 if args.version().is_some() && !capability.restore_with_version {
293 return Err(new_unsupported_error(&info, Operation::Restore, "version"));
294 }
295 if args.if_not_exists() && !capability.restore_with_if_not_exists {
296 return Err(new_unsupported_error(
297 &info,
298 Operation::Restore,
299 "if_not_exists",
300 ));
301 }
302
303 self.inner.restore(ctx, path, args).await
304 }
305
306 async fn presign(
307 &self,
308 ctx: &OperationContext,
309 path: &str,
310 args: OpPresign,
311 ) -> Result<RpPresign> {
312 self.inner.presign(ctx, path, args).await
313 }
314}
315
316#[cfg(test)]
317mod tests {
318 use super::*;
319
320 #[derive(Debug)]
321 struct MockService {
322 capability: Capability,
323 }
324
325 impl Service for MockService {
326 type Reader = ();
327 type Writer = ();
328 type Lister = ();
329 type Deleter = ();
330 type Copier = ();
331 type Composer = ();
332
333 fn info(&self) -> ServiceInfo {
334 ServiceInfo::with_scheme("mock")
335 }
336
337 fn capability(&self) -> Capability {
338 self.capability
339 }
340
341 async fn create_dir(
342 &self,
343 _: &OperationContext,
344 _: &str,
345 _: OpCreateDir,
346 ) -> Result<RpCreateDir> {
347 Err(Error::new(
348 ErrorKind::Unsupported,
349 "operation is not supported",
350 ))
351 }
352
353 async fn stat(&self, _: &OperationContext, _: &str, _: OpStat) -> Result<RpStat> {
354 Err(Error::new(
355 ErrorKind::Unsupported,
356 "operation is not supported",
357 ))
358 }
359
360 fn read(&self, _ctx: &OperationContext, _: &str, _: OpRead) -> Result<Self::Reader> {
361 Err(Error::new(
362 ErrorKind::Unsupported,
363 "operation is not supported",
364 ))
365 }
366
367 fn write(&self, _ctx: &OperationContext, _: &str, _: OpWrite) -> Result<Self::Writer> {
368 Ok(())
369 }
370
371 fn list(&self, _ctx: &OperationContext, _: &str, _: OpList) -> Result<Self::Lister> {
372 Ok(())
373 }
374
375 fn delete(&self, _ctx: &OperationContext) -> Result<Self::Deleter> {
376 Err(Error::new(
377 ErrorKind::Unsupported,
378 "operation is not supported",
379 ))
380 }
381
382 fn copy(&self, _: &OperationContext, _: &str, _: &str, _: OpCopy) -> Result<Self::Copier> {
383 Err(Error::new(
384 ErrorKind::Unsupported,
385 "operation is not supported",
386 ))
387 }
388
389 async fn rename(
390 &self,
391 _: &OperationContext,
392 _: &str,
393 _: &str,
394 _: OpRename,
395 ) -> Result<RpRename> {
396 Err(Error::new(
397 ErrorKind::Unsupported,
398 "operation is not supported",
399 ))
400 }
401
402 async fn presign(&self, _: &OperationContext, _: &str, _: OpPresign) -> Result<RpPresign> {
403 Err(Error::new(
404 ErrorKind::Unsupported,
405 "operation is not supported",
406 ))
407 }
408 }
409
410 fn new_test_operator(capability: Capability) -> Operator {
411 let srv = MockService { capability };
412
413 Operator::from_parts(OperationContext::default(), Arc::new(srv))
414 .layer(CapabilityCheckLayer::new())
415 }
416
417 #[tokio::test]
418 async fn test_writer_with() {
419 let op = new_test_operator(Capability {
420 write: true,
421 ..Default::default()
422 });
423 let res = op.writer_with("path").content_type("type").await;
424 assert!(res.is_err());
425
426 let res = op.writer_with("path").cache_control("cache").await;
427 assert!(res.is_err());
428
429 let res = op
430 .writer_with("path")
431 .content_disposition("disposition")
432 .await;
433 assert!(res.is_err());
434
435 let op = new_test_operator(Capability {
436 write: true,
437 write_with_content_type: true,
438 write_with_cache_control: true,
439 write_with_content_disposition: true,
440 ..Default::default()
441 });
442 let res = op.writer_with("path").content_type("type").await;
443 assert!(res.is_ok());
444
445 let res = op.writer_with("path").cache_control("cache").await;
446 assert!(res.is_ok());
447
448 let res = op
449 .writer_with("path")
450 .content_disposition("disposition")
451 .await;
452 assert!(res.is_ok());
453 }
454
455 #[tokio::test]
456 async fn test_list_with() {
457 let op = new_test_operator(Capability {
458 list: true,
459 ..Default::default()
460 });
461 let res = op.list_with("path/").versions(true).await;
462 assert!(res.is_err());
463 assert_eq!(res.unwrap_err().kind(), ErrorKind::Unsupported);
464
465 let op = new_test_operator(Capability {
466 list: true,
467 list_with_versions: true,
468 ..Default::default()
469 });
470 let res = op.lister_with("path/").versions(true).await;
471 assert!(res.is_ok())
472 }
473}