1pub mod error;
39pub mod events;
40pub mod signature;
41
42#[cfg(feature = "async")]
43pub use async_trait::async_trait;
44pub use error::{Result, WebhookError};
45pub use events::{
46 parse_webhook_payload, PostWebhookEvent, TopicWebhookEvent, WebhookEventPayload, WebhookPost,
47 WebhookTopic, WebhookUser,
48};
49pub use signature::{verify_json_signature, verify_signature, SignatureVerificationError};
50
51use serde::{Deserialize, Serialize};
52
53#[derive(Debug, Serialize, Deserialize, Clone)]
55pub struct DiscourseWebhookPayload {
56 #[serde(default)]
58 pub event: Option<String>,
59 #[serde(default)]
61 pub data: Option<serde_json::Value>,
62 #[serde(default)]
64 pub timestamp: Option<i64>,
65}
66
67#[cfg(not(feature = "async"))]
110pub trait WebhookEventHandler {
111 type Error;
113
114 fn handle_topic_created(
116 &mut self,
117 event: &TopicWebhookEvent,
118 ) -> std::result::Result<(), Self::Error> {
119 let _ = event;
120 Ok(())
121 }
122
123 fn handle_topic_edited(
125 &mut self,
126 event: &TopicWebhookEvent,
127 ) -> std::result::Result<(), Self::Error> {
128 let _ = event;
129 Ok(())
130 }
131
132 fn handle_topic_destroyed(
134 &mut self,
135 event: &TopicWebhookEvent,
136 ) -> std::result::Result<(), Self::Error> {
137 let _ = event;
138 Ok(())
139 }
140
141 fn handle_topic_recovered(
143 &mut self,
144 event: &TopicWebhookEvent,
145 ) -> std::result::Result<(), Self::Error> {
146 let _ = event;
147 Ok(())
148 }
149
150 fn handle_post_created(
152 &mut self,
153 event: &PostWebhookEvent,
154 ) -> std::result::Result<(), Self::Error> {
155 let _ = event;
156 Ok(())
157 }
158
159 fn handle_post_edited(
161 &mut self,
162 event: &PostWebhookEvent,
163 ) -> std::result::Result<(), Self::Error> {
164 let _ = event;
165 Ok(())
166 }
167
168 fn handle_post_destroyed(
170 &mut self,
171 event: &PostWebhookEvent,
172 ) -> std::result::Result<(), Self::Error> {
173 let _ = event;
174 Ok(())
175 }
176
177 fn handle_post_recovered(
179 &mut self,
180 event: &PostWebhookEvent,
181 ) -> std::result::Result<(), Self::Error> {
182 let _ = event;
183 Ok(())
184 }
185
186 fn handle_ping(&mut self) -> std::result::Result<(), Self::Error> {
188 Ok(())
189 }
190}
191
192#[cfg(feature = "async")]
193#[async_trait]
194pub trait WebhookEventHandler {
195 type Error;
197
198 async fn handle_topic_created(
200 &mut self,
201 event: &TopicWebhookEvent,
202 ) -> std::result::Result<(), Self::Error> {
203 let _ = event;
204 Ok(())
205 }
206
207 async fn handle_topic_edited(
209 &mut self,
210 event: &TopicWebhookEvent,
211 ) -> std::result::Result<(), Self::Error> {
212 let _ = event;
213 Ok(())
214 }
215
216 async fn handle_topic_destroyed(
218 &mut self,
219 event: &TopicWebhookEvent,
220 ) -> std::result::Result<(), Self::Error> {
221 let _ = event;
222 Ok(())
223 }
224
225 async fn handle_topic_recovered(
227 &mut self,
228 event: &TopicWebhookEvent,
229 ) -> std::result::Result<(), Self::Error> {
230 let _ = event;
231 Ok(())
232 }
233
234 async fn handle_post_created(
236 &mut self,
237 event: &PostWebhookEvent,
238 ) -> std::result::Result<(), Self::Error> {
239 let _ = event;
240 Ok(())
241 }
242
243 async fn handle_post_edited(
245 &mut self,
246 event: &PostWebhookEvent,
247 ) -> std::result::Result<(), Self::Error> {
248 let _ = event;
249 Ok(())
250 }
251
252 async fn handle_post_destroyed(
254 &mut self,
255 event: &PostWebhookEvent,
256 ) -> std::result::Result<(), Self::Error> {
257 let _ = event;
258 Ok(())
259 }
260
261 async fn handle_post_recovered(
263 &mut self,
264 event: &PostWebhookEvent,
265 ) -> std::result::Result<(), Self::Error> {
266 let _ = event;
267 Ok(())
268 }
269
270 async fn handle_ping(&mut self) -> std::result::Result<(), Self::Error> {
272 Ok(())
273 }
274}
275
276#[cfg(not(feature = "async"))]
290pub fn process_webhook_event<H: WebhookEventHandler>(
291 handler: &mut H,
292 event_type: &str,
293 payload: serde_json::Value,
294) -> std::result::Result<(), WebhookError<H::Error>> {
295 match event_type {
296 "topic_created" | "topic_edited" | "topic_destroyed" | "topic_recovered" => {
297 let event = parse_webhook_payload(event_type, payload)?;
298 if let WebhookEventPayload::TopicEvent(topic_event) = event {
299 let result = match event_type {
300 "topic_created" => handler.handle_topic_created(&topic_event),
301 "topic_edited" => handler.handle_topic_edited(&topic_event),
302 "topic_destroyed" => handler.handle_topic_destroyed(&topic_event),
303 "topic_recovered" => handler.handle_topic_recovered(&topic_event),
304 _ => unreachable!(),
305 };
306 result.map_err(WebhookError::HandlerError)?;
307 }
308 }
309 "post_created" | "post_edited" | "post_destroyed" | "post_recovered" => {
310 let event = parse_webhook_payload(event_type, payload)?;
311 if let WebhookEventPayload::PostEvent(post_event) = event {
312 let result = match event_type {
313 "post_created" => handler.handle_post_created(&post_event),
314 "post_edited" => handler.handle_post_edited(&post_event),
315 "post_destroyed" => handler.handle_post_destroyed(&post_event),
316 "post_recovered" => handler.handle_post_recovered(&post_event),
317 _ => unreachable!(),
318 };
319 result.map_err(WebhookError::HandlerError)?;
320 }
321 }
322 "ping" => {
323 handler.handle_ping().map_err(WebhookError::HandlerError)?;
324 }
325 _ => {
326 return Err(WebhookError::UnknownEventType(event_type.to_string()));
327 }
328 }
329
330 Ok(())
331}
332
333#[cfg(feature = "async")]
347pub async fn process_webhook_event<H: WebhookEventHandler + Send>(
348 handler: &mut H,
349 event_type: &str,
350 payload: serde_json::Value,
351) -> std::result::Result<(), WebhookError<H::Error>> {
352 match event_type {
353 "topic_created" | "topic_edited" | "topic_destroyed" | "topic_recovered" => {
354 let event = parse_webhook_payload(event_type, payload)?;
355 if let WebhookEventPayload::TopicEvent(topic_event) = event {
356 let result = match event_type {
357 "topic_created" => handler.handle_topic_created(&topic_event).await,
358 "topic_edited" => handler.handle_topic_edited(&topic_event).await,
359 "topic_destroyed" => handler.handle_topic_destroyed(&topic_event).await,
360 "topic_recovered" => handler.handle_topic_recovered(&topic_event).await,
361 _ => unreachable!(),
362 };
363 result.map_err(WebhookError::HandlerError)?;
364 }
365 }
366 "post_created" | "post_edited" | "post_destroyed" | "post_recovered" => {
367 let event = parse_webhook_payload(event_type, payload)?;
368 if let WebhookEventPayload::PostEvent(post_event) = event {
369 let result = match event_type {
370 "post_created" => handler.handle_post_created(&post_event).await,
371 "post_edited" => handler.handle_post_edited(&post_event).await,
372 "post_destroyed" => handler.handle_post_destroyed(&post_event).await,
373 "post_recovered" => handler.handle_post_recovered(&post_event).await,
374 _ => unreachable!(),
375 };
376 result.map_err(WebhookError::HandlerError)?;
377 }
378 }
379 "ping" => {
380 handler
381 .handle_ping()
382 .await
383 .map_err(WebhookError::HandlerError)?;
384 }
385 _ => {
386 return Err(WebhookError::UnknownEventType(event_type.to_string()));
387 }
388 }
389
390 Ok(())
391}
392
393#[derive(Debug, Clone)]
411pub struct WebhookProcessor {
412 secret: Option<String>,
413 verify_signatures: bool,
414}
415
416impl WebhookProcessor {
417 pub fn new() -> Self {
421 Self {
422 secret: None,
423 verify_signatures: false,
424 }
425 }
426
427 pub fn with_secret<S: Into<String>>(mut self, secret: S) -> Self {
432 self.secret = Some(secret.into());
433 self.verify_signatures = true;
434 self
435 }
436
437 pub fn without_signature_verification(mut self) -> Self {
442 self.verify_signatures = false;
443 self
444 }
445
446 pub fn verifies_signatures(&self) -> bool {
448 self.verify_signatures
449 }
450
451 pub fn secret(&self) -> Option<&str> {
453 self.secret.as_deref()
454 }
455
456 #[cfg(not(feature = "async"))]
464 pub fn process<H: WebhookEventHandler>(
465 &self,
466 handler: &mut H,
467 event_type: &str,
468 payload: &str,
469 signature: Option<&str>,
470 ) -> Result<(), H::Error> {
471 if self.verify_signatures {
472 if let Some(secret) = &self.secret {
473 if let Some(sig) = signature {
474 signature::verify_signature(secret, payload, sig)
475 .map_err(|_| WebhookError::InvalidSignature)?;
476 } else {
477 return Err(WebhookError::InvalidSignature);
478 }
479 }
480 }
481
482 let json_payload: serde_json::Value = serde_json::from_str(payload)?;
483 process_webhook_event(handler, event_type, json_payload)
484 }
485
486 #[cfg(feature = "async")]
494 pub async fn process<H: WebhookEventHandler + Send>(
495 &self,
496 handler: &mut H,
497 event_type: &str,
498 payload: &str,
499 signature: Option<&str>,
500 ) -> Result<(), H::Error> {
501 if self.verify_signatures {
502 if let Some(secret) = &self.secret {
503 if let Some(sig) = signature {
504 signature::verify_signature(secret, payload, sig)
505 .map_err(|_| WebhookError::InvalidSignature)?;
506 } else {
507 return Err(WebhookError::InvalidSignature);
508 }
509 }
510 }
511
512 let json_payload: serde_json::Value = serde_json::from_str(payload)?;
513 process_webhook_event(handler, event_type, json_payload).await
514 }
515
516 #[cfg(not(feature = "async"))]
524 pub fn process_json<H: WebhookEventHandler>(
525 &self,
526 handler: &mut H,
527 event_type: &str,
528 payload: serde_json::Value,
529 signature: Option<&str>,
530 ) -> Result<(), H::Error> {
531 if self.verify_signatures {
532 if let Some(secret) = &self.secret {
533 if let Some(sig) = signature {
534 signature::verify_json_signature(secret, &payload, sig)
535 .map_err(|_| WebhookError::InvalidSignature)?;
536 } else {
537 return Err(WebhookError::InvalidSignature);
538 }
539 }
540 }
541
542 process_webhook_event(handler, event_type, payload)
543 }
544
545 #[cfg(feature = "async")]
553 pub async fn process_json<H: WebhookEventHandler + Send>(
554 &self,
555 handler: &mut H,
556 event_type: &str,
557 payload: serde_json::Value,
558 signature: Option<&str>,
559 ) -> Result<(), H::Error> {
560 if self.verify_signatures {
561 if let Some(secret) = &self.secret {
562 if let Some(sig) = signature {
563 signature::verify_json_signature(secret, &payload, sig)
564 .map_err(|_| WebhookError::InvalidSignature)?;
565 } else {
566 return Err(WebhookError::InvalidSignature);
567 }
568 }
569 }
570
571 process_webhook_event(handler, event_type, payload).await
572 }
573}
574
575impl Default for WebhookProcessor {
576 fn default() -> Self {
577 Self::new()
578 }
579}
580
581#[cfg(test)]
582mod tests {
583 use super::*;
584 use hmac::Mac;
585 use serde_json::json;
586
587 struct TestHandler {
588 pub topic_created_count: usize,
589 pub post_created_count: usize,
590 pub ping_count: usize,
591 }
592
593 impl TestHandler {
594 fn new() -> Self {
595 Self {
596 topic_created_count: 0,
597 post_created_count: 0,
598 ping_count: 0,
599 }
600 }
601 }
602
603 #[cfg(not(feature = "async"))]
605 impl WebhookEventHandler for TestHandler {
606 type Error = String;
607
608 fn handle_topic_created(
609 &mut self,
610 _event: &TopicWebhookEvent,
611 ) -> std::result::Result<(), Self::Error> {
612 self.topic_created_count += 1;
613 Ok(())
614 }
615
616 fn handle_post_created(
617 &mut self,
618 _event: &PostWebhookEvent,
619 ) -> std::result::Result<(), Self::Error> {
620 self.post_created_count += 1;
621 Ok(())
622 }
623
624 fn handle_ping(&mut self) -> std::result::Result<(), Self::Error> {
625 self.ping_count += 1;
626 Ok(())
627 }
628 }
629
630 #[cfg(not(feature = "async"))]
631 #[test]
632 fn test_webhook_handler_ping() {
633 let mut handler = TestHandler::new();
634 let result = process_webhook_event(&mut handler, "ping", json!({}));
635 assert!(result.is_ok());
636 assert_eq!(handler.ping_count, 1);
637 }
638
639 #[cfg(not(feature = "async"))]
640 #[test]
641 fn test_webhook_handler_invalid_event_type() {
642 let mut handler = TestHandler::new();
643 let result = process_webhook_event(&mut handler, "hatasj", json!({}));
644 assert!(result.is_err());
645
646 if let Err(WebhookError::UnknownEventType(event)) = result {
647 assert_eq!(event, "hatasj");
648 } else {
649 panic!("Expected UnknownEventType error");
650 }
651 }
652
653 #[cfg(not(feature = "async"))]
654 #[test]
655 fn test_webhook_processor() {
656 let processor = WebhookProcessor::new();
657 let mut handler = TestHandler::new();
658
659 let result = processor.process_json(&mut handler, "ping", json!({}), None);
660
661 assert!(result.is_ok());
662 assert_eq!(handler.ping_count, 1);
663 }
664
665 #[cfg(not(feature = "async"))]
666 #[test]
667 fn test_webhook_processor_with_signature() {
668 let secret = "test_secret";
669 let processor = WebhookProcessor::new().with_secret(secret);
670 let mut handler = TestHandler::new();
671
672 let payload = json!({});
673 let payload_str = serde_json::to_string(&payload).unwrap();
674
675 let mut mac = hmac::Hmac::<sha2::Sha256>::new_from_slice(secret.as_bytes()).unwrap();
676 mac.update(payload_str.as_bytes());
677 let signature = format!("sha256={}", hex::encode(mac.finalize().into_bytes()));
678
679 let result = processor.process_json(&mut handler, "ping", payload, Some(&signature));
680
681 assert!(result.is_ok());
682 assert_eq!(handler.ping_count, 1);
683 }
684
685 #[cfg(not(feature = "async"))]
686 #[test]
687 fn test_unknown_event_type() {
688 let mut handler = TestHandler::new();
689 let result = process_webhook_event(&mut handler, "unknown_event", json!({}));
690 assert!(result.is_err());
691
692 if let Err(WebhookError::UnknownEventType(event)) = result {
693 assert_eq!(event, "unknown_event");
694 } else {
695 panic!("Expected UnknownEventType error");
696 }
697 }
698
699 #[cfg(feature = "async")]
701 #[async_trait]
702 impl WebhookEventHandler for TestHandler {
703 type Error = String;
704
705 async fn handle_topic_created(
706 &mut self,
707 _event: &TopicWebhookEvent,
708 ) -> std::result::Result<(), Self::Error> {
709 self.topic_created_count += 1;
710 Ok(())
711 }
712
713 async fn handle_post_created(
714 &mut self,
715 _event: &PostWebhookEvent,
716 ) -> std::result::Result<(), Self::Error> {
717 self.post_created_count += 1;
718 Ok(())
719 }
720
721 async fn handle_ping(&mut self) -> std::result::Result<(), Self::Error> {
722 self.ping_count += 1;
723 Ok(())
724 }
725 }
726
727 #[cfg(feature = "async")]
728 #[tokio::test]
729 async fn test_webhook_handler_ping() {
730 let mut handler = TestHandler::new();
731 let result = process_webhook_event(&mut handler, "ping", json!({})).await;
732 assert!(result.is_ok());
733 assert_eq!(handler.ping_count, 1);
734 }
735
736 #[cfg(feature = "async")]
737 #[tokio::test]
738 async fn test_webhook_handler_invalid_event_type() {
739 let mut handler = TestHandler::new();
740 let result = process_webhook_event(&mut handler, "hatasj", json!({})).await;
741 assert!(result.is_err());
742
743 if let Err(WebhookError::UnknownEventType(event)) = result {
744 assert_eq!(event, "hatasj");
745 } else {
746 panic!("Expected UnknownEventType error");
747 }
748 }
749
750 #[cfg(feature = "async")]
751 #[tokio::test]
752 async fn test_webhook_processor() {
753 let processor = WebhookProcessor::new();
754 let mut handler = TestHandler::new();
755
756 let result = processor
757 .process_json(&mut handler, "ping", json!({}), None)
758 .await;
759
760 assert!(result.is_ok());
761 assert_eq!(handler.ping_count, 1);
762 }
763
764 #[cfg(feature = "async")]
765 #[tokio::test]
766 async fn test_webhook_processor_with_signature() {
767 let secret = "test_secret";
768 let processor = WebhookProcessor::new().with_secret(secret);
769 let mut handler = TestHandler::new();
770
771 let payload = json!({});
772 let payload_str = serde_json::to_string(&payload).unwrap();
773
774 let mut mac = hmac::Hmac::<sha2::Sha256>::new_from_slice(secret.as_bytes()).unwrap();
775 mac.update(payload_str.as_bytes());
776 let signature = format!("sha256={}", hex::encode(mac.finalize().into_bytes()));
777
778 let result = processor
779 .process_json(&mut handler, "ping", payload, Some(&signature))
780 .await;
781
782 assert!(result.is_ok());
783 assert_eq!(handler.ping_count, 1);
784 }
785
786 #[cfg(feature = "async")]
787 #[tokio::test]
788 async fn test_unknown_event_type() {
789 let mut handler = TestHandler::new();
790 let result = process_webhook_event(&mut handler, "unknown_event", json!({})).await;
791 assert!(result.is_err());
792
793 if let Err(WebhookError::UnknownEventType(event)) = result {
794 assert_eq!(event, "unknown_event");
795 } else {
796 panic!("Expected UnknownEventType error");
797 }
798 }
799}