1use serde::Deserialize;
6use std::time::Duration;
7
8pub trait ApiResponse: for<'de> Deserialize<'de> + Send + Sync + 'static {
12 fn is_success(&self) -> bool;
14
15 fn error_message(&self) -> Option<&String>;
17
18 fn into_result(self) -> Result<Self, crate::Error>;
20}
21
22#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
26pub struct ApiResponseData<T> {
27 pub data: T,
29 pub success: bool,
31 pub message: Option<String>,
33 pub request_id: String,
35 pub timestamp: Option<i64>,
37 pub extra: std::collections::HashMap<String, serde_json::Value>,
39}
40
41impl<T> ApiResponseData<T> {
42 pub fn success(data: T) -> Self {
44 Self {
45 data,
46 success: true,
47 message: None,
48 request_id: uuid::Uuid::new_v4().to_string(),
49 timestamp: Some(chrono::Utc::now().timestamp()),
50 extra: std::collections::HashMap::new(),
51 }
52 }
53
54 pub fn error(message: impl Into<String>) -> Self
60 where
61 T: Default,
62 {
63 Self {
64 data: T::default(),
65 success: false,
66 message: Some(message.into()),
67 request_id: uuid::Uuid::new_v4().to_string(),
68 timestamp: Some(chrono::Utc::now().timestamp()),
69 extra: std::collections::HashMap::new(),
70 }
71 }
72
73 pub fn error_with_data(data: T, message: impl Into<String>) -> Self {
75 Self {
76 data,
77 success: false,
78 message: Some(message.into()),
79 request_id: uuid::Uuid::new_v4().to_string(),
80 timestamp: Some(chrono::Utc::now().timestamp()),
81 extra: std::collections::HashMap::new(),
82 }
83 }
84
85 pub fn is_success(&self) -> bool {
87 self.success
88 }
89
90 pub fn error_message(&self) -> Option<&String> {
92 self.message.as_ref()
93 }
94
95 pub fn into_result(self) -> Result<T, crate::Error> {
97 if self.success {
98 Ok(self.data)
99 } else {
100 Err(crate::error::api_error(
101 500,
102 "response",
103 self.message.unwrap_or_default(),
104 None,
105 ))
106 }
107 }
108}
109
110impl<T: serde::de::DeserializeOwned + Send + Sync + 'static> ApiResponse for ApiResponseData<T> {
111 fn is_success(&self) -> bool {
112 self.success
113 }
114
115 fn error_message(&self) -> Option<&String> {
116 self.message.as_ref()
117 }
118
119 fn into_result(self) -> Result<Self, crate::Error> {
120 if self.success {
121 Ok(self)
122 } else {
123 Err(crate::error::api_error(
124 500,
125 "response",
126 self.message.unwrap_or_default(),
127 None,
128 ))
129 }
130 }
131}
132
133#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
135pub struct PaginatedResponse<T> {
136 pub items: Vec<T>,
138 pub has_more: bool,
140 pub page_token: Option<String>,
142 pub total: Option<u64>,
144}
145
146impl<T> PaginatedResponse<T> {
147 pub fn new(items: Vec<T>) -> Self {
149 Self {
150 items,
151 has_more: false,
152 page_token: None,
153 total: None,
154 }
155 }
156
157 pub fn with_pagination(items: Vec<T>, has_more: bool, page_token: Option<String>) -> Self {
159 Self {
160 items,
161 has_more,
162 page_token,
163 total: None,
164 }
165 }
166
167 pub fn len(&self) -> usize {
169 self.items.len()
170 }
171
172 pub fn is_empty(&self) -> bool {
174 self.items.is_empty()
175 }
176}
177
178#[derive(Debug, Clone, Default)]
180pub struct RequestOptions {
181 pub timeout: Option<Duration>,
183 pub retry_count: Option<u32>,
185 pub headers: Option<std::collections::HashMap<String, String>>,
187}
188
189impl RequestOptions {
190 pub fn new() -> Self {
192 Self::default()
193 }
194
195 pub fn timeout(mut self, timeout: Duration) -> Self {
197 self.timeout = Some(timeout);
198 self
199 }
200
201 pub fn retry_count(mut self, count: u32) -> Self {
203 self.retry_count = Some(count);
204 self
205 }
206
207 pub fn header(mut self, key: String, value: String) -> Self {
209 self.headers
210 .get_or_insert_with(std::collections::HashMap::new)
211 .insert(key, value);
212 self
213 }
214}
215
216#[cfg(test)]
217#[allow(unused_imports)]
218mod tests {
219 use super::*;
220
221 #[test]
222 fn test_api_response_data() {
223 let response = ApiResponseData {
225 data: "test data".to_string(),
226 success: true,
227 message: None,
228 request_id: "test-request-123".to_string(),
229 timestamp: Some(1640995200),
230 extra: std::collections::HashMap::new(),
231 };
232
233 assert!(response.is_success());
234 assert_eq!(response.data, "test data");
235
236 let error_response = ApiResponseData {
237 data: String::new(),
238 success: false,
239 message: Some("测试错误".to_string()),
240 request_id: "test-request-456".to_string(),
241 timestamp: Some(1640995200),
242 extra: std::collections::HashMap::new(),
243 };
244
245 assert!(!error_response.is_success());
246 assert_eq!(
247 error_response.error_message(),
248 Some(&"测试错误".to_string())
249 );
250 }
251
252 #[test]
253 fn test_paginated_response() {
254 let items = vec!["item1", "item2", "item3"];
255 let response = PaginatedResponse::new(items.clone());
256
257 assert_eq!(response.len(), 3);
258 assert!(!response.has_more);
259 assert!(response.page_token.is_none());
260
261 let paginated =
262 PaginatedResponse::with_pagination(items.clone(), true, Some("next_token".to_string()));
263 assert!(paginated.has_more);
264 assert_eq!(paginated.page_token, Some("next_token".to_string()));
265 }
266
267 #[test]
268 fn test_request_options() {
269 let options = RequestOptions::new()
270 .timeout(Duration::from_secs(30))
271 .retry_count(3)
272 .header("User-Agent".to_string(), "test-client".to_string());
273
274 assert_eq!(options.timeout, Some(Duration::from_secs(30)));
275 assert_eq!(options.retry_count, Some(3));
276 assert!(options.headers.is_some());
277 }
278
279 #[test]
280 fn test_api_response_data_success() {
281 let response: ApiResponseData<i32> = ApiResponseData::success(42);
282 assert!(response.is_success());
283 assert_eq!(response.data, 42);
284 assert!(response.error_message().is_none());
285 assert!(!response.request_id.is_empty());
286 }
287
288 #[test]
289 fn test_api_response_data_error() {
290 let response: ApiResponseData<String> = ApiResponseData::error("发生错误");
291 assert!(!response.is_success());
292 assert_eq!(response.error_message(), Some(&"发生错误".to_string()));
293 assert!(!response.request_id.is_empty());
294 }
295
296 #[test]
297 fn test_api_response_data_error_with_data() {
298 let response = ApiResponseData::error_with_data(123, "操作失败");
299 assert!(!response.is_success());
300 assert_eq!(response.data, 123);
301 assert_eq!(response.error_message(), Some(&"操作失败".to_string()));
302 }
303
304 #[test]
305 fn test_api_response_data_into_result_success() {
306 let response: ApiResponseData<i32> = ApiResponseData {
307 data: 42,
308 success: true,
309 message: None,
310 request_id: "test".to_string(),
311 timestamp: None,
312 extra: std::collections::HashMap::new(),
313 };
314 let result = response.into_result();
315 assert!(result.is_ok());
316 assert_eq!(result.unwrap(), 42);
317 }
318
319 #[test]
320 fn test_api_response_data_into_result_error() {
321 let response: ApiResponseData<i32> = ApiResponseData {
322 data: 0,
323 success: false,
324 message: Some("出错了".to_string()),
325 request_id: "test".to_string(),
326 timestamp: None,
327 extra: std::collections::HashMap::new(),
328 };
329 let result = response.into_result();
330 assert!(result.is_err());
331 }
332
333 #[test]
334 fn test_api_response_trait() {
335 let response: ApiResponseData<String> = ApiResponseData {
336 data: "test".to_string(),
337 success: true,
338 message: None,
339 request_id: "req-123".to_string(),
340 timestamp: None,
341 extra: std::collections::HashMap::new(),
342 };
343 assert!(response.is_success());
344 assert!(response.error_message().is_none());
345 }
346
347 #[test]
348 fn test_api_response_trait_error() {
349 let response: ApiResponseData<String> = ApiResponseData {
350 data: String::new(),
351 success: false,
352 message: Some("错误信息".to_string()),
353 request_id: "req-456".to_string(),
354 timestamp: None,
355 extra: std::collections::HashMap::new(),
356 };
357 assert!(!response.is_success());
358 assert_eq!(response.error_message(), Some(&"错误信息".to_string()));
359 }
360
361 #[test]
362 fn test_paginated_response_empty() {
363 let response: PaginatedResponse<String> = PaginatedResponse::new(vec![]);
364 assert!(response.is_empty());
365 assert_eq!(response.len(), 0);
366 assert!(!response.has_more);
367 }
368
369 #[test]
370 fn test_paginated_response_with_total() {
371 let items = vec!["a", "b", "c"];
372 let response = PaginatedResponse {
373 items: items.clone(),
374 has_more: true,
375 page_token: Some("next".to_string()),
376 total: Some(100),
377 };
378 assert_eq!(response.len(), 3);
379 assert!(response.has_more);
380 assert_eq!(response.total, Some(100));
381 }
382
383 #[test]
384 fn test_request_options_default() {
385 let options: RequestOptions = Default::default();
386 assert!(options.timeout.is_none());
387 assert!(options.retry_count.is_none());
388 assert!(options.headers.is_none());
389 }
390
391 #[test]
392 fn test_request_options_new() {
393 let options = RequestOptions::new();
394 assert!(options.timeout.is_none());
395 assert!(options.retry_count.is_none());
396 }
397
398 #[test]
399 fn test_request_options_multiple_headers() {
400 let options = RequestOptions::new()
401 .header("Authorization".to_string(), "Bearer token".to_string())
402 .header("Content-Type".to_string(), "application/json".to_string());
403
404 let headers = options.headers.unwrap();
405 assert_eq!(
406 headers.get("Authorization"),
407 Some(&"Bearer token".to_string())
408 );
409 assert_eq!(
410 headers.get("Content-Type"),
411 Some(&"application/json".to_string())
412 );
413 }
414
415 #[test]
416 fn test_request_options_only_timeout() {
417 let options = RequestOptions::new().timeout(Duration::from_secs(60));
418 assert_eq!(options.timeout, Some(Duration::from_secs(60)));
419 assert!(options.retry_count.is_none());
420 }
421
422 #[test]
423 fn test_request_options_only_retry() {
424 let options = RequestOptions::new().retry_count(5);
425 assert_eq!(options.retry_count, Some(5));
426 assert!(options.timeout.is_none());
427 }
428
429 #[test]
430 fn test_api_response_data_clone() {
431 let response = ApiResponseData {
432 data: 42,
433 success: true,
434 message: Some("test".to_string()),
435 request_id: "req".to_string(),
436 timestamp: Some(1234567890),
437 extra: std::collections::HashMap::new(),
438 };
439 let cloned = response.clone();
440 assert_eq!(cloned.data, 42);
441 assert!(cloned.success);
442 }
443
444 #[test]
445 fn test_api_response_data_serialize() {
446 let response: ApiResponseData<i32> = ApiResponseData::success(42);
447 let json = serde_json::to_string(&response).unwrap();
448 assert!(json.contains("42"));
449 assert!(json.contains("true"));
450 }
451
452 #[test]
453 fn test_api_response_data_deserialize() {
454 let json = r#"{"data":42,"success":true,"message":null,"request_id":"req-123","timestamp":1234567890,"extra":{}}"#;
455 let response: ApiResponseData<i32> = serde_json::from_str(json).unwrap();
456 assert_eq!(response.data, 42);
457 assert!(response.success);
458 }
459}