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::fmt::Debug;
23use std::fmt::Formatter;
24use std::sync::Arc;
25
26use globset::Glob;
27use globset::GlobSet;
28use globset::GlobSetBuilder;
29use opendal_core::raw::*;
30use opendal_core::*;
31
32#[derive(Clone, Debug)]
61pub struct RouteLayer {
62 router: Arc<RouteRouter>,
63}
64
65impl RouteLayer {
66 pub fn builder() -> RouteLayerBuilder {
68 RouteLayerBuilder::default()
69 }
70}
71
72#[derive(Default)]
74pub struct RouteLayerBuilder {
75 routes: Vec<RouteEntry>,
76}
77
78impl RouteLayerBuilder {
79 pub fn route(mut self, pattern: impl AsRef<str>, op: Operator) -> Self {
84 let (ctx, srv) = op.into_parts();
85 self.routes.push(RouteEntry {
86 pattern: pattern.as_ref().to_string(),
87 target: RouteTarget { srv, ctx },
88 });
89 self
90 }
91
92 pub fn build(self) -> Result<RouteLayer> {
97 let mut builder = GlobSetBuilder::new();
98 let mut targets = Vec::with_capacity(self.routes.len());
99
100 for entry in self.routes {
101 let glob = Glob::new(&entry.pattern).map_err(|err| {
102 Error::new(ErrorKind::ConfigInvalid, "invalid route glob pattern")
103 .with_context("pattern", entry.pattern.clone())
104 .with_context("source", err.to_string())
105 })?;
106 builder.add(glob);
107 targets.push(entry.target);
108 }
109
110 let glob = builder.build().map_err(|err| {
111 Error::new(ErrorKind::ConfigInvalid, "failed to build route glob set")
112 .with_context("source", err.to_string())
113 })?;
114
115 Ok(RouteLayer {
116 router: Arc::new(RouteRouter { glob, targets }),
117 })
118 }
119}
120
121struct RouteEntry {
122 pattern: String,
123 target: RouteTarget,
124}
125
126#[derive(Debug)]
127struct RouteRouter {
128 glob: GlobSet,
129 targets: Vec<RouteTarget>,
130}
131
132#[derive(Clone, Debug)]
133struct RouteTarget {
134 srv: Servicer,
135 ctx: OperationContext,
136}
137
138enum RouteSelected {
139 Default(Servicer),
140 Target(RouteTarget),
141}
142
143impl RouteRouter {
144 fn match_index(&self, path: &str) -> Option<usize> {
145 self.glob.matches(path).into_iter().min()
146 }
147
148 fn select(&self, path: &str, default: &Servicer) -> RouteSelected {
149 self.match_index(path)
150 .and_then(|idx| self.targets.get(idx).cloned())
151 .map(RouteSelected::Target)
152 .unwrap_or_else(|| RouteSelected::Default(default.clone()))
153 }
154
155 fn target(&self, idx: usize) -> Option<RouteTarget> {
156 self.targets.get(idx).cloned()
157 }
158}
159
160impl Layer for RouteLayer {
161 fn apply_service(&self, inner: Servicer) -> Servicer {
162 Arc::new(self.layer(inner))
163 }
164}
165
166impl RouteLayer {
167 fn layer(&self, inner: Servicer) -> RouteAccessor {
168 RouteAccessor {
169 inner: Arc::new(inner),
170 router: self.router.clone(),
171 }
172 }
173}
174
175#[doc(hidden)]
176pub struct RouteAccessor {
177 inner: Servicer,
178 router: Arc<RouteRouter>,
179}
180
181impl Debug for RouteAccessor {
182 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
183 self.inner.fmt(f)
184 }
185}
186
187impl RouteAccessor {
188 fn select(&self, path: &str) -> RouteSelected {
189 self.router.select(path, &self.inner)
190 }
191}
192
193impl Service for RouteAccessor {
194 type Reader = oio::Reader;
195 type Writer = oio::Writer;
196 type Lister = oio::Lister;
197 type Deleter = oio::Deleter;
198 type Copier = oio::Copier;
199 type Composer = ();
200
201 fn info(&self) -> ServiceInfo {
202 self.inner.info()
203 }
204
205 fn capability(&self) -> Capability {
206 let mut capability = self.inner.capability();
207 capability.write_can_copy_from = false;
208 capability.compose = false;
209 capability.compose_with_content_type = false;
210 capability.compose_with_content_disposition = false;
211 capability.compose_with_content_encoding = false;
212 capability.compose_with_cache_control = false;
213 capability.compose_with_user_metadata = false;
214 capability.compose_with_if_match = false;
215 capability.compose_with_if_none_match = false;
216 capability.compose_with_if_version_match = false;
217 capability.compose_with_if_version_not_match = false;
218 capability.compose_with_if_not_exists = false;
219 capability.compose_with_source_version = false;
220 capability.compose_with_source_if_match = false;
221 capability
222 }
223
224 async fn create_dir(
225 &self,
226 ctx: &OperationContext,
227 path: &str,
228 args: OpCreateDir,
229 ) -> Result<RpCreateDir> {
230 match self.select(path) {
231 RouteSelected::Default(srv) => srv.create_dir(ctx, path, args).await,
232 RouteSelected::Target(target) => target.srv.create_dir(&target.ctx, path, args).await,
233 }
234 }
235
236 fn read(&self, ctx: &OperationContext, path: &str, args: OpRead) -> Result<oio::Reader> {
237 match self.select(path) {
238 RouteSelected::Default(srv) => srv.read(ctx, path, args),
239 RouteSelected::Target(target) => target.srv.read(&target.ctx, path, args),
240 }
241 }
242
243 fn write(&self, ctx: &OperationContext, path: &str, args: OpWrite) -> Result<oio::Writer> {
244 match self.select(path) {
245 RouteSelected::Default(srv) => srv.write(ctx, path, args),
246 RouteSelected::Target(target) => target.srv.write(&target.ctx, path, args),
247 }
248 }
249
250 fn copy(
251 &self,
252 ctx: &OperationContext,
253 from: &str,
254 to: &str,
255 args: OpCopy,
256 ) -> Result<oio::Copier> {
257 match self.select(from) {
258 RouteSelected::Default(srv) => srv.copy(ctx, from, to, args),
259 RouteSelected::Target(target) => target.srv.copy(&target.ctx, from, to, args),
260 }
261 }
262
263 async fn rename(
264 &self,
265 ctx: &OperationContext,
266 from: &str,
267 to: &str,
268 args: OpRename,
269 ) -> Result<RpRename> {
270 match self.select(from) {
271 RouteSelected::Default(srv) => srv.rename(ctx, from, to, args).await,
272 RouteSelected::Target(target) => target.srv.rename(&target.ctx, from, to, args).await,
273 }
274 }
275
276 async fn restore(
277 &self,
278 ctx: &OperationContext,
279 path: &str,
280 args: OpRestore,
281 ) -> Result<RpRestore> {
282 match self.select(path) {
283 RouteSelected::Default(srv) => srv.restore(ctx, path, args).await,
284 RouteSelected::Target(target) => target.srv.restore(&target.ctx, path, args).await,
285 }
286 }
287
288 async fn stat(&self, ctx: &OperationContext, path: &str, args: OpStat) -> Result<RpStat> {
289 match self.select(path) {
290 RouteSelected::Default(srv) => srv.stat(ctx, path, args).await,
291 RouteSelected::Target(target) => target.srv.stat(&target.ctx, path, args).await,
292 }
293 }
294
295 fn delete(&self, ctx: &OperationContext) -> Result<oio::Deleter> {
296 Ok(Box::new(RouteDeleter::new(
297 self.inner.clone(),
298 self.router.clone(),
299 ctx.clone(),
300 )) as oio::Deleter)
301 }
302
303 fn list(&self, ctx: &OperationContext, path: &str, args: OpList) -> Result<oio::Lister> {
304 match self.select(path) {
305 RouteSelected::Default(srv) => srv.list(ctx, path, args),
306 RouteSelected::Target(target) => target.srv.list(&target.ctx, path, args),
307 }
308 }
309
310 async fn presign(
311 &self,
312 ctx: &OperationContext,
313 path: &str,
314 args: OpPresign,
315 ) -> Result<RpPresign> {
316 match self.select(path) {
317 RouteSelected::Default(srv) => srv.presign(ctx, path, args).await,
318 RouteSelected::Target(target) => target.srv.presign(&target.ctx, path, args).await,
319 }
320 }
321}
322
323#[doc(hidden)]
324pub struct RouteDeleter {
325 default: Servicer,
326 router: Arc<RouteRouter>,
327 ctx: OperationContext,
328 default_deleter: Option<oio::Deleter>,
329 target_deleters: Vec<Option<oio::Deleter>>,
330}
331
332impl RouteDeleter {
333 fn new(default: Servicer, router: Arc<RouteRouter>, ctx: OperationContext) -> Self {
334 let mut target_deleters = Vec::with_capacity(router.targets.len());
335 target_deleters.resize_with(router.targets.len(), || None);
336 Self {
337 default,
338 router,
339 ctx,
340 default_deleter: None,
341 target_deleters,
342 }
343 }
344
345 async fn get_deleter(&mut self, key: RouteKey) -> Result<&mut oio::Deleter> {
346 match key {
347 RouteKey::Default => {
348 if self.default_deleter.is_none() {
349 let deleter = self.default.delete(&self.ctx)?;
350 self.default_deleter = Some(deleter);
351 }
352 Ok(self
353 .default_deleter
354 .as_mut()
355 .expect("default deleter must exist"))
356 }
357 RouteKey::Target(idx) => {
358 if idx >= self.target_deleters.len() {
359 if self.default_deleter.is_none() {
360 let deleter = self.default.delete(&self.ctx)?;
361 self.default_deleter = Some(deleter);
362 }
363 return Ok(self
364 .default_deleter
365 .as_mut()
366 .expect("default deleter must exist"));
367 }
368 if self.target_deleters[idx].is_none() {
369 let Some(target) = self.router.target(idx) else {
370 let deleter = self.default.delete(&self.ctx)?;
371 self.default_deleter = Some(deleter);
372 return Ok(self
373 .default_deleter
374 .as_mut()
375 .expect("default deleter must exist"));
376 };
377 let deleter = target.srv.delete(&target.ctx)?;
378 self.target_deleters[idx] = Some(deleter);
379 }
380 Ok(self.target_deleters[idx]
381 .as_mut()
382 .expect("target deleter must exist"))
383 }
384 }
385 }
386}
387
388impl oio::Delete for RouteDeleter {
389 async fn delete(&mut self, path: &str, args: OpDelete) -> Result<()> {
390 let key = match self.router.match_index(path) {
391 Some(idx) => RouteKey::Target(idx),
392 None => RouteKey::Default,
393 };
394 let deleter = self.get_deleter(key).await?;
395 deleter.delete(path, args).await
396 }
397
398 async fn close(&mut self) -> Result<()> {
399 let mut first_err = None;
400 if let Some(deleter) = self.default_deleter.as_mut()
401 && let Err(err) = deleter.close().await
402 {
403 first_err = Some(err);
404 }
405 for deleter in self.target_deleters.iter_mut().flatten() {
406 if let Err(err) = deleter.close().await
407 && first_err.is_none()
408 {
409 first_err = Some(err);
410 }
411 }
412
413 match first_err {
414 Some(err) => Err(err),
415 None => Ok(()),
416 }
417 }
418}
419
420#[derive(Debug, Hash, PartialEq, Eq)]
421enum RouteKey {
422 Default,
423 Target(usize),
424}
425
426#[cfg(test)]
427mod tests {
428 use std::collections::HashSet;
429 use std::path::Path;
430 use std::path::PathBuf;
431 use std::sync::Mutex;
432 use std::time::SystemTime;
433 use std::time::UNIX_EPOCH;
434
435 use opendal_service_fs::Fs;
436
437 use super::*;
438
439 fn build_memory_operator() -> Result<Operator> {
440 Operator::new(services::Memory::default())
441 }
442
443 fn build_mock_operator(name: &'static str) -> Operator {
444 Operator::from_parts(
445 OperationContext::default(),
446 Arc::new(MockService {
447 info: ServiceInfo::new("mock", "", name),
448 paths: Arc::new(Mutex::new(HashSet::new())),
449 }),
450 )
451 }
452
453 async fn build_fs_operator(label: &str) -> Result<(Operator, PathBuf)> {
454 let root = fs_root(label);
455 tokio::fs::create_dir_all(&root)
456 .await
457 .map_err(new_std_io_error)?;
458 let op = Operator::new(Fs::default().root(&root.to_string_lossy()))?;
459 Ok((op, root))
460 }
461
462 fn fs_root(label: &str) -> PathBuf {
463 let nanos = SystemTime::now()
464 .duration_since(UNIX_EPOCH)
465 .expect("system time before UNIX_EPOCH")
466 .as_nanos();
467 let mut root = std::env::temp_dir();
468 root.push(format!(
469 "opendal-route-{label}-{nanos}-{}",
470 std::process::id()
471 ));
472 root
473 }
474
475 async fn cleanup_fs_root(root: &Path) {
476 let _ = tokio::fs::remove_dir_all(root).await;
477 }
478
479 async fn assert_missing(op: &Operator, path: &str) -> Result<()> {
480 let err = op.stat(path).await.unwrap_err();
481 assert_eq!(err.kind(), ErrorKind::NotFound);
482 Ok(())
483 }
484
485 #[derive(Debug)]
486 struct MockService {
487 info: ServiceInfo,
488 paths: Arc<Mutex<HashSet<String>>>,
489 }
490
491 impl Service for MockService {
492 type Reader = ();
493 type Writer = MockWriter;
494 type Lister = ();
495 type Deleter = ();
496 type Copier = ();
497 type Composer = ();
498
499 fn info(&self) -> ServiceInfo {
500 self.info.clone()
501 }
502
503 fn capability(&self) -> Capability {
504 Capability {
505 stat: true,
506 write: true,
507 copy: true,
508 rename: true,
509 ..Default::default()
510 }
511 }
512
513 async fn create_dir(
514 &self,
515 _: &OperationContext,
516 _: &str,
517 _: OpCreateDir,
518 ) -> Result<RpCreateDir> {
519 Err(Error::new(
520 ErrorKind::Unsupported,
521 "operation is not supported",
522 ))
523 }
524
525 async fn stat(&self, _: &OperationContext, path: &str, _: OpStat) -> Result<RpStat> {
526 if self.paths.lock().unwrap().contains(path) {
527 Ok(RpStat::new(MetadataBuilder::file(0).build()))
528 } else {
529 Err(Error::new(ErrorKind::NotFound, "path not found"))
530 }
531 }
532
533 fn read(&self, _ctx: &OperationContext, _: &str, _: OpRead) -> Result<Self::Reader> {
534 Err(Error::new(
535 ErrorKind::Unsupported,
536 "operation is not supported",
537 ))
538 }
539
540 fn write(&self, _: &OperationContext, path: &str, _: OpWrite) -> Result<Self::Writer> {
541 Ok(MockWriter {
542 paths: self.paths.clone(),
543 path: path.to_string(),
544 })
545 }
546
547 fn delete(&self, _ctx: &OperationContext) -> Result<Self::Deleter> {
548 Err(Error::new(
549 ErrorKind::Unsupported,
550 "operation is not supported",
551 ))
552 }
553
554 fn list(&self, _ctx: &OperationContext, _: &str, _: OpList) -> Result<Self::Lister> {
555 Err(Error::new(
556 ErrorKind::Unsupported,
557 "operation is not supported",
558 ))
559 }
560
561 fn copy(
562 &self,
563 _: &OperationContext,
564 from: &str,
565 to: &str,
566 _: OpCopy,
567 ) -> Result<Self::Copier> {
568 if !self.paths.lock().unwrap().contains(from) {
569 return Err(Error::new(ErrorKind::NotFound, "source not found"));
570 }
571 self.paths.lock().unwrap().insert(to.to_string());
572 Ok(())
573 }
574
575 async fn rename(
576 &self,
577 _: &OperationContext,
578 from: &str,
579 to: &str,
580 _: OpRename,
581 ) -> Result<RpRename> {
582 let mut paths = self.paths.lock().unwrap();
583 if !paths.remove(from) {
584 return Err(Error::new(ErrorKind::NotFound, "source not found"));
585 }
586 paths.insert(to.to_string());
587 Ok(RpRename::default())
588 }
589
590 async fn presign(&self, _: &OperationContext, _: &str, _: OpPresign) -> Result<RpPresign> {
591 Err(Error::new(
592 ErrorKind::Unsupported,
593 "operation is not supported",
594 ))
595 }
596 }
597
598 struct MockWriter {
599 paths: Arc<Mutex<HashSet<String>>>,
600 path: String,
601 }
602
603 impl oio::Write for MockWriter {
604 async fn write(&mut self, _: Buffer) -> Result<()> {
605 Ok(())
606 }
607
608 async fn close(&mut self) -> Result<Metadata> {
609 self.paths.lock().unwrap().insert(self.path.clone());
610 Ok(MetadataBuilder::unknown().build())
611 }
612
613 async fn abort(&mut self) -> Result<()> {
614 Ok(())
615 }
616 }
617
618 #[tokio::test]
619 async fn test_first_match_wins() -> Result<()> {
620 let default_op = build_memory_operator()?;
621 let (fast_op, fast_root) = build_fs_operator("first-match-fast").await?;
622 let slow_op = build_memory_operator()?;
623
624 let routed = default_op.clone().layer(
625 RouteLayer::builder()
626 .route("data/*.txt", fast_op.clone())
627 .route("data/**", slow_op.clone())
628 .build()?,
629 );
630
631 routed.write("data/file.txt", "v").await?;
632
633 assert!(fast_op.stat("data/file.txt").await.is_ok());
634 assert_missing(&slow_op, "data/file.txt").await?;
635 assert_missing(&default_op, "data/file.txt").await?;
636
637 cleanup_fs_root(&fast_root).await;
638 Ok(())
639 }
640
641 #[tokio::test]
642 async fn test_fallback_to_default() -> Result<()> {
643 let default_op = build_memory_operator()?;
644 let (hot_op, hot_root) = build_fs_operator("fallback-hot").await?;
645
646 let routed = default_op.clone().layer(
647 RouteLayer::builder()
648 .route("hot/**", hot_op.clone())
649 .build()?,
650 );
651
652 routed.write("cold/file.txt", "v").await?;
653
654 assert!(default_op.stat("cold/file.txt").await.is_ok());
655 assert_missing(&hot_op, "cold/file.txt").await?;
656
657 cleanup_fs_root(&hot_root).await;
658 Ok(())
659 }
660
661 #[tokio::test]
662 async fn test_copy_and_rename_route_by_from() -> Result<()> {
663 let default_op = build_mock_operator("default");
664 let hot_op = build_mock_operator("hot");
665
666 let routed = default_op.clone().layer(
667 RouteLayer::builder()
668 .route("hot/**", hot_op.clone())
669 .build()?,
670 );
671
672 routed.write("hot/src.txt", "v").await?;
673 routed.copy("hot/src.txt", "cold/copied.txt").await?;
674
675 assert!(hot_op.stat("hot/src.txt").await.is_ok());
676 assert!(hot_op.stat("cold/copied.txt").await.is_ok());
677 assert_missing(&default_op, "cold/copied.txt").await?;
678
679 routed.write("hot/src2.txt", "v").await?;
680 routed.rename("hot/src2.txt", "cold/renamed.txt").await?;
681
682 assert_missing(&hot_op, "hot/src2.txt").await?;
683 assert!(hot_op.stat("cold/renamed.txt").await.is_ok());
684 assert_missing(&default_op, "cold/renamed.txt").await?;
685
686 Ok(())
687 }
688
689 #[tokio::test]
690 async fn test_delete_iter_routes_per_path() -> Result<()> {
691 let default_op = build_memory_operator()?;
692 let (hot_op, hot_root) = build_fs_operator("delete-hot").await?;
693
694 let routed = default_op.clone().layer(
695 RouteLayer::builder()
696 .route("hot/**", hot_op.clone())
697 .build()?,
698 );
699
700 routed.write("hot/a.txt", "v").await?;
701 routed.write("cold/b.txt", "v").await?;
702
703 routed.delete_iter(["hot/a.txt", "cold/b.txt"]).await?;
704
705 assert_missing(&hot_op, "hot/a.txt").await?;
706 assert_missing(&default_op, "cold/b.txt").await?;
707
708 cleanup_fs_root(&hot_root).await;
709 Ok(())
710 }
711}