#[cfg(feature = "hyper-support")]
mod hyper;
#[cfg(feature = "parse")]
use serde_json::Value;
#[cfg(feature = "content-type-urlencoded")]
use url::form_urlencoded;
use std::collections::HashMap;
use super::hook::Hook;
pub type HookRegistry = HashMap<String, Hook>;
#[macro_export]
macro_rules! hooks_find_match {
($source:expr, $($pattern:expr), *) => {{
let mut result: Vec<Hook> = Vec::new();
$(
if let Some(hook) = $source.get($pattern) {
result.push(hook.clone());
}
)*
result
}};
}
pub enum ContentType {
JSON,
URLENCODED,
}
#[derive(Clone, Debug)]
pub enum DeliveryType {
GitHub,
GitLab,
}
#[cfg(not(feature = "parse"))]
#[doc(hidden)]
#[derive(Debug, Clone)]
pub enum Value {}
#[derive(Clone, Default)]
pub struct Constructor {
pub hooks: HookRegistry,
}
#[derive(Debug, Clone)]
pub struct Delivery {
pub delivery_type: DeliveryType,
pub id: Option<String>,
pub event: Option<String>,
pub payload: Option<Value>,
pub unparsed_payload: Option<String>,
pub request_body: Option<String>, pub signature: Option<String>,
}
struct Executor {
matched_hooks: Vec<Hook>,
}
pub struct Handler {
hooks: HookRegistry,
}
impl Constructor {
pub fn new() -> Constructor {
Constructor {
..Default::default()
}
}
pub fn register(&mut self, hook: Hook) {
self.hooks.insert(hook.event.to_string(), hook.clone());
}
}
impl Delivery {
pub fn new(
delivery_type: DeliveryType,
id: Option<String>,
event: Option<String>,
signature: Option<String>,
content_type: ContentType,
request_body: Option<String>,
) -> Delivery {
let payload: Option<String> = match content_type {
ContentType::JSON => request_body.clone(),
#[cfg(feature = "content-type-urlencoded")]
ContentType::URLENCODED => {
if let Some(request_body_string) = &request_body {
if let Some(payload_string) =
form_urlencoded::parse(request_body_string.as_bytes())
.into_owned()
.collect::<HashMap<String, String>>()
.get("payload")
{
Some(payload_string.clone())
} else {
None
}
} else {
None
}
}
#[cfg(not(feature = "content-type-urlencoded"))]
_ => None,
};
debug!("Payload body: {:?}", &payload);
#[cfg(feature = "parse")]
let parsed_payload = if let Some(payload_string) = &payload {
serde_json::from_str(payload_string.as_str()).ok()
} else {
None
};
#[cfg(not(feature = "parse"))]
let parsed_payload = None;
debug!("Parsed payload: {:#?}", &parsed_payload);
Self {
delivery_type,
id,
event,
payload: parsed_payload,
unparsed_payload: payload,
request_body,
signature,
}
}
}
impl Executor {
fn run(self, delivery: Delivery) {
for hook in self.matched_hooks {
debug!("Running hook for '{}' event", &hook.event);
hook.handle_delivery(&delivery);
}
}
fn is_empty(&self) -> bool {
self.matched_hooks.len() == 0
}
}
impl Handler {
fn get_hooks(&self, event: &str) -> Executor {
debug!("Finding matched hooks for '{}' event", &event);
let matched: Vec<Hook> = hooks_find_match!(self.hooks, event, "*");
debug!("{} matched hook(s) found", matched.len());
Executor {
matched_hooks: matched,
}
}
}
impl From<&Constructor> for Handler {
fn from(constructor: &Constructor) -> Self {
debug!("Handler constructed");
Self {
hooks: constructor.hooks.clone(),
}
}
}