axum-github-hooks 0.1.0

Axum extractor for GitHub webhooks
Documentation
use axum::{
    async_trait,
    extract::{rejection::BytesRejection, FromRequest, Request},
    http::StatusCode,
    response::IntoResponse,
    RequestExt,
};
use bytes::Bytes;
use octocrab::models::webhook_events::WebhookEvent;
use thiserror::Error;

#[derive(Debug, Error)]
pub enum WebhookExtractRejection {
    #[error("Invalid header")]
    InvalidHeader,

    #[error("Serde error: {0}")]
    SerdeError(#[from] serde_json::error::Error),

    #[error("Bytes error: {0}")]
    BytesError(#[from] BytesRejection),
}

impl IntoResponse for WebhookExtractRejection {
    fn into_response(self) -> axum::response::Response {
        StatusCode::BAD_REQUEST.into_response()
    }
}

#[derive(Debug, Clone)]
pub struct GithubWebhook(pub WebhookEvent);

#[async_trait]
impl<S> FromRequest<S> for GithubWebhook
where
    S: Send + Sync,
{
    type Rejection = WebhookExtractRejection;

    async fn from_request(req: Request, _state: &S) -> Result<Self, Self::Rejection> {
        let Some(event) = req.headers().get("X-GitHub-Event") else {
            return Err(WebhookExtractRejection::InvalidHeader);
        };

        let Ok(event) = event.to_str() else {
            return Err(WebhookExtractRejection::InvalidHeader);
        };

        let event = event.to_string();

        let b: Bytes = req.extract().await?;

        let hook = WebhookEvent::try_from_header_and_body(&event, &b)?;

        Ok(Self(hook))
    }
}