1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum ErrorSeverity {
11 Info,
12 Warning,
13 Critical,
14}
15
16pub type CryptoResult<T> = Result<T, CargoCryptError>;
18
19#[derive(Debug, thiserror::Error)]
21pub enum CargoCryptError {
22 #[error("File operation failed: {message}")]
24 Io {
25 message: String,
26 #[source]
27 source: std::io::Error,
28 },
29
30 #[error("Cryptographic operation failed: {message}")]
32 Crypto {
33 message: String,
34 kind: CryptoErrorKind,
35 },
36
37 #[error("Configuration error: {message}")]
39 Config {
40 message: String,
41 suggestion: Option<String>,
42 },
43
44 #[error("Project structure error: {message}")]
46 Project {
47 message: String,
48 suggestion: Option<String>,
49 },
50
51 #[error("Authentication failed: {message}")]
53 Auth {
54 message: String,
55 retry_suggestion: Option<String>,
56 },
57
58 #[error("Key management error: {message}")]
60 KeyManagement {
61 message: String,
62 recovery_suggestion: Option<String>,
63 },
64
65 #[error("Serialization error: {message}")]
67 Serialization {
68 message: String,
69 #[source]
70 source: Box<dyn std::error::Error + Send + Sync>,
71 },
72
73 #[error("Network error: {message}")]
75 Network {
76 message: String,
77 #[source]
78 source: Box<dyn std::error::Error + Send + Sync>,
79 },
80
81 #[error("Git operation failed: {message}")]
83 Git {
84 message: String,
85 #[source]
86 source: Option<git2::Error>,
87 },
88
89 #[error("Validation failed: {message}")]
91 Validation {
92 message: String,
93 errors: Vec<String>,
94 warnings: Vec<String>,
95 },
96}
97
98#[derive(Debug, Clone, PartialEq)]
100pub enum CryptoErrorKind {
101 KeyDerivation,
103 Encryption,
105 Decryption,
107 InvalidKey,
109 InvalidNonce,
111 AuthenticationFailed,
113 UnsupportedAlgorithm,
115 RandomGenerationFailed,
117}
118
119impl CargoCryptError {
121 pub fn severity(&self) -> ErrorSeverity {
123 match self {
124 CargoCryptError::Crypto { kind, .. } => match kind {
125 CryptoErrorKind::AuthenticationFailed |
126 CryptoErrorKind::Decryption |
127 CryptoErrorKind::InvalidKey => ErrorSeverity::Critical,
128 _ => ErrorSeverity::Warning,
129 },
130 CargoCryptError::Validation { .. } => ErrorSeverity::Warning,
131 CargoCryptError::Auth { .. } => ErrorSeverity::Critical,
132 CargoCryptError::KeyManagement { .. } => ErrorSeverity::Critical,
133 CargoCryptError::Network { .. } => ErrorSeverity::Warning,
134 CargoCryptError::Git { .. } => ErrorSeverity::Warning,
135 CargoCryptError::Io { .. } => ErrorSeverity::Warning,
136 CargoCryptError::Config { .. } => ErrorSeverity::Info,
137 CargoCryptError::Project { .. } => ErrorSeverity::Info,
138 CargoCryptError::Serialization { .. } => ErrorSeverity::Warning,
139 }
140 }
141 pub fn project_not_found() -> Self {
143 Self::Project {
144 message: "Could not find Cargo.toml in current directory or any parent directories".to_string(),
145 suggestion: Some("Run this command from within a Rust project directory, or use 'cargo new' to create a new project".to_string()),
146 }
147 }
148
149 pub fn config_not_found() -> Self {
151 Self::Config {
152 message: "CargoCrypt configuration file not found".to_string(),
153 suggestion: Some("Run 'cargo crypt init' to create a new configuration".to_string()),
154 }
155 }
156
157 pub fn invalid_password() -> Self {
159 Self::Auth {
160 message: "Password verification failed".to_string(),
161 retry_suggestion: Some("Please check your password and try again".to_string()),
162 }
163 }
164
165 pub fn file_not_found(path: &std::path::Path) -> Self {
167 Self::Io {
168 message: format!("File not found: {}", path.display()),
169 source: std::io::Error::new(
170 std::io::ErrorKind::NotFound,
171 format!("File '{}' does not exist", path.display()),
172 ),
173 }
174 }
175
176 pub fn decryption_failed(details: &str) -> Self {
178 Self::Crypto {
179 message: format!("Decryption failed: {}", details),
180 kind: CryptoErrorKind::Decryption,
181 }
182 }
183
184 pub fn encryption_failed(details: &str) -> Self {
186 Self::Crypto {
187 message: format!("Encryption failed: {}", details),
188 kind: CryptoErrorKind::Encryption,
189 }
190 }
191
192 pub fn key_derivation_failed(details: &str) -> Self {
194 Self::Crypto {
195 message: format!("Key derivation failed: {}", details),
196 kind: CryptoErrorKind::KeyDerivation,
197 }
198 }
199
200 pub fn invalid_key(details: &str) -> Self {
202 Self::Crypto {
203 message: format!("Invalid key: {}", details),
204 kind: CryptoErrorKind::InvalidKey,
205 }
206 }
207
208 pub fn authentication_failed() -> Self {
210 Self::Crypto {
211 message: "Authentication tag verification failed - data may be corrupted or tampered with".to_string(),
212 kind: CryptoErrorKind::AuthenticationFailed,
213 }
214 }
215
216 pub fn random_generation_failed() -> Self {
218 Self::Crypto {
219 message: "Failed to generate cryptographically secure random data".to_string(),
220 kind: CryptoErrorKind::RandomGenerationFailed,
221 }
222 }
223
224 pub fn detection_error(message: &str) -> Self {
226 Self::Config {
227 message: format!("Detection error: {}", message),
228 suggestion: Some("Check detection configuration and patterns".to_string()),
229 }
230 }
231
232 pub fn crypto_kind(&self) -> Option<&CryptoErrorKind> {
234 match self {
235 CargoCryptError::Crypto { kind, .. } => Some(kind),
236 _ => None,
237 }
238 }
239
240 pub fn is_recoverable(&self) -> bool {
242 match self {
243 CargoCryptError::Auth { .. } => true,
244 CargoCryptError::Network { .. } => true,
245 CargoCryptError::Io { source, .. } => matches!(
246 source.kind(),
247 std::io::ErrorKind::NotFound
248 | std::io::ErrorKind::PermissionDenied
249 | std::io::ErrorKind::ConnectionRefused
250 | std::io::ErrorKind::TimedOut
251 ),
252 CargoCryptError::Crypto { kind, .. } => matches!(
253 kind,
254 CryptoErrorKind::RandomGenerationFailed
255 ),
256 _ => false,
257 }
258 }
259
260 pub fn suggestion(&self) -> Option<&str> {
262 match self {
263 CargoCryptError::Config { suggestion, .. } => suggestion.as_deref(),
264 CargoCryptError::Project { suggestion, .. } => suggestion.as_deref(),
265 CargoCryptError::Auth { retry_suggestion, .. } => retry_suggestion.as_deref(),
266 CargoCryptError::KeyManagement { recovery_suggestion, .. } => recovery_suggestion.as_deref(),
267 _ => None,
268 }
269 }
270}
271
272impl From<std::io::Error> for CargoCryptError {
274 fn from(error: std::io::Error) -> Self {
275 Self::Io {
276 message: error.to_string(),
277 source: error,
278 }
279 }
280}
281
282impl From<crate::crypto::CryptoError> for CargoCryptError {
284 fn from(error: crate::crypto::CryptoError) -> Self {
285 use crate::crypto::CryptoError;
286
287 let kind = match &error {
288 CryptoError::KeyDerivation { .. } => CryptoErrorKind::KeyDerivation,
289 CryptoError::Encryption { .. } => CryptoErrorKind::Encryption,
290 CryptoError::Decryption { .. } => CryptoErrorKind::Decryption,
291 CryptoError::AuthenticationFailed => CryptoErrorKind::AuthenticationFailed,
292 CryptoError::InvalidKey { .. } => CryptoErrorKind::InvalidKey,
293 CryptoError::InvalidNonce { .. } => CryptoErrorKind::InvalidNonce,
294 CryptoError::RandomGeneration { .. } => CryptoErrorKind::RandomGenerationFailed,
295 _ => CryptoErrorKind::Encryption, };
297
298 Self::Crypto {
299 message: error.to_string(),
300 kind,
301 }
302 }
303}
304
305impl From<serde_json::Error> for CargoCryptError {
307 fn from(error: serde_json::Error) -> Self {
308 Self::Serialization {
309 message: format!("JSON serialization failed: {}", error),
310 source: Box::new(error),
311 }
312 }
313}
314
315impl From<toml::de::Error> for CargoCryptError {
317 fn from(error: toml::de::Error) -> Self {
318 Self::Serialization {
319 message: format!("TOML parsing failed: {}", error),
320 source: Box::new(error),
321 }
322 }
323}
324
325impl From<reqwest::Error> for CargoCryptError {
327 fn from(error: reqwest::Error) -> Self {
328 Self::Network {
329 message: format!("HTTP request failed: {}", error),
330 source: Box::new(error),
331 }
332 }
333}
334
335impl From<git2::Error> for CargoCryptError {
337 fn from(error: git2::Error) -> Self {
338 Self::Git {
339 message: format!("Git operation failed: {}", error.message()),
340 source: Some(error),
341 }
342 }
343}
344
345impl From<crate::git::GitError> for CargoCryptError {
346 fn from(error: crate::git::GitError) -> Self {
347 Self::Git {
348 message: format!("Git integration failed: {}", error),
349 source: None,
350 }
351 }
352}
353
354#[derive(Debug, Clone, PartialEq)]
356pub enum ErrorKind {
357 Config,
359 Io,
361 Crypto,
363 Network,
365 Auth,
367 Git,
369 Project,
371 KeyManagement,
373 Serialization,
375}
376
377impl CargoCryptError {
378 pub fn kind(&self) -> ErrorKind {
380 match self {
381 CargoCryptError::Config { .. } => ErrorKind::Config,
382 CargoCryptError::Io { .. } => ErrorKind::Io,
383 CargoCryptError::Crypto { .. } => ErrorKind::Crypto,
384 CargoCryptError::Network { .. } => ErrorKind::Network,
385 CargoCryptError::Auth { .. } => ErrorKind::Auth,
386 CargoCryptError::Git { .. } => ErrorKind::Git,
387 CargoCryptError::Project { .. } => ErrorKind::Project,
388 CargoCryptError::KeyManagement { .. } => ErrorKind::KeyManagement,
389 CargoCryptError::Serialization { .. } => ErrorKind::Serialization,
390 CargoCryptError::Validation { .. } => ErrorKind::Config,
391 }
392 }
393}
394
395#[cfg(test)]
396mod tests {
397 use super::*;
398
399 #[test]
400 fn test_error_constructors() {
401 let err = CargoCryptError::project_not_found();
402 assert!(matches!(err.kind(), ErrorKind::Project));
403 assert!(err.suggestion().is_some());
404
405 let err = CargoCryptError::invalid_password();
406 assert!(matches!(err.kind(), ErrorKind::Auth));
407 assert!(err.is_recoverable());
408 }
409
410 #[test]
411 fn test_crypto_error_kinds() {
412 let err = CargoCryptError::decryption_failed("test");
413 assert_eq!(err.crypto_kind(), Some(&CryptoErrorKind::Decryption));
414
415 let err = CargoCryptError::encryption_failed("test");
416 assert_eq!(err.crypto_kind(), Some(&CryptoErrorKind::Encryption));
417 }
418
419 #[test]
420 fn test_error_conversions() {
421 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "test");
422 let crypto_err: CargoCryptError = io_err.into();
423 assert!(matches!(crypto_err.kind(), ErrorKind::Io));
424 }
425
426 #[test]
427 fn test_recoverable_errors() {
428 let auth_err = CargoCryptError::invalid_password();
429 assert!(auth_err.is_recoverable());
430
431 let config_err = CargoCryptError::config_not_found();
432 assert!(!config_err.is_recoverable());
433 }
434}