anyfs_backend/
error.rs

1//! # Error Types
2//!
3//! Comprehensive error handling for AnyFS filesystem operations.
4//!
5//! ## Overview
6//!
7//! All AnyFS operations return `Result<T, FsError>`. The [`FsError`] enum provides
8//! detailed, contextual error variants that include:
9//!
10//! - **Path information** — Which file/directory caused the error
11//! - **Operation context** — What operation was attempted
12//! - **Specific details** — Quota limits, invalid data descriptions, etc.
13//!
14//! ## Error Categories
15//!
16//! | Category | Variants | Description |
17//! |----------|----------|-------------|
18//! | Path/File | `NotFound`, `AlreadyExists`, `NotAFile`, `NotADirectory` | Path existence and type errors |
19//! | Permission | `PermissionDenied`, `AccessDenied`, `ReadOnly` | Access control errors |
20//! | Resource | `QuotaExceeded`, `FileSizeExceeded`, `RateLimitExceeded` | Limit violations |
21//! | Data | `InvalidData`, `CorruptedData`, `IntegrityError` | Content problems |
22//! | Operation | `NotSupported`, `Conflict`, `Backend` | Backend/operation failures |
23//!
24//! ## Quick Example
25//!
26//! ```rust
27//! use anyfs_backend::FsError;
28//! use std::path::PathBuf;
29//!
30//! // Errors include the path that caused the problem
31//! let err = FsError::NotFound { path: PathBuf::from("/missing.txt") };
32//! assert!(err.to_string().contains("/missing.txt"));
33//!
34//! // Permission errors include the operation
35//! let err = FsError::PermissionDenied {
36//!     path: PathBuf::from("/secret"),
37//!     operation: "read",
38//! };
39//! assert!(err.to_string().contains("read"));
40//! ```
41//!
42//! ## Conversion from std::io::Error
43//!
44//! [`FsError`] implements `From<std::io::Error>` for easy interoperability:
45//!
46//! ```rust
47//! use anyfs_backend::FsError;
48//! use std::io::{Error, ErrorKind};
49//!
50//! let io_err = Error::new(ErrorKind::NotFound, "file not found");
51//! let fs_err: FsError = io_err.into();
52//! assert!(matches!(fs_err, FsError::NotFound { .. }));
53//! ```
54
55use std::path::PathBuf;
56
57/// Comprehensive filesystem error type.
58///
59/// All AnyFS operations return `Result<T, FsError>`. Each variant includes
60/// relevant context (paths, operations, limits) to make debugging easier.
61///
62/// # Non-Exhaustive
63///
64/// This enum is marked `#[non_exhaustive]`, meaning new variants may be added
65/// in future versions without breaking changes. Always include a wildcard arm
66/// when pattern matching:
67///
68/// ```rust
69/// use anyfs_backend::FsError;
70/// use std::path::PathBuf;
71///
72/// fn handle_error(err: FsError) {
73///     match err {
74///         FsError::NotFound { path } => println!("Not found: {}", path.display()),
75///         FsError::PermissionDenied { path, operation } => {
76///             println!("Permission denied for {} on {}", operation, path.display())
77///         }
78///         other => println!("Other error: {}", other),
79///     }
80/// }
81/// ```
82///
83/// # Display Format
84///
85/// All variants implement `Display` with human-readable messages:
86///
87/// ```rust
88/// use anyfs_backend::FsError;
89/// use std::path::PathBuf;
90///
91/// let err = FsError::QuotaExceeded { limit: 100, requested: 50, usage: 80 };
92/// let msg = err.to_string();
93/// assert!(msg.contains("100") && msg.contains("50") && msg.contains("80"));
94/// ```
95///
96/// # Error Source Chain
97///
98/// The [`Io`](FsError::Io) variant wraps `std::io::Error` with the `#[source]`
99/// attribute, enabling error chain traversal via `std::error::Error::source()`.
100#[non_exhaustive]
101#[derive(Debug, thiserror::Error)]
102pub enum FsError {
103    // Path/File Errors
104    /// Path does not exist.
105    #[error("not found: {path}")]
106    NotFound {
107        /// The path that was not found.
108        path: PathBuf,
109    },
110
111    /// A threat was detected (e.g., path traversal, malicious content).
112    #[error("threat detected: {reason} in {path}")]
113    ThreatDetected {
114        /// The path where the threat was detected.
115        path: PathBuf,
116        /// Description of the threat.
117        reason: String,
118    },
119
120    /// Path already exists when it shouldn't.
121    #[error("{operation}: already exists: {path}")]
122    AlreadyExists {
123        /// The path that already exists.
124        path: PathBuf,
125        /// The operation that failed.
126        operation: &'static str,
127    },
128
129    /// Expected a file but found something else.
130    #[error("not a file: {path}")]
131    NotAFile {
132        /// The path that is not a file.
133        path: PathBuf,
134    },
135
136    /// Expected a directory but found something else.
137    #[error("not a directory: {path}")]
138    NotADirectory {
139        /// The path that is not a directory.
140        path: PathBuf,
141    },
142
143    /// Directory is not empty when it should be.
144    #[error("directory not empty: {path}")]
145    DirectoryNotEmpty {
146        /// The path to the non-empty directory.
147        path: PathBuf,
148    },
149
150    /// Inode does not exist.
151    #[error("inode not found: {inode}")]
152    InodeNotFound {
153        /// The inode number that was not found.
154        inode: u64,
155    },
156
157    /// File handle is invalid or closed.
158    #[error("invalid handle: {}", handle.0)]
159    InvalidHandle {
160        /// The invalid handle.
161        handle: crate::Handle,
162    },
163
164    /// Extended attribute not found.
165    #[error("xattr not found: {name} on {path}")]
166    XattrNotFound {
167        /// The path where the xattr was not found.
168        path: PathBuf,
169        /// The attribute name that was not found.
170        name: String,
171    },
172
173    // Permission/Access Errors
174    /// Permission denied for operation.
175    #[error("{operation}: permission denied: {path}")]
176    PermissionDenied {
177        /// The path where permission was denied.
178        path: PathBuf,
179        /// The operation that was denied.
180        operation: &'static str,
181    },
182
183    /// Access denied with reason.
184    #[error("access denied: {path} ({reason})")]
185    AccessDenied {
186        /// The path where access was denied.
187        path: PathBuf,
188        /// The reason for denial.
189        reason: String,
190    },
191
192    /// Filesystem is read-only.
193    #[error("read-only filesystem: {operation}")]
194    ReadOnly {
195        /// The operation that was attempted.
196        operation: &'static str,
197    },
198
199    /// Feature is not enabled.
200    #[error("{operation}: feature not enabled: {feature}")]
201    FeatureNotEnabled {
202        /// The feature that is not enabled.
203        feature: &'static str,
204        /// The operation that requires the feature.
205        operation: &'static str,
206    },
207
208    // Resource Limit Errors
209    /// Quota exceeded.
210    #[error("quota exceeded: limit {limit}, requested {requested}, usage {usage}")]
211    QuotaExceeded {
212        /// The quota limit.
213        limit: u64,
214        /// The amount requested.
215        requested: u64,
216        /// The current usage.
217        usage: u64,
218    },
219
220    /// File size limit exceeded.
221    #[error("file size exceeded: {path} ({size} > {limit})")]
222    FileSizeExceeded {
223        /// The path to the file.
224        path: PathBuf,
225        /// The actual size.
226        size: u64,
227        /// The size limit.
228        limit: u64,
229    },
230
231    /// Rate limit exceeded.
232    #[error("rate limit exceeded: {limit}/s (window: {window_secs}s)")]
233    RateLimitExceeded {
234        /// The rate limit.
235        limit: u32,
236        /// The time window in seconds.
237        window_secs: u64,
238    },
239
240    // Data Errors
241    /// Invalid data encountered.
242    #[error("invalid data: {path} ({details})")]
243    InvalidData {
244        /// The path with invalid data.
245        path: PathBuf,
246        /// Details about the invalid data.
247        details: String,
248    },
249
250    /// Corrupted data detected.
251    #[error("corrupted data: {path} ({details})")]
252    CorruptedData {
253        /// The path with corrupted data.
254        path: PathBuf,
255        /// Details about the corruption.
256        details: String,
257    },
258
259    /// Data integrity check failed.
260    #[error("integrity error: {path}")]
261    IntegrityError {
262        /// The path that failed integrity check.
263        path: PathBuf,
264    },
265
266    /// Serialization error.
267    #[error("serialization error: {0}")]
268    Serialization(String),
269
270    /// Deserialization error.
271    #[error("deserialization error: {0}")]
272    Deserialization(String),
273
274    // Backend/Operation Errors
275    /// Operation is not supported.
276    #[error("operation not supported: {operation}")]
277    NotSupported {
278        /// The unsupported operation.
279        operation: &'static str,
280    },
281
282    /// Invalid password provided.
283    #[error("invalid password")]
284    InvalidPassword,
285
286    /// Conflict detected (e.g., concurrent modification).
287    #[error("conflict: {path}")]
288    Conflict {
289        /// The path with a conflict.
290        path: PathBuf,
291    },
292
293    /// Generic backend error.
294    #[error("backend error: {0}")]
295    Backend(String),
296
297    /// I/O error with context.
298    #[error("{operation} failed for {path}: {source}")]
299    Io {
300        /// The operation that failed.
301        operation: &'static str,
302        /// The path involved in the operation.
303        path: PathBuf,
304        /// The underlying I/O error.
305        #[source]
306        source: std::io::Error,
307    },
308}
309
310impl From<std::io::Error> for FsError {
311    fn from(error: std::io::Error) -> Self {
312        // Convert common io::ErrorKind to more specific FsError variants when possible
313        match error.kind() {
314            std::io::ErrorKind::NotFound => FsError::NotFound {
315                path: PathBuf::new(),
316            },
317            std::io::ErrorKind::PermissionDenied => FsError::PermissionDenied {
318                path: PathBuf::new(),
319                operation: "io",
320            },
321            std::io::ErrorKind::AlreadyExists => FsError::AlreadyExists {
322                path: PathBuf::new(),
323                operation: "io",
324            },
325            _ => FsError::Io {
326                operation: "io",
327                path: PathBuf::new(),
328                source: error,
329            },
330        }
331    }
332}
333
334#[cfg(test)]
335mod tests {
336    use super::*;
337
338    #[test]
339    fn fs_error_not_found_display() {
340        let err = FsError::NotFound {
341            path: PathBuf::from("/missing"),
342        };
343        assert_eq!(err.to_string(), "not found: /missing");
344    }
345
346    #[test]
347    fn fs_error_already_exists_display() {
348        let err = FsError::AlreadyExists {
349            path: PathBuf::from("/exists"),
350            operation: "create",
351        };
352        assert_eq!(err.to_string(), "create: already exists: /exists");
353    }
354
355    #[test]
356    fn fs_error_quota_exceeded_display() {
357        let err = FsError::QuotaExceeded {
358            limit: 100,
359            requested: 50,
360            usage: 80,
361        };
362        assert!(err.to_string().contains("100"));
363        assert!(err.to_string().contains("50"));
364        assert!(err.to_string().contains("80"));
365    }
366
367    #[test]
368    fn fs_error_from_io_not_found() {
369        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "test");
370        let fs_err = FsError::from(io_err);
371        assert!(matches!(fs_err, FsError::NotFound { .. }));
372    }
373
374    #[test]
375    fn fs_error_from_io_permission_denied() {
376        let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "test");
377        let fs_err = FsError::from(io_err);
378        assert!(matches!(fs_err, FsError::PermissionDenied { .. }));
379    }
380
381    #[test]
382    fn fs_error_from_io_already_exists() {
383        let io_err = std::io::Error::new(std::io::ErrorKind::AlreadyExists, "test");
384        let fs_err = FsError::from(io_err);
385        assert!(matches!(fs_err, FsError::AlreadyExists { .. }));
386    }
387
388    #[test]
389    fn fs_error_from_io_other() {
390        let io_err = std::io::Error::new(std::io::ErrorKind::Other, "test");
391        let fs_err = FsError::from(io_err);
392        assert!(matches!(fs_err, FsError::Io { .. }));
393    }
394}