use thiserror::Error;
#[derive(Error, Debug)]
pub enum FlowGuardError {
#[error("配置错误: {0}")]
ConfigError(String),
#[error("存储错误: {0}")]
StorageError(#[from] StorageError),
#[error("限流错误: {0}")]
LimitError(String),
#[error("封禁错误: {0}")]
BanError(String),
#[error("熔断器错误: {0}")]
CircuitBreakerError(String),
#[error("降级错误: {0}")]
FallbackError(String),
#[error("审计日志错误: {0}")]
AuditLogError(String),
#[error("IO错误: {0}")]
IoError(#[from] std::io::Error),
#[error("序列化错误: {0}")]
SerdeError(#[from] serde_json::Error),
#[error("YAML解析错误: {0}")]
YamlError(#[from] serde_yaml::Error),
#[error("速率限制超出: {0}")]
RateLimitExceeded(String),
#[error("配额超出: {0}")]
QuotaExceeded(String),
#[error("并发限制超出: {0}")]
ConcurrencyLimitExceeded(String),
#[error("验证错误: {0}")]
ValidationError(String),
#[error("锁获取错误: {0}")]
LockError(String),
#[error("未知错误: {0}")]
Other(String),
}
#[derive(Error, Debug, Clone)]
pub enum StorageError {
#[error("连接错误: {0}")]
ConnectionError(String),
#[error("查询错误: {0}")]
QueryError(String),
#[error("超时错误: {0}")]
TimeoutError(String),
#[error("未找到: {0}")]
NotFound(String),
#[error("认证错误: {0}")]
AuthenticationError(String),
#[error("权限错误: {0}")]
PermissionError(String),
#[error("无效配置: {0}")]
InvalidConfig(String),
#[error("速率限制: {0}")]
RateLimitError(String),
#[error("验证错误: {0}")]
ValidationError(String),
}
impl StorageError {
pub fn is_transient(&self) -> bool {
matches!(
self,
StorageError::TimeoutError(_)
| StorageError::ConnectionError(_)
| StorageError::RateLimitError(_)
)
}
pub fn is_permanent(&self) -> bool {
matches!(
self,
StorageError::AuthenticationError(_)
| StorageError::PermissionError(_)
| StorageError::InvalidConfig(_)
)
}
}
#[cfg(feature = "postgres")]
impl From<sqlx::Error> for StorageError {
fn from(err: sqlx::Error) -> Self {
match err {
sqlx::Error::Database(db_err) => StorageError::QueryError(db_err.to_string()),
sqlx::Error::PoolTimedOut => StorageError::TimeoutError("连接池超时".to_string()),
sqlx::Error::PoolClosed => StorageError::ConnectionError("连接池已关闭".to_string()),
sqlx::Error::RowNotFound => StorageError::NotFound("记录未找到".to_string()),
_ => StorageError::QueryError(err.to_string()),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum CircuitState {
Closed,
Open,
HalfOpen,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct CircuitBreakerStats {
pub state: CircuitState,
pub failure_count: u64,
pub success_count: u64,
pub total_calls: u64,
pub last_failure_time: Option<chrono::DateTime<chrono::Utc>>,
pub last_state_change: Option<chrono::DateTime<chrono::Utc>>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Decision {
Allowed(Option<String>),
Rejected(String),
Banned(BanInfo),
}
#[derive(Debug, Clone, PartialEq)]
pub struct BanInfo {
pub reason: String,
pub banned_until: chrono::DateTime<chrono::Utc>,
pub ban_times: u32,
}
#[derive(Debug, Clone)]
pub struct ConsumeResult {
pub allowed: bool,
pub remaining: u64,
pub alert_triggered: bool,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_message() {
let error = FlowGuardError::ConfigError("测试错误".to_string());
assert_eq!(error.to_string(), "配置错误: 测试错误");
}
#[test]
fn test_storage_error_conversion() {
let storage_error = StorageError::NotFound("test_key".to_string());
let flowguard_error: FlowGuardError = storage_error.into();
assert!(matches!(flowguard_error, FlowGuardError::StorageError(_)));
}
#[test]
fn test_io_error_conversion() {
let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
let flowguard_error: FlowGuardError = io_error.into();
assert!(matches!(flowguard_error, FlowGuardError::IoError(_)));
}
#[test]
fn test_decision_allowed() {
let decision = Decision::Allowed(None);
assert_eq!(decision, Decision::Allowed(None));
assert!(matches!(decision, Decision::Allowed(_)));
}
#[test]
fn test_decision_rejected() {
let decision = Decision::Rejected("rate limit exceeded".to_string());
assert!(matches!(decision, Decision::Rejected(_)));
}
#[test]
fn test_decision_banned() {
let info = BanInfo {
reason: "spam".to_string(),
banned_until: chrono::Utc::now(),
ban_times: 3,
};
let decision = Decision::Banned(info);
assert!(matches!(decision, Decision::Banned(_)));
}
#[test]
fn test_ban_info_equality() {
let info1 = BanInfo {
reason: "test".to_string(),
banned_until: chrono::Utc::now(),
ban_times: 1,
};
let info2 = BanInfo {
reason: "test".to_string(),
banned_until: info1.banned_until,
ban_times: 1,
};
assert_eq!(info1, info2);
}
}