1use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9use crate::common::WebhookId;
10
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
29pub struct Webhook {
30 pub id: WebhookId,
32
33 #[serde(skip_serializing_if = "Option::is_none")]
35 pub description: Option<String>,
36
37 pub trigger_types: Vec<WebhookTrigger>,
39
40 pub webhook_url: String,
42
43 #[serde(skip_serializing_if = "Option::is_none")]
45 pub webhook_secret: Option<String>,
46
47 #[serde(default)]
49 pub notification_email_addresses: Vec<String>,
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
56#[serde(rename_all = "kebab-case")]
57pub enum WebhookTrigger {
58 MessageCreated,
60 MessageUpdated,
62 MessageDeleted,
64
65 ThreadCreated,
67 ThreadUpdated,
69
70 DraftCreated,
72 DraftUpdated,
74 DraftDeleted,
76
77 EventCreated,
79 EventUpdated,
81 EventDeleted,
83
84 CalendarCreated,
86 CalendarUpdated,
88 CalendarDeleted,
90
91 ContactCreated,
93 ContactUpdated,
95 ContactDeleted,
97
98 FolderCreated,
100 FolderUpdated,
102 FolderDeleted,
104
105 GrantCreated,
107 GrantUpdated,
109 GrantDeleted,
111 GrantExpired,
113}
114
115#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
137pub struct WebhookNotification {
138 pub id: String,
140
141 pub grant_id: String,
143
144 pub application_id: String,
146
147 pub trigger: String,
149
150 pub timestamp: i64,
152
153 pub data: Value,
155
156 #[serde(skip_serializing_if = "Option::is_none")]
158 pub calendar_id: Option<String>,
159
160 #[serde(skip_serializing_if = "Option::is_none")]
162 pub master_event_id: Option<String>,
163}
164
165#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
179pub struct CreateWebhookRequest {
180 #[serde(skip_serializing_if = "Option::is_none")]
182 pub description: Option<String>,
183
184 pub webhook_url: String,
186
187 pub trigger_types: Vec<WebhookTrigger>,
189
190 #[serde(skip_serializing_if = "Option::is_none")]
192 pub webhook_secret: Option<String>,
193
194 #[serde(skip_serializing_if = "Option::is_none")]
196 pub notification_email_addresses: Option<Vec<String>>,
197}
198
199impl CreateWebhookRequest {
200 pub fn builder() -> CreateWebhookRequestBuilder {
202 CreateWebhookRequestBuilder::default()
203 }
204}
205
206#[derive(Debug, Clone, Default)]
208pub struct CreateWebhookRequestBuilder {
209 description: Option<String>,
210 webhook_url: Option<String>,
211 trigger_types: Option<Vec<WebhookTrigger>>,
212 webhook_secret: Option<String>,
213 notification_email_addresses: Option<Vec<String>>,
214}
215
216impl CreateWebhookRequestBuilder {
217 pub fn description(mut self, description: impl Into<String>) -> Self {
219 self.description = Some(description.into());
220 self
221 }
222
223 pub fn webhook_url(mut self, url: impl Into<String>) -> Self {
225 self.webhook_url = Some(url.into());
226 self
227 }
228
229 pub fn trigger_types(mut self, triggers: Vec<WebhookTrigger>) -> Self {
231 self.trigger_types = Some(triggers);
232 self
233 }
234
235 pub fn webhook_secret(mut self, secret: impl Into<String>) -> Self {
237 self.webhook_secret = Some(secret.into());
238 self
239 }
240
241 pub fn notification_email_addresses(mut self, emails: Vec<String>) -> Self {
243 self.notification_email_addresses = Some(emails);
244 self
245 }
246
247 pub fn build(self) -> CreateWebhookRequest {
253 CreateWebhookRequest {
254 description: self.description,
255 webhook_url: self.webhook_url.expect("webhook_url is required"),
256 trigger_types: self.trigger_types.expect("trigger_types is required"),
257 webhook_secret: self.webhook_secret,
258 notification_email_addresses: self.notification_email_addresses,
259 }
260 }
261}
262
263#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
277pub struct UpdateWebhookRequest {
278 #[serde(skip_serializing_if = "Option::is_none")]
280 pub description: Option<String>,
281
282 #[serde(skip_serializing_if = "Option::is_none")]
284 pub webhook_url: Option<String>,
285
286 #[serde(skip_serializing_if = "Option::is_none")]
288 pub trigger_types: Option<Vec<WebhookTrigger>>,
289
290 #[serde(skip_serializing_if = "Option::is_none")]
292 pub webhook_secret: Option<String>,
293
294 #[serde(skip_serializing_if = "Option::is_none")]
296 pub notification_email_addresses: Option<Vec<String>>,
297}
298
299impl UpdateWebhookRequest {
300 pub fn builder() -> UpdateWebhookRequestBuilder {
302 UpdateWebhookRequestBuilder::default()
303 }
304}
305
306#[derive(Debug, Clone, Default)]
308pub struct UpdateWebhookRequestBuilder {
309 description: Option<String>,
310 webhook_url: Option<String>,
311 trigger_types: Option<Vec<WebhookTrigger>>,
312 webhook_secret: Option<String>,
313 notification_email_addresses: Option<Vec<String>>,
314}
315
316impl UpdateWebhookRequestBuilder {
317 pub fn description(mut self, description: impl Into<String>) -> Self {
319 self.description = Some(description.into());
320 self
321 }
322
323 pub fn webhook_url(mut self, url: impl Into<String>) -> Self {
325 self.webhook_url = Some(url.into());
326 self
327 }
328
329 pub fn trigger_types(mut self, triggers: Vec<WebhookTrigger>) -> Self {
331 self.trigger_types = Some(triggers);
332 self
333 }
334
335 pub fn webhook_secret(mut self, secret: impl Into<String>) -> Self {
337 self.webhook_secret = Some(secret.into());
338 self
339 }
340
341 pub fn notification_email_addresses(mut self, emails: Vec<String>) -> Self {
343 self.notification_email_addresses = Some(emails);
344 self
345 }
346
347 pub fn build(self) -> UpdateWebhookRequest {
349 UpdateWebhookRequest {
350 description: self.description,
351 webhook_url: self.webhook_url,
352 trigger_types: self.trigger_types,
353 webhook_secret: self.webhook_secret,
354 notification_email_addresses: self.notification_email_addresses,
355 }
356 }
357}
358
359pub fn verify_webhook_signature(payload: &[u8], signature: &str, secret: &str) -> bool {
388 use hmac::{Hmac, Mac};
389 use sha2::Sha256;
390
391 type HmacSha256 = Hmac<Sha256>;
392
393 let mut mac = match HmacSha256::new_from_slice(secret.as_bytes()) {
394 Ok(m) => m,
395 Err(_) => return false,
396 };
397
398 mac.update(payload);
399
400 let result = mac.finalize();
401 let expected = hex::encode(result.into_bytes());
402
403 signature == expected
404}
405
406#[cfg(test)]
407mod tests {
408 use super::*;
409
410 #[test]
411 fn test_webhook_serialization() {
412 let webhook = Webhook {
413 id: WebhookId::new("webhook_123"),
414 description: Some("Test webhook".to_string()),
415 trigger_types: vec![WebhookTrigger::MessageCreated],
416 webhook_url: "https://example.com/webhook".to_string(),
417 webhook_secret: Some("secret".to_string()),
418 notification_email_addresses: vec!["admin@example.com".to_string()],
419 };
420
421 let json = serde_json::to_string(&webhook).unwrap();
422 assert!(json.contains("webhook_123"));
423 assert!(json.contains("https://example.com/webhook"));
424 }
425
426 #[test]
427 fn test_webhook_notification_deserialization() {
428 let json = r#"{
429 "id": "notif_123",
430 "grant_id": "grant_456",
431 "application_id": "app_789",
432 "trigger": "message.created",
433 "timestamp": 1609459200,
434 "data": {"id": "msg_abc", "subject": "Test"}
435 }"#;
436
437 let notification: WebhookNotification = serde_json::from_str(json).unwrap();
438 assert_eq!(notification.id, "notif_123");
439 assert_eq!(notification.trigger, "message.created");
440 assert_eq!(notification.timestamp, 1609459200);
441 }
442
443 #[test]
444 fn test_webhook_trigger_serialization() {
445 let trigger = WebhookTrigger::MessageCreated;
446 let json = serde_json::to_string(&trigger).unwrap();
447 assert_eq!(json, "\"message-created\"");
448
449 let trigger = WebhookTrigger::EventUpdated;
450 let json = serde_json::to_string(&trigger).unwrap();
451 assert_eq!(json, "\"event-updated\"");
452 }
453
454 #[test]
455 fn test_create_webhook_request_builder() {
456 let request = CreateWebhookRequest::builder()
457 .description("My webhook")
458 .webhook_url("https://api.example.com/webhooks")
459 .trigger_types(vec![WebhookTrigger::MessageCreated])
460 .webhook_secret("secret_key")
461 .notification_email_addresses(vec!["admin@example.com".to_string()])
462 .build();
463
464 assert_eq!(request.description, Some("My webhook".to_string()));
465 assert_eq!(request.webhook_url, "https://api.example.com/webhooks");
466 assert_eq!(request.trigger_types.len(), 1);
467 assert_eq!(request.webhook_secret, Some("secret_key".to_string()));
468 }
469
470 #[test]
471 fn test_update_webhook_request_builder() {
472 let update = UpdateWebhookRequest::builder()
473 .description("Updated")
474 .trigger_types(vec![
475 WebhookTrigger::MessageCreated,
476 WebhookTrigger::MessageUpdated,
477 ])
478 .build();
479
480 assert_eq!(update.description, Some("Updated".to_string()));
481 assert_eq!(update.trigger_types.as_ref().unwrap().len(), 2);
482 assert!(update.webhook_url.is_none());
483 }
484
485 #[test]
486 fn test_verify_webhook_signature() {
487 let payload = b"test payload";
488 let secret = "test_secret";
489
490 use hmac::{Hmac, Mac};
492 use sha2::Sha256;
493 type HmacSha256 = Hmac<Sha256>;
494
495 let mut mac = HmacSha256::new_from_slice(secret.as_bytes()).unwrap();
496 mac.update(payload);
497 let result = mac.finalize();
498 let signature = hex::encode(result.into_bytes());
499
500 assert!(verify_webhook_signature(payload, &signature, secret));
502
503 assert!(!verify_webhook_signature(
505 payload,
506 "wrong_signature",
507 secret
508 ));
509
510 assert!(!verify_webhook_signature(
512 payload,
513 &signature,
514 "wrong_secret"
515 ));
516 }
517}