1use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9use crate::common::WebhookId;
10
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
30pub struct Webhook {
31 pub id: WebhookId,
33
34 #[serde(skip_serializing_if = "Option::is_none")]
36 pub description: Option<String>,
37
38 pub trigger_types: Vec<WebhookTrigger>,
40
41 pub webhook_url: String,
43
44 #[serde(skip_serializing_if = "Option::is_none")]
46 pub webhook_secret: Option<String>,
47
48 #[serde(default)]
50 pub notification_email_addresses: Vec<String>,
51
52 #[serde(skip_serializing_if = "Option::is_none")]
57 pub privacy_mode: Option<PrivacyMode>,
58}
59
60#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
62pub struct PrivacyMode {
63 pub enabled: bool,
65
66 #[serde(default)]
68 pub redact_emails: bool,
69
70 #[serde(default)]
72 pub redact_bodies: bool,
73
74 #[serde(default)]
76 pub redact_names: bool,
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
83#[serde(rename_all = "kebab-case")]
84pub enum WebhookTrigger {
85 MessageCreated,
87 MessageUpdated,
89 MessageDeleted,
91
92 ThreadCreated,
94 ThreadUpdated,
96
97 DraftCreated,
99 DraftUpdated,
101 DraftDeleted,
103
104 EventCreated,
106 EventUpdated,
108 EventDeleted,
110
111 CalendarCreated,
113 CalendarUpdated,
115 CalendarDeleted,
117
118 ContactCreated,
120 ContactUpdated,
122 ContactDeleted,
124
125 FolderCreated,
127 FolderUpdated,
129 FolderDeleted,
131
132 GrantCreated,
134 GrantUpdated,
136 GrantDeleted,
138 GrantExpired,
140}
141
142#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
164pub struct WebhookNotification {
165 pub id: String,
167
168 pub grant_id: String,
170
171 pub application_id: String,
173
174 pub trigger: String,
176
177 pub timestamp: i64,
179
180 pub data: Value,
182
183 #[serde(skip_serializing_if = "Option::is_none")]
185 pub calendar_id: Option<String>,
186
187 #[serde(skip_serializing_if = "Option::is_none")]
189 pub master_event_id: Option<String>,
190}
191
192#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
206pub struct CreateWebhookRequest {
207 #[serde(skip_serializing_if = "Option::is_none")]
209 pub description: Option<String>,
210
211 pub webhook_url: String,
213
214 pub trigger_types: Vec<WebhookTrigger>,
216
217 #[serde(skip_serializing_if = "Option::is_none")]
219 pub webhook_secret: Option<String>,
220
221 #[serde(skip_serializing_if = "Option::is_none")]
223 pub notification_email_addresses: Option<Vec<String>>,
224
225 #[serde(skip_serializing_if = "Option::is_none")]
227 pub privacy_mode: Option<PrivacyMode>,
228}
229
230impl CreateWebhookRequest {
231 pub fn builder() -> CreateWebhookRequestBuilder {
233 CreateWebhookRequestBuilder::default()
234 }
235}
236
237#[derive(Debug, Clone, Default)]
239pub struct CreateWebhookRequestBuilder {
240 description: Option<String>,
241 webhook_url: Option<String>,
242 trigger_types: Option<Vec<WebhookTrigger>>,
243 webhook_secret: Option<String>,
244 notification_email_addresses: Option<Vec<String>>,
245 privacy_mode: Option<PrivacyMode>,
246}
247
248impl CreateWebhookRequestBuilder {
249 pub fn description(mut self, description: impl Into<String>) -> Self {
251 self.description = Some(description.into());
252 self
253 }
254
255 pub fn webhook_url(mut self, url: impl Into<String>) -> Self {
257 self.webhook_url = Some(url.into());
258 self
259 }
260
261 pub fn trigger_types(mut self, triggers: Vec<WebhookTrigger>) -> Self {
263 self.trigger_types = Some(triggers);
264 self
265 }
266
267 pub fn webhook_secret(mut self, secret: impl Into<String>) -> Self {
269 self.webhook_secret = Some(secret.into());
270 self
271 }
272
273 pub fn notification_email_addresses(mut self, emails: Vec<String>) -> Self {
275 self.notification_email_addresses = Some(emails);
276 self
277 }
278
279 pub fn privacy_mode(mut self, mode: PrivacyMode) -> Self {
300 self.privacy_mode = Some(mode);
301 self
302 }
303
304 pub fn build(self) -> CreateWebhookRequest {
310 CreateWebhookRequest {
311 description: self.description,
312 webhook_url: self.webhook_url.expect("webhook_url is required"),
313 trigger_types: self.trigger_types.expect("trigger_types is required"),
314 webhook_secret: self.webhook_secret,
315 notification_email_addresses: self.notification_email_addresses,
316 privacy_mode: self.privacy_mode,
317 }
318 }
319}
320
321#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
335pub struct UpdateWebhookRequest {
336 #[serde(skip_serializing_if = "Option::is_none")]
338 pub description: Option<String>,
339
340 #[serde(skip_serializing_if = "Option::is_none")]
342 pub webhook_url: Option<String>,
343
344 #[serde(skip_serializing_if = "Option::is_none")]
346 pub trigger_types: Option<Vec<WebhookTrigger>>,
347
348 #[serde(skip_serializing_if = "Option::is_none")]
350 pub webhook_secret: Option<String>,
351
352 #[serde(skip_serializing_if = "Option::is_none")]
354 pub notification_email_addresses: Option<Vec<String>>,
355}
356
357impl UpdateWebhookRequest {
358 pub fn builder() -> UpdateWebhookRequestBuilder {
360 UpdateWebhookRequestBuilder::default()
361 }
362}
363
364#[derive(Debug, Clone, Default)]
366pub struct UpdateWebhookRequestBuilder {
367 description: Option<String>,
368 webhook_url: Option<String>,
369 trigger_types: Option<Vec<WebhookTrigger>>,
370 webhook_secret: Option<String>,
371 notification_email_addresses: Option<Vec<String>>,
372}
373
374impl UpdateWebhookRequestBuilder {
375 pub fn description(mut self, description: impl Into<String>) -> Self {
377 self.description = Some(description.into());
378 self
379 }
380
381 pub fn webhook_url(mut self, url: impl Into<String>) -> Self {
383 self.webhook_url = Some(url.into());
384 self
385 }
386
387 pub fn trigger_types(mut self, triggers: Vec<WebhookTrigger>) -> Self {
389 self.trigger_types = Some(triggers);
390 self
391 }
392
393 pub fn webhook_secret(mut self, secret: impl Into<String>) -> Self {
395 self.webhook_secret = Some(secret.into());
396 self
397 }
398
399 pub fn notification_email_addresses(mut self, emails: Vec<String>) -> Self {
401 self.notification_email_addresses = Some(emails);
402 self
403 }
404
405 pub fn build(self) -> UpdateWebhookRequest {
407 UpdateWebhookRequest {
408 description: self.description,
409 webhook_url: self.webhook_url,
410 trigger_types: self.trigger_types,
411 webhook_secret: self.webhook_secret,
412 notification_email_addresses: self.notification_email_addresses,
413 }
414 }
415}
416
417pub fn verify_webhook_signature(payload: &[u8], signature: &str, secret: &str) -> bool {
446 use hmac::{Hmac, Mac};
447 use sha2::Sha256;
448
449 type HmacSha256 = Hmac<Sha256>;
450
451 let mut mac = match HmacSha256::new_from_slice(secret.as_bytes()) {
452 Ok(m) => m,
453 Err(_) => return false,
454 };
455
456 mac.update(payload);
457
458 let result = mac.finalize();
459 let expected = hex::encode(result.into_bytes());
460
461 signature == expected
462}
463
464#[cfg(test)]
465mod tests {
466 use super::*;
467
468 #[test]
469 fn test_webhook_serialization() {
470 let webhook = Webhook {
471 id: WebhookId::new("webhook_123"),
472 description: Some("Test webhook".to_string()),
473 trigger_types: vec![WebhookTrigger::MessageCreated],
474 webhook_url: "https://example.com/webhook".to_string(),
475 webhook_secret: Some("secret".to_string()),
476 notification_email_addresses: vec!["admin@example.com".to_string()],
477 privacy_mode: None,
478 };
479
480 let json = serde_json::to_string(&webhook).unwrap();
481 assert!(json.contains("webhook_123"));
482 assert!(json.contains("https://example.com/webhook"));
483 }
484
485 #[test]
486 fn test_webhook_notification_deserialization() {
487 let json = r#"{
488 "id": "notif_123",
489 "grant_id": "grant_456",
490 "application_id": "app_789",
491 "trigger": "message.created",
492 "timestamp": 1609459200,
493 "data": {"id": "msg_abc", "subject": "Test"}
494 }"#;
495
496 let notification: WebhookNotification = serde_json::from_str(json).unwrap();
497 assert_eq!(notification.id, "notif_123");
498 assert_eq!(notification.trigger, "message.created");
499 assert_eq!(notification.timestamp, 1609459200);
500 }
501
502 #[test]
503 fn test_webhook_trigger_serialization() {
504 let trigger = WebhookTrigger::MessageCreated;
505 let json = serde_json::to_string(&trigger).unwrap();
506 assert_eq!(json, "\"message-created\"");
507
508 let trigger = WebhookTrigger::EventUpdated;
509 let json = serde_json::to_string(&trigger).unwrap();
510 assert_eq!(json, "\"event-updated\"");
511 }
512
513 #[test]
514 fn test_create_webhook_request_builder() {
515 let request = CreateWebhookRequest::builder()
516 .description("My webhook")
517 .webhook_url("https://api.example.com/webhooks")
518 .trigger_types(vec![WebhookTrigger::MessageCreated])
519 .webhook_secret("secret_key")
520 .notification_email_addresses(vec!["admin@example.com".to_string()])
521 .build();
522
523 assert_eq!(request.description, Some("My webhook".to_string()));
524 assert_eq!(request.webhook_url, "https://api.example.com/webhooks");
525 assert_eq!(request.trigger_types.len(), 1);
526 assert_eq!(request.webhook_secret, Some("secret_key".to_string()));
527 }
528
529 #[test]
530 fn test_update_webhook_request_builder() {
531 let update = UpdateWebhookRequest::builder()
532 .description("Updated")
533 .trigger_types(vec![
534 WebhookTrigger::MessageCreated,
535 WebhookTrigger::MessageUpdated,
536 ])
537 .build();
538
539 assert_eq!(update.description, Some("Updated".to_string()));
540 assert_eq!(update.trigger_types.as_ref().unwrap().len(), 2);
541 assert!(update.webhook_url.is_none());
542 }
543
544 #[test]
545 fn test_verify_webhook_signature() {
546 let payload = b"test payload";
547 let secret = "test_secret";
548
549 use hmac::{Hmac, Mac};
551 use sha2::Sha256;
552 type HmacSha256 = Hmac<Sha256>;
553
554 let mut mac = HmacSha256::new_from_slice(secret.as_bytes()).unwrap();
555 mac.update(payload);
556 let result = mac.finalize();
557 let signature = hex::encode(result.into_bytes());
558
559 assert!(verify_webhook_signature(payload, &signature, secret));
561
562 assert!(!verify_webhook_signature(
564 payload,
565 "wrong_signature",
566 secret
567 ));
568
569 assert!(!verify_webhook_signature(
571 payload,
572 &signature,
573 "wrong_secret"
574 ));
575 }
576
577 #[test]
578 fn test_privacy_mode_default() {
579 let mode = PrivacyMode::default();
580 assert!(!mode.enabled);
581 assert!(!mode.redact_emails);
582 }
583
584 #[test]
585 fn test_privacy_mode_enabled() {
586 let mode = PrivacyMode {
587 enabled: true,
588 redact_emails: true,
589 redact_bodies: true,
590 redact_names: true,
591 };
592
593 let json = serde_json::to_string(&mode).unwrap();
594 assert!(json.contains("\"enabled\":true"));
595 assert!(json.contains("\"redact_emails\":true"));
596 }
597
598 #[test]
599 fn test_webhook_with_privacy_mode() {
600 let privacy = PrivacyMode {
601 enabled: true,
602 redact_emails: true,
603 redact_bodies: false,
604 redact_names: false,
605 };
606
607 let request = CreateWebhookRequest::builder()
608 .webhook_url("https://example.com")
609 .trigger_types(vec![WebhookTrigger::MessageCreated])
610 .privacy_mode(privacy.clone())
611 .build();
612
613 assert!(request.privacy_mode.is_some());
614 let mode = request.privacy_mode.unwrap();
615 assert!(mode.enabled);
616 assert!(mode.redact_emails);
617 }
618}