1use serde::Serialize;
2use thiserror::Error;
3
4#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
6pub enum ErrorCategory {
7 Auth,
9 Request,
11 Server,
13 Business,
15 Network,
17 Unknown,
19}
20
21#[derive(Debug, Error, Serialize)]
22pub enum BpiError {
23 #[error("网络请求失败: {message}")]
25 Network {
26 message: String,
27 },
28
29 #[error("HTTP请求失败,状态码: {status}")]
31 Http {
32 status: u16,
33 },
34
35 #[error("数据解析失败: {message}")]
37 Parse {
38 message: String,
39 },
40
41 #[error("API错误 [{code}]: {message}")]
43 Api {
44 code: i32,
45 message: String,
46 category: ErrorCategory,
47 },
48
49 #[error("验证失败: {message}")]
51 Authentication {
52 message: String,
53 },
54
55 #[error("参数错误 [{field}]: {message}")]
57 InvalidParameter {
58 field: &'static str,
59 message: &'static str,
60 },
61}
62
63impl BpiError {
64 pub fn missing_csrf() -> Self {
65 BpiError::InvalidParameter {
66 field: "csrf",
67 message: "缺少CSRF",
68 }
69 }
70
71 pub fn missing_data() -> Self {
72 BpiError::Parse {
73 message: "数据解析失败, 缺少data字段".to_string(),
74 }
75 }
76
77 pub fn auth_required() -> Self {
78 BpiError::Authentication {
79 message: "需要登录".to_string(),
80 }
81 }
82}
83
84impl BpiError {
86 pub fn from_code(code: i32) -> Self {
88 let message = super::code::get_error_message(code);
89 let category = super::code::categorize_error(code);
90
91 BpiError::Api {
92 code,
93 message,
94 category,
95 }
96 }
97
98 pub fn from_code_message(code: i32, message: String) -> Self {
100 let category = super::code::categorize_error(code);
101 BpiError::Api {
102 code,
103 message,
104 category,
105 }
106 }
107
108 pub fn from_api_response<T>(resp: crate::response::BpiResponse<T>) -> Self {
110 if resp.code == 0 {
111 return BpiError::Api {
112 code: 0,
113 message: "API返回成功状态但被当作错误处理".to_string(),
114 category: ErrorCategory::Unknown,
115 };
116 }
117 Self::from_code(resp.code)
118 }
119}
120
121impl BpiError {
123 pub fn code(&self) -> Option<i32> {
125 match self {
126 BpiError::Api { code, .. } => Some(*code),
127 _ => None,
128 }
129 }
130
131 pub fn category(&self) -> ErrorCategory {
133 match self {
134 BpiError::Api { category, .. } => category.clone(),
135 BpiError::Network { .. } => ErrorCategory::Network,
136 BpiError::Http { .. } => ErrorCategory::Network,
137 BpiError::Parse { .. } => ErrorCategory::Request,
138 BpiError::InvalidParameter { .. } => ErrorCategory::Request,
139 BpiError::Authentication { .. } => ErrorCategory::Auth,
140 }
141 }
142}
143
144impl BpiError {
146 pub fn network(message: impl Into<String>) -> Self {
148 BpiError::Network {
149 message: message.into(),
150 }
151 }
152
153 pub fn http(status: u16) -> Self {
155 BpiError::Http { status }
156 }
157
158 pub fn parse(message: impl Into<String>) -> Self {
160 BpiError::Parse {
161 message: message.into(),
162 }
163 }
164
165 pub fn invalid_parameter(field: &'static str, message: &'static str) -> Self {
167 BpiError::InvalidParameter { field, message }
168 }
169
170 pub fn auth(message: impl Into<String>) -> Self {
171 BpiError::Api {
172 code: 401,
173 message: message.into(),
174 category: ErrorCategory::Auth,
175 }
176 }
177}
178
179impl BpiError {
181 pub fn requires_login(&self) -> bool {
183 matches!(self.code(), Some(-101) | Some(-401))
184 }
185
186 pub fn is_permission_error(&self) -> bool {
188 matches!(self.category(), ErrorCategory::Auth) ||
189 matches!(self.code(), Some(-403) | Some(-4))
190 }
191
192 pub fn requires_vip(&self) -> bool {
194 matches!(self.code(), Some(-106) | Some(-650))
195 }
196
197 pub fn is_business_error(&self) -> bool {
199 matches!(self.category(), ErrorCategory::Business)
200 }
201}