next_web_common/error/
api_error.rs

1use std::error::Error;
2
3use axum_core::response::{IntoResponse, Response};
4
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub enum ApiError {
7    // 服务器错误
8    ServerError {
9        status: u16,
10        message: String,
11    },
12    // 客户端请求错误
13    BadRequestError {
14        field: Option<String>,
15        message: String,
16    },
17    // 资源未找到
18    NotFoundError {
19        resource: String,
20    },
21    // 业务错误
22    BusinessError {
23        status: u16,
24        message: String,
25    },
26
27    // 未授权访问
28    UnauthorizedError {
29        message: String,
30    },
31    // 禁止访问
32    ForbiddenError {
33        message: String,
34    },
35    // 操作超时
36    TimeoutError {
37        operation: String,
38    },
39    // 数据验证失败
40    ValidationError {
41        details: Vec<String>,
42    },
43    // 外部服务错误
44    ExternalServiceError {
45        service: String,
46        message: String,
47    },
48
49    Created(String),
50}
51
52impl std::fmt::Display for ApiError {
53    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54        match self {
55            ApiError::ServerError { status, message } => {
56                write!(f, "Server Error [Code {}]: {}", status, message)
57            }
58            ApiError::BadRequestError { field, message } => {
59                if let Some(field) = field {
60                    write!(f, "Bad Request Error [Field '{}']: {}", field, message)
61                } else {
62                    write!(f, "Bad Request Error: {}", message)
63                }
64            }
65            ApiError::NotFoundError { resource } => write!(f, "Resource '{}' not found", resource),
66            ApiError::BusinessError { status, message } => {
67                write!(f, "Business Error [Code {}]: {}", status, message)
68            }
69            ApiError::UnauthorizedError { message } => write!(f, "Unauthorized: {}", message),
70            ApiError::ForbiddenError { message } => write!(f, "Forbidden: {}", message),
71            ApiError::TimeoutError { operation } => {
72                write!(f, "Operation '{}' timed out", operation)
73            }
74
75            ApiError::ValidationError { details } => {
76                write!(f, "Validation failed: {}", details.join(", "))
77            }
78            ApiError::ExternalServiceError { service, message } => {
79                write!(f, "External Service Error [{}]: {}", service, message)
80            }
81
82            ApiError::Created(message) => {
83                write!(f, "Created Error: {}", message)
84            }
85        }
86    }
87}
88
89impl std::error::Error for ApiError {}
90
91impl ApiError {
92    pub fn get_msg(&self) -> String {
93        match self {
94            ApiError::ServerError { message, .. } => message.into(),
95            ApiError::BadRequestError { message, .. } => message.into(),
96            ApiError::NotFoundError { resource } => resource.into(),
97            ApiError::BusinessError { message, .. } => message.into(),
98            ApiError::UnauthorizedError { message } => message.into(),
99            ApiError::ForbiddenError { message } => message.into(),
100            ApiError::TimeoutError { operation } => operation.into(),
101            ApiError::ValidationError { details } => details.join(","),
102            ApiError::ExternalServiceError { message, .. } => message.into(),
103            ApiError::Created(message) => message.into(),
104        }
105    }
106
107    pub fn with_context<T>(self, f: T) -> Self
108    where
109        T: FnOnce() -> String,
110    {
111        match self {
112            ApiError::ServerError { status, message } => ApiError::ServerError {
113                status,
114                message: format!("{}; [with_context] {}", message, f()),
115            },
116            ApiError::BadRequestError { field, message } => ApiError::BadRequestError {
117                field,
118                message: format!("{}; [with_context] {}", message, f()),
119            },
120            ApiError::NotFoundError { resource } => ApiError::NotFoundError {
121                resource: format!("{}; [with_context] {}", resource, f()),
122            },
123            ApiError::BusinessError { status, message } => ApiError::BusinessError {
124                status,
125                message: format!("{}; [with_context] {}", message, f()),
126            },
127            ApiError::UnauthorizedError { message } => ApiError::UnauthorizedError {
128                message: format!("{}; [with_context] {}", message, f()),
129            },
130            ApiError::ForbiddenError { message } => ApiError::ForbiddenError {
131                message: format!("{}; [with_context] {}", message, f()),
132            },
133            ApiError::TimeoutError { operation } => ApiError::TimeoutError {
134                operation: format!("{}; [with_context] {}", operation, f()),
135            },
136            ApiError::ValidationError { mut details } => {
137                details.push(format!("[with_context] {}", f()));
138                ApiError::ValidationError { details }
139            }
140            ApiError::ExternalServiceError { service, message } => ApiError::ExternalServiceError {
141                service,
142                message: format!("{}; [with_context] {}", message, f()),
143            },
144            ApiError::Created(message) => {
145                ApiError::Created(format!("{}; [with_context] {}", message, f()))
146            }
147        }
148    }
149}
150
151impl From<Box<dyn Error>> for ApiError {
152    fn from(err: Box<dyn Error>) -> Self {
153        ApiError::Created(err.to_string())
154    }
155}
156
157impl IntoResponse for ApiError {
158    fn into_response(self) -> Response {
159        let (http_code, status, error_message) = match self {
160            ApiError::ServerError { status, message } => (500, status, message),
161            ApiError::BadRequestError { field, message } => {
162                if let Some(field) = field {
163                    (
164                        400,
165                        400,
166                        format!("Bad Request Error [Field '{}']: {}", field, message),
167                    )
168                } else {
169                    (400, 400, format!("Bad Request Error: {}", message))
170                }
171            }
172            ApiError::NotFoundError { resource } => {
173                (404, 404, format!("Resource '{}' not found", resource))
174            }
175            ApiError::BusinessError { status, message } => (
176                400,
177                status,
178                format!("Business Error [Code {}]: {}", status, message),
179            ),
180            ApiError::UnauthorizedError { message } => {
181                (401, 401, format!("Unauthorized: {}", message))
182            }
183            ApiError::ForbiddenError { message } => (403, 403, format!("Forbidden: {}", message)),
184            ApiError::TimeoutError { operation } => {
185                (408, 408, format!("Operation '{}' timed out", operation))
186            }
187            ApiError::ValidationError { details } => (
188                422,
189                422,
190                format!("Validation failed: {}", details.join(", ")),
191            ),
192            ApiError::ExternalServiceError { service, message } => (
193                503,
194                503,
195                format!("External Service Error [{}]: {}", service, message),
196            ),
197            ApiError::Created(message) => (500, 500, message),
198        };
199
200        Response::builder()
201            .status(http_code)
202            .header("Content-Type", "application/json")
203            .body(
204                format!("{{\"status\": {status},\"message\": {error_message},\"data\": null}}")
205                    .into(),
206            )
207            .unwrap()
208    }
209}