use std::time::Duration;
use thiserror::Error;
pub type Result<T> = std::result::Result<T, RateLimitError>;
#[derive(Debug, Error)]
pub enum RateLimitError {
#[error("Storage error: {0}")]
Storage(#[from] StorageError),
#[error("Configuration error: {0}")]
Config(#[from] ConfigError),
#[error("Key extraction failed: {0}")]
KeyExtraction(String),
#[error("Connection error: {0}")]
Connection(#[from] ConnectionError),
#[error("Internal error: {0}")]
Internal(String),
#[error("Rate limit exceeded, retry after {retry_after:?}")]
RateLimitExceeded {
retry_after: Option<Duration>,
remaining: u64,
limit: u64,
},
}
#[derive(Debug, Error)]
pub enum StorageError {
#[error("{message}")]
OperationFailed {
message: String,
retryable: bool,
},
#[error("Key not found: {0}")]
KeyNotFound(String),
#[error("Serialization error: {0}")]
Serialization(String),
#[error("Atomic operation failed, state was modified concurrently")]
AtomicConflict,
#[error("Connection pool exhausted")]
PoolExhausted,
}
impl StorageError {
pub fn operation_failed(message: impl Into<String>, retryable: bool) -> Self {
Self::OperationFailed {
message: message.into(),
retryable,
}
}
pub fn is_retryable(&self) -> bool {
match self {
Self::OperationFailed { retryable, .. } => *retryable,
Self::AtomicConflict => true,
Self::PoolExhausted => true,
_ => false,
}
}
}
#[derive(Debug, Error)]
pub enum ConfigError {
#[error("Invalid quota: {0}")]
InvalidQuota(String),
#[error("Invalid algorithm configuration: {0}")]
InvalidAlgorithm(String),
#[error("Invalid storage configuration: {0}")]
InvalidStorage(String),
#[error("Missing required configuration: {0}")]
MissingRequired(String),
}
#[derive(Debug, Error)]
pub enum ConnectionError {
#[error("Failed to connect: {0}")]
ConnectionFailed(String),
#[error("Connection timeout after {0:?}")]
Timeout(Duration),
#[error("Connection closed unexpectedly")]
Closed,
#[error("Authentication failed: {0}")]
AuthFailed(String),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_storage_error_retryable() {
let err = StorageError::operation_failed("test", true);
assert!(err.is_retryable());
let err = StorageError::operation_failed("test", false);
assert!(!err.is_retryable());
let err = StorageError::AtomicConflict;
assert!(err.is_retryable());
let err = StorageError::KeyNotFound("key".into());
assert!(!err.is_retryable());
}
#[test]
fn test_error_display() {
let err = RateLimitError::KeyExtraction("missing header".into());
assert_eq!(err.to_string(), "Key extraction failed: missing header");
let err = RateLimitError::RateLimitExceeded {
retry_after: Some(Duration::from_secs(10)),
remaining: 0,
limit: 100,
};
assert!(err.to_string().contains("retry after"));
}
}