Skip to content

Commit

Permalink
Add subrouter and per-endpoint middleware support
Browse files Browse the repository at this point in the history
  • Loading branch information
tirr-c committed Nov 18, 2018
1 parent a724ef3 commit dfe72ab
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 29 deletions.
25 changes: 16 additions & 9 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use std::{
use crate::{
body::Body,
extract::Extract,
router::{Resource, Router},
router::{ResourceHandle, RouteResult, Router},
Middleware, Request, Response, RouteMatch,
};

Expand All @@ -24,7 +24,6 @@ use crate::{
pub struct App<Data> {
data: Data,
router: Router<Data>,
middleware: Vec<Box<dyn Middleware<Data> + Send + Sync>>,
}

impl<Data: Clone + Send + Sync + 'static> App<Data> {
Expand All @@ -33,26 +32,31 @@ impl<Data: Clone + Send + Sync + 'static> App<Data> {
App {
data,
router: Router::new(),
middleware: Vec::new(),
}
}

/// Get the top-level router.
pub fn router(&mut self) -> &mut Router<Data> {
&mut self.router
}

/// Add a new resource at `path`.
pub fn at<'a>(&'a mut self, path: &'a str) -> &mut Resource<Data> {
///
/// Middlewares added before will be applied to the resource.
pub fn at<'a>(&'a mut self, path: &'a str) -> ResourceHandle<Data> {
self.router.at(path)
}

/// Apply `middleware` to the whole app.
pub fn middleware(&mut self, middleware: impl Middleware<Data> + 'static) -> &mut Self {
self.middleware.push(Box::new(middleware));
self.router.middleware(middleware);
self
}

fn into_server(self) -> Server<Data> {
Server {
data: self.data,
router: Arc::new(self.router),
middleware: Arc::new(self.middleware),
}
}

Expand Down Expand Up @@ -84,7 +88,6 @@ impl<Data: Clone + Send + Sync + 'static> App<Data> {
struct Server<Data> {
data: Data,
router: Arc<Router<Data>>,
middleware: Arc<Vec<Box<dyn Middleware<Data> + Send + Sync>>>,
}

impl<Data: Clone + Send + Sync + 'static> Service for Server<Data> {
Expand All @@ -96,15 +99,19 @@ impl<Data: Clone + Send + Sync + 'static> Service for Server<Data> {
fn call(&mut self, req: http::Request<hyper::Body>) -> Self::Future {
let mut data = self.data.clone();
let router = self.router.clone();
let middleware = self.middleware.clone();

let mut req = req.map(Body::from);
let path = req.uri().path().to_owned();
let method = req.method().to_owned();

FutureObj::new(Box::new(
async move {
if let Some((endpoint, params)) = router.route(&path, &method) {
if let Some(RouteResult {
endpoint,
params,
middleware,
}) = router.route(&path, &method)
{
for m in middleware.iter() {
match await!(m.request(&mut data, req, &params)) {
Ok(new_req) => req = new_req,
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,6 @@ pub use crate::{
middleware::Middleware,
request::Request,
response::{IntoResponse, Response},
router::Resource,
router::{Resource, Router},
url_table::RouteMatch,
};
112 changes: 96 additions & 16 deletions src/router.rs
Original file line number Diff line number Diff line change
@@ -1,38 +1,125 @@
use std::collections::HashMap;
use std::sync::Arc;

use crate::{
endpoint::{BoxedEndpoint, Endpoint},
url_table::{RouteMatch, UrlTable},
Middleware,
};

pub(crate) struct Router<Data> {
/// TODO: Document `Router`
pub struct Router<Data> {
table: UrlTable<Resource<Data>>,
middleware: Vec<Arc<dyn Middleware<Data> + Send + Sync>>,
}

pub(crate) struct RouteResult<'a, Data> {
pub(crate) endpoint: &'a BoxedEndpoint<Data>,
pub(crate) params: RouteMatch<'a>,
pub(crate) middleware: &'a [Arc<dyn Middleware<Data> + Send + Sync>],
}

impl<Data> Default for Router<Data> {
fn default() -> Router<Data> {
Router::new()
}
}

impl<Data> Router<Data> {
pub(crate) fn new() -> Router<Data> {
Router {
table: UrlTable::new(),
middleware: Vec::new(),
}
}

pub(crate) fn at<'a>(&'a mut self, path: &'a str) -> &mut Resource<Data> {
self.table.setup(path)
/// Add a new resource at `path`, relative to this router.
///
/// Middlewares added before will be applied to the resource.
pub fn at<'a>(&'a mut self, path: &'a str) -> ResourceHandle<'a, Data> {
let table = self.table.setup_table(path);
let middleware = &*self.middleware;
ResourceHandle { table, middleware }
}

/// Add `middleware` to this router.
pub fn middleware(&mut self, middleware: impl Middleware<Data> + 'static) -> &mut Self {
self.middleware.push(Arc::new(middleware));
self
}

pub(crate) fn route<'a>(
&'a self,
path: &'a str,
method: &http::Method,
) -> Option<(&'a BoxedEndpoint<Data>, RouteMatch<'a>)> {
) -> Option<RouteResult<'a, Data>> {
let (route, route_match) = self.table.route(path)?;
// If it is a HTTP HEAD request then check if there is a callback in the endpoints map
// if not then fallback to the behavior of HTTP GET else proceed as usual
if method == http::Method::HEAD && !route.endpoints.contains_key(&http::Method::HEAD) {
Some((route.endpoints.get(&http::Method::GET)?, route_match))
} else {
Some((route.endpoints.get(method)?, route_match))
let endpoint =
if method == http::Method::HEAD && !route.endpoints.contains_key(&http::Method::HEAD) {
route.endpoints.get(&http::Method::GET)?
} else {
route.endpoints.get(method)?
};
let middleware = &*route.middleware;

Some(RouteResult {
endpoint,
params: route_match,
middleware,
})
}
}

/// A struct representing specific path of a router.
///
/// The struct implements `Deref` and `DerefMut` into `Resource`. You can use this to add a
/// resource to the path.
///
/// Also, using `nest`, you can set up a subrouter for the path.
pub struct ResourceHandle<'a, Data> {
table: &'a mut UrlTable<Resource<Data>>,
middleware: &'a [Arc<dyn Middleware<Data> + Send + Sync>],
}

impl<'a, Data> ResourceHandle<'a, Data> {
/// "Nest" a subrouter to the path.
pub fn nest<F>(self, builder: F)
where
F: FnOnce(&mut Router<Data>),
{
let mut subrouter = Router {
table: UrlTable::new(),
middleware: self.middleware.to_vec(),
};
builder(&mut subrouter);
std::mem::swap(self.table, &mut subrouter.table);
}
}

impl<'a, Data> std::ops::Deref for ResourceHandle<'a, Data> {
type Target = Resource<Data>;

fn deref(&self) -> &Resource<Data> {
self.table
.resource()
.expect("Resource of this path has not been initialized.")
}
}

impl<'a, Data> std::ops::DerefMut for ResourceHandle<'a, Data> {
fn deref_mut(&mut self) -> &mut Resource<Data> {
let resource = self.table.resource_mut();
if resource.is_none() {
let new_resource = Resource {
endpoints: HashMap::new(),
middleware: self.middleware.to_vec(),
};
*resource = Some(new_resource);
}

resource.as_mut().unwrap()
}
}

Expand All @@ -42,14 +129,7 @@ impl<Data> Router<Data> {
/// the `Resource` type can be used to establish endpoints for various HTTP methods at that path.
pub struct Resource<Data> {
endpoints: HashMap<http::Method, BoxedEndpoint<Data>>,
}

impl<Data> Default for Resource<Data> {
fn default() -> Self {
Resource {
endpoints: HashMap::new(),
}
}
middleware: Vec<Arc<dyn Middleware<Data> + Send + Sync>>,
}

impl<Data> Resource<Data> {
Expand Down
27 changes: 24 additions & 3 deletions src/url_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub struct RouteMatch<'a> {
pub map: HashMap<&'a str, &'a str>,
}

impl<R: Default> UrlTable<R> {
impl<R> UrlTable<R> {
/// Create an empty routing table.
pub fn new() -> UrlTable<R> {
UrlTable {
Expand All @@ -42,6 +42,16 @@ impl<R: Default> UrlTable<R> {
}
}

/// Get the resource of current path.
pub fn resource(&self) -> Option<&R> {
self.accept.as_ref()
}

/// Retrieve a mutable reference of the resource.
pub fn resource_mut(&mut self) -> &mut Option<R> {
&mut self.accept
}

/// Determine which resource, if any, the conrete `path` should be routed to.
pub fn route<'a>(&'a self, path: &'a str) -> Option<(&'a R, RouteMatch<'a>)> {
let mut table = self;
Expand Down Expand Up @@ -83,8 +93,10 @@ impl<R: Default> UrlTable<R> {
self.wildcard.as_mut().map(|b| &mut **b)
}

/// Add or access a new resource at the given routing path (which may contain wildcards).
pub fn setup(&mut self, path: &str) -> &mut R {
/// Return the table of the given routing path (which may contain wildcards).
///
/// If it doesn't already exist, this will make a new one.
pub fn setup_table(&mut self, path: &str) -> &mut UrlTable<R> {
let mut table = self;
for segment in path.split('/') {
if segment.is_empty() {
Expand Down Expand Up @@ -117,6 +129,15 @@ impl<R: Default> UrlTable<R> {
}
}

table
}
}

impl<R: Default> UrlTable<R> {
/// Add or access a new resource at the given routing path (which may contain wildcards).
pub fn setup(&mut self, path: &str) -> &mut R {
let table = self.setup_table(path);

if table.accept.is_none() {
table.accept = Some(R::default())
}
Expand Down

0 comments on commit dfe72ab

Please sign in to comment.