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}