opendal_layer_immutable_index/
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::collections::HashSet;
23use std::sync::Arc;
24use std::vec::IntoIter;
25
26use opendal_core::raw::*;
27use opendal_core::*;
28
29#[derive(Clone, Debug, Default)]
56pub struct ImmutableIndexLayer {
57 vec: Vec<String>,
58}
59
60impl ImmutableIndexLayer {
61 pub fn new() -> Self {
63 Self::default()
64 }
65}
66
67impl ImmutableIndexLayer {
68 pub fn insert(&mut self, key: String) {
70 self.vec.push(key);
71 }
72
73 pub fn extend_iter<I>(&mut self, iter: I)
75 where
76 I: IntoIterator<Item = String>,
77 {
78 self.vec.extend(iter);
79 }
80}
81
82impl Layer for ImmutableIndexLayer {
83 fn apply_service(&self, inner: Servicer) -> Servicer {
84 Arc::new(self.layer(inner))
85 }
86}
87
88impl ImmutableIndexLayer {
89 fn layer(&self, inner: Servicer) -> ImmutableIndexService {
90 ImmutableIndexService {
91 inner,
92 vec: self.vec.clone(),
93 }
94 }
95}
96
97#[doc(hidden)]
98#[derive(Debug)]
99pub struct ImmutableIndexService {
100 inner: Servicer,
101 vec: Vec<String>,
102}
103
104impl ImmutableIndexService {
105 fn children_flat(&self, path: &str) -> Vec<String> {
106 self.vec
107 .iter()
108 .filter(|v| v.starts_with(path) && v.as_str() != path)
109 .cloned()
110 .collect()
111 }
112
113 fn children_hierarchy(&self, path: &str) -> Vec<String> {
114 let mut res = HashSet::new();
115
116 for i in self.vec.iter() {
117 if !i.starts_with(path) {
119 continue;
120 }
121
122 if i == path {
124 continue;
125 }
126
127 match i[path.len()..].find('/') {
128 None => {
130 res.insert(i.to_string());
131 }
132 Some(idx) => {
133 let dir_idx = idx + 1 + path.len();
135
136 if dir_idx == i.len() {
137 res.insert(i.to_string());
139 } else {
140 res.insert(i[..dir_idx].to_string());
143 }
144 }
145 }
146 }
147
148 res.into_iter().collect()
149 }
150}
151
152impl Service for ImmutableIndexService {
153 type Reader = oio::Reader;
154 type Writer = oio::Writer;
155 type Lister = ImmutableDir;
156 type Deleter = oio::Deleter;
157 type Copier = oio::Copier;
158
159 fn info(&self) -> ServiceInfo {
160 self.inner.info()
161 }
162
163 fn capability(&self) -> Capability {
164 let mut capability = self.inner.capability();
165 capability.list = true;
166 capability.list_with_recursive = true;
167 capability
168 }
169
170 async fn create_dir(
171 &self,
172 ctx: &OperationContext,
173 path: &str,
174 args: OpCreateDir,
175 ) -> Result<RpCreateDir> {
176 self.inner.create_dir(ctx, path, args).await
177 }
178
179 async fn stat(&self, ctx: &OperationContext, path: &str, args: OpStat) -> Result<RpStat> {
180 self.inner.stat(ctx, path, args).await
181 }
182
183 fn read(&self, ctx: &OperationContext, path: &str, args: OpRead) -> Result<Self::Reader> {
184 self.inner.read(ctx, path, args)
185 }
186
187 fn write(&self, ctx: &OperationContext, path: &str, args: OpWrite) -> Result<Self::Writer> {
188 self.inner.write(ctx, path, args)
189 }
190
191 fn copy(
192 &self,
193 ctx: &OperationContext,
194 from: &str,
195 to: &str,
196 args: OpCopy,
197 opts: OpCopier,
198 ) -> Result<Self::Copier> {
199 self.inner.copy(ctx, from, to, args, opts)
200 }
201
202 fn list(&self, _ctx: &OperationContext, path: &str, args: OpList) -> Result<Self::Lister> {
203 let mut path = path;
204 if path == "/" {
205 path = ""
206 }
207
208 let idx = if args.recursive() {
209 self.children_flat(path)
210 } else {
211 self.children_hierarchy(path)
212 };
213
214 Ok(ImmutableDir::new(idx))
215 }
216
217 fn delete(&self, ctx: &OperationContext) -> Result<Self::Deleter> {
218 self.inner.delete(ctx)
219 }
220
221 async fn rename(
222 &self,
223 ctx: &OperationContext,
224 from: &str,
225 to: &str,
226 args: OpRename,
227 ) -> Result<RpRename> {
228 self.inner.rename(ctx, from, to, args).await
229 }
230
231 async fn presign(
232 &self,
233 ctx: &OperationContext,
234 path: &str,
235 args: OpPresign,
236 ) -> Result<RpPresign> {
237 self.inner.presign(ctx, path, args).await
238 }
239}
240
241#[doc(hidden)]
242pub struct ImmutableDir {
243 idx: IntoIter<String>,
244}
245
246impl ImmutableDir {
247 fn new(idx: Vec<String>) -> Self {
248 Self {
249 idx: idx.into_iter(),
250 }
251 }
252
253 fn inner_next(&mut self) -> Option<oio::Entry> {
254 self.idx.next().map(|v| {
255 let mode = if v.ends_with('/') {
256 EntryMode::DIR
257 } else {
258 EntryMode::FILE
259 };
260 let meta = Metadata::new(mode);
261 oio::Entry::with(v, meta)
262 })
263 }
264}
265
266impl oio::List for ImmutableDir {
267 async fn next(&mut self) -> Result<Option<oio::Entry>> {
268 Ok(self.inner_next())
269 }
270}
271
272#[cfg(test)]
273mod tests {
274 use std::collections::HashMap;
275 use std::sync::Arc;
276
277 use super::*;
278 use futures::TryStreamExt;
279 use log::debug;
280 use logforth::append::Testing;
281 use logforth::filter::rustlog::RustLogFilterBuilder;
282 use logforth::layout::TextLayout;
283
284 #[derive(Debug)]
285 struct MockService;
286
287 impl Service for MockService {
288 type Reader = ();
289 type Writer = ();
290 type Lister = ();
291 type Deleter = ();
292 type Copier = ();
293
294 fn info(&self) -> ServiceInfo {
295 ServiceInfo::with_scheme("mock")
296 }
297
298 fn capability(&self) -> Capability {
299 Capability {
300 list: true,
301 list_with_recursive: true,
302 ..Default::default()
303 }
304 }
305
306 async fn create_dir(
307 &self,
308 _: &OperationContext,
309 _: &str,
310 _: OpCreateDir,
311 ) -> Result<RpCreateDir> {
312 Err(Error::new(
313 ErrorKind::Unsupported,
314 "operation is not supported",
315 ))
316 }
317
318 async fn stat(&self, _: &OperationContext, _: &str, _: OpStat) -> Result<RpStat> {
319 Err(Error::new(
320 ErrorKind::Unsupported,
321 "operation is not supported",
322 ))
323 }
324
325 fn read(&self, _ctx: &OperationContext, _: &str, _: OpRead) -> Result<Self::Reader> {
326 Err(Error::new(
327 ErrorKind::Unsupported,
328 "operation is not supported",
329 ))
330 }
331
332 fn write(&self, _ctx: &OperationContext, _: &str, _: OpWrite) -> Result<Self::Writer> {
333 Err(Error::new(
334 ErrorKind::Unsupported,
335 "operation is not supported",
336 ))
337 }
338
339 fn delete(&self, _ctx: &OperationContext) -> Result<Self::Deleter> {
340 Err(Error::new(
341 ErrorKind::Unsupported,
342 "operation is not supported",
343 ))
344 }
345
346 fn list(&self, _ctx: &OperationContext, _: &str, _: OpList) -> Result<Self::Lister> {
347 Err(Error::new(
348 ErrorKind::Unsupported,
349 "operation is not supported",
350 ))
351 }
352
353 fn copy(
354 &self,
355 _: &OperationContext,
356 _: &str,
357 _: &str,
358 _: OpCopy,
359 _: OpCopier,
360 ) -> Result<Self::Copier> {
361 Err(Error::new(
362 ErrorKind::Unsupported,
363 "operation is not supported",
364 ))
365 }
366
367 async fn rename(
368 &self,
369 _: &OperationContext,
370 _: &str,
371 _: &str,
372 _: OpRename,
373 ) -> Result<RpRename> {
374 Err(Error::new(
375 ErrorKind::Unsupported,
376 "operation is not supported",
377 ))
378 }
379
380 async fn presign(&self, _: &OperationContext, _: &str, _: OpPresign) -> Result<RpPresign> {
381 Err(Error::new(
382 ErrorKind::Unsupported,
383 "operation is not supported",
384 ))
385 }
386 }
387
388 fn build_operator(layer: ImmutableIndexLayer) -> Operator {
389 Operator::from_parts(OperationContext::default(), Arc::new(MockService)).layer(layer)
390 }
391
392 fn setup() {
393 let _ = logforth::starter_log::builder()
394 .dispatch(|d| {
395 d.filter(RustLogFilterBuilder::from_default_env().build())
396 .append(Testing::default().with_layout(TextLayout::default()))
397 })
398 .try_apply();
399 }
400
401 #[tokio::test]
402 async fn test_list() -> Result<()> {
403 setup();
404
405 let mut iil = ImmutableIndexLayer::default();
406 for i in ["file", "dir/", "dir/file", "dir_without_prefix/file"] {
407 iil.insert(i.to_string())
408 }
409
410 let op = build_operator(iil);
411
412 let mut map = HashMap::new();
413 let mut set = HashSet::new();
414 let mut ds = op.lister("").await?;
415 while let Some(entry) = ds.try_next().await? {
416 debug!("got entry: {}", entry.path());
417 assert!(
418 set.insert(entry.path().to_string()),
419 "duplicated value: {}",
420 entry.path()
421 );
422 map.insert(entry.path().to_string(), entry.metadata().mode());
423 }
424
425 assert_eq!(map["file"], EntryMode::FILE);
426 assert_eq!(map["dir/"], EntryMode::DIR);
427 assert_eq!(map["dir_without_prefix/"], EntryMode::DIR);
428 Ok(())
429 }
430
431 #[tokio::test]
432 async fn test_scan() -> Result<()> {
433 setup();
434
435 let mut iil = ImmutableIndexLayer::default();
436 for i in ["file", "dir/", "dir/file", "dir_without_prefix/file"] {
437 iil.insert(i.to_string())
438 }
439
440 let op = build_operator(iil);
441
442 let mut ds = op.lister_with("/").recursive(true).await?;
443 let mut set = HashSet::new();
444 let mut map = HashMap::new();
445 while let Some(entry) = ds.try_next().await? {
446 debug!("got entry: {}", entry.path());
447 assert!(
448 set.insert(entry.path().to_string()),
449 "duplicated value: {}",
450 entry.path()
451 );
452 map.insert(entry.path().to_string(), entry.metadata().mode());
453 }
454
455 debug!("current files: {map:?}");
456
457 assert_eq!(map["file"], EntryMode::FILE);
458 assert_eq!(map["dir/"], EntryMode::DIR);
459 assert_eq!(map["dir_without_prefix/file"], EntryMode::FILE);
460 Ok(())
461 }
462
463 #[tokio::test]
464 async fn test_list_dir() -> Result<()> {
465 setup();
466
467 let mut iil = ImmutableIndexLayer::default();
468 for i in [
469 "dataset/stateful/ontime_2007_200.csv",
470 "dataset/stateful/ontime_2008_200.csv",
471 "dataset/stateful/ontime_2009_200.csv",
472 ] {
473 iil.insert(i.to_string())
474 }
475
476 let op = build_operator(iil);
477
478 let mut map = HashMap::new();
480 let mut set = HashSet::new();
481 let mut ds = op.lister("/").await?;
482 while let Some(entry) = ds.try_next().await? {
483 assert!(
484 set.insert(entry.path().to_string()),
485 "duplicated value: {}",
486 entry.path()
487 );
488 map.insert(entry.path().to_string(), entry.metadata().mode());
489 }
490
491 assert_eq!(map.len(), 1);
492 assert_eq!(map["dataset/"], EntryMode::DIR);
493
494 let mut map = HashMap::new();
496 let mut set = HashSet::new();
497 let mut ds = op.lister("dataset/stateful/").await?;
498 while let Some(entry) = ds.try_next().await? {
499 assert!(
500 set.insert(entry.path().to_string()),
501 "duplicated value: {}",
502 entry.path()
503 );
504 map.insert(entry.path().to_string(), entry.metadata().mode());
505 }
506
507 assert_eq!(map["dataset/stateful/ontime_2007_200.csv"], EntryMode::FILE);
508 assert_eq!(map["dataset/stateful/ontime_2008_200.csv"], EntryMode::FILE);
509 assert_eq!(map["dataset/stateful/ontime_2009_200.csv"], EntryMode::FILE);
510 Ok(())
511 }
512
513 #[tokio::test]
514 async fn test_walk_top_down_dir() -> Result<()> {
515 setup();
516
517 let mut iil = ImmutableIndexLayer::default();
518 for i in [
519 "dataset/stateful/ontime_2007_200.csv",
520 "dataset/stateful/ontime_2008_200.csv",
521 "dataset/stateful/ontime_2009_200.csv",
522 ] {
523 iil.insert(i.to_string())
524 }
525
526 let op = build_operator(iil);
527
528 let mut ds = op.lister_with("/").recursive(true).await?;
529
530 let mut map = HashMap::new();
531 let mut set = HashSet::new();
532 while let Some(entry) = ds.try_next().await? {
533 assert!(
534 set.insert(entry.path().to_string()),
535 "duplicated value: {}",
536 entry.path()
537 );
538 map.insert(entry.path().to_string(), entry.metadata().mode());
539 }
540
541 debug!("current files: {map:?}");
542
543 assert_eq!(map["dataset/stateful/ontime_2007_200.csv"], EntryMode::FILE);
544 assert_eq!(map["dataset/stateful/ontime_2008_200.csv"], EntryMode::FILE);
545 assert_eq!(map["dataset/stateful/ontime_2009_200.csv"], EntryMode::FILE);
546 Ok(())
547 }
548}