From d8b880e16788c510bd6ff40a538a8d1f1e615a2c Mon Sep 17 00:00:00 2001
From: Nikolay Kim <fafhrd91@gmail.com>
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<HttpResponse> {
         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<S: 'static> Router<S> {
 
         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<String, UriGenerationError>
+        where U: IntoIterator<Item=&'a str>
+    {
+        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<HashMap<String, usize>>,
 }
 
+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<HashMap<String, usize>>,
                       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<T> {
+    re: RegexSet,
     prefix: usize,
-    patterns: RegexSet,
     routes: Vec<(Pattern, T)>,
+    patterns: HashMap<String, Pattern>,
 }
 
 impl<T> Default for RouteRecognizer<T> {
@@ -212,8 +214,9 @@ impl<T> Default for RouteRecognizer<T> {
     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<T> RouteRecognizer<T> {
     {
         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<P: Into<String>>(&mut self, prefix: P) {
@@ -263,11 +264,11 @@ impl<T> RouteRecognizer<T> {
     pub fn recognize(&self, path: &str) -> Option<(Option<Params>, &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<T> RouteRecognizer<T> {
     }
 }
 
-enum PatternElement {
+#[derive(Debug, Clone, PartialEq)]
+pub enum PatternElement {
     Str(String),
     Var(String),
 }
 
-struct Pattern {
+#[derive(Clone)]
+pub struct Pattern {
     re: Regex,
     names: Rc<HashMap<String, usize>>,
     elements: Vec<PatternElement>,
@@ -309,6 +312,10 @@ impl Pattern {
 
         Some(Params::new(Rc::clone(&self.names), text, &captures))
     }
+
+    pub fn elements(&self) -> &Vec<PatternElement> {
+        &self.elements
+    }
 }
 
 pub(crate) fn check_pattern(path: &str) {
@@ -337,7 +344,7 @@ fn parse(pattern: &str) -> (String, Vec<PatternElement>) {
         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<{}>{})", &param_name, &param_pattern));
 
                 param_name.clear();
@@ -359,7 +366,7 @@ fn parse(pattern: &str) -> (String, Vec<PatternElement>) {
             }
         } 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);