opendal/layers/
capability_check.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;
19use std::fmt::Formatter;
20use std::sync::Arc;
21
22use crate::layers::correctness_check::new_unsupported_error;
23use crate::raw::*;
24
25/// Add an extra capability check layer for every operation
26///
27/// Similar to `CorrectnessChecker`, Before performing any operations, this layer will first verify
28/// its arguments against the capability of the underlying service. If the arguments is not supported,
29/// an error will be returned directly.
30///
31/// Notes
32///
33/// There are two main differences between this checker with the `CorrectnessChecker`:
34/// 1. This checker provides additional checks for capabilities like write_with_content_type and
35///    list_with_versions, among others. These capabilities do not affect data integrity, even if
36///    the underlying storage services do not support them.
37///
38/// 2. OpenDAL doesn't apply this checker by default. Users can enable this layer if they want to
39///    enforce stricter requirements.
40///
41/// # examples
42///
43/// ```no_run
44/// # use opendal::layers::CapabilityCheckLayer;
45/// # use opendal::services;
46/// # use opendal::Operator;
47/// # use opendal::Result;
48///
49/// # fn main() -> Result<()> {
50/// use opendal::layers::CapabilityCheckLayer;
51/// let _ = Operator::new(services::Memory::default())?
52///     .layer(CapabilityCheckLayer)
53///     .finish();
54/// Ok(())
55/// # }
56/// ```
57#[derive(Default)]
58pub struct CapabilityCheckLayer;
59
60impl<A: Access> Layer<A> for CapabilityCheckLayer {
61    type LayeredAccess = CapabilityAccessor<A>;
62
63    fn layer(&self, inner: A) -> Self::LayeredAccess {
64        let info = inner.info();
65
66        CapabilityAccessor { info, inner }
67    }
68}
69pub struct CapabilityAccessor<A: Access> {
70    info: Arc<AccessorInfo>,
71    inner: A,
72}
73
74impl<A: Access> Debug for CapabilityAccessor<A> {
75    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
76        f.debug_struct("CapabilityCheckAccessor")
77            .field("inner", &self.inner)
78            .finish_non_exhaustive()
79    }
80}
81
82impl<A: Access> LayeredAccess for CapabilityAccessor<A> {
83    type Inner = A;
84    type Reader = A::Reader;
85    type Writer = A::Writer;
86    type Lister = A::Lister;
87    type Deleter = A::Deleter;
88
89    fn inner(&self) -> &Self::Inner {
90        &self.inner
91    }
92
93    async fn read(&self, path: &str, args: OpRead) -> crate::Result<(RpRead, Self::Reader)> {
94        self.inner.read(path, args).await
95    }
96
97    async fn write(&self, path: &str, args: OpWrite) -> crate::Result<(RpWrite, Self::Writer)> {
98        let capability = self.info.full_capability();
99        if !capability.write_with_content_type && args.content_type().is_some() {
100            return Err(new_unsupported_error(
101                self.info.as_ref(),
102                Operation::Write,
103                "content_type",
104            ));
105        }
106        if !capability.write_with_cache_control && args.cache_control().is_some() {
107            return Err(new_unsupported_error(
108                self.info.as_ref(),
109                Operation::Write,
110                "cache_control",
111            ));
112        }
113        if !capability.write_with_content_disposition && args.content_disposition().is_some() {
114            return Err(new_unsupported_error(
115                self.info.as_ref(),
116                Operation::Write,
117                "content_disposition",
118            ));
119        }
120
121        self.inner.write(path, args).await
122    }
123
124    async fn delete(&self) -> crate::Result<(RpDelete, Self::Deleter)> {
125        self.inner.delete().await
126    }
127
128    async fn list(&self, path: &str, args: OpList) -> crate::Result<(RpList, Self::Lister)> {
129        let capability = self.info.full_capability();
130        if !capability.list_with_versions && args.versions() {
131            return Err(new_unsupported_error(
132                self.info.as_ref(),
133                Operation::List,
134                "version",
135            ));
136        }
137
138        self.inner.list(path, args).await
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use super::*;
145    use crate::Capability;
146    use crate::ErrorKind;
147    use crate::Operator;
148
149    #[derive(Debug)]
150    struct MockService {
151        capability: Capability,
152    }
153
154    impl Access for MockService {
155        type Reader = oio::Reader;
156        type Writer = oio::Writer;
157        type Lister = oio::Lister;
158        type Deleter = oio::Deleter;
159
160        fn info(&self) -> Arc<AccessorInfo> {
161            let info = AccessorInfo::default();
162            info.set_native_capability(self.capability);
163
164            info.into()
165        }
166
167        async fn write(&self, _: &str, _: OpWrite) -> crate::Result<(RpWrite, Self::Writer)> {
168            Ok((RpWrite::new(), Box::new(())))
169        }
170
171        async fn list(&self, _: &str, _: OpList) -> crate::Result<(RpList, Self::Lister)> {
172            Ok((RpList {}, Box::new(())))
173        }
174    }
175
176    fn new_test_operator(capability: Capability) -> Operator {
177        let srv = MockService { capability };
178
179        Operator::from_inner(Arc::new(srv)).layer(CapabilityCheckLayer)
180    }
181
182    #[tokio::test]
183    async fn test_writer_with() {
184        let op = new_test_operator(Capability {
185            write: true,
186            ..Default::default()
187        });
188        let res = op.writer_with("path").content_type("type").await;
189        assert!(res.is_err());
190
191        let res = op.writer_with("path").cache_control("cache").await;
192        assert!(res.is_err());
193
194        let res = op
195            .writer_with("path")
196            .content_disposition("disposition")
197            .await;
198        assert!(res.is_err());
199
200        let op = new_test_operator(Capability {
201            write: true,
202            write_with_content_type: true,
203            write_with_cache_control: true,
204            write_with_content_disposition: true,
205            ..Default::default()
206        });
207        let res = op.writer_with("path").content_type("type").await;
208        assert!(res.is_ok());
209
210        let res = op.writer_with("path").cache_control("cache").await;
211        assert!(res.is_ok());
212
213        let res = op
214            .writer_with("path")
215            .content_disposition("disposition")
216            .await;
217        assert!(res.is_ok());
218    }
219
220    #[tokio::test]
221    async fn test_list_with() {
222        let op = new_test_operator(Capability {
223            list: true,
224            ..Default::default()
225        });
226        let res = op.list_with("path/").versions(true).await;
227        assert!(res.is_err());
228        assert_eq!(res.unwrap_err().kind(), ErrorKind::Unsupported);
229
230        let op = new_test_operator(Capability {
231            list: true,
232            list_with_versions: true,
233            ..Default::default()
234        });
235        let res = op.lister_with("path/").versions(true).await;
236        assert!(res.is_ok())
237    }
238}