1use serde::{Deserialize, Serialize};
4use std::fmt;
5
6macro_rules! define_id_type {
8 ($name:ident, $doc:expr) => {
9 #[doc = $doc]
10 #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
11 #[serde(transparent)]
12 pub struct $name(String);
13
14 impl $name {
15 pub fn new(id: impl Into<String>) -> Self {
17 Self(id.into())
18 }
19
20 pub fn as_str(&self) -> &str {
22 &self.0
23 }
24 }
25
26 impl fmt::Display for $name {
27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28 write!(f, "{}", self.0)
29 }
30 }
31
32 impl From<String> for $name {
33 fn from(s: String) -> Self {
34 Self(s)
35 }
36 }
37
38 impl From<&str> for $name {
39 fn from(s: &str) -> Self {
40 Self(s.to_string())
41 }
42 }
43 };
44}
45
46define_id_type!(GrantId, "Grant ID newtype for type safety.");
48define_id_type!(MessageId, "Message ID newtype for type safety.");
49define_id_type!(ThreadId, "Thread ID newtype for type safety.");
50define_id_type!(DraftId, "Draft ID newtype for type safety.");
51define_id_type!(CalendarId, "Calendar ID newtype for type safety.");
52define_id_type!(EventId, "Event ID newtype for type safety.");
53define_id_type!(ContactId, "Contact ID newtype for type safety.");
54define_id_type!(FolderId, "Folder ID newtype for type safety.");
55define_id_type!(WebhookId, "Webhook ID newtype for type safety.");
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
59#[serde(rename_all = "lowercase")]
60pub enum Provider {
61 Google,
63 Microsoft,
65 Imap,
67 Yahoo,
69 Icloud,
71 #[serde(rename = "virtual-calendar")]
73 VirtualCalendar,
74}
75
76#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
88pub struct EmailAddress {
89 pub email: String,
91
92 #[serde(skip_serializing_if = "Option::is_none")]
94 pub name: Option<String>,
95}
96
97impl EmailAddress {
98 pub fn new(email: impl Into<String>) -> Result<Self, ValidationError> {
104 let email = email.into();
105 Self::validate(&email)?;
106
107 Ok(Self { email, name: None })
108 }
109
110 pub fn with_name(mut self, name: impl Into<String>) -> Self {
112 self.name = Some(name.into());
113 self
114 }
115
116 fn validate(email: &str) -> Result<(), ValidationError> {
118 if email.is_empty() {
119 return Err(ValidationError::Empty);
120 }
121
122 if !email.contains('@') {
123 return Err(ValidationError::MissingAt);
124 }
125
126 let parts: Vec<&str> = email.split('@').collect();
127 if parts.len() != 2 {
128 return Err(ValidationError::InvalidFormat);
129 }
130
131 if parts[0].is_empty() || parts[1].is_empty() {
132 return Err(ValidationError::InvalidFormat);
133 }
134
135 if !parts[1].contains('.') {
136 return Err(ValidationError::InvalidDomain);
137 }
138
139 Ok(())
140 }
141
142 pub fn email(&self) -> &str {
144 &self.email
145 }
146
147 pub fn name(&self) -> Option<&str> {
149 self.name.as_deref()
150 }
151}
152
153#[derive(Debug, Clone, PartialEq, Eq)]
155pub enum ValidationError {
156 Empty,
158 MissingAt,
160 InvalidFormat,
162 InvalidDomain,
164}
165
166impl fmt::Display for ValidationError {
167 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168 match self {
169 Self::Empty => write!(f, "Email address cannot be empty"),
170 Self::MissingAt => write!(f, "Email address must contain '@'"),
171 Self::InvalidFormat => write!(f, "Email address has invalid format"),
172 Self::InvalidDomain => write!(f, "Email domain is invalid"),
173 }
174 }
175}
176
177impl std::error::Error for ValidationError {}
178
179#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
183pub struct ApiResponse<T> {
184 pub data: Vec<T>,
186
187 #[serde(skip_serializing_if = "Option::is_none")]
189 pub request_id: Option<String>,
190
191 #[serde(skip_serializing_if = "Option::is_none")]
193 pub next_cursor: Option<String>,
194}
195
196impl<T> ApiResponse<T> {
197 pub fn new(data: Vec<T>) -> Self {
199 Self {
200 data,
201 request_id: None,
202 next_cursor: None,
203 }
204 }
205
206 pub fn has_next_page(&self) -> bool {
208 self.next_cursor.is_some()
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use super::*;
215
216 #[test]
217 fn test_grant_id_creation() {
218 let id = GrantId::new("grant_123");
219 assert_eq!(id.as_str(), "grant_123");
220 assert_eq!(id.to_string(), "grant_123");
221 }
222
223 #[test]
224 fn test_message_id_from_string() {
225 let id = MessageId::from("msg_456");
226 assert_eq!(id.as_str(), "msg_456");
227 }
228
229 #[test]
230 fn test_id_types_are_distinct() {
231 let grant_id = GrantId::new("123");
232 let message_id = MessageId::new("123");
233
234 assert_eq!(grant_id.as_str(), message_id.as_str());
236 }
239
240 #[test]
241 fn test_email_address_valid() {
242 let addr = EmailAddress::new("user@example.com");
243 assert!(addr.is_ok());
244
245 let addr = addr.unwrap();
246 assert_eq!(addr.email(), "user@example.com");
247 assert_eq!(addr.name(), None);
248 }
249
250 #[test]
251 fn test_email_address_with_name() {
252 let addr = EmailAddress::new("user@example.com")
253 .unwrap()
254 .with_name("User Name");
255
256 assert_eq!(addr.email(), "user@example.com");
257 assert_eq!(addr.name(), Some("User Name"));
258 }
259
260 #[test]
261 fn test_email_address_empty() {
262 let addr = EmailAddress::new("");
263 assert!(addr.is_err());
264 assert_eq!(addr.unwrap_err(), ValidationError::Empty);
265 }
266
267 #[test]
268 fn test_email_address_missing_at() {
269 let addr = EmailAddress::new("invalid");
270 assert!(addr.is_err());
271 assert_eq!(addr.unwrap_err(), ValidationError::MissingAt);
272 }
273
274 #[test]
275 fn test_email_address_invalid_format() {
276 let addr = EmailAddress::new("@example.com");
277 assert!(addr.is_err());
278 assert_eq!(addr.unwrap_err(), ValidationError::InvalidFormat);
279
280 let addr = EmailAddress::new("user@");
281 assert!(addr.is_err());
282 assert_eq!(addr.unwrap_err(), ValidationError::InvalidFormat);
283 }
284
285 #[test]
286 fn test_email_address_invalid_domain() {
287 let addr = EmailAddress::new("user@domain");
288 assert!(addr.is_err());
289 assert_eq!(addr.unwrap_err(), ValidationError::InvalidDomain);
290 }
291
292 #[test]
293 fn test_email_address_serialization() {
294 let addr = EmailAddress::new("user@example.com")
295 .unwrap()
296 .with_name("User");
297
298 let json = serde_json::to_string(&addr).unwrap();
299 assert!(json.contains("user@example.com"));
300 assert!(json.contains("User"));
301
302 let deserialized: EmailAddress = serde_json::from_str(&json).unwrap();
303 assert_eq!(deserialized, addr);
304 }
305
306 #[test]
307 fn test_api_response_creation() {
308 let response = ApiResponse::new(vec!["item1".to_string(), "item2".to_string()]);
309 assert_eq!(response.data.len(), 2);
310 assert!(!response.has_next_page());
311 }
312
313 #[test]
314 fn test_api_response_with_pagination() {
315 let mut response = ApiResponse::new(vec!["item1".to_string()]);
316 response.next_cursor = Some("cursor_123".to_string());
317
318 assert!(response.has_next_page());
319 }
320
321 #[test]
322 fn test_api_response_serialization() {
323 let response = ApiResponse {
324 data: vec!["test".to_string()],
325 request_id: Some("req_123".to_string()),
326 next_cursor: Some("cursor_456".to_string()),
327 };
328
329 let json = serde_json::to_string(&response).unwrap();
330 assert!(json.contains("test"));
331 assert!(json.contains("req_123"));
332 assert!(json.contains("cursor_456"));
333
334 let deserialized: ApiResponse<String> = serde_json::from_str(&json).unwrap();
335 assert_eq!(deserialized, response);
336 }
337
338 #[test]
339 fn test_provider_serialization() {
340 let provider = Provider::Google;
341 let json = serde_json::to_string(&provider).unwrap();
342 assert_eq!(json, "\"google\"");
343
344 let provider = Provider::VirtualCalendar;
345 let json = serde_json::to_string(&provider).unwrap();
346 assert_eq!(json, "\"virtual-calendar\"");
347 }
348
349 #[test]
350 fn test_id_serialization() {
351 let grant_id = GrantId::new("grant_123");
352 let json = serde_json::to_string(&grant_id).unwrap();
353 assert_eq!(json, "\"grant_123\"");
354
355 let deserialized: GrantId = serde_json::from_str(&json).unwrap();
356 assert_eq!(deserialized, grant_id);
357 }
358}