use crate::{error::SaphirError, http_context::HttpContext, response::Response, utils::UriPathMatcher};
use futures::{future::BoxFuture, FutureExt};
use futures_util::future::Future;
use hyper::Body;
pub trait MiddlewareHandler<Data> {
fn next(
&self,
data: &Data,
ctx: HttpContext<Body>,
chain: &dyn MiddlewareChain,
) -> BoxFuture<'static, Result<Response<Body>, SaphirError>>;
}
impl<Data, Fun, Fut> MiddlewareHandler<Data> for Fun
where
Data: 'static,
Fun: Fn(&'static Data, HttpContext<Body>, &'static dyn MiddlewareChain) -> Fut,
Fut: 'static + Future<Output = Result<Response<Body>, SaphirError>> + Send,
{
#[inline]
fn next(
&self,
data: &Data,
ctx: HttpContext<Body>,
chain: &dyn MiddlewareChain,
) -> BoxFuture<'static, Result<Response<Body>, SaphirError>> {
let (data, chain) = unsafe {
(
std::mem::transmute::<&'_ Data, &'static Data>(data),
std::mem::transmute::<&'_ dyn MiddlewareChain, &'static dyn MiddlewareChain>(chain),
)
};
(*self)(data, ctx, chain).boxed()
}
}
pub struct Builder<Chain: MiddlewareChain> {
chain: Chain,
}
impl Default for Builder<MiddleChainEnd> {
fn default() -> Self {
Self { chain: MiddleChainEnd }
}
}
impl<Chain: MiddlewareChain + 'static> Builder<Chain> {
pub fn apply<'a, Data, Handler, E>(
self,
handler: Handler,
data: Data,
include_path: Vec<&str>,
exclude_path: E,
) -> Builder<MiddlewareChainLink<Data, Handler, Chain>>
where
Data: Sync + Send,
Handler: 'static + MiddlewareHandler<Data> + Sync + Send,
E: Into<Option<Vec<&'a str>>>,
{
let rule = Rule::new(include_path, exclude_path.into());
Builder {
chain: MiddlewareChainLink {
rule,
data,
handler,
rest: self.chain,
},
}
}
pub(crate) fn build(self) -> Box<dyn MiddlewareChain> {
Box::new(self.chain)
}
}
pub(crate) struct Rule {
included_path: Vec<UriPathMatcher>,
excluded_path: Option<Vec<UriPathMatcher>>,
}
impl Rule {
#[doc(hidden)]
pub fn new(include_path: Vec<&str>, exclude_path: Option<Vec<&str>>) -> Self {
Rule {
included_path: include_path
.iter()
.filter_map(|p| {
UriPathMatcher::new(p)
.map_err(|e| error!("Unable to construct included middleware route: {}", e))
.ok()
})
.collect(),
excluded_path: exclude_path.map(|ex| {
ex.iter()
.filter_map(|p| {
UriPathMatcher::new(p)
.map_err(|e| error!("Unable to construct excluded middleware route: {}", e))
.ok()
})
.collect()
}),
}
}
#[doc(hidden)]
pub fn validate_path(&self, path: &str) -> bool {
if self.included_path.iter().any(|m_p| m_p.match_start(path)) {
if let Some(ref excluded_path) = self.excluded_path {
return !excluded_path.iter().any(|m_e_p| m_e_p.match_start(path));
} else {
return true;
}
}
false
}
}
#[doc(hidden)]
pub trait MiddlewareChain: Sync + Send {
fn next(&self, ctx: HttpContext<Body>) -> BoxFuture<'static, Result<Response<Body>, SaphirError>>;
}
#[doc(hidden)]
pub struct MiddleChainEnd;
impl MiddlewareChain for MiddleChainEnd {
#[doc(hidden)]
#[inline]
fn next(&self, ctx: HttpContext<Body>) -> BoxFuture<'static, Result<Response<Body>, SaphirError>> {
async {
let (router, request) = (ctx.router, ctx.request);
router.handle(request).await
}
.boxed()
}
}
#[doc(hidden)]
pub struct MiddlewareChainLink<Data, Handler: MiddlewareHandler<Data>, Rest: MiddlewareChain> {
rule: Rule,
data: Data,
handler: Handler,
rest: Rest,
}
#[doc(hidden)]
impl<Data, Handler, Rest> MiddlewareChain for MiddlewareChainLink<Data, Handler, Rest>
where
Data: Sync + Send,
Handler: MiddlewareHandler<Data> + Sync + Send,
Rest: MiddlewareChain,
{
#[doc(hidden)]
#[inline]
fn next(&self, ctx: HttpContext<Body>) -> BoxFuture<'static, Result<Response<Body>, SaphirError>> {
if self.rule.validate_path(ctx.request.uri().path()) {
self.handler.next(&self.data, ctx, &self.rest)
} else {
self.rest.next(ctx)
}
}
}