Skip to main content

framework_web/
error.rs

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}