1pub mod error;
34pub mod events;
35pub mod signature;
36
37pub use error::{Result, WebhookError};
38pub use events::{
39 parse_webhook_payload, PostWebhookEvent, TopicWebhookEvent, WebhookEventPayload, WebhookPost,
40 WebhookTopic, WebhookUser,
41};
42pub use signature::{verify_json_signature, verify_signature, SignatureVerificationError};
43
44use serde::{Deserialize, Serialize};
45
46#[derive(Debug, Serialize, Deserialize, Clone)]
48pub struct DiscourseWebhookPayload {
49 #[serde(default)]
51 pub event: Option<String>,
52 #[serde(default)]
54 pub data: Option<serde_json::Value>,
55 #[serde(default)]
57 pub timestamp: Option<i64>,
58}
59
60pub trait WebhookEventHandler {
66 type Error;
68
69 fn handle_topic_created(
71 &mut self,
72 event: &TopicWebhookEvent,
73 ) -> std::result::Result<(), Self::Error> {
74 let _ = event;
75 Ok(())
76 }
77
78 fn handle_topic_edited(
80 &mut self,
81 event: &TopicWebhookEvent,
82 ) -> std::result::Result<(), Self::Error> {
83 let _ = event;
84 Ok(())
85 }
86
87 fn handle_topic_destroyed(
89 &mut self,
90 event: &TopicWebhookEvent,
91 ) -> std::result::Result<(), Self::Error> {
92 let _ = event;
93 Ok(())
94 }
95
96 fn handle_topic_recovered(
98 &mut self,
99 event: &TopicWebhookEvent,
100 ) -> std::result::Result<(), Self::Error> {
101 let _ = event;
102 Ok(())
103 }
104
105 fn handle_post_created(
107 &mut self,
108 event: &PostWebhookEvent,
109 ) -> std::result::Result<(), Self::Error> {
110 let _ = event;
111 Ok(())
112 }
113
114 fn handle_post_edited(
116 &mut self,
117 event: &PostWebhookEvent,
118 ) -> std::result::Result<(), Self::Error> {
119 let _ = event;
120 Ok(())
121 }
122
123 fn handle_post_destroyed(
125 &mut self,
126 event: &PostWebhookEvent,
127 ) -> std::result::Result<(), Self::Error> {
128 let _ = event;
129 Ok(())
130 }
131
132 fn handle_post_recovered(
134 &mut self,
135 event: &PostWebhookEvent,
136 ) -> std::result::Result<(), Self::Error> {
137 let _ = event;
138 Ok(())
139 }
140
141 fn handle_ping(&mut self) -> std::result::Result<(), Self::Error> {
143 Ok(())
144 }
145}
146
147pub fn process_webhook_event<H: WebhookEventHandler>(
161 handler: &mut H,
162 event_type: &str,
163 payload: serde_json::Value,
164) -> std::result::Result<(), WebhookError<H::Error>> {
165 match event_type {
166 "topic_created" | "topic_edited" | "topic_destroyed" | "topic_recovered" => {
167 let event = parse_webhook_payload(event_type, payload)?;
168 if let WebhookEventPayload::TopicEvent(topic_event) = event {
169 match event_type {
170 "topic_created" => handler.handle_topic_created(&topic_event),
171 "topic_edited" => handler.handle_topic_edited(&topic_event),
172 "topic_destroyed" => handler.handle_topic_destroyed(&topic_event),
173 "topic_recovered" => handler.handle_topic_recovered(&topic_event),
174 _ => unreachable!(),
175 }
176 .map_err(WebhookError::HandlerError)?;
177 }
178 }
179 "post_created" | "post_edited" | "post_destroyed" | "post_recovered" => {
180 let event = parse_webhook_payload(event_type, payload)?;
181 if let WebhookEventPayload::PostEvent(post_event) = event {
182 match event_type {
183 "post_created" => handler.handle_post_created(&post_event),
184 "post_edited" => handler.handle_post_edited(&post_event),
185 "post_destroyed" => handler.handle_post_destroyed(&post_event),
186 "post_recovered" => handler.handle_post_recovered(&post_event),
187 _ => unreachable!(),
188 }
189 .map_err(WebhookError::HandlerError)?;
190 }
191 }
192 "ping" => {
193 handler.handle_ping().map_err(WebhookError::HandlerError)?;
194 }
195 _ => {
196 return Err(WebhookError::UnknownEventType(event_type.to_string()));
197 }
198 }
199
200 Ok(())
201}
202
203#[derive(Debug, Clone)]
221pub struct WebhookProcessor {
222 secret: Option<String>,
223 verify_signatures: bool,
224}
225
226impl WebhookProcessor {
227 pub fn new() -> Self {
231 Self {
232 secret: None,
233 verify_signatures: false,
234 }
235 }
236
237 pub fn with_secret<S: Into<String>>(mut self, secret: S) -> Self {
242 self.secret = Some(secret.into());
243 self.verify_signatures = true;
244 self
245 }
246
247 pub fn without_signature_verification(mut self) -> Self {
252 self.verify_signatures = false;
253 self
254 }
255
256 pub fn verifies_signatures(&self) -> bool {
258 self.verify_signatures
259 }
260
261 pub fn secret(&self) -> Option<&str> {
263 self.secret.as_deref()
264 }
265
266 pub fn process<H: WebhookEventHandler>(
274 &self,
275 handler: &mut H,
276 event_type: &str,
277 payload: &str,
278 signature: Option<&str>,
279 ) -> Result<(), H::Error> {
280 if self.verify_signatures {
281 if let Some(secret) = &self.secret {
282 if let Some(sig) = signature {
283 signature::verify_signature(secret, payload, sig)
284 .map_err(|_| WebhookError::InvalidSignature)?;
285 } else {
286 return Err(WebhookError::InvalidSignature);
287 }
288 }
289 }
290
291 let json_payload: serde_json::Value = serde_json::from_str(payload)?;
292 process_webhook_event(handler, event_type, json_payload)
293 }
294
295 pub fn process_json<H: WebhookEventHandler>(
303 &self,
304 handler: &mut H,
305 event_type: &str,
306 payload: serde_json::Value,
307 signature: Option<&str>,
308 ) -> Result<(), H::Error> {
309 if self.verify_signatures {
310 if let Some(secret) = &self.secret {
311 if let Some(sig) = signature {
312 signature::verify_json_signature(secret, &payload, sig)
313 .map_err(|_| WebhookError::InvalidSignature)?;
314 } else {
315 return Err(WebhookError::InvalidSignature);
316 }
317 }
318 }
319
320 process_webhook_event(handler, event_type, payload)
321 }
322}
323
324impl Default for WebhookProcessor {
325 fn default() -> Self {
326 Self::new()
327 }
328}
329
330#[cfg(test)]
331mod tests {
332 use super::*;
333 use hmac::Mac;
334 use serde_json::json;
335
336 struct TestHandler {
337 pub topic_created_count: usize,
338 pub post_created_count: usize,
339 pub ping_count: usize,
340 }
341
342 impl TestHandler {
343 fn new() -> Self {
344 Self {
345 topic_created_count: 0,
346 post_created_count: 0,
347 ping_count: 0,
348 }
349 }
350 }
351
352 impl WebhookEventHandler for TestHandler {
353 type Error = String;
354
355 fn handle_topic_created(
356 &mut self,
357 _event: &TopicWebhookEvent,
358 ) -> std::result::Result<(), Self::Error> {
359 self.topic_created_count += 1;
360 Ok(())
361 }
362
363 fn handle_post_created(
364 &mut self,
365 _event: &PostWebhookEvent,
366 ) -> std::result::Result<(), Self::Error> {
367 self.post_created_count += 1;
368 Ok(())
369 }
370
371 fn handle_ping(&mut self) -> std::result::Result<(), Self::Error> {
372 self.ping_count += 1;
373 Ok(())
374 }
375 }
376
377 #[test]
378 fn test_webhook_handler_ping() {
379 let mut handler = TestHandler::new();
380 let result = process_webhook_event(&mut handler, "ping", json!({}));
381 assert!(result.is_ok());
382 assert_eq!(handler.ping_count, 1);
383 }
384
385 #[test]
386 fn test_webhook_handler_invalid_event_type() {
387 let mut handler = TestHandler::new();
388 let result = process_webhook_event(&mut handler, "hatasj", json!({}));
389 assert!(result.is_err());
390
391 if let Err(WebhookError::UnknownEventType(event)) = result {
392 assert_eq!(event, "hatasj");
393 } else {
394 panic!("Expected UnknownEventType error");
395 }
396 }
397
398 #[test]
399 fn test_webhook_processor() {
400 let processor = WebhookProcessor::new();
401 let mut handler = TestHandler::new();
402
403 let result = processor.process_json(&mut handler, "ping", json!({}), None);
404
405 assert!(result.is_ok());
406 assert_eq!(handler.ping_count, 1);
407 }
408
409 #[test]
410 fn test_webhook_processor_with_signature() {
411 let secret = "test_secret";
412 let processor = WebhookProcessor::new().with_secret(secret);
413 let mut handler = TestHandler::new();
414
415 let payload = json!({});
416 let payload_str = serde_json::to_string(&payload).unwrap();
417
418 let mut mac = hmac::Hmac::<sha2::Sha256>::new_from_slice(secret.as_bytes()).unwrap();
419 mac.update(payload_str.as_bytes());
420 let signature = format!("sha256={}", hex::encode(mac.finalize().into_bytes()));
421
422 let result = processor.process_json(&mut handler, "ping", payload, Some(&signature));
423
424 assert!(result.is_ok());
425 assert_eq!(handler.ping_count, 1);
426 }
427
428 #[test]
429 fn test_unknown_event_type() {
430 let mut handler = TestHandler::new();
431 let result = process_webhook_event(&mut handler, "unknown_event", json!({}));
432 assert!(result.is_err());
433
434 if let Err(WebhookError::UnknownEventType(event)) = result {
435 assert_eq!(event, "unknown_event");
436 } else {
437 panic!("Expected UnknownEventType error");
438 }
439 }
440}