1use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct RawResponse {
18 pub code: i32,
20 pub msg: String,
22 pub request_id: Option<String>,
24 pub data: Option<serde_json::Value>,
26 pub error: Option<ErrorInfo>,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct ErrorInfo {
33 pub code: i32,
35 pub message: String,
37 pub details: Option<HashMap<String, serde_json::Value>>,
39}
40
41impl Default for RawResponse {
42 fn default() -> Self {
43 Self {
44 code: 0,
45 msg: "success".to_string(),
46 request_id: None,
47 data: None,
48 error: None,
49 }
50 }
51}
52
53impl RawResponse {
54 pub fn success() -> Self {
56 Self::default()
57 }
58
59 pub fn success_with_data(data: serde_json::Value) -> Self {
61 Self {
62 data: Some(data),
63 ..Default::default()
64 }
65 }
66
67 pub fn error(code: i32, msg: impl Into<String> + Clone) -> Self {
69 let msg_str = msg.into();
70 Self {
71 code,
72 msg: msg_str.clone(),
73 error: Some(ErrorInfo {
74 code,
75 message: msg_str,
76 details: None,
77 }),
78 ..Default::default()
79 }
80 }
81
82 pub fn is_success(&self) -> bool {
84 self.code == 0
85 }
86
87 pub fn get_error(&self) -> Option<&ErrorInfo> {
89 self.error.as_ref()
90 }
91}
92
93#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
95pub enum ResponseFormat {
96 #[serde(rename = "data")]
98 Data,
99 #[serde(rename = "flatten")]
101 Flatten,
102 #[serde(rename = "binary")]
104 Binary,
105 #[serde(rename = "text")]
107 Text,
108 #[serde(rename = "custom")]
110 Custom,
111}
112
113impl ResponseFormat {
114 pub(crate) fn as_label(self) -> &'static str {
116 match self {
117 ResponseFormat::Data => "data",
118 ResponseFormat::Flatten => "flatten",
119 ResponseFormat::Binary => "binary",
120 ResponseFormat::Text => "text",
121 ResponseFormat::Custom => "custom",
122 }
123 }
124}
125
126pub trait ApiResponseTrait: Sized + Send + Sync + 'static {
135 fn data_format() -> ResponseFormat {
137 ResponseFormat::Data
138 }
139
140 fn requires_payload() -> bool {
143 true
144 }
145
146 fn empty_success() -> Option<Self> {
151 None
152 }
153
154 fn from_binary(_file_name: String, _body: Vec<u8>) -> Option<Self> {
156 None
157 }
158
159 fn from_text(_text: String) -> Option<Self> {
161 None
162 }
163
164 fn from_custom(_body: Vec<u8>, _content_type: Option<&str>) -> Option<Self> {
166 None
167 }
168}
169
170#[derive(Debug, Clone, Serialize, Deserialize)]
172pub struct Response<T> {
173 pub data: Option<T>,
175 pub raw_response: RawResponse,
177}
178
179impl<T> Response<T> {
180 pub fn new(data: Option<T>, raw_response: RawResponse) -> Self {
182 Self { data, raw_response }
183 }
184
185 pub fn success(data: T) -> Self {
187 Self {
188 data: Some(data),
189 raw_response: RawResponse::success(),
190 }
191 }
192
193 pub fn success_empty() -> Self {
195 Self {
196 data: None,
197 raw_response: RawResponse::success(),
198 }
199 }
200
201 pub fn error(code: i32, msg: impl Into<String> + Clone) -> Self {
203 Self {
204 data: None,
205 raw_response: RawResponse::error(code, msg),
206 }
207 }
208
209 pub fn is_success(&self) -> bool {
211 self.raw_response.is_success()
212 }
213
214 pub fn code(&self) -> i32 {
216 self.raw_response.code
217 }
218
219 pub fn message(&self) -> &str {
221 &self.raw_response.msg
222 }
223
224 pub fn msg(&self) -> &str {
226 &self.raw_response.msg
227 }
228
229 pub fn data(&self) -> Option<&T> {
231 self.data.as_ref()
232 }
233
234 pub fn raw(&self) -> &RawResponse {
236 &self.raw_response
237 }
238
239 pub fn decode(self, context: &str) -> Result<T, crate::error::CoreError> {
250 if let Some(data) = self.data {
251 return Ok(data);
252 }
253 let raw = self.raw_response;
254 let request_id = raw.request_id.clone();
255 let err = if raw.code != 0 {
256 crate::error::api_error(raw.code, "response", raw.msg, request_id.clone())
258 } else {
259 crate::error::validation_error("response.data", "服务器没有返回有效的数据")
260 };
261 Err(err.map_context(|ctx| {
262 ctx.set_operation("extract_response_data")
263 .add_context("resource", context);
264 if let Some(req_id) = request_id.as_ref().filter(|r| !r.trim().is_empty()) {
265 ctx.set_request_id(req_id);
266 }
267 }))
268 }
269}
270
271impl ApiResponseTrait for serde_json::Value {}
273impl ApiResponseTrait for String {}
275impl ApiResponseTrait for Vec<u8> {
276 fn data_format() -> ResponseFormat {
277 ResponseFormat::Binary
278 }
279
280 fn from_binary(_file_name: String, body: Vec<u8>) -> Option<Self> {
281 Some(body)
282 }
283}
284impl ApiResponseTrait for () {
285 fn requires_payload() -> bool {
286 false
287 }
288}
289
290pub type BaseResponse<T> = Response<T>;
293
294#[cfg(test)]
295mod tests {
296 use super::*;
297
298 #[test]
299 fn test_raw_response_default() {
300 let response = RawResponse::default();
301 assert_eq!(response.code, 0);
302 assert_eq!(response.msg, "success");
303 assert!(response.request_id.is_none());
304 assert!(response.data.is_none());
305 assert!(response.error.is_none());
306 }
307
308 #[test]
309 fn test_raw_response_success() {
310 let response = RawResponse::success();
311 assert!(response.is_success());
312 }
313
314 #[test]
315 fn test_raw_response_success_with_data() {
316 let data = serde_json::json!({"key": "value"});
317 let response = RawResponse::success_with_data(data.clone());
318 assert!(response.is_success());
319 assert_eq!(response.data, Some(data));
320 }
321
322 #[test]
323 fn test_raw_response_error() {
324 let response = RawResponse::error(400, "Bad Request");
325 assert!(!response.is_success());
326 assert_eq!(response.code, 400);
327 assert!(response.error.is_some());
328 }
329
330 #[test]
331 fn test_raw_response_get_error() {
332 let response = RawResponse::error(404, "Not Found");
333 let error = response.get_error();
334 assert!(error.is_some());
335 assert_eq!(error.unwrap().code, 404);
336 }
337
338 #[test]
339 fn test_raw_response_serialization() {
340 let response = RawResponse::success_with_data(serde_json::json!({"test": 123}));
341 let json = serde_json::to_string(&response).unwrap();
342 let parsed: RawResponse = serde_json::from_str(&json).expect("JSON 反序列化失败");
343 assert!(parsed.is_success());
344 }
345
346 #[test]
347 fn test_error_info_creation() {
348 let error = ErrorInfo {
349 code: 500,
350 message: "Internal Error".to_string(),
351 details: None,
352 };
353 assert_eq!(error.code, 500);
354 assert_eq!(error.message, "Internal Error");
355 }
356
357 #[test]
358 fn test_response_format() {
359 assert_eq!(ResponseFormat::Data, ResponseFormat::Data);
360 assert_ne!(ResponseFormat::Data, ResponseFormat::Flatten);
361 }
362
363 #[test]
364 fn test_response_format_binary() {
365 assert_eq!(<Vec<u8>>::data_format(), ResponseFormat::Binary);
366 }
367
368 #[test]
369 fn test_response_format_default() {
370 assert_eq!(<()>::data_format(), ResponseFormat::Data);
371 }
372
373 #[test]
374 fn test_response_deserialize_requires_raw_response() {
375 let payload = r#"{"code":400,"msg":"Bad Request"}"#;
376 let parsed = serde_json::from_str::<Response<serde_json::Value>>(payload);
377 assert!(parsed.is_err());
378 }
379
380 #[test]
381 fn test_response_deserialize_with_raw_response_error_keeps_code_and_msg() {
382 let payload = r#"{"raw_response":{"code":400,"msg":"Bad Request","request_id":null,"data":null,"error":null},"data":null}"#;
383 let parsed = serde_json::from_str::<Response<serde_json::Value>>(payload)
384 .expect("JSON 反序列化失败");
385 assert_eq!(parsed.raw_response.code, 400);
386 assert_eq!(parsed.raw_response.msg, "Bad Request");
387 assert!(!parsed.is_success());
388 }
389
390 #[test]
393 fn decode_returns_data_on_success() {
394 let response: Response<String> = Response {
395 data: Some("x".to_string()),
396 raw_response: RawResponse::success(),
397 };
398 assert_eq!(response.decode("测试").unwrap(), "x");
399 }
400
401 #[test]
402 fn decode_missing_data_is_validation_error_with_context() {
403 let response: Response<String> = Response {
404 data: None,
405 raw_response: RawResponse::success(),
406 };
407 let err = response.decode("测试").expect_err("缺 data 应报错");
408 let ctx = err.ctx();
409 assert_eq!(ctx.operation(), Some("extract_response_data"));
410 assert_eq!(ctx.get_context("resource"), Some("测试"));
411 }
412
413 #[test]
415 fn decode_business_error_preserves_msg_and_classifies_raw_code() {
416 let response: Response<String> = Response {
417 data: None,
418 raw_response: RawResponse {
419 code: 99991663,
420 msg: "tenant access token invalid".to_string(),
421 request_id: Some("rid-x".to_string()),
422 ..RawResponse::success()
423 },
424 };
425 let err = response.decode("测试").expect_err("业务错误应报错");
426 assert_eq!(err.ctx().request_id(), Some("rid-x"));
427 match err {
428 crate::error::CoreError::Api(api) => {
429 assert!(
430 api.message.contains("tenant access token invalid"),
431 "msg preserved: {}",
432 api.message
433 );
434 assert_eq!(
435 api.raw_code, 99991663,
436 "raw_code must preserve full i32 feishu code"
437 );
438 assert_eq!(
439 api.code,
440 crate::error::ErrorCode::TenantAccessTokenInvalid,
441 "classification must use from_code without u16 truncation"
442 );
443 let display = api.to_string();
444 assert!(
445 display.contains("99991663"),
446 "Display must show real code, not truncated garbage: {display}"
447 );
448 }
449 other => panic!("expected Api for business error, got: {other:?}"),
450 }
451 }
452}