use std::fmt;
use thiserror::Error;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorCode {
KeyNotFound,
KeyExpired,
InvalidKeyState,
CryptoFailure,
StorageFailure,
InsufficientEntropy,
RotationFailure,
SerializationFailure,
IoFailure,
AuthenticationFailure,
ConfigurationError,
}
impl fmt::Display for ErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::KeyNotFound => write!(f, "KEY_NOT_FOUND"),
Self::KeyExpired => write!(f, "KEY_EXPIRED"),
Self::InvalidKeyState => write!(f, "INVALID_KEY_STATE"),
Self::CryptoFailure => write!(f, "CRYPTO_FAILURE"),
Self::StorageFailure => write!(f, "STORAGE_FAILURE"),
Self::InsufficientEntropy => write!(f, "INSUFFICIENT_ENTROPY"),
Self::RotationFailure => write!(f, "ROTATION_FAILURE"),
Self::SerializationFailure => write!(f, "SERIALIZATION_FAILURE"),
Self::IoFailure => write!(f, "IO_FAILURE"),
Self::AuthenticationFailure => write!(f, "AUTHENTICATION_FAILURE"),
Self::ConfigurationError => write!(f, "CONFIGURATION_ERROR"),
}
}
}
#[derive(Debug, Clone)]
pub struct ErrorContext {
pub operation: String,
pub key_id: Option<String>,
pub details: Option<String>,
}
impl ErrorContext {
pub fn new<S: Into<String>>(operation: S) -> Self {
Self {
operation: operation.into(),
key_id: None,
details: None,
}
}
pub fn with_key_id<S: Into<String>>(mut self, key_id: S) -> Self {
self.key_id = Some(key_id.into());
self
}
pub fn with_details<S: Into<String>>(mut self, details: S) -> Self {
self.details = Some(details.into());
self
}
}
#[derive(Debug, Error)]
pub enum Error {
#[error("key not found: {key_id} (operation: {operation})")]
KeyNotFound {
key_id: String,
operation: String,
},
#[error("key has expired: {key_id} (expired at: {expired_at:?})")]
KeyExpired {
key_id: String,
expired_at: std::time::SystemTime,
},
#[error("invalid key state: {state} for operation '{operation}' on key {key_id}")]
InvalidKeyState {
key_id: String,
state: String,
operation: String,
},
#[error("cryptographic error during {operation}: {message}")]
CryptoError {
operation: String,
message: String,
key_id: Option<String>,
},
#[error("storage error during {operation}: {message}")]
StorageError {
operation: String,
message: String,
path: Option<String>,
},
#[error("insufficient entropy for operation: {operation}")]
InsufficientEntropy {
operation: String,
},
#[error("rotation failed for key {key_id}: {reason}")]
RotationFailed {
key_id: String,
reason: String,
},
#[error("serialization error during {operation}: {message}")]
SerializationError {
operation: String,
message: String,
},
#[error("I/O error during {operation}: {source}")]
IoError {
operation: String,
#[source]
source: std::io::Error,
},
#[error("authentication failed: {reason}")]
AuthenticationFailed {
reason: String,
attempts: Option<u32>,
},
#[error("configuration error: {message}")]
ConfigurationError {
message: String,
},
}
impl Error {
pub fn code(&self) -> ErrorCode {
match self {
Self::KeyNotFound { .. } => ErrorCode::KeyNotFound,
Self::KeyExpired { .. } => ErrorCode::KeyExpired,
Self::InvalidKeyState { .. } => ErrorCode::InvalidKeyState,
Self::CryptoError { .. } => ErrorCode::CryptoFailure,
Self::StorageError { .. } => ErrorCode::StorageFailure,
Self::InsufficientEntropy { .. } => ErrorCode::InsufficientEntropy,
Self::RotationFailed { .. } => ErrorCode::RotationFailure,
Self::SerializationError { .. } => ErrorCode::SerializationFailure,
Self::IoError { .. } => ErrorCode::IoFailure,
Self::AuthenticationFailed { .. } => ErrorCode::AuthenticationFailure,
Self::ConfigurationError { .. } => ErrorCode::ConfigurationError,
}
}
pub fn crypto<S: Into<String>>(operation: S, message: S) -> Self {
Self::CryptoError {
operation: operation.into(),
message: message.into(),
key_id: None,
}
}
pub fn crypto_with_key<S: Into<String>>(operation: S, message: S, key_id: S) -> Self {
Self::CryptoError {
operation: operation.into(),
message: message.into(),
key_id: Some(key_id.into()),
}
}
pub fn storage<S: Into<String>>(operation: S, message: S) -> Self {
Self::StorageError {
operation: operation.into(),
message: message.into(),
path: None,
}
}
pub fn storage_with_path<S: Into<String>>(operation: S, message: S, path: S) -> Self {
Self::StorageError {
operation: operation.into(),
message: message.into(),
path: Some(path.into()),
}
}
pub fn is_retryable(&self) -> bool {
matches!(self, Self::StorageError { .. } | Self::IoError { .. })
}
pub fn is_auth_failure(&self) -> bool {
matches!(self, Self::AuthenticationFailed { .. })
}
}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
Self::IoError {
operation: "unknown".to_string(),
source: err,
}
}
}