axum_github_hooks/
lib.rs

1use axum::{
2    async_trait,
3    extract::{rejection::BytesRejection, FromRequest, Request},
4    http::StatusCode,
5    response::IntoResponse,
6    RequestExt,
7};
8use bytes::Bytes;
9use octocrab::models::webhook_events::WebhookEvent;
10use thiserror::Error;
11
12#[derive(Debug, Error)]
13pub enum WebhookExtractRejection {
14    #[error("Invalid header")]
15    InvalidHeader,
16
17    #[error("Serde error: {0}")]
18    SerdeError(#[from] serde_json::error::Error),
19
20    #[error("Bytes error: {0}")]
21    BytesError(#[from] BytesRejection),
22}
23
24impl IntoResponse for WebhookExtractRejection {
25    fn into_response(self) -> axum::response::Response {
26        StatusCode::BAD_REQUEST.into_response()
27    }
28}
29
30#[derive(Debug, Clone)]
31pub struct GithubWebhook(pub WebhookEvent);
32
33#[async_trait]
34impl<S> FromRequest<S> for GithubWebhook
35where
36    S: Send + Sync,
37{
38    type Rejection = WebhookExtractRejection;
39
40    async fn from_request(req: Request, _state: &S) -> Result<Self, Self::Rejection> {
41        let Some(event) = req.headers().get("X-GitHub-Event") else {
42            return Err(WebhookExtractRejection::InvalidHeader);
43        };
44
45        let Ok(event) = event.to_str() else {
46            return Err(WebhookExtractRejection::InvalidHeader);
47        };
48
49        let event = event.to_string();
50
51        let b: Bytes = req.extract().await?;
52
53        let hook = WebhookEvent::try_from_header_and_body(&event, &b)?;
54
55        Ok(Self(hook))
56    }
57}