diff --git a/src/app.rs b/src/app.rs index 2a14d95c2..b45b0886e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -12,7 +12,7 @@ use std::{ use crate::{ body::Body, extract::Extract, - router::{Resource, Router}, + router::{ResourceHandle, RouteResult, Router}, Middleware, Request, Response, RouteMatch, }; @@ -24,7 +24,6 @@ use crate::{ pub struct App { data: Data, router: Router, - middleware: Vec + Send + Sync>>, } impl App { @@ -33,18 +32,24 @@ impl App { App { data, router: Router::new(), - middleware: Vec::new(), } } + /// Get the top-level router. + pub fn router(&mut self) -> &mut Router { + &mut self.router + } + /// Add a new resource at `path`. - pub fn at<'a>(&'a mut self, path: &'a str) -> &mut Resource { + /// + /// Middlewares added before will be applied to the resource. + pub fn at<'a>(&'a mut self, path: &'a str) -> ResourceHandle { self.router.at(path) } /// Apply `middleware` to the whole app. pub fn middleware(&mut self, middleware: impl Middleware + 'static) -> &mut Self { - self.middleware.push(Box::new(middleware)); + self.router.middleware(middleware); self } @@ -52,7 +57,6 @@ impl App { Server { data: self.data, router: Arc::new(self.router), - middleware: Arc::new(self.middleware), } } @@ -84,7 +88,6 @@ impl App { struct Server { data: Data, router: Arc>, - middleware: Arc + Send + Sync>>>, } impl Service for Server { @@ -96,7 +99,6 @@ impl Service for Server { fn call(&mut self, req: http::Request) -> 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(); @@ -104,7 +106,12 @@ impl Service for Server { 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, ¶ms)) { Ok(new_req) => req = new_req, diff --git a/src/lib.rs b/src/lib.rs index 8057a6cfb..9510d4451 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,6 +30,6 @@ pub use crate::{ middleware::Middleware, request::Request, response::{IntoResponse, Response}, - router::Resource, + router::{Resource, Router}, url_table::RouteMatch, }; diff --git a/src/router.rs b/src/router.rs index 5f075f339..a6e0448bf 100644 --- a/src/router.rs +++ b/src/router.rs @@ -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 { +/// TODO: Document `Router` +pub struct Router { table: UrlTable>, + middleware: Vec + Send + Sync>>, +} + +pub(crate) struct RouteResult<'a, Data> { + pub(crate) endpoint: &'a BoxedEndpoint, + pub(crate) params: RouteMatch<'a>, + pub(crate) middleware: &'a [Arc + Send + Sync>], +} + +impl Default for Router { + fn default() -> Router { + Router::new() + } } impl Router { pub(crate) fn new() -> Router { Router { table: UrlTable::new(), + middleware: Vec::new(), } } - pub(crate) fn at<'a>(&'a mut self, path: &'a str) -> &mut Resource { - 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 + '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, RouteMatch<'a>)> { + ) -> Option> { 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>, + middleware: &'a [Arc + Send + Sync>], +} + +impl<'a, Data> ResourceHandle<'a, Data> { + /// "Nest" a subrouter to the path. + pub fn nest(self, builder: F) + where + F: FnOnce(&mut Router), + { + 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; + + fn deref(&self) -> &Resource { + 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 { + 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() } } @@ -42,14 +129,7 @@ impl Router { /// the `Resource` type can be used to establish endpoints for various HTTP methods at that path. pub struct Resource { endpoints: HashMap>, -} - -impl Default for Resource { - fn default() -> Self { - Resource { - endpoints: HashMap::new(), - } - } + middleware: Vec + Send + Sync>>, } impl Resource { diff --git a/src/url_table.rs b/src/url_table.rs index 2863349df..4448754ee 100644 --- a/src/url_table.rs +++ b/src/url_table.rs @@ -32,7 +32,7 @@ pub struct RouteMatch<'a> { pub map: HashMap<&'a str, &'a str>, } -impl UrlTable { +impl UrlTable { /// Create an empty routing table. pub fn new() -> UrlTable { UrlTable { @@ -42,6 +42,16 @@ impl UrlTable { } } + /// 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 { + &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; @@ -83,8 +93,10 @@ impl UrlTable { 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 { let mut table = self; for segment in path.split('/') { if segment.is_empty() { @@ -117,6 +129,15 @@ impl UrlTable { } } + table + } +} + +impl UrlTable { + /// 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()) }