From d8b880e16788c510bd6ff40a538a8d1f1e615a2c Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 5 Dec 2017 13:31:06 -0800 Subject: [PATCH] work on resource_path api --- examples/basic.rs | 2 +- src/application.rs | 33 ++++++++++++++++++++++- src/dev.rs | 4 +-- src/error.rs | 9 +++++++ src/httprequest.rs | 4 +-- src/recognizer.rs | 65 +++++++++++++++++++++++++--------------------- 6 files changed, 82 insertions(+), 35 deletions(-) diff --git a/examples/basic.rs b/examples/basic.rs index cf9c78aa..e14b36b8 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -27,7 +27,7 @@ fn index(mut req: HttpRequest) -> Result { req.session().set("counter", 1)?; } - Ok(HttpResponse::Ok().into()) + Ok("Welcome!".into()) } /// async handler diff --git a/src/application.rs b/src/application.rs index e007ea71..5db0be5d 100644 --- a/src/application.rs +++ b/src/application.rs @@ -1,10 +1,11 @@ use std::rc::Rc; use std::collections::HashMap; +use error::UriGenerationError; use handler::{Reply, RouteHandler}; use route::Route; use resource::Resource; -use recognizer::{RouteRecognizer, check_pattern}; +use recognizer::{RouteRecognizer, check_pattern, PatternElement}; use httprequest::HttpRequest; use channel::HttpHandler; use pipeline::Pipeline; @@ -22,6 +23,36 @@ impl Router { Router(Rc::new(RouteRecognizer::new(prefix, resources))) } + + pub fn has_route(&self, path: &str) -> bool { + self.0.recognize(path).is_some() + } + + pub fn resource_path<'a, U>(&self, prefix: &str, name: &str, elements: U) + -> Result + where U: IntoIterator + { + if let Some(pattern) = self.0.get_pattern(name) { + let mut iter = elements.into_iter(); + let mut vec = vec![prefix]; + for el in pattern.elements() { + match *el { + PatternElement::Str(ref s) => vec.push(s), + PatternElement::Var(_) => { + if let Some(val) = iter.next() { + vec.push(val) + } else { + return Err(UriGenerationError::NotEnoughElements) + } + } + } + } + let s = vec.join("/").to_owned(); + Ok(s) + } else { + Err(UriGenerationError::ResourceNotFound) + } + } } /// Application diff --git a/src/dev.rs b/src/dev.rs index 70f654b2..921341ef 100644 --- a/src/dev.rs +++ b/src/dev.rs @@ -12,8 +12,8 @@ pub use handler::Handler; pub use pipeline::Pipeline; pub use channel::{HttpChannel, HttpHandler}; -pub use recognizer::{FromParam, RouteRecognizer}; +pub use recognizer::{FromParam, RouteRecognizer, Pattern, PatternElement}; +pub use cookie::CookieBuilder; pub use application::ApplicationBuilder; pub use httpresponse::HttpResponseBuilder; -pub use cookie::CookieBuilder; diff --git a/src/error.rs b/src/error.rs index 053df2d7..dcd02fdf 100644 --- a/src/error.rs +++ b/src/error.rs @@ -403,6 +403,15 @@ impl ResponseError for UriSegmentError { } } +/// Errors which can occur when attempting to generate resource uri. +#[derive(Fail, Debug, PartialEq)] +pub enum UriGenerationError { + #[fail(display="Resource not found")] + ResourceNotFound, + #[fail(display="Not all path pattern covered")] + NotEnoughElements, +} + #[cfg(test)] mod tests { use std::error::Error as StdError; diff --git a/src/httprequest.rs b/src/httprequest.rs index debfefe3..6a167d7d 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -38,7 +38,7 @@ impl Default for HttpMessage { prefix: 0, version: Version::HTTP_11, headers: HeaderMap::new(), - params: Params::empty(), + params: Params::default(), cookies: Vec::new(), cookies_loaded: false, addr: None, @@ -64,7 +64,7 @@ impl HttpRequest<()> { prefix: 0, version: version, headers: headers, - params: Params::empty(), + params: Params::default(), cookies: Vec::new(), cookies_loaded: false, addr: None, diff --git a/src/recognizer.rs b/src/recognizer.rs index 2ee59679..be966719 100644 --- a/src/recognizer.rs +++ b/src/recognizer.rs @@ -32,6 +32,16 @@ pub struct Params { names: Rc>, } +impl Default for Params { + fn default() -> Params { + Params { + text: String::new(), + names: Rc::new(HashMap::new()), + matches: Vec::new(), + } + } +} + impl Params { pub(crate) fn new(names: Rc>, text: &str, @@ -47,15 +57,6 @@ impl Params { } } - pub(crate) fn empty() -> Self - { - Params { - text: String::new(), - names: Rc::new(HashMap::new()), - matches: Vec::new(), - } - } - /// Check if there are any matched patterns pub fn is_empty(&self) -> bool { self.names.is_empty() @@ -202,9 +203,10 @@ FROM_STR!(std::net::SocketAddrV4); FROM_STR!(std::net::SocketAddrV6); pub struct RouteRecognizer { + re: RegexSet, prefix: usize, - patterns: RegexSet, routes: Vec<(Pattern, T)>, + patterns: HashMap, } impl Default for RouteRecognizer { @@ -212,8 +214,9 @@ impl Default for RouteRecognizer { fn default() -> Self { RouteRecognizer { prefix: 0, - patterns: RegexSet::new([""].iter()).unwrap(), + re: RegexSet::new([""].iter()).unwrap(), routes: Vec::new(), + patterns: HashMap::new(), } } } @@ -225,30 +228,28 @@ impl RouteRecognizer { { let mut paths = Vec::new(); let mut handlers = Vec::new(); + let mut patterns = HashMap::new(); for item in routes { let (pat, elements) = parse(&item.0); - handlers.push((Pattern::new(&pat, elements), item.2)); + let pattern = Pattern::new(&pat, elements); + if let Some(ref name) = item.1 { + let _ = patterns.insert(name.clone(), pattern.clone()); + } + handlers.push((pattern, item.2)); paths.push(pat); }; let regset = RegexSet::new(&paths); RouteRecognizer { + re: regset.unwrap(), prefix: prefix.into().len() - 1, - patterns: regset.unwrap(), routes: handlers, + patterns: patterns, } } - pub fn set_routes(&mut self, routes: Vec<(&str, Option<&str>, T)>) { - let mut paths = Vec::new(); - let mut handlers = Vec::new(); - for item in routes { - let (pat, elements) = parse(item.0); - handlers.push((Pattern::new(&pat, elements), item.2)); - paths.push(pat); - }; - self.patterns = RegexSet::new(&paths).unwrap(); - self.routes = handlers; + pub fn get_pattern(&self, name: &str) -> Option<&Pattern> { + self.patterns.get(name) } pub fn set_prefix>(&mut self, prefix: P) { @@ -263,11 +264,11 @@ impl RouteRecognizer { pub fn recognize(&self, path: &str) -> Option<(Option, &T)> { let p = &path[self.prefix..]; if p.is_empty() { - if let Some(idx) = self.patterns.matches("/").into_iter().next() { + if let Some(idx) = self.re.matches("/").into_iter().next() { let (ref pattern, ref route) = self.routes[idx]; return Some((pattern.match_info(&path[self.prefix..]), route)) } - } else if let Some(idx) = self.patterns.matches(p).into_iter().next() { + } else if let Some(idx) = self.re.matches(p).into_iter().next() { let (ref pattern, ref route) = self.routes[idx]; return Some((pattern.match_info(&path[self.prefix..]), route)) } @@ -275,12 +276,14 @@ impl RouteRecognizer { } } -enum PatternElement { +#[derive(Debug, Clone, PartialEq)] +pub enum PatternElement { Str(String), Var(String), } -struct Pattern { +#[derive(Clone)] +pub struct Pattern { re: Regex, names: Rc>, elements: Vec, @@ -309,6 +312,10 @@ impl Pattern { Some(Params::new(Rc::clone(&self.names), text, &captures)) } + + pub fn elements(&self) -> &Vec { + &self.elements + } } pub(crate) fn check_pattern(path: &str) { @@ -337,7 +344,7 @@ fn parse(pattern: &str) -> (String, Vec) { if in_param { // In parameter segment: `{....}` if ch == '}' { - elems.push(PatternElement::Var(String::from(String::from(param_name.as_str())))); + elems.push(PatternElement::Var(param_name.clone())); re.push_str(&format!(r"(?P<{}>{})", ¶m_name, ¶m_pattern)); param_name.clear(); @@ -359,7 +366,7 @@ fn parse(pattern: &str) -> (String, Vec) { } } else if ch == '{' { in_param = true; - elems.push(PatternElement::Str(String::from(el.as_str()))); + elems.push(PatternElement::Str(el.clone())); el.clear(); } else { re.push(ch);