1use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use uuid::Uuid;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct ApiResponse<T> {
18 pub status: ResponseStatus,
20
21 #[serde(skip_serializing_if = "Option::is_none")]
23 pub data: Option<T>,
24
25 #[serde(skip_serializing_if = "Option::is_none")]
27 pub error: Option<ApiError>,
28
29 pub meta: ResponseMetadata,
31}
32
33impl<T> ApiResponse<T> {
34 pub fn success(data: T) -> Self {
35 Self {
36 status: ResponseStatus::Success,
37 data: Some(data),
38 error: None,
39 meta: ResponseMetadata::default(),
40 }
41 }
42
43 pub fn error(error: ApiError) -> Self {
44 Self {
45 status: ResponseStatus::Error,
46 data: None,
47 error: Some(error),
48 meta: ResponseMetadata::default(),
49 }
50 }
51
52 pub fn with_meta(mut self, meta: ResponseMetadata) -> Self {
53 self.meta = meta;
54 self
55 }
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
59#[serde(rename_all = "lowercase")]
60pub enum ResponseStatus {
61 Success,
62 Error,
63 Partial,
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct ResponseMetadata {
68 pub request_id: Uuid,
70
71 pub timestamp: DateTime<Utc>,
73
74 pub api_version: String,
76
77 #[serde(skip_serializing_if = "Option::is_none")]
79 pub response_time_ms: Option<u64>,
80
81 #[serde(flatten)]
83 pub extra: HashMap<String, serde_json::Value>,
84}
85
86impl Default for ResponseMetadata {
87 fn default() -> Self {
88 Self {
89 request_id: Uuid::new_v4(),
90 timestamp: Utc::now(),
91 api_version: "1.0.0".to_string(),
92 response_time_ms: None,
93 extra: HashMap::new(),
94 }
95 }
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct PaginatedResponse<T> {
105 pub status: ResponseStatus,
107
108 #[serde(skip_serializing_if = "Option::is_none")]
110 pub data: Option<Vec<T>>,
111
112 pub pagination: PaginationMetadata,
114
115 #[serde(skip_serializing_if = "Option::is_none")]
117 pub error: Option<ApiError>,
118
119 pub meta: ResponseMetadata,
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct PaginationMetadata {
125 pub page: u32,
127
128 pub per_page: u32,
130
131 pub total_items: u64,
133
134 pub total_pages: u32,
136
137 pub has_next: bool,
139
140 pub has_previous: bool,
142
143 #[serde(skip_serializing_if = "Option::is_none")]
145 pub links: Option<PaginationLinks>,
146}
147
148impl PaginationMetadata {
149 pub fn new(page: u32, per_page: u32, total_items: u64) -> Self {
150 let total_pages = ((total_items as f64) / (per_page as f64)).ceil() as u32;
151 Self {
152 page,
153 per_page,
154 total_items,
155 total_pages,
156 has_next: page < total_pages,
157 has_previous: page > 1,
158 links: None,
159 }
160 }
161
162 pub fn with_links(mut self, base_url: &str) -> Self {
163 self.links = Some(PaginationLinks::new(base_url, self.page, self.total_pages));
164 self
165 }
166}
167
168#[derive(Debug, Clone, Serialize, Deserialize)]
169pub struct PaginationLinks {
170 pub first: String,
172
173 pub last: String,
175
176 #[serde(skip_serializing_if = "Option::is_none")]
178 pub next: Option<String>,
179
180 #[serde(skip_serializing_if = "Option::is_none")]
182 pub prev: Option<String>,
183
184 pub self_link: String,
186}
187
188impl PaginationLinks {
189 pub fn new(base_url: &str, current_page: u32, total_pages: u32) -> Self {
190 Self {
191 first: format!("{}?page=1", base_url),
192 last: format!("{}?page={}", base_url, total_pages),
193 next: if current_page < total_pages {
194 Some(format!("{}?page={}", base_url, current_page + 1))
195 } else {
196 None
197 },
198 prev: if current_page > 1 {
199 Some(format!("{}?page={}", base_url, current_page - 1))
200 } else {
201 None
202 },
203 self_link: format!("{}?page={}", base_url, current_page),
204 }
205 }
206}
207
208#[derive(Debug, Clone, Serialize, Deserialize)]
210pub struct PaginationParams {
211 #[serde(default = "default_page")]
213 pub page: u32,
214
215 #[serde(default = "default_per_page")]
217 pub per_page: u32,
218
219 #[serde(skip_serializing_if = "Option::is_none")]
221 pub sort_by: Option<String>,
222
223 #[serde(skip_serializing_if = "Option::is_none")]
225 pub sort_order: Option<SortOrder>,
226}
227
228fn default_page() -> u32 {
229 1
230}
231
232fn default_per_page() -> u32 {
233 50
234}
235
236impl Default for PaginationParams {
237 fn default() -> Self {
238 Self {
239 page: 1,
240 per_page: 50,
241 sort_by: None,
242 sort_order: None,
243 }
244 }
245}
246
247#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
248#[serde(rename_all = "lowercase")]
249pub enum SortOrder {
250 Asc,
251 Desc,
252}
253
254#[derive(Debug, Clone, Serialize, Deserialize)]
260pub struct ApiError {
261 pub code: String,
263
264 pub message: String,
266
267 pub status_code: u16,
269
270 #[serde(skip_serializing_if = "Option::is_none")]
272 pub details: Option<ErrorDetails>,
273
274 #[serde(skip_serializing_if = "Option::is_none")]
276 pub field_errors: Option<HashMap<String, Vec<String>>>,
277
278 pub timestamp: DateTime<Utc>,
280}
281
282impl ApiError {
283 pub fn new(code: impl Into<String>, message: impl Into<String>, status_code: u16) -> Self {
284 Self {
285 code: code.into(),
286 message: message.into(),
287 status_code,
288 details: None,
289 field_errors: None,
290 timestamp: Utc::now(),
291 }
292 }
293
294 pub fn bad_request(message: impl Into<String>) -> Self {
295 Self::new("bad_request", message, 400)
296 }
297
298 pub fn unauthorized(message: impl Into<String>) -> Self {
299 Self::new("unauthorized", message, 401)
300 }
301
302 pub fn forbidden(message: impl Into<String>) -> Self {
303 Self::new("forbidden", message, 403)
304 }
305
306 pub fn not_found(message: impl Into<String>) -> Self {
307 Self::new("not_found", message, 404)
308 }
309
310 pub fn internal_error(message: impl Into<String>) -> Self {
311 Self::new("internal_error", message, 500)
312 }
313
314 pub fn with_details(mut self, details: ErrorDetails) -> Self {
315 self.details = Some(details);
316 self
317 }
318
319 pub fn with_field_errors(mut self, errors: HashMap<String, Vec<String>>) -> Self {
320 self.field_errors = Some(errors);
321 self
322 }
323}
324
325#[derive(Debug, Clone, Serialize, Deserialize)]
326pub struct ErrorDetails {
327 #[serde(skip_serializing_if = "Option::is_none")]
329 pub trace: Option<String>,
330
331 #[serde(skip_serializing_if = "Option::is_none")]
333 pub context: Option<HashMap<String, serde_json::Value>>,
334
335 #[serde(skip_serializing_if = "Option::is_none")]
337 pub suggestions: Option<Vec<String>>,
338
339 #[serde(skip_serializing_if = "Option::is_none")]
341 pub documentation_url: Option<String>,
342}
343
344#[derive(Debug, Clone, Serialize, Deserialize)]
350pub struct QueryResult<T> {
351 pub query_id: Uuid,
353
354 pub status: QueryStatus,
356
357 #[serde(skip_serializing_if = "Option::is_none")]
359 pub data: Option<T>,
360
361 pub metrics: QueryMetrics,
363
364 #[serde(default)]
366 pub warnings: Vec<String>,
367}
368
369#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
370#[serde(rename_all = "snake_case")]
371pub enum QueryStatus {
372 Success,
373 PartialSuccess,
374 Failed,
375 Timeout,
376}
377
378#[derive(Debug, Clone, Serialize, Deserialize)]
379pub struct QueryMetrics {
380 pub execution_time_ms: u64,
382
383 pub records_scanned: u64,
385
386 pub records_returned: u64,
388
389 pub bytes_processed: u64,
391
392 pub from_cache: bool,
394
395 #[serde(skip_serializing_if = "Option::is_none")]
397 pub cache_ttl: Option<u32>,
398}
399
400#[derive(Debug, Clone, Serialize, Deserialize)]
402pub struct TimeSeriesQueryResult {
403 pub query: String,
405
406 pub time_range: TimeRange,
408
409 pub series: Vec<SeriesData>,
411
412 pub metrics: QueryMetrics,
414}
415
416#[derive(Debug, Clone, Serialize, Deserialize)]
417pub struct TimeRange {
418 pub start: DateTime<Utc>,
419 pub end: DateTime<Utc>,
420}
421
422#[derive(Debug, Clone, Serialize, Deserialize)]
423pub struct SeriesData {
424 pub name: String,
426
427 #[serde(default)]
429 pub tags: HashMap<String, String>,
430
431 pub points: Vec<DataPoint>,
433}
434
435#[derive(Debug, Clone, Serialize, Deserialize)]
436pub struct DataPoint {
437 pub timestamp: DateTime<Utc>,
439
440 pub value: f64,
442
443 #[serde(flatten)]
445 pub fields: HashMap<String, serde_json::Value>,
446}
447
448#[derive(Debug, Clone, Serialize, Deserialize)]
450pub struct MetricsQueryResult {
451 pub metric: String,
453
454 pub window: String,
456
457 pub values: Vec<AggregatedValue>,
459
460 pub metrics: QueryMetrics,
462}
463
464#[derive(Debug, Clone, Serialize, Deserialize)]
465pub struct AggregatedValue {
466 pub timestamp: DateTime<Utc>,
467 pub avg: f64,
468 pub min: f64,
469 pub max: f64,
470 pub p50: f64,
471 pub p95: f64,
472 pub p99: f64,
473 pub count: u64,
474}
475
476#[derive(Debug, Clone, Serialize, Deserialize)]
482pub struct StreamEvent<T> {
483 pub event_id: Uuid,
485
486 pub event_type: StreamEventType,
488
489 pub data: T,
491
492 pub sequence: u64,
494
495 pub timestamp: DateTime<Utc>,
497}
498
499#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
500#[serde(rename_all = "snake_case")]
501pub enum StreamEventType {
502 Data,
503 Heartbeat,
504 Error,
505 Complete,
506}
507
508#[derive(Debug, Clone)]
510pub struct SseMessage {
511 pub event: Option<String>,
513
514 pub data: String,
516
517 pub id: Option<String>,
519
520 pub retry: Option<u32>,
522}
523
524impl SseMessage {
525 pub fn data(data: String) -> Self {
526 Self {
527 event: None,
528 data,
529 id: None,
530 retry: None,
531 }
532 }
533
534 pub fn event(event: String, data: String) -> Self {
535 Self {
536 event: Some(event),
537 data,
538 id: None,
539 retry: None,
540 }
541 }
542
543 pub fn with_id(mut self, id: String) -> Self {
544 self.id = Some(id);
545 self
546 }
547
548 pub fn to_string(&self) -> String {
549 let mut result = String::new();
550
551 if let Some(event) = &self.event {
552 result.push_str(&format!("event: {}\n", event));
553 }
554
555 if let Some(id) = &self.id {
556 result.push_str(&format!("id: {}\n", id));
557 }
558
559 if let Some(retry) = &self.retry {
560 result.push_str(&format!("retry: {}\n", retry));
561 }
562
563 result.push_str(&format!("data: {}\n\n", self.data));
564 result
565 }
566}
567
568#[derive(Debug, Clone, Serialize, Deserialize)]
570pub struct BatchResponse<T> {
571 pub batch_id: Uuid,
573
574 pub total_items: usize,
576
577 pub success_count: usize,
579
580 pub failure_count: usize,
582
583 pub results: Vec<BatchItemResult<T>>,
585
586 pub status: BatchStatus,
588}
589
590#[derive(Debug, Clone, Serialize, Deserialize)]
591pub struct BatchItemResult<T> {
592 pub index: usize,
594
595 pub item_id: Option<String>,
597
598 pub status: ItemStatus,
600
601 #[serde(skip_serializing_if = "Option::is_none")]
603 pub data: Option<T>,
604
605 #[serde(skip_serializing_if = "Option::is_none")]
607 pub error: Option<ApiError>,
608}
609
610#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
611#[serde(rename_all = "lowercase")]
612pub enum ItemStatus {
613 Success,
614 Failed,
615 Skipped,
616}
617
618#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
619#[serde(rename_all = "snake_case")]
620pub enum BatchStatus {
621 AllSuccess,
622 PartialSuccess,
623 AllFailed,
624}
625
626#[cfg(test)]
627mod tests {
628 use super::*;
629
630 #[test]
631 fn test_api_response_success() {
632 let response = ApiResponse::success("test data");
633 assert_eq!(response.status, ResponseStatus::Success);
634 assert!(response.data.is_some());
635 assert!(response.error.is_none());
636 }
637
638 #[test]
639 fn test_api_response_error() {
640 let error = ApiError::not_found("Resource not found");
641 let response: ApiResponse<String> = ApiResponse::error(error);
642 assert_eq!(response.status, ResponseStatus::Error);
643 assert!(response.data.is_none());
644 assert!(response.error.is_some());
645 }
646
647 #[test]
648 fn test_pagination_metadata() {
649 let pagination = PaginationMetadata::new(2, 50, 250);
650 assert_eq!(pagination.page, 2);
651 assert_eq!(pagination.total_pages, 5);
652 assert!(pagination.has_next);
653 assert!(pagination.has_previous);
654 }
655
656 #[test]
657 fn test_sse_message_format() {
658 let msg = SseMessage::event("update".to_string(), "{\"status\":\"ok\"}".to_string())
659 .with_id("123".to_string());
660
661 let formatted = msg.to_string();
662 assert!(formatted.contains("event: update"));
663 assert!(formatted.contains("id: 123"));
664 assert!(formatted.contains("data: {\"status\":\"ok\"}"));
665 }
666}