gravityfile_core/
error.rs

1//! Error types for scanning operations.
2
3use std::path::PathBuf;
4
5use serde::{Deserialize, Serialize};
6use thiserror::Error;
7
8/// Errors that can occur during scanning.
9#[derive(Debug, Error)]
10pub enum ScanError {
11    /// Permission denied for a path.
12    #[error("Permission denied: {path}")]
13    PermissionDenied { path: PathBuf },
14
15    /// Path not found.
16    #[error("Path not found: {path}")]
17    NotFound { path: PathBuf },
18
19    /// Generic I/O error.
20    #[error("I/O error at {path}: {source}")]
21    Io {
22        path: PathBuf,
23        #[source]
24        source: std::io::Error,
25    },
26
27    /// Operation was interrupted.
28    #[error("Operation interrupted")]
29    Interrupted,
30
31    /// Too many errors occurred.
32    #[error("Too many errors ({count}), aborting")]
33    TooManyErrors { count: usize },
34
35    /// Invalid configuration.
36    #[error("Invalid configuration: {message}")]
37    InvalidConfig { message: String },
38
39    /// Root path is not a directory.
40    #[error("Root path is not a directory: {path}")]
41    NotADirectory { path: PathBuf },
42
43    /// Other error.
44    #[error("{message}")]
45    Other { message: String },
46}
47
48impl ScanError {
49    /// Create an I/O error with path context.
50    pub fn io(path: impl Into<PathBuf>, source: std::io::Error) -> Self {
51        let path = path.into();
52        match source.kind() {
53            std::io::ErrorKind::PermissionDenied => Self::PermissionDenied { path },
54            std::io::ErrorKind::NotFound => Self::NotFound { path },
55            _ => Self::Io { path, source },
56        }
57    }
58}
59
60/// Kind of scan warning.
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
62pub enum WarningKind {
63    /// Permission was denied.
64    PermissionDenied,
65    /// Symbolic link target does not exist.
66    BrokenSymlink,
67    /// Error reading file/directory.
68    ReadError,
69    /// Error reading metadata.
70    MetadataError,
71    /// Filesystem boundary crossed (when not allowed).
72    CrossFilesystem,
73}
74
75/// Non-fatal warning encountered during scan.
76#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct ScanWarning {
78    /// Path where the warning occurred.
79    pub path: PathBuf,
80    /// Human-readable message.
81    pub message: String,
82    /// Kind of warning.
83    pub kind: WarningKind,
84}
85
86impl ScanWarning {
87    /// Create a new scan warning.
88    pub fn new(path: impl Into<PathBuf>, message: impl Into<String>, kind: WarningKind) -> Self {
89        Self {
90            path: path.into(),
91            message: message.into(),
92            kind,
93        }
94    }
95
96    /// Create a permission denied warning.
97    pub fn permission_denied(path: impl Into<PathBuf>) -> Self {
98        let path = path.into();
99        Self {
100            message: format!("Permission denied: {}", path.display()),
101            path,
102            kind: WarningKind::PermissionDenied,
103        }
104    }
105
106    /// Create a broken symlink warning.
107    pub fn broken_symlink(path: impl Into<PathBuf>, target: &str) -> Self {
108        let path = path.into();
109        Self {
110            message: format!("Broken symlink: {} -> {target}", path.display()),
111            path,
112            kind: WarningKind::BrokenSymlink,
113        }
114    }
115
116    /// Create a read error warning.
117    pub fn read_error(path: impl Into<PathBuf>, error: &std::io::Error) -> Self {
118        let path = path.into();
119        Self {
120            message: format!("Read error: {error}"),
121            path,
122            kind: WarningKind::ReadError,
123        }
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130
131    #[test]
132    fn test_scan_error_io() {
133        let err = ScanError::io(
134            "/test/path",
135            std::io::Error::new(std::io::ErrorKind::PermissionDenied, "denied"),
136        );
137        assert!(matches!(err, ScanError::PermissionDenied { .. }));
138    }
139
140    #[test]
141    fn test_scan_warning_creation() {
142        let warning = ScanWarning::permission_denied("/test/path");
143        assert_eq!(warning.kind, WarningKind::PermissionDenied);
144        assert!(warning.message.contains("Permission denied"));
145    }
146}