turbovault_core/
error.rs

1//! Error types for the Obsidian system.
2//!
3//! All errors in the system are represented by the [`Error`] enum.
4//! This ensures composable error handling across crates.
5
6use std::io;
7use std::path::PathBuf;
8use thiserror::Error as ThisError;
9
10/// The core error type for all Obsidian operations.
11#[derive(ThisError, Debug)]
12pub enum Error {
13    /// File system error
14    #[error("I/O error: {0}")]
15    Io(#[from] io::Error),
16
17    /// File not found
18    #[error("File not found: {path}")]
19    FileNotFound { path: PathBuf },
20
21    /// Invalid file path (outside vault, too long, etc.)
22    #[error("Invalid file path: {reason}")]
23    InvalidPath { reason: String },
24
25    /// Path traversal attempt detected
26    #[error("Path traversal detected: {path}")]
27    PathTraversalAttempt { path: PathBuf },
28
29    /// File too large for processing
30    #[error("File too large ({size} bytes, max {max} bytes): {path}")]
31    FileTooLarge { path: PathBuf, size: u64, max: u64 },
32
33    /// Parse error
34    #[error("Parse error: {reason}")]
35    ParseError { reason: String },
36
37    /// Invalid configuration
38    #[error("Configuration error: {reason}")]
39    ConfigError { reason: String },
40
41    /// Validation error
42    #[error("Validation error: {reason}")]
43    ValidationError { reason: String },
44
45    /// Concurrent access conflict
46    #[error("Concurrent access conflict: {reason}")]
47    ConcurrencyError { reason: String },
48
49    /// Not found in graph
50    #[error("Not found in graph: {key}")]
51    NotFound { key: String },
52
53    /// Generic unclassified error
54    #[error("Error: {0}")]
55    Other(String),
56
57    /// Wrapped error from other crates
58    #[error("Wrapped error: {0}")]
59    Wrapped(Box<dyn std::error::Error + Send + Sync>),
60}
61
62/// Convenient Result type alias
63pub type Result<T> = std::result::Result<T, Error>;
64
65impl Error {
66    /// Create an IO error
67    pub fn io(err: io::Error) -> Self {
68        Error::Io(err)
69    }
70
71    /// Create a file not found error
72    pub fn file_not_found(path: impl Into<PathBuf>) -> Self {
73        Error::FileNotFound { path: path.into() }
74    }
75
76    /// Create an invalid path error
77    pub fn invalid_path(reason: impl Into<String>) -> Self {
78        Error::InvalidPath {
79            reason: reason.into(),
80        }
81    }
82
83    /// Create a path traversal error
84    pub fn path_traversal(path: impl Into<PathBuf>) -> Self {
85        Error::PathTraversalAttempt { path: path.into() }
86    }
87
88    /// Create a file too large error
89    pub fn file_too_large(path: impl Into<PathBuf>, size: u64, max: u64) -> Self {
90        Error::FileTooLarge {
91            path: path.into(),
92            size,
93            max,
94        }
95    }
96
97    /// Create a parse error
98    pub fn parse_error(reason: impl Into<String>) -> Self {
99        Error::ParseError {
100            reason: reason.into(),
101        }
102    }
103
104    /// Create a configuration error
105    pub fn config_error(reason: impl Into<String>) -> Self {
106        Error::ConfigError {
107            reason: reason.into(),
108        }
109    }
110
111    /// Create a validation error
112    pub fn validation_error(reason: impl Into<String>) -> Self {
113        Error::ValidationError {
114            reason: reason.into(),
115        }
116    }
117
118    /// Create a concurrency error
119    pub fn concurrency_error(reason: impl Into<String>) -> Self {
120        Error::ConcurrencyError {
121            reason: reason.into(),
122        }
123    }
124
125    /// Create a not found error
126    pub fn not_found(key: impl Into<String>) -> Self {
127        Error::NotFound { key: key.into() }
128    }
129
130    /// Create a generic error
131    pub fn other(msg: impl Into<String>) -> Self {
132        Error::Other(msg.into())
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139
140    #[test]
141    fn test_error_creation() {
142        let err = Error::file_not_found("/path/to/file");
143        assert!(err.to_string().contains("File not found"));
144
145        let err = Error::invalid_path("contains .. traversal");
146        assert!(err.to_string().contains("Invalid file path"));
147    }
148}