1use crate::WebRequest;
2use anyhow::Error;
3use framework_core::types::RCodeKind;
4use log::error;
5use std::any::Any;
6use std::fmt::{Display, Formatter};
7use std::panic::Location;
8
9#[derive(Debug, PartialEq, Eq)]
10pub enum WebErrorKind {
11 Message,
12 Parameter,
13 ReturnConversion,
14 NotFound,
15 Unauthorized,
16 Forbidden,
17 Internal,
18 Panic,
19}
20
21#[derive(Debug)]
22pub struct WebError {
23 kind: WebErrorKind,
24 message: String,
25 source: Option<Error>,
26 location: Option<String>,
27}
28
29impl WebError {
30 #[track_caller]
31 pub fn message(message: impl Into<String>) -> Self {
32 Self::new(WebErrorKind::Message, message)
33 }
34
35 #[track_caller]
36 pub fn parameter(
37 message: impl Into<String>,
38 source: impl Display + Send + Sync + 'static,
39 ) -> Self {
40 Self::with_source(WebErrorKind::Parameter, message, source)
41 }
42
43 #[track_caller]
44 pub fn return_conversion(
45 message: impl Into<String>,
46 source: impl Display + Send + Sync + 'static,
47 ) -> Self {
48 Self::with_source(WebErrorKind::ReturnConversion, message, source)
49 }
50
51 #[track_caller]
52 pub fn not_found(message: impl Into<String>) -> Self {
53 Self::new(WebErrorKind::NotFound, message)
54 }
55
56 #[track_caller]
57 pub fn unauthorized(message: impl Into<String>) -> Self {
58 Self::new(WebErrorKind::Unauthorized, message)
59 }
60
61 #[track_caller]
62 pub fn forbidden(message: impl Into<String>) -> Self {
63 Self::new(WebErrorKind::Forbidden, message)
64 }
65
66 #[track_caller]
67 pub fn internal(
68 message: impl Into<String>,
69 source: impl Display + Send + Sync + 'static,
70 ) -> Self {
71 Self::with_source(WebErrorKind::Internal, message, source)
72 }
73
74 #[track_caller]
75 pub fn panic(message: impl Into<String>) -> Self {
76 Self::new(WebErrorKind::Panic, message)
77 }
78
79 pub fn kind(&self) -> &WebErrorKind {
80 &self.kind
81 }
82
83 pub fn status(&self) -> u16 {
84 match self.kind {
85 WebErrorKind::Message => RCodeKind::Internal.code() as u16,
86 WebErrorKind::Parameter => RCodeKind::Parameter.code() as u16,
87 WebErrorKind::Unauthorized => RCodeKind::Unauthorized.code() as u16,
88 WebErrorKind::Forbidden => RCodeKind::Forbidden.code() as u16,
89 WebErrorKind::NotFound => 404,
90 WebErrorKind::ReturnConversion | WebErrorKind::Internal | WebErrorKind::Panic => {
91 RCodeKind::Internal.code() as u16
92 }
93 }
94 }
95
96 pub fn public_message(&self) -> &str {
97 match self.kind {
98 WebErrorKind::Internal | WebErrorKind::ReturnConversion | WebErrorKind::Panic => {
99 "服务器内部错误"
100 }
101 _ => &self.message,
102 }
103 }
104
105 pub fn log(error: &Self, request: Option<&WebRequest>) {
106 let trace_id = request.map_or("未知", |item| item.trace_id.as_str());
107 let method = request.map_or_else(|| "未知".to_string(), |item| item.method.to_string());
108 let path = request.map_or("未知", |item| item.path.as_str());
109 Self::log_request(error, trace_id, &method, path);
110 }
111
112 pub fn log_request(error: &Self, trace_id: &str, method: &str, path: &str) {
113 let status = error.status();
114 let kind = error.label();
115 let location = error.location.as_deref().unwrap_or("未知");
116 let chain = std::iter::successors(Some(error as &dyn std::error::Error), |error| {
117 error.source()
118 })
119 .map(ToString::to_string)
120 .collect::<Vec<_>>()
121 .join(" -> ");
122 let backtrace = error
123 .source_backtrace()
124 .map(ToString::to_string)
125 .unwrap_or_else(|| "未知".to_string());
126
127 error!(
128 "Web 请求异常 category={kind} status={status} \
129 trace_id={trace_id} method={method} path={path} \
130 source={location} error_chain={chain} backtrace={backtrace}"
131 )
132 }
133
134 #[track_caller]
135 fn new(kind: WebErrorKind, message: impl Into<String>) -> Self {
136 Self {
137 kind,
138 message: message.into(),
139 source: None,
140 location: Some(Self::location(Location::caller())),
141 }
142 }
143
144 #[track_caller]
145 pub fn with_source(
146 kind: WebErrorKind,
147 message: impl Into<String>,
148 source: impl Display + Send + Sync + 'static,
149 ) -> Self {
150 let source_message = source.to_string();
151 let source = Box::new(source) as Box<dyn Any + Send + Sync>;
152 let source = match source.downcast::<Error>() {
153 Ok(source) => *source,
154 Err(source) => match source.downcast::<WebError>() {
155 Ok(source) => Error::new(*source),
156 Err(_) => Error::msg(source_message),
157 },
158 };
159 #[cfg(debug_assertions)]
160 let location = Self::location_from_error(&source);
161 #[cfg(not(debug_assertions))]
162 let location = None;
163
164 Self {
165 kind,
166 message: message.into(),
167 source: Some(source),
168 location,
169 }
170 }
171
172 fn location(location: &Location<'_>) -> String {
173 format!(
174 "{}:{}:{}",
175 location.file(),
176 location.line(),
177 location.column()
178 )
179 }
180
181 fn source_backtrace(&self) -> Option<&std::backtrace::Backtrace> {
182 self.source.as_ref().map(Error::backtrace)
183 }
184
185 fn location_from_error(error: &Error) -> Option<String> {
186 error
187 .chain()
188 .find_map(|error| error.downcast_ref::<Self>())
189 .and_then(|error| error.location.clone())
190 .or_else(|| Self::backtrace_location(error.backtrace()))
191 }
192
193 fn backtrace_location(backtrace: &std::backtrace::Backtrace) -> Option<String> {
194 backtrace
195 .to_string()
196 .lines()
197 .map(str::trim)
198 .filter_map(|line| line.strip_prefix("at "))
199 .find(|location| {
200 !location.starts_with("/rustc/")
201 && !location.contains("\\.cargo\\registry\\")
202 && !location.contains("/.cargo/registry/")
203 })
204 .map(ToOwned::to_owned)
205 }
206
207 fn label(&self) -> &'static str {
208 match self.kind {
209 WebErrorKind::Message => "消息错误",
210 WebErrorKind::Parameter => "参数转换",
211 WebErrorKind::ReturnConversion => "返回值转换",
212 WebErrorKind::NotFound => "路由不存在",
213 WebErrorKind::Unauthorized => "未授权",
214 WebErrorKind::Forbidden => "无权限",
215 WebErrorKind::Internal => "内部错误",
216 WebErrorKind::Panic => "程序崩溃",
217 }
218 }
219}
220
221pub trait WebErrorExt<T> {
222 fn message(self) -> anyhow::Result<T>;
223 fn parameter(self, message: impl Into<String>) -> anyhow::Result<T>;
224 fn internal(self, message: impl Into<String>) -> anyhow::Result<T>;
225}
226
227impl<T, E> WebErrorExt<T> for Result<T, E>
228where
229 E: Display + Send + Sync + 'static,
230{
231 #[track_caller]
232 fn message(self) -> anyhow::Result<T> {
233 self.map_err(|error| {
234 WebError::with_source(WebErrorKind::Message, error.to_string(), error).into()
235 })
236 }
237
238 #[track_caller]
239 fn parameter(self, message: impl Into<String>) -> anyhow::Result<T> {
240 self.map_err(|error| WebError::parameter(message, error).into())
241 }
242
243 #[track_caller]
244 fn internal(self, message: impl Into<String>) -> anyhow::Result<T> {
245 self.map_err(|error| WebError::internal(message, error).into())
246 }
247}
248
249impl Display for WebError {
250 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
251 formatter.write_str(&self.message)
252 }
253}
254
255impl std::error::Error for WebError {
256 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
257 self.source.as_ref().map(|error| error.as_ref())
258 }
259}