opendal/layers/
capability_check.rs1use 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#[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}