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 the pre-flight validation in
35    // `profiler::validate` — gated on `operations` since that's the
36    // only feature that ever constructs them, matching the
37    // `compress`-gated variants above.
38    #[cfg(feature = "operations")]
39    #[error("filename differs only by case from another entry, which the destination filesystem cannot represent: {path} collides with {other}")]
40    CaseCollision { path: PathBuf, other: PathBuf },
41
42    #[cfg(feature = "operations")]
43    #[error("file exceeds the destination filesystem's maximum file size: {path} ({size} bytes, max {max} bytes)")]
44    FileTooLargeForDest { path: PathBuf, size: u64, max: u64 },
45
46    #[cfg(feature = "operations")]
47    #[error("filename is reserved or invalid on the destination filesystem: {path}")]
48    ReservedName { path: PathBuf },
49
50    /// Unlike the three variants above (per-entry, governed by
51    /// `ErrorStrategy`), this describes a whole-destination risk, not a
52    /// property of any specific entry — `is_fatal` accordingly.
53    #[cfg(feature = "operations")]
54    #[error(
55        "destination filesystem ({filesystem}) has a known write-integrity issue on this platform"
56    )]
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. `PermissionDenied`/`Io` default to per-entry despite
64    /// being genuinely ambiguous.
65    ///
66    /// Only consumed by `operations`-gated code (`dispatcher.rs`,
67    /// `move_path.rs`, `sync.rs`, `compress.rs`) — gated to match, since
68    /// a `watch`-only build (which doesn't imply `operations`) would
69    /// otherwise leave this genuinely dead and fail the crate's
70    /// warnings-as-errors build (`.cargo/config.toml`).
71    #[cfg(feature = "operations")]
72    pub(crate) fn is_fatal(&self) -> bool {
73        matches!(
74            self,
75            Error::Cancelled | Error::NoSpace { .. } | Error::FilesystemIntegrityRisk { .. }
76        )
77    }
78}
79
80pub type Result<T> = std::result::Result<T, Error>;