fs_more/error/
directory.rs

1use std::path::PathBuf;
2
3use thiserror::Error;
4
5use super::FileError;
6use crate::directory::DestinationDirectoryRule;
7
8
9/// Source directory path validation error.
10#[derive(Error, Debug)]
11pub enum SourceDirectoryPathValidationError {
12    /// The source directory (path to the directory you want to copy)
13    /// does not exist.
14    #[error(
15        "source directory path does not exist: {}",
16        .directory_path.display()
17    )]
18    NotFound {
19        /// Source directory path.
20        directory_path: PathBuf,
21    },
22
23    /// The source path (path to the directory you want to copy)
24    /// exists, but does not point to a directory.
25    #[error(
26        "source path exists, but is not a directory: {}",
27         .path.display()
28    )]
29    NotADirectory {
30        /// The base source path that was supposed to be a directory.
31        path: PathBuf,
32    },
33
34    /// The source directory could not be read, or its path could not be canonicalized.
35    ///
36    /// Among other things, this can happen due to missing read permissions.
37    ///
38    /// The inner [`std::io::Error`] will likely describe a more precise cause of this error.
39    #[error("unable to access source directory: {}", .directory_path.display())]
40    UnableToAccess {
41        /// The exact path we are unable to access.
42        directory_path: PathBuf,
43
44        /// IO error describing why the source directory could not be accessed.
45        #[source]
46        error: std::io::Error,
47    },
48}
49
50
51
52/// Destination directory path validation error.
53#[derive(Error, Debug)]
54pub enum DestinationDirectoryPathValidationError {
55    /// The base source path (path to the directory you want to copy)
56    /// exists, but does not point to a directory.
57    #[error(
58        "destination path exists, but is not a directory: {}",
59         .directory_path.display()
60    )]
61    NotADirectory {
62        /// Destination directory path.
63        directory_path: PathBuf,
64    },
65
66    /// The destination directory could not be read, or its path could not be canonicalized.
67    ///
68    /// Among other things, this can happen due to missing read permissions.
69    ///
70    /// The inner [`std::io::Error`] will likely describe a more precise cause of this error.
71    #[error("unable to access destination directory: {}", .directory_path.display())]
72    UnableToAccess {
73        /// The exact path we were unable to access.
74        directory_path: PathBuf,
75
76        /// IO error describing why the source directory could not be accessed.
77        #[source]
78        error: std::io::Error,
79    },
80
81    /// A destination directory or a file inside it already exists,
82    /// which is against the provided [`DestinationDirectoryRule`].
83    #[error(
84        "destination path already exists, which is against \
85        the configured destination directory rule ({:?}): {}",
86        .destination_directory_rule,
87        .path.display()
88    )]
89    AlreadyExists {
90        /// Path to the file or directory that should not exist based on the provided rules.
91        path: PathBuf,
92
93        /// Destination directory rule that made the existing destination
94        /// directory invalid (see [`DestinationDirectoryRule::DisallowExisting`]).
95        destination_directory_rule: DestinationDirectoryRule,
96    },
97
98    /// A destination directory or a file inside it exists and is not empty,
99    /// which is against the provided [`DestinationDirectoryRule`].
100    #[error(
101        "destination directory exists and is not empty, which is against \
102        the configured destination directory rule ({:?}): {}",
103        .destination_directory_rule,
104        .directory_path.display(),
105    )]
106    NotEmpty {
107        /// Path to the destination directory that should be empty based on the provided rules.
108        directory_path: PathBuf,
109
110        /// Destination directory rule that made the existing destination
111        /// directory invalid (see [`DestinationDirectoryRule::AllowEmpty`]).
112        destination_directory_rule: DestinationDirectoryRule,
113    },
114
115    /// The destination directory path equals or points inside the source directory,
116    /// which is very problematic for copies or moves.
117    #[error(
118        "destination directory path equals or points inside the source directory, \
119        which is invalid: {} (but source path is {})",
120        .destination_directory_path.display(),
121        .source_directory_path.display()
122    )]
123    DescendantOfSourceDirectory {
124        /// Invalid destination directory path.
125        destination_directory_path: PathBuf,
126
127        /// Corresponding source directory path.
128        source_directory_path: PathBuf,
129    },
130}
131
132
133
134/// Directory copy or move planning error.
135#[derive(Error, Debug)]
136pub enum DirectoryExecutionPlanError {
137    /// A source or destination directory, one of its sub-directories or a file
138    /// in it (or its metadata) cannot be read.
139    ///
140    /// For example, this can happen due to missing read permissions.
141    ///
142    /// The inner [`std::io::Error`] will likely describe a more precise cause of this error.
143    #[error("unable to access path: {}", .path.display())]
144    UnableToAccess {
145        /// The path we were unable to access.
146        path: PathBuf,
147
148        /// IO error describing why the path could not be accessed.
149        #[source]
150        error: std::io::Error,
151    },
152
153    /// An item inside the source directory "escaped" outside of
154    /// the base source directory.
155    ///
156    /// # Implementation detail
157    /// This is an extremely unlikely error, because its requirement
158    /// is that [`std::fs::read_dir`]'s iterator returns a directory entry
159    /// outside the provided directory path.
160    ///
161    /// Even though this seems extremely unlikely, a `panic!` would be
162    /// an extreme measure due to the many types of filesystems that exist.
163    /// Instead, treat this as a truly fatal error.
164    #[error(
165        "a directory entry inside the source directory escaped out of it: {}",
166        .path.display()
167    )]
168    EntryEscapesSourceDirectory {
169        /// The path that "escaped" the source directory.
170        path: PathBuf,
171    },
172
173    /// A destination directory or a file inside it already exists,
174    /// which is against the configured [`DestinationDirectoryRule`].
175    ///
176    /// This can also happen when we intended to copy a file to the destination,
177    /// but a directory with the same name appeared mid-copy
178    /// (an unavoidable [time-of-check time-of-use](https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use) bug).
179    ///
180    /// The `path` field contains the path that already existed, causing this error.
181    #[error("destination directory or file already exists: {}", .path.display())]
182    DestinationItemAlreadyExists {
183        /// Path of the target directory or file that already exists.
184        path: PathBuf,
185    },
186
187    /// A broken symbolic link has been encountered inside the source directory.
188    ///
189    /// This error can occur only when `broken_symlink_behaviour` is set to
190    /// [`BrokenSymlinkBehaviour::Abort`].
191    ///
192    ///
193    /// [`BrokenSymlinkBehaviour::Abort`]: crate::directory::BrokenSymlinkBehaviour::Abort
194    #[error(
195        "symbolic link inside source directory is broken, \
196        and the behaviour is set to abort"
197    )]
198    SymbolicLinkIsBroken {
199        /// Path of the broken symbolic link.
200        path: PathBuf,
201    },
202}
203
204
205
206/// An item inside the source directory "escaped" outside of
207/// the base source directory.
208///
209/// # Implementation detail
210/// This is an extremely unlikely error, because its requirement
211/// is that [`fs::read_dir`]'s iterator returns a directory entry
212/// outside the provided directory path.
213///
214/// Even though this seems extremely unlikely, a `panic!` would be
215/// an extreme measure due to the many types of filesystems that exist.
216/// Instead, treat this as a truly fatal error.
217#[derive(Error, Debug)]
218#[error(
219    "a directory entry inside the source directory escaped out of it: {}",
220    .path.display()
221)]
222pub(crate) struct SourceSubPathNotUnderBaseSourceDirectory {
223    /// The path that "escaped" the source directory.
224    pub(crate) path: PathBuf,
225}
226
227
228
229/// Directory copy preparation error.
230#[derive(Error, Debug)]
231pub enum CopyDirectoryPreparationError {
232    /// A source directory validation error.
233    #[error(transparent)]
234    SourceDirectoryValidationError(#[from] SourceDirectoryPathValidationError),
235
236    /// A destination directory validation error.
237    #[error(transparent)]
238    DestinationDirectoryValidationError(#[from] DestinationDirectoryPathValidationError),
239
240    /// A directory copy planning error.
241    #[error(transparent)]
242    CopyPlanningError(#[from] DirectoryExecutionPlanError),
243}
244
245
246
247/// Directory copy execution error.
248#[derive(Error, Debug)]
249pub enum CopyDirectoryExecutionError {
250    /// Failed to create a directory inside the destination folder.
251    ///
252    /// For example, this can happen due to missing write permissions.
253    ///
254    /// The inner [`std::io::Error`] will likely describe a more precise cause of this error.
255    #[error("unable to create directory: {}", .directory_path.display())]
256    UnableToCreateDirectory {
257        /// Directory we were unable to create.
258        directory_path: PathBuf,
259
260        /// IO error describing why the directory could not be created.
261        #[source]
262        error: std::io::Error,
263    },
264
265    /// A file or directory inside the destination directory could not be accessed.
266    #[error("unable to access destination path: {}", .path.display())]
267    UnableToAccessDestination {
268        /// The path we were unable to access.
269        path: PathBuf,
270
271        /// IO error describing why the directory could not be created.
272        #[source]
273        error: std::io::Error,
274    },
275
276    /// An error occurred while trying to copy a file to the destination.
277    #[error(
278        "an error occurred while copying a file to the destination: {}",
279        .file_path.display(),
280    )]
281    FileCopyError {
282        /// The file path that could not be copied.
283        file_path: PathBuf,
284
285        /// The underlying file copying error.
286        #[source]
287        error: FileError,
288    },
289
290    /// An error occurred while trying to create a symlink at the destination.
291    #[error(
292        "failed while creating a symlink at {}",
293        .symlink_path.display()
294    )]
295    SymlinkCreationError {
296        /// The path to the symbolic link that could not be created.
297        symlink_path: PathBuf,
298
299        /// The underlying symlink creation error.
300        #[source]
301        error: std::io::Error,
302    },
303
304    /// A destination directory, a file, or a sub-directory inside it
305    /// has changed since the preparation phase of the directory copy.
306    ///
307    /// We can't guarantee that all destination directory changes
308    /// will trigger this, but some more obvious problematic ones, like
309    /// a file appearing in one of the destinations we wanted to copy to, will.
310    ///
311    /// This is essentially an unavoidable
312    /// [time-of-check time-of-use](https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use)
313    /// bug, but we try to catch it if possible.
314    ///
315    /// The `path` field contains the path that already existed, causing this error.
316    #[error("destination directory or file has been created externally mid-execution: {}", .path.display())]
317    DestinationEntryUnexpected {
318        /// The path of the target directory or file that already exists.
319        path: PathBuf,
320    },
321}
322
323
324
325/// Directory copying error (see [`copy_directory`] / [`copy_directory_with_progress`]).
326///
327///
328/// [`copy_directory`]: crate::directory::copy_directory
329/// [`copy_directory_with_progress`]: crate::directory::copy_directory_with_progress
330#[derive(Error, Debug)]
331pub enum CopyDirectoryError {
332    /// Directory copy preparation error.
333    #[error(transparent)]
334    PreparationError(#[from] CopyDirectoryPreparationError),
335
336    /// Directory copy execution error.
337    #[error(transparent)]
338    ExecutionError(#[from] CopyDirectoryExecutionError),
339}
340
341
342
343/// Directory move preparation error.
344#[derive(Error, Debug)]
345pub enum MoveDirectoryPreparationError {
346    /// Source directory validation error.
347    #[error(transparent)]
348    SourceDirectoryValidationError(#[from] SourceDirectoryPathValidationError),
349
350    /// Destination directory validation error.
351    #[error(transparent)]
352    DestinationDirectoryValidationError(#[from] DestinationDirectoryPathValidationError),
353
354    /// Source directory entry scanning error.
355    #[error(transparent)]
356    DirectoryScanError(#[from] DirectoryScanError),
357
358    /// Directory copy planning error. These errors can happen
359    /// when a move-by-rename fails and a copy-and-delete is attempted instead.
360    #[error(transparent)]
361    CopyPlanningError(#[from] DirectoryExecutionPlanError),
362}
363
364
365
366/// Directory move execution error.
367#[derive(Error, Debug)]
368pub enum MoveDirectoryExecutionError {
369    /// A file or directory inside the source directory could not be accessed.
370    #[error("unable to access source path: {}", .path.display())]
371    UnableToAccessSource {
372        /// The path we were unable to access.
373        path: PathBuf,
374
375        /// IO error describing why the directory could not be created.
376        #[source]
377        error: std::io::Error,
378    },
379
380    /// An item inside the source directory "escaped" outside of
381    /// the base source directory.
382    ///
383    /// # Implementation detail
384    /// This is an extremely unlikely error, because its requirement
385    /// is that [`std::fs::read_dir`]'s iterator returns a directory entry
386    /// outside the provided directory path.
387    ///
388    /// Even though this seems extremely unlikely, a `panic!` would be
389    /// an extreme measure due to the many types of filesystems that exist.
390    /// Instead, treat this as a truly fatal error.
391    #[error(
392        "a directory entry inside the source directory escaped out of it: {}",
393        .path.display()
394    )]
395    EntryEscapesSourceDirectory {
396        /// The path that "escaped" the source directory.
397        path: PathBuf,
398    },
399
400    /// A destination directory, a file, or a sub-directory inside it
401    /// has changed since the preparation phase of the directory move call.
402    ///
403    /// We can't guarantee that all destination directory changes
404    /// will trigger this, but some more obvious problematic ones, like
405    /// a file appearing in one of the destinations we wanted to copy to, will.
406    ///
407    /// This is essentially an unavoidable
408    /// [time-of-check time-of-use](https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use)
409    /// bug, but we try to catch it if possible.
410    ///
411    /// The `path` field contains the path that already existed, causing this error.
412    #[error("destination directory or file has been created externally mid-execution: {}", .path.display())]
413    DestinationEntryUnexpected {
414        /// The path of the target directory or file that already exists.
415        path: PathBuf,
416    },
417
418    /// Directory copy execution error.
419    ///
420    /// These errors can happen when a move-by-rename fails
421    /// and a copy-and-delete is performed instead.
422    #[error(transparent)]
423    CopyDirectoryError(#[from] CopyDirectoryExecutionError),
424
425    /// Occurs when renaming is the only enabled directory move strategy,
426    /// but it fails.
427    ///
428    /// This commonly indicates that the source and destination directory are
429    /// on different mount points, which would require copy-and-delete, and sometimes
430    /// even following (instead of preserving) symbolic links.
431    #[error(
432        "only rename strategy is enabled (with no copy-and-delete \
433        fallback strategy), but we were unable to rename the directory"
434    )]
435    RenameFailedAndNoFallbackStrategy,
436
437    /// An uncategorized unrecoverable IO error.
438    /// See `error` field for more information.
439    #[error("uncategorized std::io::Error")]
440    OtherIoError {
441        /// IO error describing the cause of the outer error.
442        #[source]
443        error: std::io::Error,
444    },
445}
446
447
448
449/// Directory moving error (see [`move_directory`] / [`move_directory_with_progress`]).
450///
451///
452/// [`move_directory`]: crate::directory::move_directory
453/// [`move_directory_with_progress`]: crate::directory::move_directory_with_progress
454#[derive(Error, Debug)]
455pub enum MoveDirectoryError {
456    /// Directory move preparation error.
457    #[error(transparent)]
458    PreparationError(#[from] MoveDirectoryPreparationError),
459
460    /// Directory move execution error.
461    #[error(transparent)]
462    ExecutionError(#[from] MoveDirectoryExecutionError),
463}
464
465
466
467/// An error that can occur when querying the size of a directory
468/// (see [`directory_size_in_bytes`]).
469///
470///
471/// [`directory_size_in_bytes`]: crate::directory::directory_size_in_bytes
472#[derive(Error, Debug)]
473pub enum DirectorySizeScanError {
474    /// An error occurred while scanning the directory.
475    #[error("failed while scanning directory: {}", .directory_path.display())]
476    ScanError {
477        /// The scanning error.
478        #[source]
479        error: DirectoryScanError,
480
481        /// Base directory path for the scan.
482        directory_path: PathBuf,
483    },
484}
485
486
487
488/// An error that can occur when checking whether a directory is empty
489/// (see [`is_directory_empty`]).
490///
491///
492/// [`is_directory_empty`]: crate::directory::is_directory_empty
493#[derive(Error, Debug)]
494pub enum DirectoryEmptinessScanError {
495    /// The provided directory path to scan doesn't exist.
496    #[error("path doesn't exist: {}", .path.display())]
497    NotFound {
498        /// The directory path that couldn't be scanned.
499        path: PathBuf,
500    },
501
502    /// The provided directory path exists, but is not a directory.
503    #[error(
504        "path exists, but is not a directory nor a symlink to one: {}",
505        .path.display()
506    )]
507    NotADirectory {
508        /// The directory path that couldn't be scanned.
509        path: PathBuf,
510    },
511
512    /// The provided directory path is a directory,
513    /// but could not be read due to an IO error.
514    ///
515    /// The inner [`std::io::Error`] will likely describe a more precise cause of this error.
516    #[error("unable to read directory: {}", .directory_path.display())]
517    UnableToReadDirectory {
518        /// The directory path that could not be read.
519        directory_path: PathBuf,
520
521        /// IO error describing why the given root directory could not be read.
522        #[source]
523        error: std::io::Error,
524    },
525
526    /// A directory contains an entry (i.e. directory or file)
527    /// that could not be read due to an IO error.
528    ///
529    /// The inner [`std::io::Error`] will likely describe a more precise cause of this error.
530    #[error("unable to read directory entry for {}", .directory_path.display())]
531    UnableToReadDirectoryEntry {
532        /// The directory path whose entries could not be read.
533        directory_path: PathBuf,
534
535        /// IO error describing why the given file or directory could not be read.
536        #[source]
537        error: std::io::Error,
538    },
539}
540
541
542
543
544/// An error that can occur when scanning a directory.
545#[derive(Error, Debug)]
546pub enum DirectoryScanError {
547    /// The provided directory path to scan doesn't exist.
548    #[error("path doesn't exist: {}", .path.display())]
549    NotFound {
550        /// The directory path that couldn't be scanned.
551        path: PathBuf,
552    },
553
554    /// The provided directory path exists, but is not a directory.
555    #[error(
556        "path exists, but is not a directory nor a symlink to one: {}",
557        .path.display()
558    )]
559    NotADirectory {
560        /// The directory path that couldn't be scanned.
561        path: PathBuf,
562    },
563
564    /// The provided directory path is a directory,
565    /// but could not be read due to an IO error.
566    ///
567    /// The inner [`std::io::Error`] will likely describe a more precise cause of this error.
568    #[error("unable to read directory: {}", .directory_path.display())]
569    UnableToReadDirectory {
570        /// The drectory path that could not be read.
571        directory_path: PathBuf,
572
573        /// IO error describing why the given root directory could not be read.
574        #[source]
575        error: std::io::Error,
576    },
577
578    /// A directory contains an entry (i.e. directory or file)
579    /// that could not be read due to an IO error.
580    ///
581    /// The inner [`std::io::Error`] will likely describe a more precise cause of this error.
582    #[error("unable to read directory entry for {}", .directory_path.display())]
583    UnableToReadDirectoryEntry {
584        /// The directory path whose entries could not be read.
585        directory_path: PathBuf,
586
587        /// IO error describing why the given file or directory could not be read.
588        #[source]
589        error: std::io::Error,
590    },
591
592    /// A symlink inside the scan tree is cyclical.
593    #[error("encountered a directory symlink cycle at {}", .directory_path.display())]
594    SymlinkCycleEncountered {
595        /// The directory path at which the cycle loops around (i.e. where the cycle was detected).
596        directory_path: PathBuf,
597    },
598}