1use std::{fmt, io, path::PathBuf};
4
5#[derive(Debug)]
7pub enum DiskUseError {
8 ScanError { path: PathBuf, source: io::Error },
10 MetadataError { path: PathBuf, source: io::Error },
12 CacheReadError { path: PathBuf, source: io::Error },
14 CacheWriteError { path: PathBuf, source: io::Error },
16 CacheSerializationError { path: PathBuf, message: String },
18 PathNotFound { path: PathBuf },
20 PermissionDenied { path: PathBuf },
22}
23
24impl fmt::Display for DiskUseError {
25 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26 match self {
27 DiskUseError::ScanError { path, source } => {
28 write!(
29 f,
30 "Failed to scan directory '{}': {}",
31 path.display(),
32 get_user_friendly_error(source)
33 )
34 }
35 DiskUseError::MetadataError { path, source } => {
36 write!(
37 f,
38 "Failed to read metadata for '{}': {}",
39 path.display(),
40 get_user_friendly_error(source)
41 )
42 }
43 DiskUseError::CacheReadError { path, source } => {
44 write!(
45 f,
46 "Failed to read cache file '{}': {}",
47 path.display(),
48 get_user_friendly_error(source)
49 )
50 }
51 DiskUseError::CacheWriteError { path, source } => {
52 write!(
53 f,
54 "Failed to write cache file '{}': {}",
55 path.display(),
56 get_user_friendly_error(source)
57 )
58 }
59 DiskUseError::CacheSerializationError { path, message } => {
60 write!(
61 f,
62 "Failed to serialize/deserialize cache file '{}': {}",
63 path.display(),
64 message
65 )
66 }
67 DiskUseError::PathNotFound { path } => {
68 write!(f, "Path '{}' does not exist", path.display())
69 }
70 DiskUseError::PermissionDenied { path } => {
71 write!(f, "Permission denied when accessing '{}'", path.display())
72 }
73 }
74 }
75}
76
77impl std::error::Error for DiskUseError {
78 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
79 match self {
80 DiskUseError::ScanError { source, .. }
81 | DiskUseError::MetadataError { source, .. }
82 | DiskUseError::CacheReadError { source, .. }
83 | DiskUseError::CacheWriteError { source, .. } => Some(source),
84 _ => None,
85 }
86 }
87}
88
89impl From<DiskUseError> for io::Error {
91 fn from(err: DiskUseError) -> io::Error {
92 let err_string = err.to_string();
93 match err {
94 DiskUseError::PathNotFound { .. } => {
95 io::Error::new(io::ErrorKind::NotFound, err_string)
96 }
97 DiskUseError::PermissionDenied { .. } => {
98 io::Error::new(io::ErrorKind::PermissionDenied, err_string)
99 }
100 DiskUseError::ScanError { source, .. }
101 | DiskUseError::MetadataError { source, .. }
102 | DiskUseError::CacheReadError { source, .. }
103 | DiskUseError::CacheWriteError { source, .. } => {
104 io::Error::new(source.kind(), err_string)
105 }
106 DiskUseError::CacheSerializationError { .. } => {
107 io::Error::new(io::ErrorKind::InvalidData, err_string)
108 }
109 }
110 }
111}
112
113fn get_user_friendly_error(err: &io::Error) -> String {
115 match err.kind() {
116 io::ErrorKind::NotFound => "The path does not exist".to_string(),
117 io::ErrorKind::PermissionDenied => {
118 "Permission denied. You may need elevated privileges to access this location."
119 .to_string()
120 }
121 io::ErrorKind::InvalidInput => "Invalid path or filename".to_string(),
122 io::ErrorKind::OutOfMemory => "Out of memory".to_string(),
123 io::ErrorKind::StorageFull => "Disk quota exceeded or insufficient disk space".to_string(),
124 _ => {
125 let err_str = err.to_string().to_lowercase();
127 if err_str.contains("quota") || err_str.contains("disk quota") {
128 "Disk quota exceeded. You have reached your storage limit.".to_string()
129 } else if err_str.contains("no space") || err_str.contains("nospc") {
130 "Insufficient disk space available".to_string()
131 } else if err_str.contains("read-only") {
132 "The filesystem is read-only".to_string()
133 } else if err_str.contains("device") {
134 "Device is not available or not ready".to_string()
135 } else if err_str.contains("stale") {
136 "Stale file handle (remote filesystem may be unavailable)".to_string()
137 } else {
138 format!("{}", err)
139 }
140 }
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn test_error_display() {
150 let err = DiskUseError::PathNotFound {
151 path: PathBuf::from("/nonexistent"),
152 };
153 assert_eq!(err.to_string(), "Path '/nonexistent' does not exist");
154
155 let err = DiskUseError::PermissionDenied {
156 path: PathBuf::from("/root/secret"),
157 };
158 assert_eq!(
159 err.to_string(),
160 "Permission denied when accessing '/root/secret'"
161 );
162 }
163
164 #[test]
165 fn test_user_friendly_errors() {
166 let err = io::Error::new(io::ErrorKind::NotFound, "test");
167 assert_eq!(get_user_friendly_error(&err), "The path does not exist");
168
169 let err = io::Error::new(io::ErrorKind::PermissionDenied, "test");
170 assert!(get_user_friendly_error(&err).contains("Permission denied"));
171 }
172
173 #[test]
174 fn test_disk_quota_detection() {
175 let err = io::Error::other("Disk quota exceeded");
176 let msg = get_user_friendly_error(&err);
177 assert!(
178 msg.contains("quota") || msg.contains("storage limit"),
179 "Expected quota message, got: {}",
180 msg
181 );
182
183 let err = io::Error::other("No space left on device");
184 let msg = get_user_friendly_error(&err);
185 assert!(
186 msg.contains("space") || msg.contains("disk"),
187 "Expected space message, got: {}",
188 msg
189 );
190
191 let err = io::Error::new(io::ErrorKind::StorageFull, "storage full");
193 let msg = get_user_friendly_error(&err);
194 assert!(
195 msg.contains("quota") || msg.contains("space"),
196 "Expected quota/space message for StorageFull, got: {}",
197 msg
198 );
199 }
200}