Skip to main content

file_engine/
error.rs

1use std::io;
2use std::path::PathBuf;
3
4use thiserror::Error;
5
6#[derive(Debug, Error)]
7pub enum Error {
8    #[error("source not found: {path}")]
9    SourceNotFound { path: PathBuf },
10
11    #[error("destination already exists: {path}")]
12    DestExists { path: PathBuf },
13
14    #[error("operation cancelled")]
15    Cancelled,
16
17    #[error("insufficient disk space: needed {needed} bytes, available {available} bytes")]
18    NoSpace { needed: u64, available: u64 },
19
20    #[error("permission denied: {path}")]
21    PermissionDenied { path: PathBuf },
22
23    #[error("io error on {path}: {source}")]
24    Io { path: PathBuf, source: io::Error },
25
26    #[cfg(feature = "compress")]
27    #[error("could not infer compression format from destination: {path}")]
28    UnknownCompressFormat { path: PathBuf },
29
30    #[cfg(feature = "analyze")]
31    #[error("invalid glob pattern {pattern:?}: {source}")]
32    InvalidGlobPattern {
33        pattern: String,
34        source: globset::Error,
35    },
36
37    #[cfg(feature = "compress")]
38    #[error("gzip compression requires a single file, got a directory: {path}")]
39    GzipRequiresFile { path: PathBuf },
40
41    // The four variants below back the pre-flight validation in
42    // `profiler::validate` — gated on `operations` since that's the
43    // only feature that ever constructs them, matching the
44    // `compress`-gated variants above.
45    #[cfg(feature = "operations")]
46    #[error("filename differs only by case from another entry, which the destination filesystem cannot represent: {path} collides with {other}")]
47    CaseCollision { path: PathBuf, other: PathBuf },
48
49    #[cfg(feature = "operations")]
50    #[error("file exceeds the destination filesystem's maximum file size: {path} ({size} bytes, max {max} bytes)")]
51    FileTooLargeForDest { path: PathBuf, size: u64, max: u64 },
52
53    #[cfg(feature = "operations")]
54    #[error("filename is reserved or invalid on the destination filesystem: {path}")]
55    ReservedName { path: PathBuf },
56
57    /// Unlike the three variants above (per-entry, governed by
58    /// `ErrorStrategy`), this describes a whole-destination risk, not a
59    /// property of any specific entry — `is_fatal` accordingly.
60    #[cfg(feature = "operations")]
61    #[error(
62        "destination filesystem ({filesystem}) has a known write-integrity issue on this platform"
63    )]
64    FilesystemIntegrityRisk { filesystem: String },
65}
66
67impl Error {
68    /// Fatal errors stop all remaining dispatch regardless of the
69    /// configured `ErrorStrategy`; per-entry errors are handled per that
70    /// strategy. `PermissionDenied`/`Io` default to per-entry despite
71    /// being genuinely ambiguous.
72    ///
73    /// Only consumed by `operations`-gated code (`dispatcher.rs`,
74    /// `move_path.rs`, `sync.rs`, `compress.rs`) — gated to match, since
75    /// a `watch`-only build (which doesn't imply `operations`) would
76    /// otherwise leave this genuinely dead and fail the crate's
77    /// warnings-as-errors build (`.cargo/config.toml`).
78    #[cfg(feature = "operations")]
79    pub(crate) fn is_fatal(&self) -> bool {
80        matches!(
81            self,
82            Error::Cancelled | Error::NoSpace { .. } | Error::FilesystemIntegrityRisk { .. }
83        )
84    }
85}
86
87pub type Result<T> = std::result::Result<T, Error>;