2019-01-05 13:19:25 -08:00
|
|
|
use std::collections::HashMap;
|
|
|
|
use std::rc::Rc;
|
|
|
|
|
2019-02-09 07:24:35 -08:00
|
|
|
use crate::resource::ResourceDef;
|
2019-03-04 11:47:03 -08:00
|
|
|
use crate::{Resource, ResourcePath};
|
2019-01-05 13:19:25 -08:00
|
|
|
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
|
|
pub(crate) enum ResourceId {
|
|
|
|
Default,
|
|
|
|
Normal(u16),
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Information about current resource
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub struct ResourceInfo {
|
|
|
|
rmap: Rc<ResourceMap>,
|
|
|
|
resource: ResourceId,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Default, Debug)]
|
|
|
|
pub(crate) struct ResourceMap {
|
2019-02-09 07:24:35 -08:00
|
|
|
root: Option<ResourceDef>,
|
|
|
|
named: HashMap<String, ResourceDef>,
|
|
|
|
patterns: Vec<ResourceDef>,
|
2019-01-05 13:19:25 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Resource router.
|
2019-03-04 11:47:03 -08:00
|
|
|
pub struct Router<T, U = ()> {
|
2019-01-05 13:19:25 -08:00
|
|
|
rmap: Rc<ResourceMap>,
|
2019-02-09 07:24:35 -08:00
|
|
|
named: HashMap<String, ResourceDef>,
|
2019-03-04 11:47:03 -08:00
|
|
|
resources: Vec<(T, Option<U>)>,
|
2019-01-05 13:19:25 -08:00
|
|
|
}
|
|
|
|
|
2019-03-04 11:47:03 -08:00
|
|
|
impl<T, U> Router<T, U> {
|
|
|
|
pub fn build() -> RouterBuilder<T, U> {
|
2019-01-05 13:19:25 -08:00
|
|
|
RouterBuilder {
|
|
|
|
rmap: ResourceMap::default(),
|
|
|
|
named: HashMap::new(),
|
|
|
|
resources: Vec::new(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-04 11:47:03 -08:00
|
|
|
pub fn recognize<R: Resource<P>, P: ResourcePath>(
|
|
|
|
&self,
|
|
|
|
res: &mut R,
|
|
|
|
) -> Option<(&T, ResourceInfo)> {
|
2019-03-03 21:00:58 -08:00
|
|
|
for (idx, resource) in self.rmap.patterns.iter().enumerate() {
|
2019-03-04 11:47:03 -08:00
|
|
|
if resource.match_path(res.resource_path()) {
|
2019-03-03 21:00:58 -08:00
|
|
|
let info = ResourceInfo {
|
|
|
|
rmap: self.rmap.clone(),
|
|
|
|
resource: ResourceId::Normal(idx as u16),
|
|
|
|
};
|
2019-03-04 11:47:03 -08:00
|
|
|
return Some((&self.resources[idx].0, info));
|
2019-01-05 13:19:25 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
2019-03-04 11:47:03 -08:00
|
|
|
pub fn recognize_mut<R: Resource<P>, P: ResourcePath>(
|
2019-01-05 13:19:25 -08:00
|
|
|
&mut self,
|
2019-03-04 11:47:03 -08:00
|
|
|
res: &mut R,
|
2019-01-05 13:19:25 -08:00
|
|
|
) -> Option<(&mut T, ResourceInfo)> {
|
2019-03-03 21:00:58 -08:00
|
|
|
for (idx, resource) in self.rmap.patterns.iter().enumerate() {
|
2019-03-04 11:47:03 -08:00
|
|
|
if resource.match_path(res.resource_path()) {
|
2019-03-03 21:00:58 -08:00
|
|
|
let info = ResourceInfo {
|
|
|
|
rmap: self.rmap.clone(),
|
|
|
|
resource: ResourceId::Normal(idx as u16),
|
|
|
|
};
|
2019-03-04 11:47:03 -08:00
|
|
|
return Some((&mut self.resources[idx].0, info));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn recognize_mut_checked<R: Resource<P>, P: ResourcePath, F>(
|
|
|
|
&mut self,
|
|
|
|
res: &mut R,
|
|
|
|
check: F,
|
|
|
|
) -> Option<(&mut T, ResourceInfo)>
|
|
|
|
where
|
|
|
|
F: Fn(&R, &Option<U>) -> bool,
|
|
|
|
{
|
|
|
|
for (idx, resource) in self.rmap.patterns.iter().enumerate() {
|
|
|
|
if resource.match_path(res.resource_path()) && check(res, &self.resources[idx].1) {
|
|
|
|
let info = ResourceInfo {
|
|
|
|
rmap: self.rmap.clone(),
|
|
|
|
resource: ResourceId::Normal(idx as u16),
|
|
|
|
};
|
|
|
|
return Some((&mut self.resources[idx].0, info));
|
2019-01-05 13:19:25 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-04 11:47:03 -08:00
|
|
|
impl<'a, T, U> IntoIterator for &'a Router<T, U> {
|
|
|
|
type Item = &'a (T, Option<U>);
|
|
|
|
type IntoIter = std::slice::Iter<'a, (T, Option<U>)>;
|
2019-01-05 22:00:38 -08:00
|
|
|
|
|
|
|
fn into_iter(self) -> Self::IntoIter {
|
|
|
|
self.resources.iter()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-04 11:47:03 -08:00
|
|
|
impl<'a, T, U> IntoIterator for &'a mut Router<T, U> {
|
|
|
|
type Item = &'a mut (T, Option<U>);
|
|
|
|
type IntoIter = std::slice::IterMut<'a, (T, Option<U>)>;
|
2019-01-05 22:00:38 -08:00
|
|
|
|
|
|
|
fn into_iter(self) -> Self::IntoIter {
|
|
|
|
self.resources.iter_mut()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-05 13:19:25 -08:00
|
|
|
impl ResourceMap {
|
2019-02-09 07:24:35 -08:00
|
|
|
fn register(&mut self, pattern: ResourceDef) {
|
2019-01-05 13:19:25 -08:00
|
|
|
self.patterns.push(pattern);
|
|
|
|
}
|
|
|
|
|
2019-02-09 07:24:35 -08:00
|
|
|
fn register_named(&mut self, name: String, pattern: ResourceDef) {
|
2019-01-05 13:19:25 -08:00
|
|
|
self.patterns.push(pattern.clone());
|
|
|
|
self.named.insert(name, pattern);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn has_resource(&self, path: &str) -> bool {
|
|
|
|
unimplemented!()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-04 14:03:46 -08:00
|
|
|
pub struct RouterBuilder<T, U = ()> {
|
2019-01-05 13:19:25 -08:00
|
|
|
rmap: ResourceMap,
|
2019-02-09 07:24:35 -08:00
|
|
|
named: HashMap<String, ResourceDef>,
|
2019-03-04 11:47:03 -08:00
|
|
|
resources: Vec<(T, Option<U>)>,
|
2019-01-05 13:19:25 -08:00
|
|
|
}
|
|
|
|
|
2019-03-04 11:47:03 -08:00
|
|
|
impl<T, U> RouterBuilder<T, U> {
|
2019-02-09 20:43:39 -08:00
|
|
|
/// Register resource for specified path.
|
2019-01-05 13:19:25 -08:00
|
|
|
pub fn path(&mut self, path: &str, resource: T) {
|
2019-02-09 07:24:35 -08:00
|
|
|
self.rmap.register(ResourceDef::new(path));
|
2019-03-04 11:47:03 -08:00
|
|
|
self.resources.push((resource, None));
|
2019-01-05 13:19:25 -08:00
|
|
|
}
|
|
|
|
|
2019-02-09 20:43:39 -08:00
|
|
|
/// Register resource for specified path prefix.
|
2019-01-05 13:19:25 -08:00
|
|
|
pub fn prefix(&mut self, prefix: &str, resource: T) {
|
2019-02-09 07:24:35 -08:00
|
|
|
self.rmap.register(ResourceDef::prefix(prefix));
|
2019-03-04 11:47:03 -08:00
|
|
|
self.resources.push((resource, None));
|
2019-01-05 13:19:25 -08:00
|
|
|
}
|
|
|
|
|
2019-02-09 20:43:39 -08:00
|
|
|
/// Register resource for ResourceDef
|
|
|
|
pub fn rdef(&mut self, rdef: ResourceDef, resource: T) {
|
|
|
|
self.rmap.register(rdef);
|
2019-03-04 11:47:03 -08:00
|
|
|
self.resources.push((resource, None));
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Method attachs user data to lastly added resource.
|
|
|
|
///
|
|
|
|
/// This panics if no resources were added.
|
|
|
|
pub fn set_user_data(&mut self, userdata: Option<U>) {
|
|
|
|
self.resources.last_mut().unwrap().1 = userdata;
|
2019-02-09 20:43:39 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Finish configuration and create router instance.
|
2019-03-04 11:47:03 -08:00
|
|
|
pub fn finish(self) -> Router<T, U> {
|
2019-01-05 13:19:25 -08:00
|
|
|
Router {
|
|
|
|
rmap: Rc::new(self.rmap),
|
|
|
|
named: self.named,
|
|
|
|
resources: self.resources,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use crate::path::Path;
|
|
|
|
use crate::router::{ResourceId, Router};
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_recognizer_1() {
|
|
|
|
let mut router = Router::<usize>::build();
|
|
|
|
router.path("/name", 10);
|
|
|
|
router.path("/name/{val}", 11);
|
|
|
|
router.path("/name/{val}/index.html", 12);
|
|
|
|
router.path("/file/{file}.{ext}", 13);
|
|
|
|
router.path("/v{val}/{val2}/index.html", 14);
|
|
|
|
router.path("/v/{tail:.*}", 15);
|
|
|
|
router.path("/test2/{test}.html", 16);
|
|
|
|
router.path("/{test}/index.html", 17);
|
|
|
|
let mut router = router.finish();
|
|
|
|
|
|
|
|
let mut path = Path::new("/unknown");
|
|
|
|
assert!(router.recognize_mut(&mut path).is_none());
|
|
|
|
|
|
|
|
let mut path = Path::new("/name");
|
|
|
|
let (h, info) = router.recognize_mut(&mut path).unwrap();
|
|
|
|
assert_eq!(*h, 10);
|
|
|
|
assert_eq!(info.resource, ResourceId::Normal(0));
|
|
|
|
assert!(path.is_empty());
|
|
|
|
|
|
|
|
let mut path = Path::new("/name/value");
|
|
|
|
let (h, info) = router.recognize_mut(&mut path).unwrap();
|
|
|
|
assert_eq!(*h, 11);
|
|
|
|
assert_eq!(info.resource, ResourceId::Normal(1));
|
|
|
|
assert_eq!(path.get("val").unwrap(), "value");
|
|
|
|
assert_eq!(&path["val"], "value");
|
|
|
|
|
|
|
|
let mut path = Path::new("/name/value2/index.html");
|
|
|
|
let (h, info) = router.recognize_mut(&mut path).unwrap();
|
|
|
|
assert_eq!(*h, 12);
|
|
|
|
assert_eq!(info.resource, ResourceId::Normal(2));
|
|
|
|
assert_eq!(path.get("val").unwrap(), "value2");
|
|
|
|
|
|
|
|
let mut path = Path::new("/file/file.gz");
|
|
|
|
let (h, info) = router.recognize_mut(&mut path).unwrap();
|
|
|
|
assert_eq!(*h, 13);
|
|
|
|
assert_eq!(info.resource, ResourceId::Normal(3));
|
|
|
|
assert_eq!(path.get("file").unwrap(), "file");
|
|
|
|
assert_eq!(path.get("ext").unwrap(), "gz");
|
|
|
|
|
|
|
|
let mut path = Path::new("/vtest/ttt/index.html");
|
|
|
|
let (h, info) = router.recognize_mut(&mut path).unwrap();
|
|
|
|
assert_eq!(*h, 14);
|
|
|
|
assert_eq!(info.resource, ResourceId::Normal(4));
|
|
|
|
assert_eq!(path.get("val").unwrap(), "test");
|
|
|
|
assert_eq!(path.get("val2").unwrap(), "ttt");
|
|
|
|
|
|
|
|
let mut path = Path::new("/v/blah-blah/index.html");
|
|
|
|
let (h, info) = router.recognize_mut(&mut path).unwrap();
|
|
|
|
assert_eq!(*h, 15);
|
|
|
|
assert_eq!(info.resource, ResourceId::Normal(5));
|
|
|
|
assert_eq!(path.get("tail").unwrap(), "blah-blah/index.html");
|
|
|
|
|
|
|
|
let mut path = Path::new("/test2/index.html");
|
|
|
|
let (h, info) = router.recognize_mut(&mut path).unwrap();
|
|
|
|
assert_eq!(*h, 16);
|
|
|
|
assert_eq!(info.resource, ResourceId::Normal(6));
|
|
|
|
assert_eq!(path.get("test").unwrap(), "index");
|
|
|
|
|
|
|
|
let mut path = Path::new("/bbb/index.html");
|
|
|
|
let (h, info) = router.recognize_mut(&mut path).unwrap();
|
|
|
|
assert_eq!(*h, 17);
|
|
|
|
assert_eq!(info.resource, ResourceId::Normal(7));
|
|
|
|
assert_eq!(path.get("test").unwrap(), "bbb");
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_recognizer_2() {
|
|
|
|
let mut router = Router::<usize>::build();
|
|
|
|
router.path("/index.json", 10);
|
|
|
|
router.path("/{source}.json", 11);
|
|
|
|
let mut router = router.finish();
|
|
|
|
|
|
|
|
let mut path = Path::new("/index.json");
|
|
|
|
let (h, _) = router.recognize_mut(&mut path).unwrap();
|
|
|
|
assert_eq!(*h, 10);
|
|
|
|
|
|
|
|
let mut path = Path::new("/test.json");
|
|
|
|
let (h, _) = router.recognize_mut(&mut path).unwrap();
|
|
|
|
assert_eq!(*h, 11);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_recognizer_with_prefix() {
|
|
|
|
let mut router = Router::<usize>::build();
|
|
|
|
router.path("/name", 10);
|
|
|
|
router.path("/name/{val}", 11);
|
|
|
|
let mut router = router.finish();
|
|
|
|
|
|
|
|
let mut path = Path::new("/name");
|
|
|
|
path.skip(5);
|
|
|
|
assert!(router.recognize_mut(&mut path).is_none());
|
|
|
|
|
|
|
|
let mut path = Path::new("/test/name");
|
|
|
|
path.skip(5);
|
|
|
|
let (h, _) = router.recognize_mut(&mut path).unwrap();
|
|
|
|
assert_eq!(*h, 10);
|
|
|
|
|
|
|
|
let mut path = Path::new("/test/name/value");
|
|
|
|
path.skip(5);
|
|
|
|
let (h, info) = router.recognize_mut(&mut path).unwrap();
|
|
|
|
assert_eq!(*h, 11);
|
|
|
|
assert_eq!(info.resource, ResourceId::Normal(1));
|
|
|
|
assert_eq!(path.get("val").unwrap(), "value");
|
|
|
|
assert_eq!(&path["val"], "value");
|
|
|
|
|
|
|
|
// same patterns
|
|
|
|
let mut router = Router::<usize>::build();
|
|
|
|
router.path("/name", 10);
|
|
|
|
router.path("/name/{val}", 11);
|
|
|
|
let mut router = router.finish();
|
|
|
|
|
|
|
|
let mut path = Path::new("/name");
|
|
|
|
path.skip(6);
|
|
|
|
assert!(router.recognize_mut(&mut path).is_none());
|
|
|
|
|
|
|
|
let mut path = Path::new("/test2/name");
|
|
|
|
path.skip(6);
|
|
|
|
let (h, _) = router.recognize_mut(&mut path).unwrap();
|
|
|
|
assert_eq!(*h, 10);
|
|
|
|
|
|
|
|
let mut path = Path::new("/test2/name-test");
|
|
|
|
path.skip(6);
|
|
|
|
assert!(router.recognize_mut(&mut path).is_none());
|
|
|
|
|
|
|
|
let mut path = Path::new("/test2/name/ttt");
|
|
|
|
path.skip(6);
|
|
|
|
let (h, _) = router.recognize_mut(&mut path).unwrap();
|
|
|
|
assert_eq!(*h, 11);
|
|
|
|
assert_eq!(&path["val"], "ttt");
|
|
|
|
}
|
|
|
|
|
|
|
|
// #[test]
|
|
|
|
// fn test_request_resource() {
|
|
|
|
// let mut router = Router::<()>::default();
|
|
|
|
// let mut resource = Resource::new(ResourcePattern::new("/index.json"));
|
|
|
|
// resource.name("r1");
|
|
|
|
// router.register_resource(resource);
|
|
|
|
// let mut resource = Resource::new(ResourcePattern::new("/test.json"));
|
|
|
|
// resource.name("r2");
|
|
|
|
// router.register_resource(resource);
|
|
|
|
|
|
|
|
// let req = TestRequest::with_uri("/index.json").finish();
|
|
|
|
// let info = router.recognize(&req, &(), 0);
|
|
|
|
// assert_eq!(info.resource, ResourceId::Normal(0));
|
|
|
|
|
|
|
|
// assert_eq!(info.name(), "r1");
|
|
|
|
|
|
|
|
// let req = TestRequest::with_uri("/test.json").finish();
|
|
|
|
// let info = router.recognize(&req, &(), 0);
|
|
|
|
// assert_eq!(info.resource, ResourceId::Normal(1));
|
|
|
|
// assert_eq!(info.name(), "r2");
|
|
|
|
// }
|
|
|
|
|
|
|
|
// #[test]
|
|
|
|
// fn test_has_resource() {
|
|
|
|
// let mut router = Router::<()>::default();
|
|
|
|
// let scope = Scope::new("/test").resource("/name", |_| "done");
|
|
|
|
// router.register_scope(scope);
|
|
|
|
|
|
|
|
// {
|
|
|
|
// let info = router.default_route_info();
|
|
|
|
// assert!(!info.has_resource("/test"));
|
|
|
|
// assert!(info.has_resource("/test/name"));
|
|
|
|
// }
|
|
|
|
|
|
|
|
// let scope = Scope::new("/test2").nested("/test10", |s| s.resource("/name", |_| "done"));
|
|
|
|
// router.register_scope(scope);
|
|
|
|
|
|
|
|
// let info = router.default_route_info();
|
|
|
|
// assert!(info.has_resource("/test2/test10/name"));
|
|
|
|
// }
|
|
|
|
|
|
|
|
// #[test]
|
|
|
|
// fn test_url_for() {
|
|
|
|
// let mut router = Router::<()>::new(ResourcePattern::prefix(""));
|
|
|
|
|
|
|
|
// let mut resource = Resource::new(ResourcePattern::new("/tttt"));
|
|
|
|
// resource.name("r0");
|
|
|
|
// router.register_resource(resource);
|
|
|
|
|
|
|
|
// let scope = Scope::new("/test").resource("/name", |r| {
|
|
|
|
// r.name("r1");
|
|
|
|
// });
|
|
|
|
// router.register_scope(scope);
|
|
|
|
|
|
|
|
// let scope =
|
|
|
|
// Scope::new("/test2").nested("/test10", |s| s.resource("/name", |r| r.name("r2")));
|
|
|
|
// router.register_scope(scope);
|
|
|
|
// router.finish();
|
|
|
|
|
|
|
|
// let req = TestRequest::with_uri("/test").request();
|
|
|
|
// {
|
|
|
|
// let info = router.default_route_info();
|
|
|
|
|
|
|
|
// let res = info
|
|
|
|
// .url_for(&req, "r0", Vec::<&'static str>::new())
|
|
|
|
// .unwrap();
|
|
|
|
// assert_eq!(res.as_str(), "http://localhost:8080/tttt");
|
|
|
|
|
|
|
|
// let res = info
|
|
|
|
// .url_for(&req, "r1", Vec::<&'static str>::new())
|
|
|
|
// .unwrap();
|
|
|
|
// assert_eq!(res.as_str(), "http://localhost:8080/test/name");
|
|
|
|
|
|
|
|
// let res = info
|
|
|
|
// .url_for(&req, "r2", Vec::<&'static str>::new())
|
|
|
|
// .unwrap();
|
|
|
|
// assert_eq!(res.as_str(), "http://localhost:8080/test2/test10/name");
|
|
|
|
// }
|
|
|
|
|
|
|
|
// let req = TestRequest::with_uri("/test/name").request();
|
|
|
|
// let info = router.recognize(&req, &(), 0);
|
|
|
|
// assert_eq!(info.resource, ResourceId::Normal(1));
|
|
|
|
|
|
|
|
// let res = info
|
|
|
|
// .url_for(&req, "r0", Vec::<&'static str>::new())
|
|
|
|
// .unwrap();
|
|
|
|
// assert_eq!(res.as_str(), "http://localhost:8080/tttt");
|
|
|
|
|
|
|
|
// let res = info
|
|
|
|
// .url_for(&req, "r1", Vec::<&'static str>::new())
|
|
|
|
// .unwrap();
|
|
|
|
// assert_eq!(res.as_str(), "http://localhost:8080/test/name");
|
|
|
|
|
|
|
|
// let res = info
|
|
|
|
// .url_for(&req, "r2", Vec::<&'static str>::new())
|
|
|
|
// .unwrap();
|
|
|
|
// assert_eq!(res.as_str(), "http://localhost:8080/test2/test10/name");
|
|
|
|
// }
|
|
|
|
|
|
|
|
// #[test]
|
|
|
|
// fn test_url_for_dynamic() {
|
|
|
|
// let mut router = Router::<()>::new(ResourcePattern::prefix(""));
|
|
|
|
|
|
|
|
// let mut resource = Resource::new(ResourcePattern::new("/{name}/test/index.{ext}"));
|
|
|
|
// resource.name("r0");
|
|
|
|
// router.register_resource(resource);
|
|
|
|
|
|
|
|
// let scope = Scope::new("/{name1}").nested("/{name2}", |s| {
|
|
|
|
// s.resource("/{name3}/test/index.{ext}", |r| r.name("r2"))
|
|
|
|
// });
|
|
|
|
// router.register_scope(scope);
|
|
|
|
// router.finish();
|
|
|
|
|
|
|
|
// let req = TestRequest::with_uri("/test").request();
|
|
|
|
// {
|
|
|
|
// let info = router.default_route_info();
|
|
|
|
|
|
|
|
// let res = info.url_for(&req, "r0", vec!["sec1", "html"]).unwrap();
|
|
|
|
// assert_eq!(res.as_str(), "http://localhost:8080/sec1/test/index.html");
|
|
|
|
|
|
|
|
// let res = info
|
|
|
|
// .url_for(&req, "r2", vec!["sec1", "sec2", "sec3", "html"])
|
|
|
|
// .unwrap();
|
|
|
|
// assert_eq!(
|
|
|
|
// res.as_str(),
|
|
|
|
// "http://localhost:8080/sec1/sec2/sec3/test/index.html"
|
|
|
|
// );
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
}
|