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 { message: String },
26
27 #[error("HTTP请求失败,状态码: {status}")]
29 Http { status: u16 },
30
31 #[error("数据解析失败: {message}")]
33 Parse { message: String },
34
35 #[error("API错误 [{code}]: {message}")]
37 Api {
38 code: i32,
39 message: String,
40 category: ErrorCategory,
41 },
42
43 #[error("验证失败: {message}")]
45 Authentication { message: String },
46
47 #[error("参数错误 [{field}]: {message}")]
49 InvalidParameter {
50 field: &'static str,
51 message: &'static str,
52 },
53}
54
55impl BpiError {
56 pub fn missing_csrf() -> Self {
57 BpiError::InvalidParameter {
58 field: "csrf",
59 message: "缺少CSRF",
60 }
61 }
62
63 pub fn missing_data() -> Self {
64 BpiError::Parse {
65 message: "数据解析失败, 缺少data字段".to_string(),
66 }
67 }
68
69 pub fn auth_required() -> Self {
70 BpiError::Authentication {
71 message: "需要登录".to_string(),
72 }
73 }
74}
75
76impl BpiError {
78 pub fn from_code(code: i32) -> Self {
80 let message = super::code::get_error_message(code);
81 let category = super::code::categorize_error(code);
82
83 BpiError::Api {
84 code,
85 message,
86 category,
87 }
88 }
89
90 pub fn from_code_message(code: i32, message: String) -> Self {
92 let category = super::code::categorize_error(code);
93 BpiError::Api {
94 code,
95 message,
96 category,
97 }
98 }
99
100 pub fn from_api_response<T>(resp: crate::response::BpiResponse<T>) -> Self {
102 if resp.code == 0 {
103 return BpiError::Api {
104 code: 0,
105 message: "API返回成功状态但被当作错误处理".to_string(),
106 category: ErrorCategory::Unknown,
107 };
108 }
109 Self::from_code(resp.code)
110 }
111}
112
113impl BpiError {
115 pub fn code(&self) -> Option<i32> {
117 match self {
118 BpiError::Api { code, .. } => Some(*code),
119 _ => None,
120 }
121 }
122
123 pub fn category(&self) -> ErrorCategory {
125 match self {
126 BpiError::Api { category, .. } => category.clone(),
127 BpiError::Network { .. } => ErrorCategory::Network,
128 BpiError::Http { .. } => ErrorCategory::Network,
129 BpiError::Parse { .. } => ErrorCategory::Request,
130 BpiError::InvalidParameter { .. } => ErrorCategory::Request,
131 BpiError::Authentication { .. } => ErrorCategory::Auth,
132 }
133 }
134}
135
136impl BpiError {
138 pub fn network(message: impl Into<String>) -> Self {
140 BpiError::Network {
141 message: message.into(),
142 }
143 }
144
145 pub fn http(status: u16) -> Self {
147 BpiError::Http { status }
148 }
149
150 pub fn parse(message: impl Into<String>) -> Self {
152 BpiError::Parse {
153 message: message.into(),
154 }
155 }
156
157 pub fn invalid_parameter(field: &'static str, message: &'static str) -> Self {
159 BpiError::InvalidParameter { field, message }
160 }
161
162 pub fn auth(message: impl Into<String>) -> Self {
163 BpiError::Api {
164 code: 401,
165 message: message.into(),
166 category: ErrorCategory::Auth,
167 }
168 }
169}
170
171impl BpiError {
173 pub fn requires_login(&self) -> bool {
175 matches!(self.code(), Some(-101) | Some(-401))
176 }
177
178 pub fn is_permission_error(&self) -> bool {
180 matches!(self.category(), ErrorCategory::Auth)
181 || matches!(self.code(), Some(-403) | Some(-4))
182 }
183
184 pub fn requires_vip(&self) -> bool {
186 matches!(self.code(), Some(-106) | Some(-650))
187 }
188
189 pub fn is_business_error(&self) -> bool {
191 matches!(self.category(), ErrorCategory::Business)
192 }
193}