1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
//! Universal part between of different frameworks
//!
//! `Constructor` accepts settings from the user, and generates `Handler` from itself.
//!
//! The `Handler` struct should be created automatically by constructor, it is the actual handler of requests.

#[cfg(feature = "hyper-support")]
mod hyper;

#[cfg(feature = "parse")]
use serde_json::Value;
use url::form_urlencoded;

use std::collections::HashMap;

use super::hook::Hook;

/// Registry of hooks
pub type HookRegistry = HashMap<String, Hook>;

/// Find matched hooks from `HookRegistry`, accepting multiple keys.
#[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
    }};
}

/// Type of content
pub enum ContentType {
    JSON,
    URLENCODED,
}

#[cfg(not(feature = "parse"))]
#[doc(hidden)]
#[derive(Debug, Clone)]
pub enum Value {}

/// Constructor of the server
#[derive(Clone, Default)]
pub struct Constructor {
    pub hooks: HookRegistry,
}

/// Information gathered from the received request
/// Not sure what is included in the request, so all of the fields are wrapped in `Option<T>`
#[derive(Default, Debug, Clone)]
pub struct Delivery {
    pub id: Option<String>,
    pub event: Option<String>,
    pub payload: Option<Value>,
    pub unparsed_payload: Option<String>,
    pub request_body: Option<String>, // for x-www-form-urlencoded authentication support
    pub signature: Option<String>,
}

/// (Private) Executor of the hooks, passed into futures.
/// It should not be used outside of the crate.
struct Executor {
    matched_hooks: Vec<Hook>,
}

/// The main handler struct.
pub struct Handler {
    hooks: HookRegistry,
}

/// Main impl clause of the `Constructor`
impl Constructor {
    /// Create a new, empty `Constructor`
    pub fn new() -> Constructor {
        Constructor {
            ..Default::default()
        }
    }

    /// Register a hook to `Constructor`
    pub fn register(&mut self, hook: Hook) {
        self.hooks.insert(hook.event.to_string(), hook.clone());
    }
}

/// The main impl clause of `Delivery`
impl Delivery {
    /// Create a new Delivery
    pub fn new(
        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(),
            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
                }
            }
        };
        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 {
            id,
            event,
            payload: parsed_payload,
            unparsed_payload: payload,
            request_body,
            signature,
        }
    }
}

/// The main impl clause of `Executor`
impl Executor {
    /// Run the hooks
    fn run(self, delivery: Delivery) {
        for hook in self.matched_hooks {
            debug!("Running hook for '{}' event", &hook.event);
            hook.handle_delivery(&delivery);
        }
    }

    /// Test if there are no matched hook found
    fn is_empty(&self) -> bool {
        self.matched_hooks.len() == 0
    }
}

/// The main impl clause of Handler
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,
        }
    }
}

/// Implement `From<&Constructor>` trait for `Handler`
/// As currently we don't have Generic Associate Types, I can only clone the registry.
impl From<&Constructor> for Handler {
    /// Create a handler object from constructor
    fn from(constructor: &Constructor) -> Self {
        debug!("Handler constructed");
        Self {
            hooks: constructor.hooks.clone(),
        }
    }
}