Skip to main content

tibba_error/
lib.rs

1// Copyright 2026 Tree xie.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use axum::Json;
16use axum::http::{HeaderValue, StatusCode, header};
17use axum::response::{IntoResponse, Response};
18use serde::{Deserialize, Serialize};
19use std::ops::{Deref, DerefMut};
20
21/// 不常用的可选字段,装箱存放以控制 `Error` 的内存占用。
22#[derive(Debug, Default, Serialize, Deserialize, Clone)]
23pub struct ErrorData {
24    /// 错误子分类,用于在同一 category 下进一步区分错误来源。
25    pub sub_category: Option<String>,
26    /// 业务错误码,供前端按码处理特定错误。
27    pub code: Option<String>,
28    /// 是否为需要告警的异常级错误。
29    pub exception: Option<bool>,
30    /// 附加信息列表,可携带多条上下文说明。
31    pub extra: Option<Vec<String>>,
32}
33
34// 仅用于将 Error 序列化为扁平 JSON 对象的内部视图。
35#[derive(Serialize)]
36struct ErrorSerialize<'a> {
37    category: &'a str,
38    message: &'a str,
39    #[serde(flatten)]
40    data: &'a ErrorData,
41}
42
43// 仅用于从扁平 JSON 对象反序列化 Error 的内部视图。
44#[derive(Deserialize)]
45struct ErrorDeserialize {
46    #[serde(default)]
47    category: String,
48    #[serde(default)]
49    message: String,
50    #[serde(flatten)]
51    data: ErrorData,
52}
53
54/// 全局 HTTP 错误类型,贯穿整个应用。
55///
56/// `category` 与 `message` 始终存在,直接置于结构体上。
57/// 可选字段通过 `Box<ErrorData>` 装箱,将 `Err` 变体保持在
58/// `result_large_err` 的 128 字节限制以内。
59#[derive(Debug, Clone, Default)]
60pub struct Error {
61    /// HTTP 状态码,0 表示未设置,`IntoResponse` 时回退为 500。
62    pub status: u16,
63    /// 错误来源模块或分类,如 "cache"、"db"。
64    pub category: String,
65    /// 面向用户或日志的错误描述信息。
66    pub message: String,
67    data: Box<ErrorData>,
68}
69
70impl std::fmt::Display for Error {
71    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72        write!(f, "{}", self.message)
73    }
74}
75
76impl std::error::Error for Error {}
77
78/// 序列化为扁平 JSON 对象:`{ category, message, sub_category?, … }`。
79impl Serialize for Error {
80    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
81    where
82        S: serde::Serializer,
83    {
84        ErrorSerialize {
85            category: &self.category,
86            message: &self.message,
87            data: &self.data,
88        }
89        .serialize(serializer)
90    }
91}
92
93impl<'de> Deserialize<'de> for Error {
94    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
95    where
96        D: serde::Deserializer<'de>,
97    {
98        let d = ErrorDeserialize::deserialize(deserializer)?;
99        Ok(Self {
100            status: 0,
101            category: d.category,
102            message: d.message,
103            data: Box::new(d.data),
104        })
105    }
106}
107
108/// 通过 `Deref`/`DerefMut` 将 `ErrorData` 的可选字段(`sub_category`、
109/// `code`、`exception`、`extra`)直接暴露在 `Error` 上,无需手动访问 `.data`。
110impl Deref for Error {
111    type Target = ErrorData;
112    fn deref(&self) -> &Self::Target {
113        &self.data
114    }
115}
116
117impl DerefMut for Error {
118    fn deref_mut(&mut self) -> &mut Self::Target {
119        &mut self.data
120    }
121}
122
123impl Error {
124    /// 以错误信息创建新的 `Error` 实例,其余字段均为默认值。
125    #[must_use]
126    pub fn new(message: impl ToString) -> Self {
127        Self {
128            message: message.to_string(),
129            ..Default::default()
130        }
131    }
132
133    /// 设置错误分类(模块来源),支持链式调用。
134    #[must_use]
135    pub fn with_category(mut self, category: impl ToString) -> Self {
136        self.category = category.to_string();
137        self
138    }
139
140    /// 设置错误子分类,支持链式调用。
141    #[must_use]
142    pub fn with_sub_category(mut self, sub_category: impl ToString) -> Self {
143        self.sub_category = Some(sub_category.to_string());
144        self
145    }
146
147    /// 设置业务错误码,支持链式调用。
148    #[must_use]
149    pub fn with_code(mut self, code: impl ToString) -> Self {
150        self.code = Some(code.to_string());
151        self
152    }
153
154    /// 设置 HTTP 状态码,支持链式调用。
155    #[must_use]
156    pub fn with_status(mut self, status: u16) -> Self {
157        self.status = status;
158        self
159    }
160
161    /// 标记是否为需要告警的异常级错误,支持链式调用。
162    #[must_use]
163    pub fn with_exception(mut self, exception: bool) -> Self {
164        self.exception = Some(exception);
165        self
166    }
167
168    /// 追加一条附加上下文信息,支持链式调用。
169    #[must_use]
170    pub fn add_extra(mut self, value: impl ToString) -> Self {
171        self.extra
172            .get_or_insert_with(Vec::new)
173            .push(value.to_string());
174        self
175    }
176}
177
178/// 将 `Error` 转换为带 JSON 响应体和 `no-cache` 头的 HTTP 响应。
179impl IntoResponse for Error {
180    fn into_response(self) -> Response {
181        let status = StatusCode::from_u16(self.status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
182        // 错误响应禁止缓存
183        let mut res = (status, Json(&self)).into_response();
184        res.extensions_mut().insert(self);
185        res.headers_mut()
186            .insert(header::CACHE_CONTROL, HeaderValue::from_static("no-cache"));
187        res
188    }
189}