pub mod dispatch;
pub mod matcher;
use std::marker::PhantomData;
use std::panic::RefUnwindSafe;
use std::pin::Pin;
use hyper::{Body, Response, Uri};
use log::debug;
use crate::extractor::{self, PathExtractor, QueryStringExtractor};
use crate::handler::HandlerFuture;
use crate::helpers::http::request::query_string;
use crate::router::non_match::RouteNonMatch;
use crate::router::route::dispatch::Dispatcher;
use crate::router::route::matcher::RouteMatcher;
use crate::router::tree::segment::SegmentMapping;
use crate::state::{request_id, State};
#[derive(Clone, Copy, PartialEq)]
pub enum Delegation {
Internal,
External,
}
pub trait Route: RefUnwindSafe {
type ResBody;
fn is_match(&self, state: &State) -> Result<(), RouteNonMatch>;
fn delegation(&self) -> Delegation;
fn extract_request_path<'a>(
&self,
state: &mut State,
params: SegmentMapping<'a>,
) -> Result<(), ExtractorFailed>;
fn extend_response_on_path_error(&self, state: &mut State, res: &mut Response<Self::ResBody>);
fn extract_query_string(&self, state: &mut State) -> Result<(), ExtractorFailed>;
fn extend_response_on_query_string_error(
&self,
state: &mut State,
res: &mut Response<Self::ResBody>,
);
fn dispatch(&self, state: State) -> Pin<Box<HandlerFuture>>;
}
pub struct ExtractorFailed;
pub struct RouteImpl<RM, PE, QSE>
where
RM: RouteMatcher,
PE: PathExtractor<Body>,
QSE: QueryStringExtractor<Body>,
{
matcher: RM,
dispatcher: Box<dyn Dispatcher + Send + Sync>,
_extractors: Extractors<PE, QSE>,
delegation: Delegation,
}
pub struct Extractors<PE, QSE>
where
PE: PathExtractor<Body>,
QSE: QueryStringExtractor<Body>,
{
rpe_phantom: PhantomData<PE>,
qse_phantom: PhantomData<QSE>,
}
impl<RM, PE, QSE> RouteImpl<RM, PE, QSE>
where
RM: RouteMatcher,
PE: PathExtractor<Body>,
QSE: QueryStringExtractor<Body>,
{
pub fn new(
matcher: RM,
dispatcher: Box<dyn Dispatcher + Send + Sync>,
_extractors: Extractors<PE, QSE>,
delegation: Delegation,
) -> Self {
RouteImpl {
matcher,
dispatcher,
_extractors,
delegation,
}
}
}
impl<PE, QSE> Extractors<PE, QSE>
where
PE: PathExtractor<Body>,
QSE: QueryStringExtractor<Body>,
{
pub fn new() -> Self {
Extractors {
rpe_phantom: PhantomData,
qse_phantom: PhantomData,
}
}
}
impl<RM, PE, QSE> Route for RouteImpl<RM, PE, QSE>
where
RM: RouteMatcher,
PE: PathExtractor<Body>,
QSE: QueryStringExtractor<Body>,
{
type ResBody = Body;
fn is_match(&self, state: &State) -> Result<(), RouteNonMatch> {
self.matcher.is_match(state)
}
fn delegation(&self) -> Delegation {
self.delegation
}
fn dispatch(&self, state: State) -> Pin<Box<HandlerFuture>> {
self.dispatcher.dispatch(state)
}
fn extract_request_path<'a>(
&self,
state: &mut State,
params: SegmentMapping<'a>,
) -> Result<(), ExtractorFailed> {
match extractor::internal::from_segment_mapping::<PE>(params) {
Ok(val) => Ok(state.put(val)),
Err(e) => {
debug!("[{}] path extractor failed: {}", request_id(&state), e);
Err(ExtractorFailed)
}
}
}
fn extend_response_on_path_error(&self, state: &mut State, res: &mut Response<Self::ResBody>) {
PE::extend(state, res)
}
fn extract_query_string(&self, state: &mut State) -> Result<(), ExtractorFailed> {
let result: Result<QSE, _> = {
let uri = state.borrow::<Uri>();
let query_string_mapping = query_string::split(uri.query());
extractor::internal::from_query_string_mapping(&query_string_mapping)
};
match result {
Ok(val) => Ok(state.put(val)),
Err(e) => {
debug!(
"[{}] query string extractor failed: {}",
request_id(&state),
e
);
Err(ExtractorFailed)
}
}
}
fn extend_response_on_query_string_error(
&self,
state: &mut State,
res: &mut Response<Self::ResBody>,
) {
QSE::extend(state, res)
}
}
#[cfg(test)]
mod tests {
use super::*;
use futures::prelude::*;
use hyper::{HeaderMap, Method, StatusCode, Uri};
use std::str::FromStr;
use crate::extractor::{NoopPathExtractor, NoopQueryStringExtractor};
use crate::helpers::http::request::path::RequestPathSegments;
use crate::helpers::http::response::create_empty_response;
use crate::pipeline::set::*;
use crate::router::builder::*;
use crate::router::route::dispatch::DispatcherImpl;
use crate::router::route::matcher::MethodOnlyRouteMatcher;
use crate::state::set_request_id;
#[test]
fn internal_route_tests() {
fn handler(state: State) -> (State, Response<Body>) {
let res = create_empty_response(&state, StatusCode::ACCEPTED);
(state, res)
}
let pipeline_set = finalize_pipeline_set(new_pipeline_set());
let methods = vec![Method::GET];
let matcher = MethodOnlyRouteMatcher::new(methods);
let dispatcher = Box::new(DispatcherImpl::new(|| Ok(handler), (), pipeline_set));
let extractors: Extractors<NoopPathExtractor, NoopQueryStringExtractor> = Extractors::new();
let route = RouteImpl::new(matcher, dispatcher, extractors, Delegation::Internal);
let mut state = State::new();
state.put(HeaderMap::new());
state.put(Method::GET);
set_request_id(&mut state);
match route.dispatch(state).now_or_never() {
Some(Ok((_state, response))) => assert_eq!(response.status(), StatusCode::ACCEPTED),
Some(Err((_state, e))) => panic!("error polling future: {:?}", e),
None => panic!("expected future to be completed already"),
}
}
#[test]
fn external_route_tests() {
fn handler(state: State) -> (State, Response<Body>) {
let res = create_empty_response(&state, StatusCode::ACCEPTED);
(state, res)
}
let secondary_router = build_simple_router(|route| {
route.get("/").to(handler);
});
let pipeline_set = finalize_pipeline_set(new_pipeline_set());
let methods = vec![Method::GET];
let matcher = MethodOnlyRouteMatcher::new(methods);
let dispatcher = Box::new(DispatcherImpl::new(secondary_router, (), pipeline_set));
let extractors: Extractors<NoopPathExtractor, NoopQueryStringExtractor> = Extractors::new();
let route = RouteImpl::new(matcher, dispatcher, extractors, Delegation::External);
let mut state = State::new();
state.put(Method::GET);
state.put(Uri::from_str("https://example.com/").unwrap());
state.put(HeaderMap::new());
state.put(RequestPathSegments::new("/"));
set_request_id(&mut state);
match route.dispatch(state).now_or_never() {
Some(Ok((_state, response))) => assert_eq!(response.status(), StatusCode::ACCEPTED),
Some(Err((_state, e))) => panic!("error polling future: {:?}", e),
None => panic!("expected future to be completed already"),
}
}
}