#![deny(missing_docs)]
use async_trait::async_trait;
use std::collections::HashMap;
use tide::{prelude::*, Request, StatusCode};
use std::sync::Arc;
mod middleware;
pub mod payload;
use payload::Payload;
pub fn new<S: Into<String>>(webhook_secret: S) -> ServerBuilder {
ServerBuilder::new(webhook_secret.into())
}
type HandlerMap = HashMap<
Event,
Arc<dyn Send + Sync + 'static + Fn(Payload)>,
>;
pub struct ServerBuilder {
webhook_secret: String,
handlers: HandlerMap,
}
impl ServerBuilder {
fn new(webhook_secret: String) -> Self {
ServerBuilder {
webhook_secret,
handlers: HashMap::new(),
}
}
pub fn on<E: Into<Event>>(
mut self,
event: E,
handler: impl Fn(Payload)
+ Send
+ Sync
+ 'static,
) -> Self {
let event: Event = event.into();
self.handlers.insert(event, Arc::new(handler));
self
}
pub fn build(self) -> tide::Server<()> {
let mut server = tide::new();
let dispatcher = Box::new(EventHandlerDispatcher::new(self.handlers));
server.with(middleware::WebhookVerification::new(self.webhook_secret));
server
.at("/")
.post(dispatcher as Box<dyn tide::Endpoint<()>>);
server
}
}
#[non_exhaustive]
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
pub enum Event {
IssueComment,
}
use std::fmt;
impl fmt::Display for Event {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::IssueComment => write!(f, "issue_comment"),
}
}
}
impl ::std::str::FromStr for Event {
type Err = EventDispatchError;
fn from_str(event: &str) -> Result<Event, Self::Err> {
use self::Event::*;
match event {
"issue_comment" => Ok(IssueComment),
event => {
log::warn!("Unsupported event: {}", event);
Err(EventDispatchError::UnsupportedEvent)
},
}
}
}
#[derive(thiserror::Error, Clone, Debug)]
pub enum EventDispatchError {
#[error("Received an Event of an unsupported type")]
UnsupportedEvent,
#[error("No `X-Github-Event` header found")]
MissingEventHeader,
#[error("No handler registered for Event '{0}'")]
MissingHandlerForEvent(Event),
}
struct EventHandlerDispatcher {
handlers: HandlerMap,
}
impl EventHandlerDispatcher {
fn new(handlers: HandlerMap) -> Self {
EventHandlerDispatcher { handlers }
}
}
#[async_trait]
impl tide::Endpoint<()> for EventHandlerDispatcher
where
EventHandlerDispatcher: Send + Sync,
{
async fn call(&self, mut req: Request<()>) -> tide::Result {
use std::str::FromStr;
use async_std::task;
let event_header = req
.header("X-Github-Event")
.ok_or(EventDispatchError::MissingEventHeader)
.status(StatusCode::BadRequest)?.as_str();
let event = Event::from_str(event_header).status(StatusCode::NotImplemented)?;
let payload: payload::Payload = req.body_json().await?;
let handler = self
.handlers
.get(&event)
.ok_or_else(|| { println!("Missing Handler for Event {:?}", event); EventDispatchError::MissingHandlerForEvent(event)})
.status(StatusCode::NotImplemented)?;
let handler = handler.clone();
task::spawn_blocking(move || {handler(payload)});
Ok("".into())
}
}