heroforge_core/fs/
errors.rs

1//! Filesystem-specific error types
2//!
3//! This module defines error types specific to filesystem operations.
4//! All errors wrap the underlying FossilError but provide additional context
5//! specific to filesystem operations.
6
7use crate::error::FossilError;
8use std::fmt;
9
10/// Filesystem operation result type
11pub type FsResult<T> = std::result::Result<T, FsError>;
12
13/// Filesystem-specific errors
14#[derive(Debug)]
15pub enum FsError {
16    /// Path does not exist
17    PathNotFound(String),
18
19    /// Alias for PathNotFound (matches spec naming)
20    NotFound(String),
21
22    /// Path is a file but directory was expected
23    NotADirectory(String),
24
25    /// Path is a directory but file was expected
26    NotAFile(String),
27
28    /// Path already exists (when it shouldn't)
29    PathAlreadyExists(String),
30
31    /// Permission denied for operation
32    PermissionDenied(String),
33
34    /// Invalid path (e.g., contains null bytes)
35    InvalidPath(String),
36
37    /// Operation requires write access but repository is read-only
38    ReadOnlyRepository,
39
40    /// Cannot modify file at this commit/branch
41    CannotModify { path: String, reason: String },
42
43    /// Symlink target is invalid or circular
44    SymlinkError(String),
45
46    /// Pattern matching failed
47    PatternError(String),
48
49    /// Transaction-related error
50    TransactionError(String),
51
52    /// Underlying Fossil repository error
53    Repository(FossilError),
54
55    /// IO error
56    Io(std::io::Error),
57
58    /// UTF-8 encoding error
59    Encoding(String),
60
61    /// Operation not supported
62    NotSupported(String),
63
64    /// File exceeds maximum size limit
65    FileTooLarge { path: String, size: u64, max: u64 },
66
67    /// Lock acquisition timeout
68    LockTimeout,
69
70    /// Commit is currently in progress
71    CommitInProgress,
72
73    /// Database/storage error
74    DatabaseError(String),
75
76    /// Staging directory error
77    StagingError(String),
78
79    /// Other errors
80    Other(String),
81}
82
83impl From<FossilError> for FsError {
84    fn from(err: FossilError) -> Self {
85        FsError::Repository(err)
86    }
87}
88
89impl From<std::io::Error> for FsError {
90    fn from(err: std::io::Error) -> Self {
91        FsError::Io(err)
92    }
93}
94
95impl From<std::string::FromUtf8Error> for FsError {
96    fn from(err: std::string::FromUtf8Error) -> Self {
97        FsError::Encoding(format!("UTF-8 conversion failed: {}", err))
98    }
99}
100
101impl fmt::Display for FsError {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        match self {
104            FsError::PathNotFound(path) => write!(f, "Path not found: {}", path),
105            FsError::NotFound(path) => write!(f, "Not found: {}", path),
106            FsError::NotADirectory(path) => write!(f, "Not a directory: {}", path),
107            FsError::NotAFile(path) => write!(f, "Not a file: {}", path),
108            FsError::PathAlreadyExists(path) => write!(f, "Path already exists: {}", path),
109            FsError::PermissionDenied(msg) => write!(f, "Permission denied: {}", msg),
110            FsError::InvalidPath(path) => write!(f, "Invalid path: {}", path),
111            FsError::ReadOnlyRepository => {
112                write!(
113                    f,
114                    "Repository is read-only. Use Repository::open_rw() for write access."
115                )
116            }
117            FsError::CannotModify { path, reason } => {
118                write!(f, "Cannot modify {}: {}", path, reason)
119            }
120            FsError::SymlinkError(msg) => write!(f, "Symlink error: {}", msg),
121            FsError::PatternError(msg) => write!(f, "Pattern error: {}", msg),
122            FsError::TransactionError(msg) => write!(f, "Transaction error: {}", msg),
123            FsError::Repository(err) => write!(f, "Repository error: {}", err),
124            FsError::Io(err) => write!(f, "IO error: {}", err),
125            FsError::Encoding(msg) => write!(f, "Encoding error: {}", msg),
126            FsError::NotSupported(msg) => write!(f, "Not supported: {}", msg),
127            FsError::FileTooLarge { path, size, max } => {
128                write!(
129                    f,
130                    "File too large: {} ({} bytes, max {} bytes)",
131                    path, size, max
132                )
133            }
134            FsError::LockTimeout => write!(f, "Lock acquisition timeout"),
135            FsError::CommitInProgress => write!(f, "Commit is currently in progress"),
136            FsError::DatabaseError(msg) => write!(f, "Database error: {}", msg),
137            FsError::StagingError(msg) => write!(f, "Staging error: {}", msg),
138            FsError::Other(msg) => write!(f, "{}", msg),
139        }
140    }
141}
142
143impl std::error::Error for FsError {}
144
145/// Convert FsError to FossilError for compatibility
146impl From<FsError> for FossilError {
147    fn from(err: FsError) -> Self {
148        match err {
149            FsError::Repository(fossil_err) => fossil_err,
150            other => FossilError::Io(std::io::Error::new(
151                std::io::ErrorKind::Other,
152                other.to_string(),
153            )),
154        }
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161
162    #[test]
163    fn test_error_display() {
164        let err = FsError::PathNotFound("/tmp/test.txt".to_string());
165        assert_eq!(err.to_string(), "Path not found: /tmp/test.txt");
166
167        let err = FsError::ReadOnlyRepository;
168        assert!(err.to_string().contains("read-only"));
169    }
170
171    #[test]
172    fn test_error_conversion() {
173        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "test");
174        let fs_err = FsError::from(io_err);
175        assert!(matches!(fs_err, FsError::Io(_)));
176    }
177}