rocket_webhook/
webhooks.rs1use rocket::{Request, data::Outcome, http::Status, tokio::io::AsyncRead};
4
5use crate::WebhookError;
6
7pub mod built_in;
8pub mod generic;
9pub mod interface;
10
11mod utils;
12
13pub trait Webhook {
15 fn validate_body(
18 &self,
19 req: &Request<'_>,
20 body_reader: impl AsyncRead + Unpin + Send + Sync,
21 time_bounds: (u32, u32),
22 ) -> impl Future<Output = Outcome<'_, Vec<u8>, WebhookError>> + Send + Sync;
23
24 fn validate_timestamp(
27 &self,
28 timestamp: &str,
29 (min, max): (u32, u32),
30 ) -> Outcome<'_, (), WebhookError> {
31 let unix_timestamp = timestamp.parse::<u32>().ok();
32 match unix_timestamp.map(|t| t >= min && t <= max) {
33 Some(true) => Outcome::Success(()),
34 Some(false) | None => Outcome::Error((
35 Status::BadRequest,
36 WebhookError::Timestamp(timestamp.into()),
37 )),
38 }
39 }
40
41 fn get_header<'r>(
45 &self,
46 req: &'r Request<'_>,
47 name: &str,
48 prefix: Option<&str>,
49 ) -> Outcome<'_, &'r str, WebhookError> {
50 let Some(mut header) = req.headers().get_one(name) else {
51 return Outcome::Error((Status::BadRequest, WebhookError::MissingHeader(name.into())));
52 };
53 if let Some(prefix) = prefix {
54 let Some(stripped) = header.strip_prefix(prefix) else {
55 return Outcome::Error((
56 Status::BadRequest,
57 WebhookError::InvalidHeader(format!(
58 "'{name}' is missing prefix '{prefix}': {header}"
59 )),
60 ));
61 };
62 header = stripped;
63 }
64 Outcome::Success(header)
65 }
66}