Skip to main content

smriti/
error.rs

1//! Application error handling.
2
3use std::fmt;
4use std::io;
5use std::path::PathBuf;
6
7/// Application error types.
8#[derive(Debug)]
9pub enum AppError {
10    Database(rusqlite::Error),
11    Io(io::Error),
12    Image(image::ImageError),
13    Exif(String),
14    Ml(String),
15    FileNotFound(PathBuf),
16    DriveNotMounted(PathBuf),
17    Config(String),
18    Cancelled,
19    Other(String),
20}
21
22impl fmt::Display for AppError {
23    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24        match self {
25            AppError::Database(e) => write!(f, "Database error: {}", e),
26            AppError::Io(e) => write!(f, "I/O error: {}", e),
27            AppError::Image(e) => write!(f, "Image error: {}", e),
28            AppError::Exif(msg) => write!(f, "EXIF error: {}", msg),
29            AppError::Ml(msg) => write!(f, "ML error: {}", msg),
30            AppError::FileNotFound(path) => write!(f, "File not found: {}", path.display()),
31            AppError::DriveNotMounted(path) => {
32                write!(f, "Drive not mounted: {}", path.display())
33            }
34            AppError::Config(msg) => write!(f, "Configuration error: {}", msg),
35            AppError::Cancelled => write!(f, "Operation cancelled"),
36            AppError::Other(msg) => write!(f, "{}", msg),
37        }
38    }
39}
40
41impl std::error::Error for AppError {}
42
43impl From<rusqlite::Error> for AppError {
44    fn from(e: rusqlite::Error) -> Self {
45        AppError::Database(e)
46    }
47}
48
49impl From<io::Error> for AppError {
50    fn from(e: io::Error) -> Self {
51        AppError::Io(e)
52    }
53}
54
55impl From<image::ImageError> for AppError {
56    fn from(e: image::ImageError) -> Self {
57        AppError::Image(e)
58    }
59}
60
61pub type AppResult<T> = Result<T, AppError>;
62
63impl AppError {
64    pub fn recovery_suggestion(&self) -> &str {
65        match self {
66            AppError::Database(_) => {
67                "Try restarting the app. If it persists, database may be corrupted."
68            }
69            AppError::Io(_) => "Check file permissions and available disk space.",
70            AppError::Image(_) => "This image may be corrupted or unsupported.",
71            AppError::Exif(_) => "Metadata could not be read. Photo can still be indexed.",
72            AppError::Ml(_) => "Face detection failed. Try restarting the app.",
73            AppError::FileNotFound(_) => "The file may have been moved or deleted.",
74            AppError::DriveNotMounted(_) => "Reconnect the drive and try again.",
75            AppError::Config(_) => "Reset settings to defaults and try again.",
76            AppError::Cancelled => "",
77            AppError::Other(_) => "Please try again.",
78        }
79    }
80
81    pub fn should_notify_user(&self) -> bool {
82        !matches!(self, AppError::Cancelled | AppError::Exif(_))
83    }
84
85    pub fn is_recoverable(&self) -> bool {
86        !matches!(self, AppError::Database(_))
87    }
88}
89
90#[derive(Debug, Clone)]
91pub struct ErrorNotification {
92    pub title: String,
93    pub message: String,
94    pub suggestion: String,
95    pub is_critical: bool,
96}
97
98impl From<&AppError> for ErrorNotification {
99    fn from(error: &AppError) -> Self {
100        let title = match error {
101            AppError::Database(_) => "Database Error",
102            AppError::Io(_) => "File Error",
103            AppError::Image(_) => "Image Error",
104            AppError::Exif(_) => "Metadata Error",
105            AppError::Ml(_) => "Face Detection Error",
106            AppError::FileNotFound(_) => "File Not Found",
107            AppError::DriveNotMounted(_) => "Drive Not Connected",
108            AppError::Config(_) => "Settings Error",
109            AppError::Cancelled => "Cancelled",
110            AppError::Other(_) => "Error",
111        };
112
113        Self {
114            title: title.to_string(),
115            message: error.to_string(),
116            suggestion: error.recovery_suggestion().to_string(),
117            is_critical: !error.is_recoverable(),
118        }
119    }
120}