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.");
56define_id_type!(
57 SchedulerConfigId,
58 "Scheduler configuration ID newtype for type safety."
59);
60define_id_type!(
61 SchedulerSessionId,
62 "Scheduler session ID newtype for type safety."
63);
64define_id_type!(
65 SchedulerBookingId,
66 "Scheduler booking ID newtype for type safety."
67);
68define_id_type!(NotetakerId, "Notetaker ID newtype for type safety.");
69define_id_type!(RecordingId, "Recording ID newtype for type safety.");
70define_id_type!(ResourceId, "Resource ID newtype for type safety.");
71
72#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
74#[serde(rename_all = "lowercase")]
75pub enum Provider {
76 Google,
78 Microsoft,
80 Imap,
82 Yahoo,
84 Icloud,
86 #[serde(rename = "virtual-calendar")]
88 VirtualCalendar,
89}
90
91#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
103pub struct EmailAddress {
104 pub email: String,
106
107 #[serde(skip_serializing_if = "Option::is_none")]
109 pub name: Option<String>,
110}
111
112impl EmailAddress {
113 pub fn new(email: impl Into<String>) -> Result<Self, ValidationError> {
119 let email = email.into();
120 Self::validate(&email)?;
121
122 Ok(Self { email, name: None })
123 }
124
125 pub fn with_name(mut self, name: impl Into<String>) -> Self {
127 self.name = Some(name.into());
128 self
129 }
130
131 fn validate(email: &str) -> Result<(), ValidationError> {
133 if email.is_empty() {
134 return Err(ValidationError::Empty);
135 }
136
137 if !email.contains('@') {
138 return Err(ValidationError::MissingAt);
139 }
140
141 let parts: Vec<&str> = email.split('@').collect();
142 if parts.len() != 2 {
143 return Err(ValidationError::InvalidFormat);
144 }
145
146 if parts[0].is_empty() || parts[1].is_empty() {
147 return Err(ValidationError::InvalidFormat);
148 }
149
150 if !parts[1].contains('.') {
151 return Err(ValidationError::InvalidDomain);
152 }
153
154 Ok(())
155 }
156
157 pub fn email(&self) -> &str {
159 &self.email
160 }
161
162 pub fn name(&self) -> Option<&str> {
164 self.name.as_deref()
165 }
166}
167
168#[derive(Debug, Clone, PartialEq, Eq)]
170pub enum ValidationError {
171 Empty,
173 MissingAt,
175 InvalidFormat,
177 InvalidDomain,
179}
180
181impl fmt::Display for ValidationError {
182 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183 match self {
184 Self::Empty => write!(f, "Email address cannot be empty"),
185 Self::MissingAt => write!(f, "Email address must contain '@'"),
186 Self::InvalidFormat => write!(f, "Email address has invalid format"),
187 Self::InvalidDomain => write!(f, "Email domain is invalid"),
188 }
189 }
190}
191
192impl std::error::Error for ValidationError {}
193
194#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
198pub struct ApiResponse<T> {
199 pub data: Vec<T>,
201
202 #[serde(skip_serializing_if = "Option::is_none")]
204 pub request_id: Option<String>,
205
206 #[serde(skip_serializing_if = "Option::is_none")]
208 pub next_cursor: Option<String>,
209}
210
211impl<T> ApiResponse<T> {
212 pub fn new(data: Vec<T>) -> Self {
214 Self {
215 data,
216 request_id: None,
217 next_cursor: None,
218 }
219 }
220
221 pub fn has_next_page(&self) -> bool {
223 self.next_cursor.is_some()
224 }
225}
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230
231 #[test]
232 fn test_grant_id_creation() {
233 let id = GrantId::new("grant_123");
234 assert_eq!(id.as_str(), "grant_123");
235 assert_eq!(id.to_string(), "grant_123");
236 }
237
238 #[test]
239 fn test_message_id_from_string() {
240 let id = MessageId::from("msg_456");
241 assert_eq!(id.as_str(), "msg_456");
242 }
243
244 #[test]
245 fn test_id_types_are_distinct() {
246 let grant_id = GrantId::new("123");
247 let message_id = MessageId::new("123");
248
249 assert_eq!(grant_id.as_str(), message_id.as_str());
251 }
254
255 #[test]
256 fn test_email_address_valid() {
257 let addr = EmailAddress::new("user@example.com");
258 assert!(addr.is_ok());
259
260 let addr = addr.unwrap();
261 assert_eq!(addr.email(), "user@example.com");
262 assert_eq!(addr.name(), None);
263 }
264
265 #[test]
266 fn test_email_address_with_name() {
267 let addr = EmailAddress::new("user@example.com")
268 .unwrap()
269 .with_name("User Name");
270
271 assert_eq!(addr.email(), "user@example.com");
272 assert_eq!(addr.name(), Some("User Name"));
273 }
274
275 #[test]
276 fn test_email_address_empty() {
277 let addr = EmailAddress::new("");
278 assert!(addr.is_err());
279 assert_eq!(addr.unwrap_err(), ValidationError::Empty);
280 }
281
282 #[test]
283 fn test_email_address_missing_at() {
284 let addr = EmailAddress::new("invalid");
285 assert!(addr.is_err());
286 assert_eq!(addr.unwrap_err(), ValidationError::MissingAt);
287 }
288
289 #[test]
290 fn test_email_address_invalid_format() {
291 let addr = EmailAddress::new("@example.com");
292 assert!(addr.is_err());
293 assert_eq!(addr.unwrap_err(), ValidationError::InvalidFormat);
294
295 let addr = EmailAddress::new("user@");
296 assert!(addr.is_err());
297 assert_eq!(addr.unwrap_err(), ValidationError::InvalidFormat);
298 }
299
300 #[test]
301 fn test_email_address_invalid_domain() {
302 let addr = EmailAddress::new("user@domain");
303 assert!(addr.is_err());
304 assert_eq!(addr.unwrap_err(), ValidationError::InvalidDomain);
305 }
306
307 #[test]
308 fn test_email_address_serialization() {
309 let addr = EmailAddress::new("user@example.com")
310 .unwrap()
311 .with_name("User");
312
313 let json = serde_json::to_string(&addr).unwrap();
314 assert!(json.contains("user@example.com"));
315 assert!(json.contains("User"));
316
317 let deserialized: EmailAddress = serde_json::from_str(&json).unwrap();
318 assert_eq!(deserialized, addr);
319 }
320
321 #[test]
322 fn test_api_response_creation() {
323 let response = ApiResponse::new(vec!["item1".to_string(), "item2".to_string()]);
324 assert_eq!(response.data.len(), 2);
325 assert!(!response.has_next_page());
326 }
327
328 #[test]
329 fn test_api_response_with_pagination() {
330 let mut response = ApiResponse::new(vec!["item1".to_string()]);
331 response.next_cursor = Some("cursor_123".to_string());
332
333 assert!(response.has_next_page());
334 }
335
336 #[test]
337 fn test_api_response_serialization() {
338 let response = ApiResponse {
339 data: vec!["test".to_string()],
340 request_id: Some("req_123".to_string()),
341 next_cursor: Some("cursor_456".to_string()),
342 };
343
344 let json = serde_json::to_string(&response).unwrap();
345 assert!(json.contains("test"));
346 assert!(json.contains("req_123"));
347 assert!(json.contains("cursor_456"));
348
349 let deserialized: ApiResponse<String> = serde_json::from_str(&json).unwrap();
350 assert_eq!(deserialized, response);
351 }
352
353 #[test]
354 fn test_provider_serialization() {
355 let provider = Provider::Google;
356 let json = serde_json::to_string(&provider).unwrap();
357 assert_eq!(json, "\"google\"");
358
359 let provider = Provider::VirtualCalendar;
360 let json = serde_json::to_string(&provider).unwrap();
361 assert_eq!(json, "\"virtual-calendar\"");
362 }
363
364 #[test]
365 fn test_id_serialization() {
366 let grant_id = GrantId::new("grant_123");
367 let json = serde_json::to_string(&grant_id).unwrap();
368 assert_eq!(json, "\"grant_123\"");
369
370 let deserialized: GrantId = serde_json::from_str(&json).unwrap();
371 assert_eq!(deserialized, grant_id);
372 }
373}