mod associated;
mod draw;
mod modify;
mod single;
use std::marker::PhantomData;
use std::panic::RefUnwindSafe;
use hyper::{Body, StatusCode};
use crate::extractor::{
NoopPathExtractor, NoopQueryStringExtractor, PathExtractor, QueryStringExtractor,
};
use crate::pipeline::{finalize_pipeline_set, new_pipeline_set, PipelineHandleChain, PipelineSet};
use crate::router::response::{ResponseExtender, ResponseFinalizerBuilder};
use crate::router::route::dispatch::DispatcherImpl;
use crate::router::route::matcher::{AndRouteMatcher, RouteMatcher};
use crate::router::route::{Delegation, Extractors, RouteImpl};
use crate::router::tree::node::Node;
use crate::router::tree::Tree;
use crate::router::Router;
pub use self::associated::{AssociatedRouteBuilder, AssociatedSingleRouteBuilder};
pub use self::draw::DrawRoutes;
pub use self::modify::{ExtendRouteMatcher, ReplacePathExtractor, ReplaceQueryStringExtractor};
pub use self::single::DefineSingleRoute;
pub fn build_router<C, P, F>(pipeline_chain: C, pipelines: PipelineSet<P>, f: F) -> Router
where
C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
P: Send + Sync + 'static,
F: FnOnce(&mut RouterBuilder<C, P>),
{
let mut tree = Tree::new();
let response_finalizer = {
let mut builder = RouterBuilder {
node_builder: tree.borrow_root_mut(),
pipeline_chain,
pipelines,
response_finalizer_builder: ResponseFinalizerBuilder::new(),
};
f(&mut builder);
builder.response_finalizer_builder.finalize()
};
Router::new(tree, response_finalizer)
}
pub fn build_simple_router<F>(f: F) -> Router
where
F: FnOnce(&mut RouterBuilder<(), ()>),
{
let pipelines = finalize_pipeline_set(new_pipeline_set());
build_router((), pipelines, f)
}
pub struct RouterBuilder<'a, C, P>
where
C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
P: Send + Sync + 'static,
{
node_builder: &'a mut Node,
pipeline_chain: C,
pipelines: PipelineSet<P>,
response_finalizer_builder: ResponseFinalizerBuilder,
}
impl<'a, C, P> RouterBuilder<'a, C, P>
where
C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
P: Send + Sync + 'static,
{
pub fn add_response_extender<E>(&mut self, status_code: StatusCode, extender: E)
where
E: ResponseExtender<Body> + Send + Sync + 'static,
{
self.response_finalizer_builder
.add(status_code, Box::new(extender))
}
}
pub struct ScopeBuilder<'a, C, P>
where
C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
P: Send + Sync + 'static,
{
node_builder: &'a mut Node,
pipeline_chain: C,
pipelines: PipelineSet<P>,
}
pub struct DelegateRouteBuilder<'a, M, C, P>
where
M: RouteMatcher + Send + Sync + 'static,
C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
P: Send + Sync + 'static,
{
matcher: M,
node_builder: &'a mut Node,
pipeline_chain: C,
pipelines: PipelineSet<P>,
}
type DelegatedRoute<M> = RouteImpl<M, NoopPathExtractor, NoopQueryStringExtractor>;
impl<'a, M, C, P> DelegateRouteBuilder<'a, M, C, P>
where
M: RouteMatcher + Send + Sync + 'static,
C: PipelineHandleChain<P> + Copy + Send + Sync + 'static,
P: RefUnwindSafe + Send + Sync + 'static,
{
pub fn to_router(self, router: Router) {
let dispatcher = DispatcherImpl::new(router, self.pipeline_chain, self.pipelines);
let route: DelegatedRoute<M> = DelegatedRoute::new(
self.matcher,
Box::new(dispatcher),
Extractors::new(),
Delegation::External,
);
self.node_builder.add_route(Box::new(route));
}
pub fn add_route_matcher<NM: RouteMatcher + Send + Sync + 'static>(
self,
matcher: NM,
) -> DelegateRouteBuilder<'a, AndRouteMatcher<M, NM>, C, P> {
DelegateRouteBuilder {
matcher: AndRouteMatcher::<M, NM>::new(self.matcher, matcher),
node_builder: self.node_builder,
pipeline_chain: self.pipeline_chain,
pipelines: self.pipelines,
}
}
}
pub struct SingleRouteBuilder<'a, M, C, P, PE, QSE>
where
M: RouteMatcher + Send + Sync + 'static,
C: PipelineHandleChain<P> + Send + Sync + 'static,
P: Send + Sync + 'static,
PE: PathExtractor<Body> + Send + Sync + 'static,
QSE: QueryStringExtractor<Body> + Send + Sync + 'static,
{
node_builder: &'a mut Node,
matcher: M,
pipeline_chain: C,
pipelines: PipelineSet<P>,
phantom: PhantomData<(PE, QSE)>,
}
impl<'a, M, C, P, PE, QSE> SingleRouteBuilder<'a, M, C, P, PE, QSE>
where
M: RouteMatcher + Send + Sync + 'static,
C: PipelineHandleChain<P> + Send + Sync + 'static,
P: Send + Sync + 'static,
PE: PathExtractor<Body> + Send + Sync + 'static,
QSE: QueryStringExtractor<Body> + Send + Sync + 'static,
{
fn coerce<NPE, NQSE>(self) -> SingleRouteBuilder<'a, M, C, P, NPE, NQSE>
where
NPE: PathExtractor<Body> + Send + Sync + 'static,
NQSE: QueryStringExtractor<Body> + Send + Sync + 'static,
{
SingleRouteBuilder {
node_builder: self.node_builder,
matcher: self.matcher,
pipeline_chain: self.pipeline_chain,
pipelines: self.pipelines,
phantom: PhantomData,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use hyper::service::Service;
use hyper::{body, Body, Request, Response, StatusCode};
use serde::Deserialize;
use crate::middleware::cookie::CookieParser;
use crate::pipeline::new_pipeline;
use crate::router::response::StaticResponseExtender;
use crate::service::GothamService;
use crate::state::{State, StateData};
#[derive(Deserialize)]
struct SalutationParams {
name: String,
}
impl StateData for SalutationParams {}
impl StaticResponseExtender for SalutationParams {
type ResBody = Body;
fn extend(_: &mut State, _: &mut Response<Body>) {}
}
#[derive(Deserialize)]
struct AddParams {
x: u64,
y: u64,
}
impl StateData for AddParams {}
impl StaticResponseExtender for AddParams {
type ResBody = Body;
fn extend(_: &mut State, _: &mut Response<Body>) {}
}
mod welcome {
use super::*;
pub(crate) fn index(state: State) -> (State, Response<Body>) {
(
state,
Response::builder()
.status(StatusCode::OK)
.body(Body::empty())
.unwrap(),
)
}
pub(crate) fn literal(state: State) -> (State, Response<Body>) {
(
state,
Response::builder()
.status(StatusCode::CREATED)
.body(Body::empty())
.unwrap(),
)
}
pub(crate) fn hello(mut state: State) -> (State, Response<Body>) {
let params = state.take::<SalutationParams>();
let response = Response::builder()
.status(StatusCode::OK)
.body(format!("Hello, {}!", params.name).into())
.unwrap();
(state, response)
}
pub(crate) fn globbed(state: State) -> (State, Response<Body>) {
let response = Response::builder()
.status(StatusCode::OK)
.body("Globbed".into())
.unwrap();
(state, response)
}
pub(crate) fn delegated(state: State) -> (State, Response<Body>) {
let response = Response::builder()
.status(StatusCode::OK)
.body("Delegated".into())
.unwrap();
(state, response)
}
pub(crate) fn goodbye(mut state: State) -> (State, Response<Body>) {
let params = state.take::<SalutationParams>();
let response = Response::builder()
.status(StatusCode::OK)
.body(format!("Goodbye, {}!", params.name).into())
.unwrap();
(state, response)
}
pub(crate) fn add(mut state: State) -> (State, Response<Body>) {
let params = state.take::<AddParams>();
let response = Response::builder()
.status(StatusCode::OK)
.body(format!("{} + {} = {}", params.x, params.y, params.x + params.y,).into())
.unwrap();
(state, response)
}
pub(crate) fn trailing_slash(state: State) -> (State, Response<Body>) {
let response = Response::builder()
.status(StatusCode::OK)
.body("Trailing slash!".into())
.unwrap();
(state, response)
}
}
mod resource {
use super::*;
pub(crate) fn create(state: State) -> (State, Response<Body>) {
let response = Response::builder()
.status(StatusCode::CREATED)
.body(Body::empty())
.unwrap();
(state, response)
}
pub(crate) fn destroy(state: State) -> (State, Response<Body>) {
let response = Response::builder()
.status(StatusCode::ACCEPTED)
.body(Body::empty())
.unwrap();
(state, response)
}
pub(crate) fn show(state: State) -> (State, Response<Body>) {
let response = Response::builder()
.status(StatusCode::OK)
.body("It's a resource.".into())
.unwrap();
(state, response)
}
pub(crate) fn update(state: State) -> (State, Response<Body>) {
let response = Response::builder()
.status(StatusCode::ACCEPTED)
.body(Body::empty())
.unwrap();
(state, response)
}
}
mod api {
use super::*;
pub(crate) fn submit(state: State) -> (State, Response<Body>) {
(
state,
Response::builder()
.status(StatusCode::ACCEPTED)
.body(Body::empty())
.unwrap(),
)
}
}
#[test]
fn build_router_test() {
let pipelines = new_pipeline_set();
let (pipelines, default) = pipelines.add(new_pipeline().add(CookieParser).build());
let pipelines = finalize_pipeline_set(pipelines);
let default_pipeline_chain = (default, ());
let delegated_router = build_simple_router(|route| {
route.get("/b").to(welcome::delegated);
});
let router = build_router(default_pipeline_chain, pipelines, |route| {
route.get("/").to(welcome::index);
route
.get("/hello/:name")
.with_path_extractor::<SalutationParams>()
.to(welcome::hello);
route
.get("/hello/:name/*")
.with_path_extractor::<SalutationParams>()
.to(welcome::globbed);
route
.get("/goodbye/:name:[a-zA-Z]+")
.with_path_extractor::<SalutationParams>()
.to(welcome::goodbye);
route
.get("/add")
.with_query_string_extractor::<AddParams>()
.to(welcome::add);
route.get(r"/literal/\:param/\*").to(welcome::literal);
route.scope("/api", |route| {
route.post("/submit").to(api::submit);
});
route.associate("/resource", |route| {
route.post().to(resource::create);
route.patch().to(resource::update);
route.delete().to(resource::destroy);
route.get_or_head().to(resource::show);
});
route.delegate("/delegated").to_router(delegated_router);
route.get("/trailing-slash/").to(welcome::trailing_slash);
});
let new_service = GothamService::new(router);
let call = move |req| {
let mut service = new_service.connect("127.0.0.1:10000".parse().unwrap());
futures_executor::block_on(service.call(req)).unwrap()
};
let response = call(Request::get("/").body(Body::empty()).unwrap());
assert_eq!(response.status(), StatusCode::OK);
let response = call(Request::post("/api/submit").body(Body::empty()).unwrap());
assert_eq!(response.status(), StatusCode::ACCEPTED);
let response = call(Request::get("/hello/world").body(Body::empty()).unwrap());
assert_eq!(response.status(), StatusCode::OK);
let response_bytes = futures_executor::block_on(body::to_bytes(response.into_body()))
.unwrap()
.to_vec();
assert_eq!(&String::from_utf8(response_bytes).unwrap(), "Hello, world!");
let response = call(
Request::get("/hello/world/more/path/here/handled/by/glob")
.body(Body::empty())
.unwrap(),
);
assert_eq!(response.status(), StatusCode::OK);
let response_bytes = futures_executor::block_on(body::to_bytes(response.into_body()))
.unwrap()
.to_vec();
assert_eq!(&String::from_utf8(response_bytes).unwrap(), "Globbed");
let response = call(Request::get("/delegated/b").body(Body::empty()).unwrap());
assert_eq!(response.status(), StatusCode::OK);
let response_bytes = futures_executor::block_on(body::to_bytes(response.into_body()))
.unwrap()
.to_vec();
assert_eq!(&String::from_utf8(response_bytes).unwrap(), "Delegated");
let response = call(Request::get("/goodbye/world").body(Body::empty()).unwrap());
assert_eq!(response.status(), StatusCode::OK);
let response_bytes = futures_executor::block_on(body::to_bytes(response.into_body()))
.unwrap()
.to_vec();
assert_eq!(
&String::from_utf8(response_bytes).unwrap(),
"Goodbye, world!"
);
let response = call(Request::get("/goodbye/9875").body(Body::empty()).unwrap());
assert_eq!(response.status(), StatusCode::NOT_FOUND);
let response = call(
Request::get("/literal/:param/*")
.body(Body::empty())
.unwrap(),
);
assert_eq!(response.status(), StatusCode::CREATED);
let response = call(Request::get("/literal/a/b").body(Body::empty()).unwrap());
assert_eq!(response.status(), StatusCode::NOT_FOUND);
let response = call(Request::get("/add?x=16&y=71").body(Body::empty()).unwrap());
assert_eq!(response.status(), StatusCode::OK);
let response_bytes = futures_executor::block_on(body::to_bytes(response.into_body()))
.unwrap()
.to_vec();
assert_eq!(&String::from_utf8(response_bytes).unwrap(), "16 + 71 = 87");
let response = call(Request::post("/resource").body(Body::empty()).unwrap());
assert_eq!(response.status(), StatusCode::CREATED);
let response = call(Request::patch("/resource").body(Body::empty()).unwrap());
assert_eq!(response.status(), StatusCode::ACCEPTED);
let response = call(Request::delete("/resource").body(Body::empty()).unwrap());
assert_eq!(response.status(), StatusCode::ACCEPTED);
let response = call(Request::get("/resource").body(Body::empty()).unwrap());
assert_eq!(response.status(), StatusCode::OK);
let response_bytes = futures_executor::block_on(body::to_bytes(response.into_body()))
.unwrap()
.to_vec();
assert_eq!(&response_bytes[..], b"It's a resource.");
let response = call(
Request::get("/trailing-slash/")
.body(Body::empty())
.unwrap(),
);
assert_eq!(response.status(), StatusCode::OK);
let response = call(Request::get("/trailing-slash").body(Body::empty()).unwrap());
assert_eq!(response.status(), StatusCode::OK);
}
}