1use serde::{Deserialize, Serialize};
4use std::collections::BTreeMap;
5use std::fmt;
6#[cfg(feature = "openapi")]
7use utoipa::ToSchema;
8
9#[derive(Debug, Serialize, Deserialize)]
11#[cfg_attr(feature = "openapi", derive(ToSchema))]
12#[cfg_attr(feature = "openapi", schema(
13 title ="Response returned from synchronization endpoints",
14 example = json!({
15 "status": "success",
16 "message": "Synchronization started successfully"
17 })
18))]
19pub struct SyncResponse {
20 #[cfg_attr(feature = "openapi", schema(example = "success"))]
22 pub status: String,
23
24 #[cfg_attr(feature = "openapi", schema(example = "Contact sync started in background"))]
26 pub message: String,
27}
28
29#[derive(Debug, Serialize, Deserialize)]
31#[cfg_attr(feature = "openapi", derive(ToSchema))]
32#[cfg_attr(feature = "openapi", schema(
33 title ="Response containing data retrieved from cache"
34))]
35pub struct CacheResponse {
36 #[cfg_attr(feature = "openapi", schema(example = "cache"))]
38 pub source: String,
39
40 pub data: serde_json::Value,
42
43 #[serde(skip_serializing_if = "Option::is_none")]
45 #[cfg_attr(feature = "openapi", schema(example = 42))]
46 pub count: Option<usize>,
47
48 #[serde(skip_serializing_if = "Option::is_none")]
50 pub additional_info: Option<serde_json::Value>,
51}
52
53#[derive(Debug, Serialize, Deserialize)]
55#[cfg_attr(feature = "openapi", derive(ToSchema))]
56#[cfg_attr(feature = "openapi", schema(
57 title ="Error response when cache operation fails"
58))]
59pub struct CacheErrorResponse {
60 #[cfg_attr(feature = "openapi", schema(example = "Redis connection timeout"))]
62 pub error: String,
63
64 #[cfg_attr(feature = "openapi", schema(example = "cache"))]
66 pub source: String,
67}
68
69#[derive(Debug, Serialize, Deserialize)]
71#[cfg_attr(feature = "openapi", derive(ToSchema))]
72#[cfg_attr(feature = "openapi", schema(
73 title ="Statistics about cached data for an account",
74 example = json!({
75 "source": "cache",
76 "account_id": "507f1f77bcf86cd799439011",
77 "stats": {
78 "summary": {
79 "total_contacts": 150
80 },
81 "groups": {
82 "sales": 50,
83 "support": 30,
84 "engineering": 70
85 }
86 }
87 })
88))]
89pub struct CacheStatsResponse {
90 pub source: String,
92
93 #[cfg_attr(feature = "openapi", schema(example = "507f1f77bcf86cd799439011"))]
95 pub account_id: String,
96
97 pub stats: BTreeMap<String, BTreeMap<String, usize>>,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
103#[cfg_attr(feature = "openapi", derive(ToSchema))]
104#[cfg_attr(feature = "openapi", schema(title ="Pagination parameters for list requests"))]
105#[serde(rename_all = "camelCase")]
106pub struct PaginationParams {
107 #[serde(default)]
109 #[cfg_attr(feature = "openapi", schema(example = 0, minimum = 0))]
110 pub page: u32,
111
112 #[serde(default = "default_page_size")]
114 #[cfg_attr(feature = "openapi", schema(example = 20, minimum = 1, maximum = 100))]
115 pub page_size: u32,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize)]
120#[cfg_attr(feature = "openapi", derive(ToSchema))]
121#[cfg_attr(feature = "openapi", schema(title ="Paginated list response"))]
122#[serde(rename_all = "camelCase")]
123pub struct PaginatedResponse<T> {
124 pub data: Vec<T>,
126
127 pub pagination: PaginationMeta,
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize)]
133#[cfg_attr(feature = "openapi", derive(ToSchema))]
134#[cfg_attr(feature = "openapi", schema(
135 title ="Metadata about pagination",
136 example = json!({
137 "total": 150,
138 "page": 0,
139 "pageSize": 20,
140 "totalPages": 8,
141 "hasNext": true,
142 "hasPrevious": false
143 })
144))]
145#[serde(rename_all = "camelCase")]
146pub struct PaginationMeta {
147 pub total: u64,
149
150 pub page: u32,
152
153 pub page_size: u32,
155
156 pub total_pages: u32,
158
159 pub has_next: bool,
161
162 pub has_previous: bool,
164}
165
166#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
168#[cfg_attr(feature = "openapi", derive(ToSchema))]
169#[cfg_attr(feature = "openapi", schema(title ="Sort order direction"))]
170#[serde(rename_all = "lowercase")]
171pub enum SortOrder {
172 #[cfg_attr(feature = "openapi", schema(rename = "asc"))]
174 Asc,
175
176 #[cfg_attr(feature = "openapi", schema(rename = "desc"))]
178 Desc,
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize)]
183#[cfg_attr(feature = "openapi", derive(ToSchema))]
184#[cfg_attr(feature = "openapi", schema(title ="Sorting parameters for list requests"))]
185#[serde(rename_all = "camelCase")]
186pub struct SortParams {
187 #[serde(skip_serializing_if = "Option::is_none")]
189 #[cfg_attr(feature = "openapi", schema(example = "name"))]
190 pub sort_by: Option<String>,
191
192 #[serde(default = "default_sort_order")]
194 pub sort_order: SortOrder,
195}
196
197#[derive(Debug, Clone, Serialize, Deserialize)]
199#[cfg_attr(feature = "openapi", derive(ToSchema))]
200#[cfg_attr(feature = "openapi", schema(title ="Time range filter for queries"))]
201#[serde(rename_all = "camelCase")]
202pub struct TimeRange {
203 #[serde(skip_serializing_if = "Option::is_none")]
205 #[cfg_attr(feature = "openapi", schema(value_type = String, format = DateTime, example = "2024-01-01T00:00:00Z"))]
206 pub start: Option<chrono::DateTime<chrono::Utc>>,
207
208 #[serde(skip_serializing_if = "Option::is_none")]
210 #[cfg_attr(feature = "openapi", schema(value_type = String, format = DateTime, example = "2024-12-31T23:59:59Z"))]
211 pub end: Option<chrono::DateTime<chrono::Utc>>,
212}
213
214#[derive(Debug, Clone, Serialize, Deserialize)]
216#[cfg_attr(feature = "openapi", derive(ToSchema))]
217#[cfg_attr(feature = "openapi", schema(
218 title ="Standard error response",
219 example = json!({
220 "code": "VALIDATION_ERROR",
221 "message": "Invalid input provided",
222 "timestamp": 1704067200
223 })
224))]
225#[serde(rename_all = "camelCase")]
226pub struct ApiError {
227 #[cfg_attr(feature = "openapi", schema(example = "VALIDATION_ERROR"))]
229 pub code: String,
230
231 #[cfg_attr(feature = "openapi", schema(example = "Invalid email format"))]
233 pub message: String,
234
235 #[serde(skip_serializing_if = "Option::is_none")]
237 pub details: Option<ErrorDetails>,
238
239 #[serde(skip_serializing_if = "Option::is_none")]
241 #[cfg_attr(feature = "openapi", schema(example = "req_1234567890"))]
242 pub request_id: Option<String>,
243
244 #[cfg_attr(feature = "openapi", schema(value_type = i64, example = 1704067200))]
246 pub timestamp: Option<i64>,
247}
248
249#[derive(Debug, Clone, Serialize, Deserialize)]
251#[cfg_attr(feature = "openapi", derive(ToSchema))]
252#[cfg_attr(feature = "openapi", schema(title ="Additional details about an error"))]
253#[serde(rename_all = "camelCase")]
254pub struct ErrorDetails {
255 #[serde(skip_serializing_if = "Option::is_none")]
257 #[cfg_attr(feature = "openapi", schema(example = "email"))]
258 pub field: Option<String>,
259
260 #[serde(skip_serializing_if = "Option::is_none")]
262 #[cfg_attr(feature = "openapi", schema(example = "Invalid format"))]
263 pub reason: Option<String>,
264
265 #[serde(skip_serializing_if = "Vec::is_empty")]
267 pub validation_errors: Vec<ValidationError>,
268}
269
270#[derive(Debug, Clone, Serialize, Deserialize)]
272#[cfg_attr(feature = "openapi", derive(ToSchema))]
273#[cfg_attr(feature = "openapi", schema(title ="Details about a field validation error"))]
274#[serde(rename_all = "camelCase")]
275pub struct ValidationError {
276 #[cfg_attr(feature = "openapi", schema(example = "email"))]
278 pub field: String,
279
280 #[cfg_attr(feature = "openapi", schema(example = "format"))]
282 pub code: String,
283
284 #[cfg_attr(feature = "openapi", schema(example = "Email must be a valid email address"))]
286 pub message: String,
287}
288
289#[derive(Debug, Clone, Serialize, Deserialize)]
291#[cfg_attr(feature = "openapi", derive(ToSchema))]
292#[cfg_attr(feature = "openapi", schema(
293 title ="Result of a batch operation showing successes and failures"
294))]
295#[serde(rename_all = "camelCase")]
296pub struct BatchResult<T, E> {
297 pub successful: Vec<T>,
299
300 pub failed: Vec<BatchError<E>>,
302
303 #[cfg_attr(feature = "openapi", schema(example = 100))]
305 pub total: usize,
306
307 #[cfg_attr(feature = "openapi", schema(example = 95))]
309 pub success_count: usize,
310
311 #[cfg_attr(feature = "openapi", schema(example = 5))]
313 pub failure_count: usize,
314}
315
316#[derive(Debug, Clone, Serialize, Deserialize)]
318#[cfg_attr(feature = "openapi", derive(ToSchema))]
319#[cfg_attr(feature = "openapi", schema(title ="Error details for a failed batch item"))]
320#[serde(rename_all = "camelCase")]
321pub struct BatchError<E> {
322 #[cfg_attr(feature = "openapi", schema(example = 42))]
324 pub index: usize,
325
326 #[serde(skip_serializing_if = "Option::is_none")]
328 #[cfg_attr(feature = "openapi", schema(example = "507f1f77bcf86cd799439011"))]
329 pub id: Option<String>,
330
331 pub error: E,
333}
334
335#[derive(Debug, Clone, Serialize, Deserialize)]
337#[cfg_attr(feature = "openapi", derive(ToSchema))]
338#[cfg_attr(feature = "openapi", schema(
339 title ="Standard API response wrapper with success/error handling"
340))]
341#[serde(rename_all = "camelCase")]
342pub struct ApiResponse<T> {
343 #[cfg_attr(feature = "openapi", schema(example = true))]
345 pub success: bool,
346
347 #[serde(skip_serializing_if = "Option::is_none")]
349 pub data: Option<T>,
350
351 #[serde(skip_serializing_if = "Option::is_none")]
353 pub error: Option<ApiError>,
354
355 #[serde(skip_serializing_if = "Option::is_none")]
357 pub metadata: Option<ResponseMetadata>,
358}
359
360#[derive(Debug, Clone, Serialize, Deserialize)]
362#[cfg_attr(feature = "openapi", derive(ToSchema))]
363#[cfg_attr(feature = "openapi", schema(title ="Metadata about the API response"))]
364#[serde(rename_all = "camelCase")]
365pub struct ResponseMetadata {
366 #[cfg_attr(feature = "openapi", schema(example = "req_1234567890"))]
368 pub request_id: String,
369
370 #[cfg_attr(feature = "openapi", schema(value_type = String, format = DateTime, example = "2024-01-01T00:00:00Z"))]
372 pub timestamp: chrono::DateTime<chrono::Utc>,
373
374 #[serde(skip_serializing_if = "Option::is_none")]
376 #[cfg_attr(feature = "openapi", schema(example = "1.0.0"))]
377 pub version: Option<String>,
378
379 #[serde(skip_serializing_if = "Option::is_none")]
381 #[cfg_attr(feature = "openapi", schema(example = 42))]
382 pub processing_time_ms: Option<u64>,
383}
384
385#[derive(Debug, Clone, Serialize, Deserialize)]
387#[cfg_attr(feature = "openapi", derive(ToSchema))]
388#[cfg_attr(feature = "openapi", schema(title ="Parameters for search queries"))]
389#[serde(rename_all = "camelCase")]
390pub struct SearchParams {
391 #[cfg_attr(feature = "openapi", schema(example = "john doe"))]
393 pub query: String,
394
395 #[serde(skip_serializing_if = "Option::is_none")]
397 #[cfg_attr(feature = "openapi", schema(example = json!(["name", "email"])))]
398 pub fields: Option<Vec<String>>,
399
400 #[serde(default)]
402 #[cfg_attr(feature = "openapi", schema(example = false))]
403 pub fuzzy: bool,
404
405 #[serde(skip_serializing_if = "Option::is_none")]
407 #[cfg_attr(feature = "openapi", schema(example = true))]
408 pub highlight: Option<bool>,
409}
410
411#[derive(Debug, Clone, Serialize, Deserialize)]
413#[cfg_attr(feature = "openapi", derive(ToSchema))]
414#[cfg_attr(feature = "openapi", schema(
415 title ="Dynamic filter parameters",
416 example = json!({
417 "status": "active",
418 "category": "premium"
419 })
420))]
421#[serde(rename_all = "camelCase")]
422pub struct FilterParams {
423 #[serde(flatten)]
425 pub filters: serde_json::Map<String, serde_json::Value>,
426}
427
428#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
430#[cfg_attr(feature = "openapi", derive(ToSchema))]
431#[cfg_attr(feature = "openapi", schema(
432 title ="ID wrapper for consistent handling",
433 value_type = String,
434 example = "507f1f77bcf86cd799439011"
435))]
436pub struct Id(pub String);
437
438#[derive(Debug, Clone, Serialize, Deserialize)]
440#[cfg_attr(feature = "openapi", derive(ToSchema))]
441#[cfg_attr(feature = "openapi", schema(title ="Common parameters for list endpoints"))]
442#[serde(rename_all = "camelCase")]
443pub struct ListRequest {
444 #[serde(flatten)]
446 pub pagination: PaginationParams,
447
448 #[serde(flatten)]
450 pub sort: SortParams,
451
452 #[serde(skip_serializing_if = "Option::is_none")]
454 pub search: Option<SearchParams>,
455
456 #[serde(skip_serializing_if = "Option::is_none")]
458 pub time_range: Option<TimeRange>,
459
460 #[serde(skip_serializing_if = "Option::is_none")]
462 pub filters: Option<FilterParams>,
463}
464
465fn default_page_size() -> u32 {
467 20
468}
469
470fn default_sort_order() -> SortOrder {
471 SortOrder::Asc
472}
473
474impl Default for PaginationParams {
476 fn default() -> Self {
477 Self {
478 page: 0,
479 page_size: default_page_size(),
480 }
481 }
482}
483
484impl PaginationParams {
485 pub fn new(page: u32, page_size: u32) -> Self {
486 Self { page, page_size }
487 }
488
489 pub fn offset(&self) -> u64 {
490 (self.page as u64) * (self.page_size as u64)
491 }
492
493 pub fn limit(&self) -> u64 {
494 self.page_size as u64
495 }
496}
497
498impl PaginationMeta {
499 pub fn new(total: u64, page: u32, page_size: u32) -> Self {
500 let total_pages = ((total as f64) / (page_size as f64)).ceil() as u32;
501 Self {
502 total,
503 page,
504 page_size,
505 total_pages,
506 has_next: page < total_pages.saturating_sub(1),
507 has_previous: page > 0,
508 }
509 }
510}
511
512impl<T> PaginatedResponse<T> {
513 pub fn new(data: Vec<T>, total: u64, page: u32, page_size: u32) -> Self {
514 Self {
515 data,
516 pagination: PaginationMeta::new(total, page, page_size),
517 }
518 }
519}
520
521impl Default for SortOrder {
522 fn default() -> Self {
523 Self::Asc
524 }
525}
526
527impl fmt::Display for SortOrder {
528 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
529 match self {
530 SortOrder::Asc => write!(f, "asc"),
531 SortOrder::Desc => write!(f, "desc"),
532 }
533 }
534}
535
536impl Default for SortParams {
537 fn default() -> Self {
538 Self {
539 sort_by: None,
540 sort_order: default_sort_order(),
541 }
542 }
543}
544
545impl ApiError {
546 pub fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
547 Self {
548 code: code.into(),
549 message: message.into(),
550 details: None,
551 request_id: None,
552 timestamp: Some(chrono::Utc::now().timestamp()),
553 }
554 }
555
556 pub fn with_field(mut self, field: impl Into<String>) -> Self {
557 self.details = Some(ErrorDetails {
558 field: Some(field.into()),
559 reason: None,
560 validation_errors: Vec::new(),
561 });
562 self
563 }
564
565 pub fn with_request_id(mut self, request_id: impl Into<String>) -> Self {
566 self.request_id = Some(request_id.into());
567 self
568 }
569}
570
571impl<T> ApiResponse<T> {
572 pub fn success(data: T) -> Self {
573 Self {
574 success: true,
575 data: Some(data),
576 error: None,
577 metadata: None,
578 }
579 }
580
581 pub fn error(error: ApiError) -> Self {
582 Self {
583 success: false,
584 data: None,
585 error: Some(error),
586 metadata: None,
587 }
588 }
589
590 pub fn with_metadata(mut self, metadata: ResponseMetadata) -> Self {
591 self.metadata = Some(metadata);
592 self
593 }
594}
595
596impl<T, E> BatchResult<T, E> {
597 pub fn new(successful: Vec<T>, failed: Vec<BatchError<E>>) -> Self {
598 let success_count = successful.len();
599 let failure_count = failed.len();
600 Self {
601 successful,
602 failed,
603 total: success_count + failure_count,
604 success_count,
605 failure_count,
606 }
607 }
608
609 pub fn all_succeeded(&self) -> bool {
610 self.failure_count == 0
611 }
612
613 pub fn all_failed(&self) -> bool {
614 self.success_count == 0
615 }
616}
617
618impl fmt::Display for Id {
619 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
620 write!(f, "{}", self.0)
621 }
622}
623
624impl From<String> for Id {
625 fn from(s: String) -> Self {
626 Id(s)
627 }
628}
629
630impl From<&str> for Id {
631 fn from(s: &str) -> Self {
632 Id(s.to_string())
633 }
634}
635
636impl TimeRange {
637 pub fn new(start: Option<chrono::DateTime<chrono::Utc>>, end: Option<chrono::DateTime<chrono::Utc>>) -> Self {
638 Self { start, end }
639 }
640
641 pub fn is_valid(&self) -> bool {
642 match (self.start, self.end) {
643 (Some(start), Some(end)) => start <= end,
644 _ => true,
645 }
646 }
647}