#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorSeverity {
Info,
Warning,
Critical,
}
pub type CryptoResult<T> = Result<T, CargoCryptError>;
#[derive(Debug, thiserror::Error)]
pub enum CargoCryptError {
#[error("File operation failed: {message}")]
Io {
message: String,
#[source]
source: std::io::Error,
},
#[error("Cryptographic operation failed: {message}")]
Crypto {
message: String,
kind: CryptoErrorKind,
},
#[error("Configuration error: {message}")]
Config {
message: String,
suggestion: Option<String>,
},
#[error("Project structure error: {message}")]
Project {
message: String,
suggestion: Option<String>,
},
#[error("Authentication failed: {message}")]
Auth {
message: String,
retry_suggestion: Option<String>,
},
#[error("Key management error: {message}")]
KeyManagement {
message: String,
recovery_suggestion: Option<String>,
},
#[error("Serialization error: {message}")]
Serialization {
message: String,
#[source]
source: Box<dyn std::error::Error + Send + Sync>,
},
#[error("Network error: {message}")]
Network {
message: String,
#[source]
source: Box<dyn std::error::Error + Send + Sync>,
},
#[error("Git operation failed: {message}")]
Git {
message: String,
#[source]
source: Option<git2::Error>,
},
#[error("Validation failed: {message}")]
Validation {
message: String,
errors: Vec<String>,
warnings: Vec<String>,
},
}
#[derive(Debug, Clone, PartialEq)]
pub enum CryptoErrorKind {
KeyDerivation,
Encryption,
Decryption,
InvalidKey,
InvalidNonce,
AuthenticationFailed,
UnsupportedAlgorithm,
RandomGenerationFailed,
}
impl CargoCryptError {
pub fn severity(&self) -> ErrorSeverity {
match self {
CargoCryptError::Crypto { kind, .. } => match kind {
CryptoErrorKind::AuthenticationFailed |
CryptoErrorKind::Decryption |
CryptoErrorKind::InvalidKey => ErrorSeverity::Critical,
_ => ErrorSeverity::Warning,
},
CargoCryptError::Validation { .. } => ErrorSeverity::Warning,
CargoCryptError::Auth { .. } => ErrorSeverity::Critical,
CargoCryptError::KeyManagement { .. } => ErrorSeverity::Critical,
CargoCryptError::Network { .. } => ErrorSeverity::Warning,
CargoCryptError::Git { .. } => ErrorSeverity::Warning,
CargoCryptError::Io { .. } => ErrorSeverity::Warning,
CargoCryptError::Config { .. } => ErrorSeverity::Info,
CargoCryptError::Project { .. } => ErrorSeverity::Info,
CargoCryptError::Serialization { .. } => ErrorSeverity::Warning,
}
}
pub fn project_not_found() -> Self {
Self::Project {
message: "Could not find Cargo.toml in current directory or any parent directories".to_string(),
suggestion: Some("Run this command from within a Rust project directory, or use 'cargo new' to create a new project".to_string()),
}
}
pub fn config_not_found() -> Self {
Self::Config {
message: "CargoCrypt configuration file not found".to_string(),
suggestion: Some("Run 'cargo crypt init' to create a new configuration".to_string()),
}
}
pub fn invalid_password() -> Self {
Self::Auth {
message: "Password verification failed".to_string(),
retry_suggestion: Some("Please check your password and try again".to_string()),
}
}
pub fn file_not_found(path: &std::path::Path) -> Self {
Self::Io {
message: format!("File not found: {}", path.display()),
source: std::io::Error::new(
std::io::ErrorKind::NotFound,
format!("File '{}' does not exist", path.display()),
),
}
}
pub fn decryption_failed(details: &str) -> Self {
Self::Crypto {
message: format!("Decryption failed: {}", details),
kind: CryptoErrorKind::Decryption,
}
}
pub fn encryption_failed(details: &str) -> Self {
Self::Crypto {
message: format!("Encryption failed: {}", details),
kind: CryptoErrorKind::Encryption,
}
}
pub fn key_derivation_failed(details: &str) -> Self {
Self::Crypto {
message: format!("Key derivation failed: {}", details),
kind: CryptoErrorKind::KeyDerivation,
}
}
pub fn invalid_key(details: &str) -> Self {
Self::Crypto {
message: format!("Invalid key: {}", details),
kind: CryptoErrorKind::InvalidKey,
}
}
pub fn authentication_failed() -> Self {
Self::Crypto {
message: "Authentication tag verification failed - data may be corrupted or tampered with".to_string(),
kind: CryptoErrorKind::AuthenticationFailed,
}
}
pub fn random_generation_failed() -> Self {
Self::Crypto {
message: "Failed to generate cryptographically secure random data".to_string(),
kind: CryptoErrorKind::RandomGenerationFailed,
}
}
pub fn detection_error(message: &str) -> Self {
Self::Config {
message: format!("Detection error: {}", message),
suggestion: Some("Check detection configuration and patterns".to_string()),
}
}
pub fn crypto_kind(&self) -> Option<&CryptoErrorKind> {
match self {
CargoCryptError::Crypto { kind, .. } => Some(kind),
_ => None,
}
}
pub fn is_recoverable(&self) -> bool {
match self {
CargoCryptError::Auth { .. } => true,
CargoCryptError::Network { .. } => true,
CargoCryptError::Io { source, .. } => matches!(
source.kind(),
std::io::ErrorKind::NotFound
| std::io::ErrorKind::PermissionDenied
| std::io::ErrorKind::ConnectionRefused
| std::io::ErrorKind::TimedOut
),
CargoCryptError::Crypto { kind, .. } => matches!(
kind,
CryptoErrorKind::RandomGenerationFailed
),
_ => false,
}
}
pub fn suggestion(&self) -> Option<&str> {
match self {
CargoCryptError::Config { suggestion, .. } => suggestion.as_deref(),
CargoCryptError::Project { suggestion, .. } => suggestion.as_deref(),
CargoCryptError::Auth { retry_suggestion, .. } => retry_suggestion.as_deref(),
CargoCryptError::KeyManagement { recovery_suggestion, .. } => recovery_suggestion.as_deref(),
_ => None,
}
}
}
impl From<std::io::Error> for CargoCryptError {
fn from(error: std::io::Error) -> Self {
Self::Io {
message: error.to_string(),
source: error,
}
}
}
impl From<crate::crypto::CryptoError> for CargoCryptError {
fn from(error: crate::crypto::CryptoError) -> Self {
use crate::crypto::CryptoError;
let kind = match &error {
CryptoError::KeyDerivation { .. } => CryptoErrorKind::KeyDerivation,
CryptoError::Encryption { .. } => CryptoErrorKind::Encryption,
CryptoError::Decryption { .. } => CryptoErrorKind::Decryption,
CryptoError::AuthenticationFailed => CryptoErrorKind::AuthenticationFailed,
CryptoError::InvalidKey { .. } => CryptoErrorKind::InvalidKey,
CryptoError::InvalidNonce { .. } => CryptoErrorKind::InvalidNonce,
CryptoError::RandomGeneration { .. } => CryptoErrorKind::RandomGenerationFailed,
_ => CryptoErrorKind::Encryption, };
Self::Crypto {
message: error.to_string(),
kind,
}
}
}
impl From<serde_json::Error> for CargoCryptError {
fn from(error: serde_json::Error) -> Self {
Self::Serialization {
message: format!("JSON serialization failed: {}", error),
source: Box::new(error),
}
}
}
impl From<toml::de::Error> for CargoCryptError {
fn from(error: toml::de::Error) -> Self {
Self::Serialization {
message: format!("TOML parsing failed: {}", error),
source: Box::new(error),
}
}
}
impl From<reqwest::Error> for CargoCryptError {
fn from(error: reqwest::Error) -> Self {
Self::Network {
message: format!("HTTP request failed: {}", error),
source: Box::new(error),
}
}
}
impl From<git2::Error> for CargoCryptError {
fn from(error: git2::Error) -> Self {
Self::Git {
message: format!("Git operation failed: {}", error.message()),
source: Some(error),
}
}
}
impl From<crate::git::GitError> for CargoCryptError {
fn from(error: crate::git::GitError) -> Self {
Self::Git {
message: format!("Git integration failed: {}", error),
source: None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ErrorKind {
Config,
Io,
Crypto,
Network,
Auth,
Git,
Project,
KeyManagement,
Serialization,
}
impl CargoCryptError {
pub fn kind(&self) -> ErrorKind {
match self {
CargoCryptError::Config { .. } => ErrorKind::Config,
CargoCryptError::Io { .. } => ErrorKind::Io,
CargoCryptError::Crypto { .. } => ErrorKind::Crypto,
CargoCryptError::Network { .. } => ErrorKind::Network,
CargoCryptError::Auth { .. } => ErrorKind::Auth,
CargoCryptError::Git { .. } => ErrorKind::Git,
CargoCryptError::Project { .. } => ErrorKind::Project,
CargoCryptError::KeyManagement { .. } => ErrorKind::KeyManagement,
CargoCryptError::Serialization { .. } => ErrorKind::Serialization,
CargoCryptError::Validation { .. } => ErrorKind::Config,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_constructors() {
let err = CargoCryptError::project_not_found();
assert!(matches!(err.kind(), ErrorKind::Project));
assert!(err.suggestion().is_some());
let err = CargoCryptError::invalid_password();
assert!(matches!(err.kind(), ErrorKind::Auth));
assert!(err.is_recoverable());
}
#[test]
fn test_crypto_error_kinds() {
let err = CargoCryptError::decryption_failed("test");
assert_eq!(err.crypto_kind(), Some(&CryptoErrorKind::Decryption));
let err = CargoCryptError::encryption_failed("test");
assert_eq!(err.crypto_kind(), Some(&CryptoErrorKind::Encryption));
}
#[test]
fn test_error_conversions() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "test");
let crypto_err: CargoCryptError = io_err.into();
assert!(matches!(crypto_err.kind(), ErrorKind::Io));
}
#[test]
fn test_recoverable_errors() {
let auth_err = CargoCryptError::invalid_password();
assert!(auth_err.is_recoverable());
let config_err = CargoCryptError::config_not_found();
assert!(!config_err.is_recoverable());
}
}