1#![doc = include_str!("../README.md")]
19#![cfg_attr(docsrs, feature(doc_cfg))]
20#![cfg_attr(docsrs, doc(auto_cfg))]
21#![deny(missing_docs)]
22mod deleter;
23mod error;
24mod full;
25mod writer;
26
27use std::{
28 collections::HashSet,
29 ops::{Bound, Deref, Range, RangeBounds},
30 sync::{Arc, Mutex},
31};
32
33use foyer::{Code, HybridCache, Result as FoyerResult};
34
35use opendal_core::raw::*;
36use opendal_core::*;
37
38pub use deleter::Deleter;
39pub use writer::Writer;
40
41#[derive(Debug, Clone, PartialEq, Eq, Hash)]
51pub struct FoyerKey {
52 pub path: String,
54 pub version: Option<String>,
56}
57
58impl Code for FoyerKey {
59 fn encode(&self, writer: &mut impl std::io::Write) -> FoyerResult<()> {
60 write_bytes(writer, self.path.as_bytes())?;
61 match &self.version {
62 None => writer.write_all(&[0u8])?,
63 Some(version) => {
64 writer.write_all(&[1u8])?;
65 write_bytes(writer, version.as_bytes())?;
66 }
67 }
68 Ok(())
69 }
70
71 fn decode(reader: &mut impl std::io::Read) -> FoyerResult<Self> {
72 let path = read_string(reader)?;
73 let mut tag = [0u8; 1];
74 reader.read_exact(&mut tag)?;
75 let version = match tag[0] {
76 0 => None,
77 1 => Some(read_string(reader)?),
78 other => {
79 return Err(std::io::Error::new(
80 std::io::ErrorKind::InvalidData,
81 format!("invalid FoyerKey version tag: {other}"),
82 )
83 .into());
84 }
85 };
86 Ok(FoyerKey { path, version })
87 }
88
89 fn estimated_size(&self) -> usize {
90 8 + self.path.len() + 1 + self.version.as_ref().map_or(0, |v| 8 + v.len())
91 }
92}
93
94fn write_bytes(writer: &mut impl std::io::Write, bytes: &[u8]) -> FoyerResult<()> {
95 writer.write_all(&(bytes.len() as u64).to_le_bytes())?;
96 writer.write_all(bytes)?;
97 Ok(())
98}
99
100fn read_string(reader: &mut impl std::io::Read) -> FoyerResult<String> {
101 let mut len = [0u8; 8];
102 reader.read_exact(&mut len)?;
103 let len = u64::from_le_bytes(len) as usize;
104 let mut buf = vec![0u8; len];
105 reader.read_exact(&mut buf)?;
106 String::from_utf8(buf)
107 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e).into())
108}
109
110#[derive(Debug)]
112pub struct FoyerValue(
113 pub Buffer,
115);
116
117impl Deref for FoyerValue {
118 type Target = Buffer;
119
120 fn deref(&self) -> &Self::Target {
121 &self.0
122 }
123}
124
125impl Code for FoyerValue {
126 fn encode(&self, writer: &mut impl std::io::Write) -> FoyerResult<()> {
127 let len = self.0.len() as u64;
128 writer.write_all(&len.to_le_bytes())?;
129 std::io::copy(&mut self.0.clone(), writer)?;
130 Ok(())
131 }
132
133 fn decode(reader: &mut impl std::io::Read) -> FoyerResult<Self>
134 where
135 Self: Sized,
136 {
137 let mut len_bytes = [0u8; 8];
138 reader.read_exact(&mut len_bytes)?;
139 let len = u64::from_le_bytes(len_bytes) as usize;
140 let mut buffer = vec![0u8; len];
141 reader.read_exact(&mut buffer[..len])?;
142 Ok(FoyerValue(buffer.into()))
143 }
144
145 fn estimated_size(&self) -> usize {
146 8 + self.0.len()
147 }
148}
149
150#[derive(Debug)]
188pub struct FoyerLayer {
189 cache: HybridCache<FoyerKey, FoyerValue>,
190 size_limit: Range<usize>,
191 deleted_keys: Arc<Mutex<HashSet<FoyerKey>>>,
192}
193
194impl FoyerLayer {
195 pub fn new(cache: HybridCache<FoyerKey, FoyerValue>) -> Self {
197 FoyerLayer {
198 cache,
199 size_limit: 0..usize::MAX,
200 deleted_keys: Arc::default(),
201 }
202 }
203
204 pub fn with_size_limit<R: RangeBounds<usize>>(mut self, size_limit: R) -> Self {
210 let start = match size_limit.start_bound() {
211 Bound::Included(v) => *v,
212 Bound::Excluded(v) => *v + 1,
213 Bound::Unbounded => 0,
214 };
215 let end = match size_limit.end_bound() {
216 Bound::Included(v) => *v + 1,
217 Bound::Excluded(v) => *v,
218 Bound::Unbounded => usize::MAX,
219 };
220 self.size_limit = start..end;
221 self
222 }
223}
224
225impl Layer for FoyerLayer {
226 fn apply_service(&self, inner: Servicer) -> Servicer {
227 Arc::new(self.layer(inner))
228 }
229}
230
231impl FoyerLayer {
232 fn layer(&self, inner: Servicer) -> FoyerService {
233 FoyerService {
234 inner,
235 cache: self.cache.clone(),
236 size_limit: self.size_limit.clone(),
237 deleted_keys: self.deleted_keys.clone(),
238 }
239 }
240}
241
242#[derive(Debug)]
243pub(crate) struct Inner {
244 pub(crate) srv: Servicer,
245 pub(crate) ctx: OperationContext,
246 pub(crate) cache: HybridCache<FoyerKey, FoyerValue>,
247 pub(crate) deleted_keys: Arc<Mutex<HashSet<FoyerKey>>>,
248}
249
250#[derive(Debug)]
251#[doc(hidden)]
252pub struct FoyerService {
253 inner: Servicer,
254 cache: HybridCache<FoyerKey, FoyerValue>,
255 size_limit: Range<usize>,
256 deleted_keys: Arc<Mutex<HashSet<FoyerKey>>>,
257}
258
259impl FoyerService {
260 fn operation_inner(&self, ctx: &OperationContext) -> Arc<Inner> {
261 Arc::new(Inner {
262 srv: self.inner.clone(),
263 ctx: ctx.clone(),
264 cache: self.cache.clone(),
265 deleted_keys: self.deleted_keys.clone(),
266 })
267 }
268}
269
270impl Service for FoyerService {
271 type Reader = full::FullReader;
272 type Writer = Writer<oio::Writer>;
273 type Lister = oio::Lister;
274 type Deleter = Deleter<oio::Deleter>;
275 type Copier = oio::Copier;
276 type Composer = ();
277
278 fn info(&self) -> ServiceInfo {
279 self.inner.info()
280 }
281
282 fn capability(&self) -> Capability {
283 let mut capability = self.inner.capability();
284 capability.write_can_copy_from = false;
285 capability.compose = false;
286 capability.compose_with_content_type = false;
287 capability.compose_with_content_disposition = false;
288 capability.compose_with_content_encoding = false;
289 capability.compose_with_cache_control = false;
290 capability.compose_with_user_metadata = false;
291 capability.compose_with_if_match = false;
292 capability.compose_with_if_none_match = false;
293 capability.compose_with_if_version_match = false;
294 capability.compose_with_if_version_not_match = false;
295 capability.compose_with_if_not_exists = false;
296 capability.compose_with_source_version = false;
297 capability.compose_with_source_if_match = false;
298 capability.restore = false;
299 capability.restore_with_version = false;
300 capability.restore_with_if_not_exists = false;
301 capability
302 }
303
304 fn read(&self, ctx: &OperationContext, path: &str, args: OpRead) -> Result<Self::Reader> {
305 Ok(full::FullReader::new(
306 self.operation_inner(ctx),
307 self.size_limit.clone(),
308 path.to_string(),
309 args,
310 ))
311 }
312
313 fn write(&self, ctx: &OperationContext, path: &str, args: OpWrite) -> Result<Self::Writer> {
314 let inner = self.operation_inner(ctx);
315 let size_limit = self.size_limit.clone();
316 let path = path.to_string();
317 let w = inner.srv.write(&inner.ctx, &path, args)?;
318 Ok(Writer::new(w, path, inner, size_limit))
319 }
320
321 fn delete(&self, ctx: &OperationContext) -> Result<Self::Deleter> {
322 let inner = self.operation_inner(ctx);
323 let d = inner.srv.delete(&inner.ctx)?;
324 Ok(Deleter::new(d, inner))
325 }
326
327 fn copy(
328 &self,
329 ctx: &OperationContext,
330 from: &str,
331 to: &str,
332 args: OpCopy,
333 ) -> Result<Self::Copier> {
334 self.inner.copy(ctx, from, to, args)
335 }
336
337 fn list(&self, ctx: &OperationContext, path: &str, args: OpList) -> Result<Self::Lister> {
338 self.inner.list(ctx, path, args)
339 }
340
341 async fn create_dir(
342 &self,
343 ctx: &OperationContext,
344 path: &str,
345 args: OpCreateDir,
346 ) -> Result<RpCreateDir> {
347 self.inner.create_dir(ctx, path, args).await
348 }
349
350 async fn stat(&self, ctx: &OperationContext, path: &str, args: OpStat) -> Result<RpStat> {
351 self.inner.stat(ctx, path, args).await
352 }
353
354 async fn rename(
355 &self,
356 ctx: &OperationContext,
357 from: &str,
358 to: &str,
359 args: OpRename,
360 ) -> Result<RpRename> {
361 self.inner.rename(ctx, from, to, args).await
362 }
363
364 async fn restore(
365 &self,
366 _ctx: &OperationContext,
367 _path: &str,
368 _args: OpRestore,
369 ) -> Result<RpRestore> {
370 Err(
371 Error::new(ErrorKind::Unsupported, "operation is not supported")
372 .with_operation(Operation::Restore),
373 )
374 }
375
376 async fn presign(
377 &self,
378 ctx: &OperationContext,
379 path: &str,
380 args: OpPresign,
381 ) -> Result<RpPresign> {
382 self.inner.presign(ctx, path, args).await
383 }
384
385 }
387
388#[cfg(test)]
389mod tests {
390 use foyer::{
391 BlockEngineConfig, DeviceBuilder, Error as FoyerError, ErrorKind as FoyerErrorKind,
392 FsDeviceBuilder, HybridCache, HybridCacheBuilder, RecoverMode,
393 };
394 use opendal_core::raw::oio::Read as _;
395 use opendal_core::raw::oio::ReadStream as _;
396 use opendal_core::{Buffer, Operator, services::Memory};
397 use size::consts::MiB;
398 use std::io::Cursor;
399 use std::sync::Arc;
400 use std::sync::atomic::{AtomicUsize, Ordering};
401
402 use super::*;
403 use crate::error::extract_err;
404
405 fn key(i: u8) -> String {
406 format!("obj-{i}")
407 }
408
409 fn value(i: u8) -> Vec<u8> {
410 vec![i; 63 * 1024]
412 }
413
414 async fn memory_cache() -> HybridCache<FoyerKey, FoyerValue> {
415 HybridCacheBuilder::new()
416 .memory(1024 * 1024)
417 .with_shards(1)
418 .storage()
419 .with_recover_mode(RecoverMode::None)
420 .build()
421 .await
422 .unwrap()
423 }
424
425 #[derive(Debug)]
426 struct MockReadState {
427 data: Buffer,
428 stat_calls: AtomicUsize,
429 open_calls: AtomicUsize,
430 read_calls: AtomicUsize,
431 last_stat_args: Mutex<Option<OpStat>>,
432 last_read_args: Mutex<Option<OpRead>>,
433 }
434
435 impl MockReadState {
436 fn new(data: Buffer) -> Self {
437 Self {
438 data,
439 stat_calls: AtomicUsize::new(0),
440 open_calls: AtomicUsize::new(0),
441 read_calls: AtomicUsize::new(0),
442 last_stat_args: Mutex::new(None),
443 last_read_args: Mutex::new(None),
444 }
445 }
446
447 fn metadata(&self) -> Metadata {
448 {
449 let metadata = MetadataBuilder::file(self.data.len() as _);
450 metadata.build()
451 }
452 }
453
454 fn rp_read(&self) -> RpRead {
455 RpRead::new(self.metadata())
456 }
457
458 fn read_range(&self, range: BytesRange) -> Buffer {
459 self.data.slice(range.to_range_as_usize())
460 }
461 }
462
463 #[derive(Debug, Clone)]
464 struct MockReadService {
465 state: Arc<MockReadState>,
466 }
467
468 impl MockReadService {
469 fn new(data: impl Into<Buffer>) -> Self {
470 Self {
471 state: Arc::new(MockReadState::new(data.into())),
472 }
473 }
474 }
475
476 #[derive(Debug)]
477 struct MockReadReader {
478 state: Arc<MockReadState>,
479 }
480
481 impl oio::Read for MockReadReader {
482 async fn open(&self, range: BytesRange) -> Result<(RpRead, Box<dyn oio::ReadStreamDyn>)> {
483 self.state.open_calls.fetch_add(1, Ordering::Relaxed);
484 let buffer = self.state.read_range(range);
485 Ok((
486 self.state.rp_read(),
487 Box::new(buffer) as Box<dyn oio::ReadStreamDyn>,
488 ))
489 }
490
491 async fn read(&self, range: BytesRange) -> Result<(RpRead, Buffer)> {
492 self.state.read_calls.fetch_add(1, Ordering::Relaxed);
493 if range.size().is_none() {
494 return Err(Error::new(
495 ErrorKind::Unsupported,
496 "mock reader requires a bounded read range",
497 ));
498 }
499
500 Ok((self.state.rp_read(), self.state.read_range(range)))
501 }
502 }
503
504 impl Service for MockReadService {
505 type Reader = MockReadReader;
506 type Writer = ();
507 type Lister = ();
508 type Deleter = ();
509 type Copier = ();
510 type Composer = ();
511
512 fn info(&self) -> ServiceInfo {
513 ServiceInfo::with_scheme("mock")
514 }
515
516 fn capability(&self) -> Capability {
517 Capability {
518 read: true,
519 stat: true,
520 restore: true,
521 restore_with_version: true,
522 restore_with_if_not_exists: true,
523 ..Default::default()
524 }
525 }
526
527 async fn create_dir(
528 &self,
529 _: &OperationContext,
530 _: &str,
531 _: OpCreateDir,
532 ) -> Result<RpCreateDir> {
533 Err(Error::new(
534 ErrorKind::Unsupported,
535 "operation is not supported",
536 ))
537 }
538
539 async fn stat(&self, _: &OperationContext, _: &str, args: OpStat) -> Result<RpStat> {
540 self.state.stat_calls.fetch_add(1, Ordering::Relaxed);
541 *self.state.last_stat_args.lock().unwrap() = Some(args);
542 Ok(RpStat::new(self.state.metadata()))
543 }
544
545 fn read(&self, _ctx: &OperationContext, _: &str, args: OpRead) -> Result<Self::Reader> {
546 *self.state.last_read_args.lock().unwrap() = Some(args);
547 Ok(MockReadReader {
548 state: self.state.clone(),
549 })
550 }
551
552 fn write(&self, _ctx: &OperationContext, _: &str, _: OpWrite) -> Result<Self::Writer> {
553 Err(Error::new(
554 ErrorKind::Unsupported,
555 "operation is not supported",
556 ))
557 }
558
559 fn delete(&self, _ctx: &OperationContext) -> Result<Self::Deleter> {
560 Err(Error::new(
561 ErrorKind::Unsupported,
562 "operation is not supported",
563 ))
564 }
565
566 fn list(&self, _ctx: &OperationContext, _: &str, _: OpList) -> Result<Self::Lister> {
567 Err(Error::new(
568 ErrorKind::Unsupported,
569 "operation is not supported",
570 ))
571 }
572
573 fn copy(&self, _: &OperationContext, _: &str, _: &str, _: OpCopy) -> Result<Self::Copier> {
574 Err(Error::new(
575 ErrorKind::Unsupported,
576 "operation is not supported",
577 ))
578 }
579
580 async fn rename(
581 &self,
582 _: &OperationContext,
583 _: &str,
584 _: &str,
585 _: OpRename,
586 ) -> Result<RpRename> {
587 Err(Error::new(
588 ErrorKind::Unsupported,
589 "operation is not supported",
590 ))
591 }
592
593 async fn presign(&self, _: &OperationContext, _: &str, _: OpPresign) -> Result<RpPresign> {
594 Err(Error::new(
595 ErrorKind::Unsupported,
596 "operation is not supported",
597 ))
598 }
599 }
600
601 fn service_context(_: &Servicer) -> OperationContext {
602 OperationContext::new()
603 }
604
605 #[tokio::test]
606 async fn test_restore_is_not_supported() {
607 let cache = memory_cache().await;
608 let source = Arc::new(MockReadService::new("0123456789"));
609 assert!(source.capability().restore);
610
611 let service = FoyerLayer::new(cache).apply_service(source);
612 let capability = service.capability();
613 assert!(!capability.restore);
614 assert!(!capability.restore_with_version);
615 assert!(!capability.restore_with_if_not_exists);
616
617 let err = service
618 .restore(&OperationContext::new(), "test", OpRestore::new())
619 .await
620 .expect_err("FoyerLayer must reject restore operations");
621 assert_eq!(err.kind(), ErrorKind::Unsupported);
622 }
623
624 #[tokio::test]
625 async fn test_full_reader_open_fallback_preserves_stream() {
626 let cache = memory_cache().await;
627 let source = Arc::new(MockReadService::new("0123456789"));
628 let state = source.state.clone();
629 let service = FoyerLayer::new(cache)
630 .with_size_limit(0..1)
631 .apply_service(source);
632 let ctx = service_context(&service);
633
634 let reader = service.read(&ctx, "test", OpRead::default()).unwrap();
635 let (_, mut stream) = reader.open(BytesRange::new(0, None)).await.unwrap();
636 let buffer = stream.read_all().await.unwrap();
637
638 assert_eq!(buffer.to_vec(), b"0123456789");
639 assert_eq!(state.open_calls.load(Ordering::Relaxed), 1);
640 assert_eq!(state.read_calls.load(Ordering::Relaxed), 0);
641 assert_eq!(state.stat_calls.load(Ordering::Relaxed), 1);
642 }
643
644 #[tokio::test]
645 async fn test_full_reader_open_fills_cache() {
646 let cache = memory_cache().await;
647 let source = Arc::new(MockReadService::new("0123456789"));
648 let state = source.state.clone();
649 let service = FoyerLayer::new(cache)
650 .with_size_limit(0..100)
651 .apply_service(source);
652 let ctx = service_context(&service);
653
654 let reader = service.read(&ctx, "test", OpRead::default()).unwrap();
655 let (_, mut stream) = reader.open(BytesRange::from(0_u64..2)).await.unwrap();
656 let buffer = stream.read_all().await.unwrap();
657
658 assert_eq!(buffer.to_vec(), b"01");
659 assert_eq!(state.stat_calls.load(Ordering::Relaxed), 1);
660 assert_eq!(state.open_calls.load(Ordering::Relaxed), 1);
661 assert_eq!(state.read_calls.load(Ordering::Relaxed), 0);
662
663 let reader = service.read(&ctx, "test", OpRead::default()).unwrap();
664 let (_, mut stream) = reader.open(BytesRange::from(4_u64..7)).await.unwrap();
665 let buffer = stream.read_all().await.unwrap();
666
667 assert_eq!(buffer.to_vec(), b"456");
668 assert_eq!(state.stat_calls.load(Ordering::Relaxed), 1);
669 assert_eq!(state.open_calls.load(Ordering::Relaxed), 1);
670 assert_eq!(state.read_calls.load(Ordering::Relaxed), 0);
671 }
672
673 #[tokio::test]
674 async fn test_cache_fill_preserves_read_args() {
675 let cache = memory_cache().await;
676 let source = Arc::new(MockReadService::new("0123456789"));
677 let state = source.state.clone();
678 let service = FoyerLayer::new(cache)
679 .with_size_limit(0..100)
680 .apply_service(source);
681 let ctx = service_context(&service);
682
683 let (_, args, _) = options::ReadOptions {
684 version: Some("v1".to_owned()),
685 if_match: Some("etag-1".to_owned()),
686 ..Default::default()
687 }
688 .into();
689 let reader = service.read(&ctx, "test", args).unwrap();
690 let (_, mut stream) = reader.open(BytesRange::new(0, None)).await.unwrap();
691 stream.read_all().await.unwrap();
692
693 let stat_args = state.last_stat_args.lock().unwrap().clone().unwrap();
694 assert_eq!(stat_args.version(), Some("v1"));
695 assert_eq!(stat_args.if_match(), Some("etag-1"));
696
697 let read_args = state.last_read_args.lock().unwrap().clone().unwrap();
698 assert_eq!(read_args.version(), Some("v1"));
699 assert_eq!(read_args.if_match(), Some("etag-1"));
700 }
701
702 #[tokio::test]
703 async fn test() {
704 let dir = tempfile::tempdir().unwrap();
705
706 let cache = HybridCacheBuilder::new()
707 .memory(10)
708 .with_shards(1)
709 .storage()
710 .with_engine_config(
711 BlockEngineConfig::new(
712 FsDeviceBuilder::new(dir.path())
713 .with_capacity(16 * MiB as usize)
714 .build()
715 .unwrap(),
716 )
717 .with_block_size(MiB as usize),
718 )
719 .with_recover_mode(RecoverMode::None)
720 .build()
721 .await
722 .unwrap();
723
724 let op = Operator::new(Memory::default())
725 .unwrap()
726 .layer(FoyerLayer::new(cache.clone()));
727
728 assert!(op.list("/").await.unwrap().is_empty());
729
730 for i in 0..64 {
731 op.write(&key(i), value(i)).await.unwrap();
732 }
733
734 assert_eq!(op.list("/").await.unwrap().len(), 64);
735
736 for i in 0..64 {
737 let buf = op.read(&key(i)).await.unwrap();
738 assert_eq!(buf.to_vec(), value(i));
739 }
740
741 cache.clear().await.unwrap();
742
743 for i in 0..64 {
744 let buf = op.read(&key(i)).await.unwrap();
745 assert_eq!(buf.to_vec(), value(i));
746 }
747
748 for i in 0..64 {
749 op.delete(&key(i)).await.unwrap();
750 }
751
752 assert!(op.list("/").await.unwrap().is_empty());
753
754 for i in 0..64 {
755 let res = op.read(&key(i)).await;
756 assert!(res.is_err(), "should fail to read deleted file");
757 }
758 }
759
760 #[tokio::test]
761 async fn test_size_limit() {
762 let dir = tempfile::tempdir().unwrap();
763
764 let cache = HybridCacheBuilder::new()
765 .memory(1024 * 1024)
766 .with_shards(1)
767 .storage()
768 .with_engine_config(
769 BlockEngineConfig::new(
770 FsDeviceBuilder::new(dir.path())
771 .with_capacity(16 * MiB as usize)
772 .build()
773 .unwrap(),
774 )
775 .with_block_size(MiB as usize),
776 )
777 .with_recover_mode(RecoverMode::None)
778 .build()
779 .await
780 .unwrap();
781
782 let op = Operator::new(Memory::default())
784 .unwrap()
785 .layer(FoyerLayer::new(cache.clone()).with_size_limit(1024..10 * 1024));
786
787 let small_data = vec![1u8; 5 * 1024]; let large_data = vec![2u8; 20 * 1024]; let tiny_data = vec![3u8; 512]; op.write("small.txt", small_data.clone()).await.unwrap();
793 op.write("large.txt", large_data.clone()).await.unwrap();
794 op.write("tiny.txt", tiny_data.clone()).await.unwrap();
795
796 let read_small = op.read("small.txt").await.unwrap();
798 assert_eq!(read_small.to_vec(), small_data);
799
800 let read_large = op.read("large.txt").await.unwrap();
801 assert_eq!(read_large.to_vec(), large_data);
802
803 let read_tiny = op.read("tiny.txt").await.unwrap();
804 assert_eq!(read_tiny.to_vec(), tiny_data);
805
806 cache.clear().await.unwrap();
808
809 let read_small = op.read("small.txt").await.unwrap();
811 assert_eq!(read_small.to_vec(), small_data);
812
813 let read_large = op.read("large.txt").await.unwrap();
814 assert_eq!(read_large.to_vec(), large_data);
815
816 let read_tiny = op.read("tiny.txt").await.unwrap();
817 assert_eq!(read_tiny.to_vec(), tiny_data);
818
819 let read_small_range = op.read_with("small.txt").range(0..1024).await.unwrap();
822 assert_eq!(read_small_range.len(), 1024);
823 assert_eq!(read_small_range.to_vec(), small_data[0..1024]);
824 }
825
826 #[test]
827 fn test_error() {
828 let e = Error::new(ErrorKind::NotFound, "not found");
829 let fe = FoyerError::new(FoyerErrorKind::External, "external error").with_source(e);
830 let oe = extract_err(fe);
831 assert_eq!(oe.kind(), ErrorKind::NotFound);
832 }
833
834 #[test]
835 fn test_foyer_key_version_none_vs_empty() {
836 let key_none = FoyerKey {
837 path: "test/path".to_string(),
838 version: None,
839 };
840
841 let key_empty = FoyerKey {
842 path: "test/path".to_string(),
843 version: Some("".to_string()),
844 };
845
846 let mut buf_none = Vec::new();
847 key_none.encode(&mut buf_none).unwrap();
848
849 let mut buf_empty = Vec::new();
850 key_empty.encode(&mut buf_empty).unwrap();
851
852 assert_ne!(
853 buf_none, buf_empty,
854 "Serialization of version=None and version=\"\" should be different"
855 );
856
857 let decoded_none = FoyerKey::decode(&mut Cursor::new(&buf_none)).unwrap();
858 assert_eq!(decoded_none, key_none);
859 let decoded_empty = FoyerKey::decode(&mut Cursor::new(&buf_empty)).unwrap();
860 assert_eq!(decoded_empty, key_empty);
861 }
862
863 #[test]
864 fn test_foyer_key_serde() {
865 use std::io::Cursor;
866
867 let test_cases = vec![
868 FoyerKey {
869 path: "simple".to_string(),
870 version: None,
871 },
872 FoyerKey {
873 path: "with/slash/path".to_string(),
874 version: None,
875 },
876 FoyerKey {
877 path: "versioned".to_string(),
878 version: Some("v1.0.0".to_string()),
879 },
880 FoyerKey {
881 path: "empty-version".to_string(),
882 version: Some("".to_string()),
883 },
884 FoyerKey {
885 path: "".to_string(),
886 version: None,
887 },
888 FoyerKey {
889 path: "unicode/θ·―εΎ/π".to_string(),
890 version: Some("ηζ¬-1".to_string()),
891 },
892 FoyerKey {
893 path: "long/".to_string().repeat(100),
894 version: Some("long-version-".to_string().repeat(50)),
895 },
896 ];
897
898 for original in test_cases {
899 let mut buffer = Vec::new();
900 original
901 .encode(&mut buffer)
902 .expect("encoding should succeed");
903
904 let decoded =
905 FoyerKey::decode(&mut Cursor::new(&buffer)).expect("decoding should succeed");
906
907 assert_eq!(
908 decoded, original,
909 "decode(encode(key)) should equal original key"
910 );
911 }
912 }
913}