use crate::error::ErrorCode;
use serde::{Deserialize, Serialize};
use std::fmt::{Debug, Display};
use std::time::Duration;
pub trait ErrorTrait: std::error::Error + Send + Sync + 'static {
fn severity(&self) -> ErrorSeverity;
fn is_retryable(&self) -> bool;
fn retry_delay(&self, attempt: u32) -> Option<Duration>;
fn user_message(&self) -> Option<&str>;
fn context(&self) -> &ErrorContext;
fn error_type(&self) -> ErrorType;
fn error_code(&self) -> Option<&str> {
None
}
fn is_user_error(&self) -> bool {
matches!(
self.error_type(),
ErrorType::Validation | ErrorType::Authentication
)
}
fn is_system_error(&self) -> bool {
matches!(
self.error_type(),
ErrorType::Network | ErrorType::ServiceUnavailable | ErrorType::Internal
)
}
fn is_network_error(&self) -> bool {
matches!(self.error_type(), ErrorType::Network)
}
fn is_auth_error(&self) -> bool {
matches!(self.error_type(), ErrorType::Authentication)
}
fn is_validation_error(&self) -> bool {
matches!(self.error_type(), ErrorType::Validation)
}
fn is_timeout_error(&self) -> bool {
matches!(self.error_type(), ErrorType::Timeout)
}
fn is_rate_limited(&self) -> bool {
matches!(self.error_type(), ErrorType::RateLimit)
}
fn is_config_error(&self) -> bool {
matches!(self.error_type(), ErrorType::Configuration)
}
fn is_serialization_error(&self) -> bool {
matches!(self.error_type(), ErrorType::Serialization)
}
fn is_business_error(&self) -> bool {
matches!(self.error_type(), ErrorType::Business)
}
fn is_api_error(&self) -> bool {
matches!(self.error_type(), ErrorType::Api)
}
fn is_service_unavailable_error(&self) -> bool {
matches!(self.error_type(), ErrorType::ServiceUnavailable)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum ErrorSeverity {
Info,
Warning,
Error,
Critical,
}
impl ErrorSeverity {
pub fn as_level(&self) -> u8 {
match self {
Self::Info => 0,
Self::Warning => 1,
Self::Error => 2,
Self::Critical => 3,
}
}
pub fn description(&self) -> &'static str {
match self {
Self::Info => "信息",
Self::Warning => "警告",
Self::Error => "错误",
Self::Critical => "严重错误",
}
}
pub fn from_level(level: u8) -> Self {
match level {
0 => Self::Info,
1 => Self::Warning,
2 => Self::Error,
3 => Self::Critical,
_ => Self::Error,
}
}
pub fn requires_immediate_action(&self) -> bool {
matches!(self, Self::Critical | Self::Error)
}
pub fn requires_user_intervention(&self) -> bool {
matches!(self, Self::Error | Self::Critical)
}
pub fn is_auto_recoverable(&self) -> bool {
matches!(self, Self::Info | Self::Warning)
}
}
impl Display for ErrorSeverity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.description())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorType {
Network,
Authentication,
Validation,
Api,
Configuration,
Serialization,
Business,
Timeout,
RateLimit,
ServiceUnavailable,
Internal,
}
impl Display for ErrorType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Network => write!(f, "Network"),
Self::Authentication => write!(f, "Authentication"),
Self::Validation => write!(f, "Validation"),
Self::Api => write!(f, "Api"),
Self::Configuration => write!(f, "Configuration"),
Self::Serialization => write!(f, "Serialization"),
Self::Business => write!(f, "Business"),
Self::Timeout => write!(f, "Timeout"),
Self::RateLimit => write!(f, "RateLimit"),
Self::ServiceUnavailable => write!(f, "ServiceUnavailable"),
Self::Internal => write!(f, "Internal"),
}
}
}
impl From<ErrorCode> for ErrorType {
fn from(code: ErrorCode) -> Self {
match code {
ErrorCode::NetworkTimeout
| ErrorCode::NetworkConnectionFailed
| ErrorCode::DnsResolutionFailed
| ErrorCode::SslCertificateError
| ErrorCode::ConnectionRefused => ErrorType::Network,
ErrorCode::Unauthorized
| ErrorCode::AccessTokenInvalid
| ErrorCode::AppAccessTokenInvalid
| ErrorCode::TenantAccessTokenInvalid
| ErrorCode::AuthenticationFailed
| ErrorCode::TokenExpired
| ErrorCode::PermissionDenied
| ErrorCode::AccessDenied => ErrorType::Authentication,
ErrorCode::BadRequest
| ErrorCode::ValidationError
| ErrorCode::MissingRequiredParameter
| ErrorCode::InvalidParameterFormat
| ErrorCode::ParameterOutOfRange => ErrorType::Validation,
ErrorCode::NotFound
| ErrorCode::MethodNotAllowed
| ErrorCode::Conflict
| ErrorCode::Forbidden => ErrorType::Api,
ErrorCode::TooManyRequests | ErrorCode::RateLimitExceeded => ErrorType::RateLimit,
ErrorCode::GatewayTimeout => ErrorType::Timeout,
ErrorCode::ServiceUnavailable
| ErrorCode::BadGateway
| ErrorCode::CacheServiceUnavailable => ErrorType::ServiceUnavailable,
ErrorCode::ConfigurationError => ErrorType::Configuration,
ErrorCode::Success => ErrorType::Api,
_ => ErrorType::Internal,
}
}
}
pub trait ErrorContextTrait {
fn user_message(&self) -> Option<&str>;
fn request_id(&self) -> Option<&str>;
fn operation(&self) -> Option<&str>;
fn component(&self) -> Option<&str>;
fn timestamp(&self) -> Option<chrono::DateTime<chrono::Utc>>;
fn get_context(&self, key: &str) -> Option<&str>;
fn all_context(&self) -> &std::collections::HashMap<String, String>;
fn has_context(&self, key: &str) -> bool {
self.get_context(key).is_some()
}
fn is_empty(&self) -> bool {
self.user_message().is_none()
&& self.request_id().is_none()
&& self.operation().is_none()
&& self.component().is_none()
&& self.all_context().is_empty()
}
}
use super::context::ErrorContext;
impl ErrorContextTrait for ErrorContext {
fn user_message(&self) -> Option<&str> {
ErrorContext::user_message(self)
}
fn request_id(&self) -> Option<&str> {
ErrorContext::request_id(self)
}
fn operation(&self) -> Option<&str> {
ErrorContext::operation(self)
}
fn component(&self) -> Option<&str> {
ErrorContext::component(self)
}
fn timestamp(&self) -> Option<chrono::DateTime<chrono::Utc>> {
ErrorContext::timestamp(self).map(|st| {
let duration = st.duration_since(std::time::UNIX_EPOCH).unwrap_or_default();
chrono::DateTime::from_timestamp(duration.as_secs() as i64, 0)
.unwrap_or_else(chrono::Utc::now)
})
}
fn get_context(&self, key: &str) -> Option<&str> {
ErrorContext::get_context(self, key)
}
fn all_context(&self) -> &std::collections::HashMap<String, String> {
ErrorContext::all_context(self)
}
}
pub trait ErrorBuilderTrait: Sized {
fn message(self, message: impl Into<String>) -> Self;
fn request_id(self, request_id: impl Into<String>) -> Self;
fn operation(self, operation: impl Into<String>) -> Self;
fn component(self, component: impl Into<String>) -> Self;
fn context(self, key: impl Into<String>, value: impl Into<String>) -> Self;
fn build(self) -> super::CoreError;
}
pub trait ErrorFormatTrait {
fn format_user_message(&self) -> String;
fn format_developer_message(&self) -> String;
fn format_log_message(&self) -> String;
fn format_json(&self) -> serde_json::Result<serde_json::Value>;
}
pub trait FullErrorTrait:
ErrorTrait + ErrorContextTrait + ErrorFormatTrait + Debug + Display + Clone + Send + Sync + 'static
{
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_severity() {
assert!(ErrorSeverity::Info < ErrorSeverity::Warning);
assert!(ErrorSeverity::Warning < ErrorSeverity::Error);
assert!(ErrorSeverity::Error < ErrorSeverity::Critical);
assert!(ErrorSeverity::Critical.requires_immediate_action());
assert!(ErrorSeverity::Info.is_auto_recoverable());
}
#[test]
fn test_error_type_display() {
assert_eq!(ErrorType::Network.to_string(), "Network");
assert_eq!(ErrorType::Authentication.to_string(), "Authentication");
assert_eq!(ErrorType::Validation.to_string(), "Validation");
}
#[test]
fn test_error_severity_display() {
assert_eq!(ErrorSeverity::Info.to_string(), "信息");
assert_eq!(ErrorSeverity::Warning.to_string(), "警告");
assert_eq!(ErrorSeverity::Error.to_string(), "错误");
assert_eq!(ErrorSeverity::Critical.to_string(), "严重错误");
}
}