opendal_layer_capability_check/
lib.rs1#![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
104 fn info(&self) -> ServiceInfo {
105 self.inner.info()
106 }
107
108 fn capability(&self) -> Capability {
109 self.inner.capability()
110 }
111
112 async fn create_dir(
113 &self,
114 ctx: &OperationContext,
115 path: &str,
116 args: OpCreateDir,
117 ) -> Result<RpCreateDir> {
118 self.inner.create_dir(ctx, path, args).await
119 }
120
121 async fn stat(&self, ctx: &OperationContext, path: &str, args: OpStat) -> Result<RpStat> {
122 self.inner.stat(ctx, path, args).await
123 }
124
125 fn read(&self, ctx: &OperationContext, path: &str, args: OpRead) -> Result<Self::Reader> {
126 self.inner.read(ctx, path, args)
127 }
128
129 fn write(&self, ctx: &OperationContext, path: &str, args: OpWrite) -> Result<Self::Writer> {
130 let capability = self.capability();
131 let info = self.info();
132 if !capability.write_with_content_type && args.content_type().is_some() {
133 return Err(new_unsupported_error(
134 &info,
135 Operation::Write,
136 "content_type",
137 ));
138 }
139 if !capability.write_with_cache_control && args.cache_control().is_some() {
140 return Err(new_unsupported_error(
141 &info,
142 Operation::Write,
143 "cache_control",
144 ));
145 }
146 if !capability.write_with_content_disposition && args.content_disposition().is_some() {
147 return Err(new_unsupported_error(
148 &info,
149 Operation::Write,
150 "content_disposition",
151 ));
152 }
153
154 self.inner.write(ctx, path, args)
155 }
156
157 fn copy(
158 &self,
159 ctx: &OperationContext,
160 from: &str,
161 to: &str,
162 args: OpCopy,
163 opts: OpCopier,
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, opts)
186 }
187
188 fn delete(&self, ctx: &OperationContext) -> Result<Self::Deleter> {
189 self.inner.delete(ctx)
190 }
191
192 fn list(&self, ctx: &OperationContext, path: &str, args: OpList) -> Result<Self::Lister> {
193 let capability = self.capability();
194 if !capability.list_with_versions && args.versions() {
195 let info = self.info();
196 return Err(new_unsupported_error(&info, Operation::List, "version"));
197 }
198
199 self.inner.list(ctx, path, args)
200 }
201
202 async fn rename(
203 &self,
204 ctx: &OperationContext,
205 from: &str,
206 to: &str,
207 args: OpRename,
208 ) -> Result<RpRename> {
209 self.inner.rename(ctx, from, to, args).await
210 }
211
212 async fn presign(
213 &self,
214 ctx: &OperationContext,
215 path: &str,
216 args: OpPresign,
217 ) -> Result<RpPresign> {
218 self.inner.presign(ctx, path, args).await
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225
226 #[derive(Debug)]
227 struct MockService {
228 capability: Capability,
229 }
230
231 impl Service for MockService {
232 type Reader = ();
233 type Writer = ();
234 type Lister = ();
235 type Deleter = ();
236 type Copier = ();
237
238 fn info(&self) -> ServiceInfo {
239 ServiceInfo::with_scheme("mock")
240 }
241
242 fn capability(&self) -> Capability {
243 self.capability
244 }
245
246 async fn create_dir(
247 &self,
248 _: &OperationContext,
249 _: &str,
250 _: OpCreateDir,
251 ) -> Result<RpCreateDir> {
252 Err(Error::new(
253 ErrorKind::Unsupported,
254 "operation is not supported",
255 ))
256 }
257
258 async fn stat(&self, _: &OperationContext, _: &str, _: OpStat) -> Result<RpStat> {
259 Err(Error::new(
260 ErrorKind::Unsupported,
261 "operation is not supported",
262 ))
263 }
264
265 fn read(&self, _ctx: &OperationContext, _: &str, _: OpRead) -> Result<Self::Reader> {
266 Err(Error::new(
267 ErrorKind::Unsupported,
268 "operation is not supported",
269 ))
270 }
271
272 fn write(&self, _ctx: &OperationContext, _: &str, _: OpWrite) -> Result<Self::Writer> {
273 Ok(())
274 }
275
276 fn list(&self, _ctx: &OperationContext, _: &str, _: OpList) -> Result<Self::Lister> {
277 Ok(())
278 }
279
280 fn delete(&self, _ctx: &OperationContext) -> Result<Self::Deleter> {
281 Err(Error::new(
282 ErrorKind::Unsupported,
283 "operation is not supported",
284 ))
285 }
286
287 fn copy(
288 &self,
289 _: &OperationContext,
290 _: &str,
291 _: &str,
292 _: OpCopy,
293 _: OpCopier,
294 ) -> Result<Self::Copier> {
295 Err(Error::new(
296 ErrorKind::Unsupported,
297 "operation is not supported",
298 ))
299 }
300
301 async fn rename(
302 &self,
303 _: &OperationContext,
304 _: &str,
305 _: &str,
306 _: OpRename,
307 ) -> Result<RpRename> {
308 Err(Error::new(
309 ErrorKind::Unsupported,
310 "operation is not supported",
311 ))
312 }
313
314 async fn presign(&self, _: &OperationContext, _: &str, _: OpPresign) -> Result<RpPresign> {
315 Err(Error::new(
316 ErrorKind::Unsupported,
317 "operation is not supported",
318 ))
319 }
320 }
321
322 fn new_test_operator(capability: Capability) -> Operator {
323 let srv = MockService { capability };
324
325 Operator::from_parts(OperationContext::default(), Arc::new(srv))
326 .layer(CapabilityCheckLayer::new())
327 }
328
329 #[tokio::test]
330 async fn test_writer_with() {
331 let op = new_test_operator(Capability {
332 write: true,
333 ..Default::default()
334 });
335 let res = op.writer_with("path").content_type("type").await;
336 assert!(res.is_err());
337
338 let res = op.writer_with("path").cache_control("cache").await;
339 assert!(res.is_err());
340
341 let res = op
342 .writer_with("path")
343 .content_disposition("disposition")
344 .await;
345 assert!(res.is_err());
346
347 let op = new_test_operator(Capability {
348 write: true,
349 write_with_content_type: true,
350 write_with_cache_control: true,
351 write_with_content_disposition: true,
352 ..Default::default()
353 });
354 let res = op.writer_with("path").content_type("type").await;
355 assert!(res.is_ok());
356
357 let res = op.writer_with("path").cache_control("cache").await;
358 assert!(res.is_ok());
359
360 let res = op
361 .writer_with("path")
362 .content_disposition("disposition")
363 .await;
364 assert!(res.is_ok());
365 }
366
367 #[tokio::test]
368 async fn test_list_with() {
369 let op = new_test_operator(Capability {
370 list: true,
371 ..Default::default()
372 });
373 let res = op.list_with("path/").versions(true).await;
374 assert!(res.is_err());
375 assert_eq!(res.unwrap_err().kind(), ErrorKind::Unsupported);
376
377 let op = new_test_operator(Capability {
378 list: true,
379 list_with_versions: true,
380 ..Default::default()
381 });
382 let res = op.lister_with("path/").versions(true).await;
383 assert!(res.is_ok())
384 }
385}