mkt_core/models/
common.rs1use std::fmt;
4
5use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct Paginated<T> {
11 pub data: Vec<T>,
13 pub next_cursor: Option<String>,
15 pub total: Option<u64>,
17}
18
19impl<T> Default for Paginated<T> {
20 fn default() -> Self {
21 Self {
22 data: Vec::new(),
23 next_cursor: None,
24 total: None,
25 }
26 }
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct Budget {
32 pub amount: f64,
34 pub currency: String,
36 pub kind: BudgetKind,
38}
39
40#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
42#[serde(rename_all = "snake_case")]
43pub enum BudgetKind {
44 Daily,
46 Lifetime,
48}
49
50impl fmt::Display for BudgetKind {
51 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52 match self {
53 Self::Daily => write!(f, "daily"),
54 Self::Lifetime => write!(f, "lifetime"),
55 }
56 }
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct DateRange {
62 pub start: DateTime<Utc>,
64 pub end: DateTime<Utc>,
66}
67
68#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
70#[serde(rename_all = "UPPERCASE")]
71pub enum HttpMethod {
72 Get,
74 Post,
76 Delete,
78}
79
80impl fmt::Display for HttpMethod {
81 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82 match self {
83 Self::Get => write!(f, "GET"),
84 Self::Post => write!(f, "POST"),
85 Self::Delete => write!(f, "DELETE"),
86 }
87 }
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct ProviderHealth {
93 pub provider: String,
95 pub healthy: bool,
97 pub latency_ms: u64,
99 #[serde(skip_serializing_if = "Option::is_none")]
101 pub message: Option<String>,
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 #[test]
109 fn paginated_default_is_empty() {
110 let page: Paginated<String> = Paginated::default();
111 assert!(page.data.is_empty());
112 assert!(page.next_cursor.is_none());
113 assert!(page.total.is_none());
114 }
115
116 #[test]
117 fn budget_kind_display() {
118 assert_eq!(BudgetKind::Daily.to_string(), "daily");
119 assert_eq!(BudgetKind::Lifetime.to_string(), "lifetime");
120 }
121
122 #[test]
123 #[allow(clippy::expect_used)]
124 fn budget_kind_serde_roundtrip() {
125 let json = serde_json::to_string(&BudgetKind::Daily).expect("serialize");
126 assert_eq!(json, r#""daily""#);
127 let back: BudgetKind = serde_json::from_str(&json).expect("deserialize");
128 assert_eq!(back, BudgetKind::Daily);
129 }
130
131 #[test]
132 fn http_method_display() {
133 assert_eq!(HttpMethod::Get.to_string(), "GET");
134 assert_eq!(HttpMethod::Post.to_string(), "POST");
135 assert_eq!(HttpMethod::Delete.to_string(), "DELETE");
136 }
137
138 #[test]
139 #[allow(clippy::expect_used)]
140 fn http_method_serde_roundtrip() {
141 let json = serde_json::to_string(&HttpMethod::Post).expect("serialize");
142 assert_eq!(json, r#""POST""#);
143 let back: HttpMethod = serde_json::from_str(&json).expect("deserialize");
144 assert_eq!(back, HttpMethod::Post);
145 }
146
147 #[test]
148 #[allow(clippy::expect_used)]
149 fn provider_health_serializes_without_none_message() {
150 let health = ProviderHealth {
151 provider: "meta".into(),
152 healthy: true,
153 latency_ms: 42,
154 message: None,
155 };
156 let json = serde_json::to_string(&health).expect("serialize");
157 assert!(!json.contains("message"));
158 }
159}