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 = "compress")]
31    #[error("gzip compression requires a single file, got a directory: {path}")]
32    GzipRequiresFile { path: PathBuf },
33
34    // The four variants below back
35    // dev-docs/design/filesystem-detection.md's pre-flight validation
36    // (`profiler::validate`) — gated on `operations` since that's the
37    // only feature that ever constructs them, matching the
38    // `compress`-gated variants above.
39    #[cfg(feature = "operations")]
40    #[error("filename differs only by case from another entry, which the destination filesystem cannot represent: {path} collides with {other}")]
41    CaseCollision { path: PathBuf, other: PathBuf },
42
43    #[cfg(feature = "operations")]
44    #[error("file exceeds the destination filesystem's maximum file size: {path} ({size} bytes, max {max} bytes)")]
45    FileTooLargeForDest { path: PathBuf, size: u64, max: u64 },
46
47    #[cfg(feature = "operations")]
48    #[error("filename is reserved or invalid on the destination filesystem: {path}")]
49    ReservedName { path: PathBuf },
50
51    /// Unlike the three variants above (per-entry, governed by
52    /// `ErrorStrategy` — see dev-docs/design/filesystem-detection.md,
53    /// "Decisions"), this describes a whole-destination risk, not a
54    /// property of any specific entry — `is_fatal` accordingly.
55    #[cfg(feature = "operations")]
56    #[error("destination filesystem ({filesystem}) has a known write-integrity issue on this platform")]
57    FilesystemIntegrityRisk { filesystem: String },
58}
59
60impl Error {
61    /// Fatal errors stop all remaining dispatch regardless of the
62    /// configured `ErrorStrategy`; per-entry errors are handled per that
63    /// strategy. See dev-docs/design/batching-engine.md for the rationale,
64    /// including why `PermissionDenied`/`Io` default to per-entry despite
65    /// being genuinely ambiguous.
66    ///
67    /// Only consumed by `operations`-gated code (`dispatcher.rs`,
68    /// `move_path.rs`, `sync.rs`, `compress.rs`) — gated to match, since
69    /// a `watch`-only build (which doesn't imply `operations`) would
70    /// otherwise leave this genuinely dead and fail the crate's
71    /// warnings-as-errors build (`.cargo/config.toml`).
72    #[cfg(feature = "operations")]
73    pub(crate) fn is_fatal(&self) -> bool {
74        matches!(self, Error::Cancelled | Error::NoSpace { .. } | Error::FilesystemIntegrityRisk { .. })
75    }
76}
77
78pub type Result<T> = std::result::Result<T, Error>;