use std::path::PathBuf;
use thiserror::Error;
pub type Result<T> = std::result::Result<T, TitorError>;
#[derive(Debug, Error)]
pub enum TitorError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
#[error("Bincode error: {0}")]
Bincode(String),
#[error("Checkpoint not found: {0}")]
CheckpointNotFound(String),
#[error("Object not found: {0}")]
ObjectNotFound(String),
#[error("Corruption detected: {0}")]
CorruptionDetected(String),
#[error("Hash mismatch - expected: {expected}, actual: {actual}")]
HashMismatch {
expected: String,
actual: String,
},
#[error("Storage error: {0}")]
Storage(String),
#[error("Compression error: {0}")]
Compression(String),
#[error("Decompression error: {0}")]
Decompression(String),
#[error("Invalid checkpoint: {0}")]
InvalidCheckpoint(String),
#[error("Invalid timeline: {0}")]
InvalidTimeline(String),
#[error("File too large: {path:?} ({size} bytes exceeds limit of {limit} bytes)")]
FileTooLarge {
path: PathBuf,
size: u64,
limit: u64,
},
#[error("Permission denied: {path:?}")]
PermissionDenied {
path: PathBuf,
},
#[error("Unsupported file type: {path:?}")]
UnsupportedFileType {
path: PathBuf,
},
#[error("Circular dependency detected in timeline")]
CircularDependency,
#[error("Cannot delete checkpoint {0}: it has child checkpoints")]
CheckpointHasChildren(String),
#[error("Storage not initialized at path: {0:?}")]
StorageNotInitialized(PathBuf),
#[error("Storage already exists at path: {0:?}")]
StorageAlreadyExists(PathBuf),
#[error("Invalid configuration: {0}")]
InvalidConfiguration(String),
#[error("Concurrent modification detected: {0}")]
ConcurrentModification(String),
#[error("Garbage collection error: {0}")]
GarbageCollection(String),
#[error("Verification failed: {0}")]
VerificationFailed(String),
#[error("Restore failed: {0}")]
RestoreFailed(String),
#[error("Fork failed: {0}")]
ForkFailed(String),
#[error("Diff failed: {0}")]
DiffFailed(String),
#[error("Invalid ignore pattern: {0}")]
InvalidPattern(String),
#[error("Walk directory error")]
WalkDir(#[from] walkdir::Error),
#[error("UUID error")]
Uuid(#[from] uuid::Error),
#[error("UTF-8 error: {0}")]
Utf8(#[from] std::str::Utf8Error),
#[error("Path conversion error: {0:?}")]
PathConversion(std::ffi::OsString),
#[error("Lock acquisition timeout")]
LockTimeout,
#[error("Memory map error: {0}")]
MemoryMap(String),
#[error("Thread pool error: {0}")]
ThreadPool(String),
#[error("Auto-checkpoint error: {0}")]
AutoCheckpoint(String),
#[error("Hook execution error: {0}")]
HookExecution(String),
#[error("Internal error: {0}")]
Internal(String),
#[error("{0}")]
Custom(String),
}
impl From<bincode::error::DecodeError> for TitorError {
fn from(err: bincode::error::DecodeError) -> Self {
TitorError::Bincode(err.to_string())
}
}
impl From<bincode::error::EncodeError> for TitorError {
fn from(err: bincode::error::EncodeError) -> Self {
TitorError::Bincode(err.to_string())
}
}
impl TitorError {
pub fn storage(msg: impl Into<String>) -> Self {
TitorError::Storage(msg.into())
}
pub fn compression(msg: impl Into<String>) -> Self {
TitorError::Compression(msg.into())
}
pub fn decompression(msg: impl Into<String>) -> Self {
TitorError::Decompression(msg.into())
}
pub fn internal(msg: impl Into<String>) -> Self {
TitorError::Internal(msg.into())
}
pub fn custom(msg: impl Into<String>) -> Self {
TitorError::Custom(msg.into())
}
pub fn is_recoverable(&self) -> bool {
matches!(
self,
TitorError::LockTimeout
| TitorError::ConcurrentModification(_)
| TitorError::PermissionDenied { .. }
)
}
pub fn is_corruption(&self) -> bool {
matches!(
self,
TitorError::CorruptionDetected(_)
| TitorError::HashMismatch { .. }
| TitorError::InvalidCheckpoint(_)
| TitorError::InvalidTimeline(_)
)
}
pub fn user_message(&self) -> String {
match self {
TitorError::CheckpointNotFound(id) => {
format!("Checkpoint '{}' not found. Use 'list_checkpoints()' to see available checkpoints.", id)
}
TitorError::PermissionDenied { path } => {
format!("Permission denied for {:?}. Check file permissions or run with appropriate privileges.", path)
}
TitorError::FileTooLarge { path, size, limit } => {
format!(
"File {:?} is too large ({} bytes). Maximum allowed size is {} bytes. \
Consider increasing the limit or excluding large files.",
path, size, limit
)
}
TitorError::StorageNotInitialized(path) => {
format!("Storage not initialized at {:?}. Run 'Titor::init()' first.", path)
}
TitorError::LockTimeout => {
"Operation timed out waiting for lock. Another operation may be in progress. Try again later.".to_string()
}
_ => self.to_string(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display() {
let err = TitorError::CheckpointNotFound("abc123".to_string());
assert_eq!(err.to_string(), "Checkpoint not found: abc123");
}
#[test]
fn test_error_recoverable() {
assert!(TitorError::LockTimeout.is_recoverable());
assert!(!TitorError::CorruptionDetected("test".to_string()).is_recoverable());
}
#[test]
fn test_error_corruption() {
assert!(TitorError::HashMismatch {
expected: "abc".to_string(),
actual: "def".to_string(),
}.is_corruption());
assert!(!TitorError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
"test"
)).is_corruption());
}
}