1use std::borrow::Cow;
29use std::error::Error;
30use std::fmt;
31use std::io;
32use std::path::Path;
33use std::path::PathBuf;
34use std::time::SystemTime;
35
36use crate::BaseFsCanonicalize;
37use crate::BaseFsChown;
38use crate::BaseFsCloneFile;
39use crate::BaseFsCopy;
40use crate::BaseFsCreateDir;
41use crate::BaseFsCreateJunction;
42use crate::BaseFsHardLink;
43use crate::BaseFsMetadata;
44use crate::BaseFsOpen;
45use crate::BaseFsRead;
46use crate::BaseFsReadDir;
47use crate::BaseFsReadLink;
48use crate::BaseFsRemoveDir;
49use crate::BaseFsRemoveDirAll;
50use crate::BaseFsRemoveFile;
51use crate::BaseFsRename;
52use crate::BaseFsSetFileTimes;
53use crate::BaseFsSetPermissions;
54use crate::BaseFsSetSymlinkFileTimes;
55use crate::BaseFsSymlinkChown;
56use crate::BaseFsSymlinkDir;
57use crate::BaseFsSymlinkFile;
58use crate::BaseFsWrite;
59use crate::CreateDirOptions;
60use crate::FileType;
61use crate::FsFile;
62use crate::FsFileAsRaw;
63use crate::FsFileIsTerminal;
64use crate::FsFileLock;
65use crate::FsFileLockMode;
66use crate::FsFileMetadata;
67use crate::FsFileSetLen;
68use crate::FsFileSetPermissions;
69use crate::FsFileSetTimes;
70use crate::FsFileSyncAll;
71use crate::FsFileSyncData;
72use crate::FsFileTimes;
73use crate::FsMetadata;
74use crate::FsMetadataValue;
75use crate::FsRead;
76use crate::OpenOptions;
77
78use crate::boxed::BoxedFsFile;
79use crate::boxed::BoxedFsMetadataValue;
80use crate::boxed::FsOpenBoxed;
81
82#[derive(Debug)]
84pub struct OperationError {
85 operation: &'static str,
86 kind: OperationErrorKind,
87 pub err: io::Error,
89}
90
91impl OperationError {
92 pub fn operation(&self) -> &'static str {
94 self.operation
95 }
96
97 pub fn kind(&self) -> &OperationErrorKind {
99 &self.kind
100 }
101}
102
103impl fmt::Display for OperationError {
104 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105 write!(f, "failed to {}", self.operation)?;
106 match &self.kind {
107 OperationErrorKind::WithPath(path) => write!(f, " '{}'", path)?,
108 OperationErrorKind::WithTwoPaths(from, to) => {
109 write!(f, " '{}' to '{}'", from, to)?
110 }
111 }
112 write!(f, ": {}", self.err)
113 }
114}
115
116impl Error for OperationError {}
117
118#[derive(Debug, Clone, PartialEq, Eq)]
120pub enum OperationErrorKind {
121 WithPath(String),
123 WithTwoPaths(String, String),
125}
126
127#[derive(Debug)]
131pub struct SysWithPathsInErrors<'a, T: ?Sized>(pub &'a T);
132
133impl<T: ?Sized> Copy for SysWithPathsInErrors<'_, T> {}
136
137impl<T: ?Sized> Clone for SysWithPathsInErrors<'_, T> {
138 fn clone(&self) -> Self {
139 *self
140 }
141}
142
143impl<'a, T: ?Sized> SysWithPathsInErrors<'a, T> {
144 pub fn new(inner: &'a T) -> Self {
146 Self(inner)
147 }
148
149 #[allow(clippy::should_implement_trait)]
151 pub fn as_ref(&self) -> &T {
152 self.0
157 }
158}
159
160pub trait PathsInErrorsExt {
164 fn with_paths_in_errors(&self) -> SysWithPathsInErrors<'_, Self> {
166 SysWithPathsInErrors(self)
167 }
168}
169
170impl<T: ?Sized> PathsInErrorsExt for T {}
171
172#[derive(Debug)]
176pub struct FsFileWithPathsInErrors<F> {
177 file: F,
178 path: PathBuf,
179}
180
181impl<F> FsFileWithPathsInErrors<F> {
182 pub fn new(file: F, path: PathBuf) -> Self {
184 Self { file, path }
185 }
186
187 pub fn path(&self) -> &Path {
189 &self.path
190 }
191
192 pub fn inner(&self) -> &F {
194 &self.file
195 }
196
197 pub fn inner_mut(&mut self) -> &mut F {
199 &mut self.file
200 }
201
202 pub fn into_inner(self) -> F {
204 self.file
205 }
206
207 fn wrap_err(&self, operation: &'static str, err: io::Error) -> io::Error {
208 err_with_path(operation, &self.path, err)
209 }
210}
211
212impl<F: io::Read> io::Read for FsFileWithPathsInErrors<F> {
213 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
214 self.file.read(buf).map_err(|e| self.wrap_err("read", e))
215 }
216}
217
218impl<F: io::Write> io::Write for FsFileWithPathsInErrors<F> {
219 fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
220 self.file.write(buf).map_err(|e| self.wrap_err("write", e))
221 }
222
223 fn flush(&mut self) -> io::Result<()> {
224 self.file.flush().map_err(|e| self.wrap_err("flush", e))
225 }
226}
227
228impl<F: io::Seek> io::Seek for FsFileWithPathsInErrors<F> {
229 fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
230 self.file.seek(pos).map_err(|e| self.wrap_err("seek", e))
231 }
232}
233
234impl<F: FsFileIsTerminal> FsFileIsTerminal for FsFileWithPathsInErrors<F> {
235 fn fs_file_is_terminal(&self) -> bool {
236 self.file.fs_file_is_terminal()
237 }
238}
239
240impl<F: FsFileLock> FsFileLock for FsFileWithPathsInErrors<F> {
241 fn fs_file_lock(&mut self, mode: FsFileLockMode) -> io::Result<()> {
242 self
243 .file
244 .fs_file_lock(mode)
245 .map_err(|e| self.wrap_err("lock", e))
246 }
247
248 fn fs_file_try_lock(&mut self, mode: FsFileLockMode) -> io::Result<()> {
249 self
250 .file
251 .fs_file_try_lock(mode)
252 .map_err(|e| self.wrap_err("try lock", e))
253 }
254
255 fn fs_file_unlock(&mut self) -> io::Result<()> {
256 self
257 .file
258 .fs_file_unlock()
259 .map_err(|e| self.wrap_err("unlock", e))
260 }
261}
262
263impl<F: FsFileMetadata> FsFileMetadata for FsFileWithPathsInErrors<F> {
264 fn fs_file_metadata(&self) -> io::Result<BoxedFsMetadataValue> {
265 self
266 .file
267 .fs_file_metadata()
268 .map_err(|e| self.wrap_err("stat", e))
269 }
270}
271
272impl<F: FsFileSetPermissions> FsFileSetPermissions
273 for FsFileWithPathsInErrors<F>
274{
275 fn fs_file_set_permissions(&mut self, mode: u32) -> io::Result<()> {
276 self
277 .file
278 .fs_file_set_permissions(mode)
279 .map_err(|e| self.wrap_err("set permissions", e))
280 }
281}
282
283impl<F: FsFileSetTimes> FsFileSetTimes for FsFileWithPathsInErrors<F> {
284 fn fs_file_set_times(&mut self, times: FsFileTimes) -> io::Result<()> {
285 self
286 .file
287 .fs_file_set_times(times)
288 .map_err(|e| self.wrap_err("set file times", e))
289 }
290}
291
292impl<F: FsFileSetLen> FsFileSetLen for FsFileWithPathsInErrors<F> {
293 fn fs_file_set_len(&mut self, size: u64) -> io::Result<()> {
294 self
295 .file
296 .fs_file_set_len(size)
297 .map_err(|e| self.wrap_err("truncate", e))
298 }
299}
300
301impl<F: FsFileSyncAll> FsFileSyncAll for FsFileWithPathsInErrors<F> {
302 fn fs_file_sync_all(&mut self) -> io::Result<()> {
303 self
304 .file
305 .fs_file_sync_all()
306 .map_err(|e| self.wrap_err("sync", e))
307 }
308}
309
310impl<F: FsFileSyncData> FsFileSyncData for FsFileWithPathsInErrors<F> {
311 fn fs_file_sync_data(&mut self) -> io::Result<()> {
312 self
313 .file
314 .fs_file_sync_data()
315 .map_err(|e| self.wrap_err("sync data", e))
316 }
317}
318
319impl<F: FsFileAsRaw> FsFileAsRaw for FsFileWithPathsInErrors<F> {
320 #[cfg(windows)]
321 fn fs_file_as_raw_handle(&self) -> Option<std::os::windows::io::RawHandle> {
322 self.file.fs_file_as_raw_handle()
323 }
324
325 #[cfg(unix)]
326 fn fs_file_as_raw_fd(&self) -> Option<std::os::fd::RawFd> {
327 self.file.fs_file_as_raw_fd()
328 }
329}
330
331impl<F: FsFile> FsFile for FsFileWithPathsInErrors<F> {}
332
333fn err_with_path(
335 operation: &'static str,
336 path: &Path,
337 err: io::Error,
338) -> io::Error {
339 io::Error::new(
340 err.kind(),
341 OperationError {
342 operation,
343 kind: OperationErrorKind::WithPath(path.to_string_lossy().into_owned()),
344 err,
345 },
346 )
347}
348
349fn err_with_two_paths(
351 operation: &'static str,
352 from: &Path,
353 to: &Path,
354 err: io::Error,
355) -> io::Error {
356 io::Error::new(
357 err.kind(),
358 OperationError {
359 operation,
360 kind: OperationErrorKind::WithTwoPaths(
361 from.to_string_lossy().into_owned(),
362 to.to_string_lossy().into_owned(),
363 ),
364 err,
365 },
366 )
367}
368
369impl<T: BaseFsCanonicalize> SysWithPathsInErrors<'_, T> {
372 pub fn fs_canonicalize(&self, path: impl AsRef<Path>) -> io::Result<PathBuf> {
373 let path = path.as_ref();
374 self
375 .0
376 .base_fs_canonicalize(path)
377 .map_err(|e| err_with_path("canonicalize", path, e))
378 }
379}
380
381impl<T: BaseFsChown> SysWithPathsInErrors<'_, T> {
384 pub fn fs_chown(
385 &self,
386 path: impl AsRef<Path>,
387 uid: Option<u32>,
388 gid: Option<u32>,
389 ) -> io::Result<()> {
390 let path = path.as_ref();
391 self
392 .0
393 .base_fs_chown(path, uid, gid)
394 .map_err(|e| err_with_path("chown", path, e))
395 }
396}
397
398impl<T: BaseFsSymlinkChown> SysWithPathsInErrors<'_, T> {
401 pub fn fs_symlink_chown(
402 &self,
403 path: impl AsRef<Path>,
404 uid: Option<u32>,
405 gid: Option<u32>,
406 ) -> io::Result<()> {
407 let path = path.as_ref();
408 self
409 .0
410 .base_fs_symlink_chown(path, uid, gid)
411 .map_err(|e| err_with_path("chown symlink", path, e))
412 }
413}
414
415impl<T: BaseFsCloneFile> SysWithPathsInErrors<'_, T> {
418 pub fn fs_clone_file(
419 &self,
420 from: impl AsRef<Path>,
421 to: impl AsRef<Path>,
422 ) -> io::Result<()> {
423 let from = from.as_ref();
424 let to = to.as_ref();
425 self
426 .0
427 .base_fs_clone_file(from, to)
428 .map_err(|e| err_with_two_paths("clone", from, to, e))
429 }
430}
431
432impl<T: BaseFsCopy> SysWithPathsInErrors<'_, T> {
435 pub fn fs_copy(
436 &self,
437 from: impl AsRef<Path>,
438 to: impl AsRef<Path>,
439 ) -> io::Result<u64> {
440 let from = from.as_ref();
441 let to = to.as_ref();
442 self
443 .0
444 .base_fs_copy(from, to)
445 .map_err(|e| err_with_two_paths("copy", from, to, e))
446 }
447}
448
449impl<T: BaseFsCreateDir> SysWithPathsInErrors<'_, T> {
452 pub fn fs_create_dir(
453 &self,
454 path: impl AsRef<Path>,
455 options: &CreateDirOptions,
456 ) -> io::Result<()> {
457 let path = path.as_ref();
458 self
459 .0
460 .base_fs_create_dir(path, options)
461 .map_err(|e| err_with_path("create directory", path, e))
462 }
463
464 pub fn fs_create_dir_all(&self, path: impl AsRef<Path>) -> io::Result<()> {
465 let path = path.as_ref();
466 self
467 .0
468 .base_fs_create_dir(
469 path,
470 &CreateDirOptions {
471 recursive: true,
472 mode: None,
473 },
474 )
475 .map_err(|e| err_with_path("create directory", path, e))
476 }
477}
478
479impl<T: BaseFsHardLink> SysWithPathsInErrors<'_, T> {
482 pub fn fs_hard_link(
483 &self,
484 src: impl AsRef<Path>,
485 dst: impl AsRef<Path>,
486 ) -> io::Result<()> {
487 let src = src.as_ref();
488 let dst = dst.as_ref();
489 self
490 .0
491 .base_fs_hard_link(src, dst)
492 .map_err(|e| err_with_two_paths("hard link", src, dst, e))
493 }
494}
495
496impl<T: BaseFsCreateJunction> SysWithPathsInErrors<'_, T> {
499 pub fn fs_create_junction(
500 &self,
501 original: impl AsRef<Path>,
502 junction: impl AsRef<Path>,
503 ) -> io::Result<()> {
504 let original = original.as_ref();
505 let junction = junction.as_ref();
506 self
507 .0
508 .base_fs_create_junction(original, junction)
509 .map_err(|e| err_with_two_paths("create junction", original, junction, e))
510 }
511}
512
513impl<T: BaseFsMetadata> SysWithPathsInErrors<'_, T> {
516 pub fn fs_metadata(&self, path: impl AsRef<Path>) -> io::Result<T::Metadata> {
517 let path = path.as_ref();
518 self
519 .0
520 .base_fs_metadata(path)
521 .map_err(|e| err_with_path("stat", path, e))
522 }
523
524 pub fn fs_symlink_metadata(
525 &self,
526 path: impl AsRef<Path>,
527 ) -> io::Result<T::Metadata> {
528 let path = path.as_ref();
529 self
530 .0
531 .base_fs_symlink_metadata(path)
532 .map_err(|e| err_with_path("lstat", path, e))
533 }
534
535 pub fn fs_is_file(&self, path: impl AsRef<Path>) -> io::Result<bool> {
536 Ok(self.fs_metadata(path)?.file_type() == FileType::File)
537 }
538
539 pub fn fs_is_dir(&self, path: impl AsRef<Path>) -> io::Result<bool> {
540 Ok(self.fs_metadata(path)?.file_type() == FileType::Dir)
541 }
542
543 pub fn fs_is_symlink(&self, path: impl AsRef<Path>) -> io::Result<bool> {
544 Ok(self.fs_symlink_metadata(path)?.file_type() == FileType::Symlink)
545 }
546
547 pub fn fs_exists(&self, path: impl AsRef<Path>) -> io::Result<bool> {
548 let path = path.as_ref();
549 match self.0.base_fs_exists(path) {
550 Ok(exists) => Ok(exists),
551 Err(e) => Err(err_with_path("stat", path, e)),
552 }
553 }
554
555 pub fn fs_exists_no_err(&self, path: impl AsRef<Path>) -> bool {
556 self.0.base_fs_exists_no_err(path.as_ref())
557 }
558
559 pub fn fs_is_file_no_err(&self, path: impl AsRef<Path>) -> bool {
560 self.0.fs_is_file_no_err(path)
561 }
562
563 pub fn fs_is_dir_no_err(&self, path: impl AsRef<Path>) -> bool {
564 self.0.fs_is_dir_no_err(path)
565 }
566
567 pub fn fs_is_symlink_no_err(&self, path: impl AsRef<Path>) -> bool {
568 self.0.fs_is_symlink_no_err(path)
569 }
570}
571
572impl<T: BaseFsOpen> SysWithPathsInErrors<'_, T> {
575 pub fn fs_open(
576 &self,
577 path: impl AsRef<Path>,
578 options: &OpenOptions,
579 ) -> io::Result<FsFileWithPathsInErrors<T::File>> {
580 let path = path.as_ref();
581 let file = self
582 .0
583 .base_fs_open(path, options)
584 .map_err(|e| err_with_path("open", path, e))?;
585 Ok(FsFileWithPathsInErrors::new(file, path.to_path_buf()))
586 }
587}
588
589impl<T: FsOpenBoxed + ?Sized> SysWithPathsInErrors<'_, T> {
592 pub fn fs_open_boxed(
593 &self,
594 path: impl AsRef<Path>,
595 options: &OpenOptions,
596 ) -> io::Result<FsFileWithPathsInErrors<BoxedFsFile>> {
597 let path = path.as_ref();
598 let file = self
599 .0
600 .fs_open_boxed(path, options)
601 .map_err(|e| err_with_path("open", path, e))?;
602 Ok(FsFileWithPathsInErrors::new(file, path.to_path_buf()))
603 }
604}
605
606impl<T: BaseFsRead> SysWithPathsInErrors<'_, T> {
609 pub fn fs_read(
610 &self,
611 path: impl AsRef<Path>,
612 ) -> io::Result<Cow<'static, [u8]>> {
613 let path = path.as_ref();
614 self
615 .0
616 .base_fs_read(path)
617 .map_err(|e| err_with_path("read", path, e))
618 }
619
620 pub fn fs_read_to_string(
621 &self,
622 path: impl AsRef<Path>,
623 ) -> io::Result<Cow<'static, str>> {
624 let path = path.as_ref();
625 self
626 .0
627 .fs_read_to_string(path)
628 .map_err(|e| err_with_path("read", path, e))
629 }
630
631 pub fn fs_read_to_string_lossy(
632 &self,
633 path: impl AsRef<Path>,
634 ) -> io::Result<Cow<'static, str>> {
635 let path = path.as_ref();
636 self
637 .0
638 .fs_read_to_string_lossy(path)
639 .map_err(|e| err_with_path("read", path, e))
640 }
641}
642
643impl<T: BaseFsReadDir> SysWithPathsInErrors<'_, T> {
646 pub fn fs_read_dir(
647 &self,
648 path: impl AsRef<Path>,
649 ) -> io::Result<Box<dyn Iterator<Item = io::Result<T::ReadDirEntry>>>> {
650 let path = path.as_ref();
651 self
652 .0
653 .base_fs_read_dir(path)
654 .map_err(|e| err_with_path("read directory", path, e))
655 }
656}
657
658impl<T: BaseFsReadLink> SysWithPathsInErrors<'_, T> {
661 pub fn fs_read_link(&self, path: impl AsRef<Path>) -> io::Result<PathBuf> {
662 let path = path.as_ref();
663 self
664 .0
665 .base_fs_read_link(path)
666 .map_err(|e| err_with_path("read link", path, e))
667 }
668}
669
670impl<T: BaseFsRemoveDir> SysWithPathsInErrors<'_, T> {
673 pub fn fs_remove_dir(&self, path: impl AsRef<Path>) -> io::Result<()> {
674 let path = path.as_ref();
675 self
676 .0
677 .base_fs_remove_dir(path)
678 .map_err(|e| err_with_path("remove directory", path, e))
679 }
680}
681
682impl<T: BaseFsRemoveDirAll> SysWithPathsInErrors<'_, T> {
685 pub fn fs_remove_dir_all(&self, path: impl AsRef<Path>) -> io::Result<()> {
686 let path = path.as_ref();
687 self
688 .0
689 .base_fs_remove_dir_all(path)
690 .map_err(|e| err_with_path("remove directory", path, e))
691 }
692}
693
694impl<T: BaseFsRemoveFile> SysWithPathsInErrors<'_, T> {
697 pub fn fs_remove_file(&self, path: impl AsRef<Path>) -> io::Result<()> {
698 let path = path.as_ref();
699 self
700 .0
701 .base_fs_remove_file(path)
702 .map_err(|e| err_with_path("remove", path, e))
703 }
704}
705
706impl<T: BaseFsRename> SysWithPathsInErrors<'_, T> {
709 pub fn fs_rename(
710 &self,
711 from: impl AsRef<Path>,
712 to: impl AsRef<Path>,
713 ) -> io::Result<()> {
714 let from = from.as_ref();
715 let to = to.as_ref();
716 self
717 .0
718 .base_fs_rename(from, to)
719 .map_err(|e| err_with_two_paths("rename", from, to, e))
720 }
721}
722
723impl<T: BaseFsSetFileTimes> SysWithPathsInErrors<'_, T> {
726 pub fn fs_set_file_times(
727 &self,
728 path: impl AsRef<Path>,
729 atime: SystemTime,
730 mtime: SystemTime,
731 ) -> io::Result<()> {
732 let path = path.as_ref();
733 self
734 .0
735 .base_fs_set_file_times(path, atime, mtime)
736 .map_err(|e| err_with_path("set file times", path, e))
737 }
738}
739
740impl<T: BaseFsSetSymlinkFileTimes> SysWithPathsInErrors<'_, T> {
743 pub fn fs_set_symlink_file_times(
744 &self,
745 path: impl AsRef<Path>,
746 atime: SystemTime,
747 mtime: SystemTime,
748 ) -> io::Result<()> {
749 let path = path.as_ref();
750 self
751 .0
752 .base_fs_set_symlink_file_times(path, atime, mtime)
753 .map_err(|e| err_with_path("set symlink file times", path, e))
754 }
755}
756
757impl<T: BaseFsSetPermissions> SysWithPathsInErrors<'_, T> {
760 pub fn fs_set_permissions(
761 &self,
762 path: impl AsRef<Path>,
763 mode: u32,
764 ) -> io::Result<()> {
765 let path = path.as_ref();
766 self
767 .0
768 .base_fs_set_permissions(path, mode)
769 .map_err(|e| err_with_path("set permissions", path, e))
770 }
771}
772
773impl<T: BaseFsSymlinkDir> SysWithPathsInErrors<'_, T> {
776 pub fn fs_symlink_dir(
777 &self,
778 original: impl AsRef<Path>,
779 link: impl AsRef<Path>,
780 ) -> io::Result<()> {
781 let original = original.as_ref();
782 let link = link.as_ref();
783 self
784 .0
785 .base_fs_symlink_dir(original, link)
786 .map_err(|e| err_with_two_paths("symlink directory", original, link, e))
787 }
788}
789
790impl<T: BaseFsSymlinkFile> SysWithPathsInErrors<'_, T> {
793 pub fn fs_symlink_file(
794 &self,
795 original: impl AsRef<Path>,
796 link: impl AsRef<Path>,
797 ) -> io::Result<()> {
798 let original = original.as_ref();
799 let link = link.as_ref();
800 self
801 .0
802 .base_fs_symlink_file(original, link)
803 .map_err(|e| err_with_two_paths("symlink", original, link, e))
804 }
805}
806
807impl<T: BaseFsWrite> SysWithPathsInErrors<'_, T> {
810 pub fn fs_write(
811 &self,
812 path: impl AsRef<Path>,
813 data: impl AsRef<[u8]>,
814 ) -> io::Result<()> {
815 let path = path.as_ref();
816 self
817 .0
818 .base_fs_write(path, data.as_ref())
819 .map_err(|e| err_with_path("write", path, e))
820 }
821}
822
823#[cfg(all(test, feature = "memory"))]
824mod tests {
825 use super::*;
826 use crate::impls::InMemorySys;
827 use crate::FsCreateDir;
828 use crate::FsMetadata;
829 use crate::FsRead;
830 use crate::FsWrite;
831 use std::io::Read;
832 use std::io::Write;
833
834 #[test]
835 fn test_error_display_single_path() {
836 let sys = InMemorySys::default();
837 let err = sys
838 .with_paths_in_errors()
839 .fs_read("/nonexistent")
840 .unwrap_err();
841 let inner = err.get_ref().unwrap();
842 let op_err = inner.downcast_ref::<OperationError>().unwrap();
843 assert_eq!(op_err.operation(), "read");
844 assert_eq!(
845 op_err.kind(),
846 &OperationErrorKind::WithPath("/nonexistent".to_string())
847 );
848 assert_eq!(
849 err.to_string(),
850 format!("failed to read '/nonexistent': {}", op_err.err)
851 );
852 }
853
854 #[test]
855 fn test_error_display_two_paths() {
856 let sys = InMemorySys::default();
857 let err = sys
858 .with_paths_in_errors()
859 .fs_copy("/src", "/dst")
860 .unwrap_err();
861 let inner = err.get_ref().unwrap();
862 let op_err = inner.downcast_ref::<OperationError>().unwrap();
863 assert_eq!(op_err.operation(), "copy");
864 assert_eq!(
865 op_err.kind(),
866 &OperationErrorKind::WithTwoPaths("/src".to_string(), "/dst".to_string())
867 );
868 assert_eq!(
869 err.to_string(),
870 format!("failed to copy '/src' to '/dst': {}", op_err.err)
871 );
872 }
873
874 #[test]
875 fn test_error_preserves_kind() {
876 let sys = InMemorySys::default();
877 let err = sys
878 .with_paths_in_errors()
879 .fs_read("/nonexistent")
880 .unwrap_err();
881 assert_eq!(err.kind(), io::ErrorKind::NotFound);
882 }
883
884 #[test]
885 fn test_error_downcast_to_operation_error() {
886 let sys = InMemorySys::default();
887 let err = sys
888 .with_paths_in_errors()
889 .fs_read("/nonexistent")
890 .unwrap_err();
891 let inner = err.get_ref().unwrap();
892 let op_err = inner.downcast_ref::<OperationError>().unwrap();
893 assert_eq!(op_err.operation(), "read");
894 assert_eq!(
895 op_err.kind(),
896 &OperationErrorKind::WithPath("/nonexistent".to_string())
897 );
898 }
899
900 #[test]
901 fn test_fs_read_success() {
902 let sys = InMemorySys::new_with_cwd("/");
903 sys.fs_write("/test.txt", b"hello").unwrap();
904 let data = sys.with_paths_in_errors().fs_read("/test.txt").unwrap();
905 assert_eq!(&*data, b"hello");
906 }
907
908 #[test]
909 fn test_fs_read_to_string_success() {
910 let sys = InMemorySys::new_with_cwd("/");
911 sys.fs_write("/test.txt", b"hello").unwrap();
912 let data = sys
913 .with_paths_in_errors()
914 .fs_read_to_string("/test.txt")
915 .unwrap();
916 assert_eq!(&*data, "hello");
917 }
918
919 #[test]
920 fn test_fs_read_to_string_lossy_success() {
921 let sys = InMemorySys::new_with_cwd("/");
922 sys.fs_write("/test.txt", b"hello").unwrap();
923 let data = sys
924 .with_paths_in_errors()
925 .fs_read_to_string_lossy("/test.txt")
926 .unwrap();
927 assert_eq!(&*data, "hello");
928 }
929
930 #[test]
931 fn test_fs_write_success() {
932 let sys = InMemorySys::new_with_cwd("/");
933 sys
934 .with_paths_in_errors()
935 .fs_write("/test.txt", b"hello")
936 .unwrap();
937 let data = sys.fs_read("/test.txt").unwrap();
938 assert_eq!(&*data, b"hello");
939 }
940
941 #[test]
942 fn test_fs_write_error() {
943 let sys = InMemorySys::default();
944 let err = sys
946 .with_paths_in_errors()
947 .fs_write("/nonexistent/test.txt", b"hello")
948 .unwrap_err();
949 let inner = err.get_ref().unwrap();
950 let op_err = inner.downcast_ref::<OperationError>().unwrap();
951 assert_eq!(op_err.operation(), "write");
952 assert_eq!(
953 op_err.kind(),
954 &OperationErrorKind::WithPath("/nonexistent/test.txt".to_string())
955 );
956 }
957
958 #[test]
959 fn test_fs_create_dir() {
960 let sys = InMemorySys::default();
961 sys
962 .with_paths_in_errors()
963 .fs_create_dir("/newdir", &CreateDirOptions::default())
964 .unwrap();
965 assert!(sys.fs_is_dir("/newdir").unwrap());
966 }
967
968 #[test]
969 fn test_fs_create_dir_all() {
970 let sys = InMemorySys::default();
971 sys
972 .with_paths_in_errors()
973 .fs_create_dir_all("/a/b/c")
974 .unwrap();
975 assert!(sys.fs_is_dir("/a/b/c").unwrap());
976 }
977
978 #[test]
979 fn test_fs_remove_file() {
980 let sys = InMemorySys::new_with_cwd("/");
981 sys.fs_write("/test.txt", b"hello").unwrap();
982 sys
983 .with_paths_in_errors()
984 .fs_remove_file("/test.txt")
985 .unwrap();
986 assert!(!sys.fs_exists("/test.txt").unwrap());
987 }
988
989 #[test]
990 fn test_fs_remove_file_error() {
991 let sys = InMemorySys::default();
992 let err = sys
993 .with_paths_in_errors()
994 .fs_remove_file("/nonexistent")
995 .unwrap_err();
996 let inner = err.get_ref().unwrap();
997 let op_err = inner.downcast_ref::<OperationError>().unwrap();
998 assert_eq!(op_err.operation(), "remove");
999 assert_eq!(
1000 op_err.kind(),
1001 &OperationErrorKind::WithPath("/nonexistent".to_string())
1002 );
1003 }
1004
1005 #[test]
1006 fn test_fs_remove_dir() {
1007 let sys = InMemorySys::default();
1008 sys
1009 .fs_create_dir("/testdir", &CreateDirOptions::default())
1010 .unwrap();
1011 sys
1012 .with_paths_in_errors()
1013 .fs_remove_dir("/testdir")
1014 .unwrap();
1015 assert!(!sys.fs_exists("/testdir").unwrap());
1016 }
1017
1018 #[test]
1019 fn test_fs_rename() {
1020 let sys = InMemorySys::new_with_cwd("/");
1021 sys.fs_write("/old.txt", b"hello").unwrap();
1022 sys
1023 .with_paths_in_errors()
1024 .fs_rename("/old.txt", "/new.txt")
1025 .unwrap();
1026 assert!(!sys.fs_exists("/old.txt").unwrap());
1027 assert!(sys.fs_exists("/new.txt").unwrap());
1028 }
1029
1030 #[test]
1031 fn test_fs_rename_error() {
1032 let sys = InMemorySys::default();
1033 let err = sys
1034 .with_paths_in_errors()
1035 .fs_rename("/nonexistent", "/new.txt")
1036 .unwrap_err();
1037 let inner = err.get_ref().unwrap();
1038 let op_err = inner.downcast_ref::<OperationError>().unwrap();
1039 assert_eq!(op_err.operation(), "rename");
1040 assert_eq!(
1041 op_err.kind(),
1042 &OperationErrorKind::WithTwoPaths(
1043 "/nonexistent".to_string(),
1044 "/new.txt".to_string()
1045 )
1046 );
1047 }
1048
1049 #[test]
1050 fn test_fs_copy() {
1051 let sys = InMemorySys::new_with_cwd("/");
1052 sys.fs_write("/src.txt", b"hello").unwrap();
1053 let bytes = sys
1054 .with_paths_in_errors()
1055 .fs_copy("/src.txt", "/dst.txt")
1056 .unwrap();
1057 assert_eq!(bytes, 5);
1058 assert_eq!(&*sys.fs_read("/dst.txt").unwrap(), b"hello");
1059 }
1060
1061 #[test]
1062 fn test_fs_metadata() {
1063 let sys = InMemorySys::new_with_cwd("/");
1064 sys.fs_write("/test.txt", b"hello").unwrap();
1065 let meta = sys.with_paths_in_errors().fs_metadata("/test.txt").unwrap();
1066 assert_eq!(meta.file_type(), FileType::File);
1067 assert_eq!(meta.len(), 5);
1068 }
1069
1070 #[test]
1071 fn test_fs_metadata_error() {
1072 let sys = InMemorySys::default();
1073 let err = sys
1074 .with_paths_in_errors()
1075 .fs_metadata("/nonexistent")
1076 .unwrap_err();
1077 let inner = err.get_ref().unwrap();
1078 let op_err = inner.downcast_ref::<OperationError>().unwrap();
1079 assert_eq!(op_err.operation(), "stat");
1080 assert_eq!(
1081 op_err.kind(),
1082 &OperationErrorKind::WithPath("/nonexistent".to_string())
1083 );
1084 }
1085
1086 #[test]
1087 fn test_fs_is_file() {
1088 let sys = InMemorySys::new_with_cwd("/");
1089 sys.fs_write("/test.txt", b"hello").unwrap();
1090 assert!(sys.with_paths_in_errors().fs_is_file("/test.txt").unwrap());
1091 sys
1092 .fs_create_dir("/testdir", &CreateDirOptions::default())
1093 .unwrap();
1094 assert!(!sys.with_paths_in_errors().fs_is_file("/testdir").unwrap());
1095 }
1096
1097 #[test]
1098 fn test_fs_is_dir() {
1099 let sys = InMemorySys::new_with_cwd("/");
1100 sys
1101 .fs_create_dir("/testdir", &CreateDirOptions::default())
1102 .unwrap();
1103 assert!(sys.with_paths_in_errors().fs_is_dir("/testdir").unwrap());
1104 sys.fs_write("/test.txt", b"hello").unwrap();
1105 assert!(!sys.with_paths_in_errors().fs_is_dir("/test.txt").unwrap());
1106 }
1107
1108 #[test]
1109 fn test_fs_read_dir() {
1110 let sys = InMemorySys::new_with_cwd("/");
1111 sys
1112 .fs_create_dir("/testdir", &CreateDirOptions::default())
1113 .unwrap();
1114 sys.fs_write("/testdir/a.txt", b"a").unwrap();
1115 sys.fs_write("/testdir/b.txt", b"b").unwrap();
1116 let entries: Vec<_> = sys
1117 .with_paths_in_errors()
1118 .fs_read_dir("/testdir")
1119 .unwrap()
1120 .collect::<Result<_, _>>()
1121 .unwrap();
1122 assert_eq!(entries.len(), 2);
1123 }
1124
1125 #[test]
1126 fn test_fs_read_dir_error() {
1127 let sys = InMemorySys::default();
1128 let result = sys.with_paths_in_errors().fs_read_dir("/nonexistent");
1129 let err = match result {
1130 Ok(_) => panic!("expected error"),
1131 Err(e) => e,
1132 };
1133 let inner = err.get_ref().unwrap();
1134 let op_err = inner.downcast_ref::<OperationError>().unwrap();
1135 assert_eq!(op_err.operation(), "read directory");
1136 assert_eq!(
1137 op_err.kind(),
1138 &OperationErrorKind::WithPath("/nonexistent".to_string())
1139 );
1140 }
1141
1142 #[test]
1143 fn test_fs_hard_link() {
1144 let sys = InMemorySys::new_with_cwd("/");
1145 sys.fs_write("/original.txt", b"hello").unwrap();
1146 sys
1147 .with_paths_in_errors()
1148 .fs_hard_link("/original.txt", "/link.txt")
1149 .unwrap();
1150 assert_eq!(&*sys.fs_read("/link.txt").unwrap(), b"hello");
1151 }
1152
1153 #[test]
1154 fn test_fs_hard_link_error() {
1155 let sys = InMemorySys::default();
1156 let err = sys
1157 .with_paths_in_errors()
1158 .fs_hard_link("/nonexistent", "/link.txt")
1159 .unwrap_err();
1160 let inner = err.get_ref().unwrap();
1161 let op_err = inner.downcast_ref::<OperationError>().unwrap();
1162 assert_eq!(op_err.operation(), "hard link");
1163 assert_eq!(
1164 op_err.kind(),
1165 &OperationErrorKind::WithTwoPaths(
1166 "/nonexistent".to_string(),
1167 "/link.txt".to_string()
1168 )
1169 );
1170 }
1171
1172 #[test]
1173 fn test_fs_open_error() {
1174 let sys = InMemorySys::default();
1175 let err = sys
1176 .with_paths_in_errors()
1177 .fs_open("/nonexistent", &OpenOptions::default())
1178 .unwrap_err();
1179 let inner = err.get_ref().unwrap();
1180 let op_err = inner.downcast_ref::<OperationError>().unwrap();
1181 assert_eq!(op_err.operation(), "open");
1182 assert_eq!(
1183 op_err.kind(),
1184 &OperationErrorKind::WithPath("/nonexistent".to_string())
1185 );
1186 }
1187
1188 #[test]
1189 fn test_fs_open_success() {
1190 let sys = InMemorySys::new_with_cwd("/");
1191 sys.fs_write("/test.txt", b"hello").unwrap();
1192 let mut file = sys
1193 .with_paths_in_errors()
1194 .fs_open(
1195 "/test.txt",
1196 &OpenOptions {
1197 read: true,
1198 ..Default::default()
1199 },
1200 )
1201 .unwrap();
1202 let mut buf = [0u8; 5];
1203 file.read_exact(&mut buf).unwrap();
1204 assert_eq!(&buf, b"hello");
1205 }
1206
1207 #[test]
1208 fn test_fs_file_read_write_success() {
1209 let sys = InMemorySys::new_with_cwd("/");
1210 let mut file = sys
1212 .with_paths_in_errors()
1213 .fs_open(
1214 "/test.txt",
1215 &OpenOptions {
1216 write: true,
1217 create: true,
1218 ..Default::default()
1219 },
1220 )
1221 .unwrap();
1222 file.write_all(b"hello").unwrap();
1223 drop(file);
1224
1225 let mut file = sys
1227 .with_paths_in_errors()
1228 .fs_open(
1229 "/test.txt",
1230 &OpenOptions {
1231 read: true,
1232 ..Default::default()
1233 },
1234 )
1235 .unwrap();
1236 let mut buf = Vec::new();
1237 file.read_to_end(&mut buf).unwrap();
1238 assert_eq!(&buf, b"hello");
1239 }
1240
1241 #[test]
1242 fn test_fs_file_path_accessor() {
1243 let sys = InMemorySys::new_with_cwd("/");
1244 sys.fs_write("/test.txt", b"hello").unwrap();
1245 let file = sys
1246 .with_paths_in_errors()
1247 .fs_open(
1248 "/test.txt",
1249 &OpenOptions {
1250 read: true,
1251 ..Default::default()
1252 },
1253 )
1254 .unwrap();
1255 assert_eq!(file.path(), Path::new("/test.txt"));
1256 }
1257
1258 #[test]
1259 fn test_fs_exists() {
1260 let sys = InMemorySys::new_with_cwd("/");
1261 sys.fs_write("/test.txt", b"hello").unwrap();
1262 assert!(sys.with_paths_in_errors().fs_exists("/test.txt").unwrap());
1263 assert!(!sys
1264 .with_paths_in_errors()
1265 .fs_exists("/nonexistent")
1266 .unwrap());
1267 }
1268
1269 #[test]
1270 fn test_fs_exists_no_err() {
1271 let sys = InMemorySys::new_with_cwd("/");
1272 sys.fs_write("/test.txt", b"hello").unwrap();
1273 assert!(sys.with_paths_in_errors().fs_exists_no_err("/test.txt"));
1274 assert!(!sys.with_paths_in_errors().fs_exists_no_err("/nonexistent"));
1275 }
1276
1277 #[test]
1278 fn test_fs_is_file_no_err() {
1279 let sys = InMemorySys::new_with_cwd("/");
1280 sys
1281 .fs_create_dir("/dir", &CreateDirOptions::default())
1282 .unwrap();
1283 sys.fs_write("/dir/file.txt", b"hello").unwrap();
1284 assert!(sys
1285 .with_paths_in_errors()
1286 .fs_is_file_no_err("/dir/file.txt"));
1287 assert!(!sys.with_paths_in_errors().fs_is_file_no_err("/dir"));
1288 assert!(!sys.with_paths_in_errors().fs_is_file_no_err("/nonexistent"));
1289 }
1290
1291 #[test]
1292 fn test_fs_is_dir_no_err() {
1293 let sys = InMemorySys::new_with_cwd("/");
1294 sys
1295 .fs_create_dir("/dir", &CreateDirOptions::default())
1296 .unwrap();
1297 sys.fs_write("/dir/file.txt", b"hello").unwrap();
1298 assert!(sys.with_paths_in_errors().fs_is_dir_no_err("/dir"));
1299 assert!(!sys.with_paths_in_errors().fs_is_dir_no_err("/dir/file.txt"));
1300 assert!(!sys.with_paths_in_errors().fs_is_dir_no_err("/nonexistent"));
1301 }
1302}