<commit_before>//!Router asigns handlers to paths and resolves them per request

#[cfg(test)]
use http::method;
use http::method::{ Method, Get, Post, Put, Delete };
use http::server::request::{AbsolutePath};
use regex::Regex;
use std::collections::hashmap::HashMap;
use request::Request;
use response::Response;
use middleware::{ Middleware, Action, Halt, Continue };
use nickel_error::NickelError;

/// A Route is the basic data structure that stores both the path
/// and the handler that gets executed for the route.
/// The path can contain variable pattern such as `user/:userid/invoices`
pub struct Route {
    pub path: String,
    pub method: Method,
    pub handler: fn(request: &Request, response: &mut Response),
    pub variables: HashMap<String, uint>,
    matcher: Regex
}

impl Clone for Route {
    fn clone(&self) -> Route {
        Route {
            path: self.path.clone(),
            method: self.method.clone(),
            handler: self.handler,
            matcher: self.matcher.clone(),
            variables: self.variables.clone()
        }
    }
}

/// A RouteResult is what the router returns when `match_route` is called.
/// It contains the matched `route` and also a `params` property holding
/// a HashMap with the keys being the variable names and the value being the
/// evaluated string
pub struct RouteResult<'a> {
    pub route: &'a Route,
    pub params: HashMap<String, String>
}

/// The PathUtils collects some small helper methods that operate on the path
struct PathUtils;

// matches named variables (e.g. :userid)
static REGEX_VAR_SEQ: Regex                 = regex!(r":([,a-zA-Z0-9_-]*)");
static VAR_SEQ:&'static str                 = "[,a-zA-Z0-9_-]*";
static VAR_SEQ_WITH_SLASH:&'static str      = "[,/a-zA-Z0-9_-]*";
static VAR_SEQ_WITH_CAPTURE:&'static str    = "([,a-zA-Z0-9%_-]*)";
// matches request params (e.g. ?foo=true&bar=false)
static REGEX_PARAM_SEQ:&'static str         = "(\\?[a-zA-Z0-9%_=&-]*)?";
static REGEX_START:&'static str             = "^";
static REGEX_END:&'static str               = "$";

impl PathUtils {
    fn create_regex (route_path: &str) -> Regex {

        let updated_path = route_path.to_string()
                                     // first mark all double wildcards for replacement. We can't directly replace them
                                     // since the replacement does contain the * symbol as well, which would get overwritten
                                     // with the next replace call
                                     .replace("**", "___DOUBLE_WILDCARD___")
                                     // then replace the regular wildcard symbols (*) with the appropriate regex
                                     .replace("*", VAR_SEQ)
                                     // now replace the previously marked double wild cards (**)
                                     .replace("___DOUBLE_WILDCARD___", VAR_SEQ_WITH_SLASH);

        // then replace the variable symbols (:variable) with the appropriate regex
        let result = REGEX_START.to_string()
                                .append(REGEX_VAR_SEQ.replace_all(updated_path.as_slice(), VAR_SEQ_WITH_CAPTURE).as_slice())
                                .append(REGEX_PARAM_SEQ)
                                .append(REGEX_END);

        match Regex::new(result.as_slice()) {
            Ok(re) => re,
            Err(err) => fail!("{}", err)
        }
    }

    fn get_variable_info (route_path: &str) -> HashMap<String, uint> {
        REGEX_VAR_SEQ.captures_iter(route_path)
             .enumerate()
             .map(|(i, matched)| (matched.at(1).to_string(), i))
             .collect()
    }
}

/// The Router's job is it to hold routes and to resolve them later against
/// concrete URLs. The router is also a regular middleware and needs to be
/// added to the middleware stack with `server.utilize(router)`.

#[deriving(Clone)]
pub struct Router{
    routes: Vec<Route>,
}

impl Router {
    pub fn new () -> Router {
        Router {
            routes: Vec::new()
        }
    }

    /// Registers a handler to be used for a specific GET request.
    /// Handlers are assigned to paths and paths are allowed to contain
    /// variables and wildcards.
    ///
    /// # Example without variables and wildcards
    ///
    /// ```{rust,ignore}
    /// fn handler (request: Request, response: &mut Response) {
    ///     response.send("This matches /user");
    /// };
    /// router.get("/user", handler);
    /// ```
    /// # Example with variables
    ///
    /// ```{rust,ignore}
    /// fn handler (request: Request, response: &mut Response) {
    ///     let text = format!("This is user: {}", request.params.get(&"userid".to_string()));
    ///     response.send(text.as_slice());
    /// };
    /// router.get("/user/:userid", handler);
    /// ```
    /// # Example with simple wildcard
    ///
    /// ```{rust,ignore}
    /// fn handler (request: Request, response: &mut Response) {
    ///     response.send("This matches /user/list/4711 but not /user/extended/list/4711");
    /// };
    /// router.get("/user/*/:userid", handler);
    /// ```
    /// # Example with double wildcard
    ///
    /// ```{rust,ignore}
    /// fn handler (request: Request, response: &mut Response) {
    ///     response.send("This matches /user/list/4711 and also /user/extended/list/4711");
    /// };
    /// router.get("/user/**/:userid", handler);
    /// ```
    pub fn get(&mut self, uri: &str, handler: fn(request: &Request, response: &mut Response)){
        self.add_route(Get, String::from_str(uri), handler);
    }

    /// Registers a handler to be used for a specific POST request.
    ///
    /// # Example
    ///
    /// ```{rust,ignore}
    /// fn handler (request: Request, response: &mut Response) {
    ///     response.send("This matches a POST request to /a/post/request");
    /// };
    /// router.post("/a/post/request", handler);
    /// ```
    /// Take a look at `get()` for a more detailed description.
    pub fn post(&mut self, uri: &str, handler: fn(request: &Request, response: &mut Response)){
        self.add_route(Post, String::from_str(uri), handler);
    }

    /// Registers a handler to be used for a specific PUT request.
    ///
    /// # Example
    ///
    /// ```{rust,ignore}
    /// fn handler (request: Request, response: &mut Response) {
    ///     response.send("This matches a POST request to /a/put/request");
    /// };
    /// router.put("/a/put/request", handler);
    /// ```
    /// Take a look at `get(..)` for a more detailed description.
    pub fn put(&mut self, uri: &str, handler: fn(request: &Request, response: &mut Response)){
        self.add_route(Put, String::from_str(uri), handler);
    }

    /// Registers a handler to be used for a specific DELETE request.
    ///
    /// # Example
    ///
    /// ```{rust,ignore}
    /// fn handler (request: Request, response: &mut Response) {
    ///     response.send("This matches a DELETE request to /a/delete/request");
    /// };
    /// router.delete("/a/delete/request", handler);
    /// ```
    /// Take a look at `get(...)` for a more detailed description.
    pub fn delete(&mut self, uri: &str, handler: fn(request: &Request, response: &mut Response)){
        self.add_route(Delete, String::from_str(uri), handler);
    }

    pub fn add_route (&mut self, method: Method, path: String, handler: fn(request: &Request, response: &mut Response)) -> () {
        let matcher = PathUtils::create_regex(path.as_slice());
        let variable_infos = PathUtils::get_variable_info(path.as_slice());
        let route = Route {
            path: path,
            method: method,
            matcher: matcher,
            handler: handler,
            variables: variable_infos
        };
        self.routes.push(route);
    }

    pub fn match_route(&self, method: Method, path: String) -> Option<RouteResult> {
        self.routes.iter().find(|item| item.method == method && item.matcher.is_match(path.as_slice()))
            .and_then(|route| {
                match route.matcher.captures(path.as_slice()) {
                    Some(captures) => {
                        let mut map = HashMap::new();
                        for (name, pos) in route.variables.iter() {
                            map.insert(name.to_string(), captures.at(pos + 1).to_string());
                        }
                        Some(RouteResult {
                            route: route,
                            params: map
                        })
                    },
                    None => Some(RouteResult{
                        route: route,
                        params: HashMap::new()
                    })
                }
            })
    }
}

impl Middleware for Router {
    fn invoke (&self, req: &mut Request, res: &mut Response) -> Result<Action, NickelError> {
        match &req.origin.request_uri {
            &AbsolutePath(ref url) => {
                match self.match_route(req.origin.method.clone(), url.clone()) {
                    Some(route_result) => {
                        res.origin.status = ::http::status::Ok;
                        req.params = route_result.params.clone();
                        (route_result.route.handler)(req, res);
                        Ok(Halt)
                    },
                    None => Ok(Continue)
                }
            },
            _ => Ok(Continue)
        }
    }
}

#[test]
fn creates_map_with_var_variable_infos () {
    let map = PathUtils::get_variable_info("foo/:uid/bar/:groupid");

    assert_eq!(map.len(), 2);
    assert_eq!(map.get(&"uid".to_string()), &0);
    assert_eq!(map.get(&"groupid".to_string()), &1);
}

#[test]
fn creates_regex_with_captures () {
    let regex = PathUtils::create_regex("foo/:uid/bar/:groupid");
    let caps = regex.captures("foo/4711/bar/5490").unwrap();

    assert_eq!(caps.at(1), "4711");
    assert_eq!(caps.at(2), "5490");

    let regex = PathUtils::create_regex("foo/*/:uid/bar/:groupid");
    let caps = regex.captures("foo/test/4711/bar/5490").unwrap();

    assert_eq!(caps.at(1), "4711");
    assert_eq!(caps.at(2), "5490");

    let regex = PathUtils::create_regex("foo/**/:uid/bar/:groupid");
    let caps = regex.captures("foo/test/another/4711/bar/5490").unwrap();

    assert_eq!(caps.at(1), "4711");
    assert_eq!(caps.at(2), "5490");
}

#[test]
fn creates_valid_regex_for_routes () {
    let regex1 = PathUtils::create_regex("foo/:uid/bar/:groupid");
    let regex2 = PathUtils::create_regex("foo/*/bar");
    let regex3 = PathUtils::create_regex("foo/**/bar");

    assert_eq!(regex1.is_match("foo/4711/bar/5490"), true);
    assert_eq!(regex1.is_match("foo/4711/bar/5490?foo=true&bar=false"), true);
    assert_eq!(regex1.is_match("foo/4711/bar"), false);
    assert_eq!(regex1.is_match("foo/4711/bar?foo=true&bar=false"), false);
    assert_eq!(regex1.is_match("foo/4711/bar/test%20spacing"), true);
    assert_eq!(regex1.is_match("foo/4711/bar/5281?foo=test%20spacing&bar=false"), true);

    assert_eq!(regex2.is_match("foo/4711/bar"), true);
    assert_eq!(regex2.is_match("foo/4711/barr"), false);
    assert_eq!(regex2.is_match("foo/4711/bar?foo=true&bar=false"), true);
    assert_eq!(regex2.is_match("foo/4711/4712/bar"), false);
    assert_eq!(regex2.is_match("foo/4711/4712/bar?foo=true&bar=false"), false);

    assert_eq!(regex3.is_match("foo/4711/bar"), true);
    assert_eq!(regex3.is_match("foo/4711/bar?foo=true&bar=false"), true);
    assert_eq!(regex3.is_match("foo/4711/4712/bar"), true);
    assert_eq!(regex3.is_match("foo/4711/4712/bar?foo=true&bar=false"), true);

    //ensure that this works with commas too
    assert_eq!(regex1.is_match("foo/4711/bar/5490,1234"), true);
    assert_eq!(regex1.is_match("foo/4711/bar/5490,1234?foo=true&bar=false"), true);
    assert_eq!(regex1.is_match("foo/4711/bar"), false);
    assert_eq!(regex1.is_match("foo/4711/bar?foo=1,2,3&bar=false"), false);
}

#[test]
fn can_match_var_routes () {
    let route_store = &mut Router::new();

    fn handler (_request: &Request, response: &mut Response) -> () {
        let _ = response.origin.write("hello from foo".as_bytes());
    };

    route_store.add_route(method::Get, "/foo/:userid".to_string(), handler);
    route_store.add_route(method::Get, "/bar".to_string(), handler);

    let route_result = route_store.match_route(method::Get, "/foo/4711".to_string()).unwrap();
    let route = route_result.route;

    assert_eq!(route_result.params.get(&"userid".to_string()), &"4711".to_string());

    //assert the route has identified the variable
    assert_eq!(route.variables.len(), 1);
    assert_eq!(route.variables.get(&"userid".to_string()), &0);

    let route_result = route_store.match_route(method::Get, "/bar/4711".to_string());

    let result = match route_result {
        Some(_res) => true,
        None => false
    };

    assert_eq!(result, false);

    let route_result = route_store.match_route(method::Get, "/foo".to_string());

    let result = match route_result{
        Some(_res) => true,
        None => false
    };

    assert_eq!(result, false);

    //ensure that this will work with commas too
    let route_result = route_store.match_route(method::Get, "/foo/123,456".to_string());
    let result = match route_result {
        Some(_) => true,
        None => false
    };
    assert_eq!(result, true);
    let route_result = route_result.unwrap();
    assert_eq!(route_result.params.get(&"userid".to_string()), &"123,456".to_string());

    //ensure that this will work with spacing too
    let route_result = route_store.match_route(method::Get, "/foo/John%20Doe".to_string());
    let result = match route_result {
        Some(_) => true,
        None => false
    };
    assert_eq!(result, true);
    let route_result = route_result.unwrap();
    assert_eq!(route_result.params.get(&"userid".to_string()), &"John%20Doe".to_string());
}
<commit_msg>Minor style change.<commit_after>//!Router asigns handlers to paths and resolves them per request

#[cfg(test)]
use http::method;
use http::method::{ Method, Get, Post, Put, Delete };
use http::server::request::{AbsolutePath};
use regex::Regex;
use std::collections::hashmap::HashMap;
use request::Request;
use response::Response;
use middleware::{ Middleware, Action, Halt, Continue };
use nickel_error::NickelError;

/// A Route is the basic data structure that stores both the path
/// and the handler that gets executed for the route.
/// The path can contain variable pattern such as `user/:userid/invoices`
pub struct Route {
    pub path: String,
    pub method: Method,
    pub handler: fn(request: &Request, response: &mut Response),
    pub variables: HashMap<String, uint>,
    matcher: Regex
}

impl Clone for Route {
    fn clone(&self) -> Route {
        Route {
            path: self.path.clone(),
            method: self.method.clone(),
            handler: self.handler,
            matcher: self.matcher.clone(),
            variables: self.variables.clone()
        }
    }
}

/// A RouteResult is what the router returns when `match_route` is called.
/// It contains the matched `route` and also a `params` property holding
/// a HashMap with the keys being the variable names and the value being the
/// evaluated string
pub struct RouteResult<'a> {
    pub route: &'a Route,
    pub params: HashMap<String, String>
}

/// The PathUtils collects some small helper methods that operate on the path
struct PathUtils;

// matches named variables (e.g. :userid)
static REGEX_VAR_SEQ: Regex                 = regex!(r":([,a-zA-Z0-9_-]*)");
static VAR_SEQ:&'static str                 = "[,a-zA-Z0-9_-]*";
static VAR_SEQ_WITH_SLASH:&'static str      = "[,/a-zA-Z0-9_-]*";
static VAR_SEQ_WITH_CAPTURE:&'static str    = "([,a-zA-Z0-9%_-]*)";
// matches request params (e.g. ?foo=true&bar=false)
static REGEX_PARAM_SEQ:&'static str         = "(\\?[a-zA-Z0-9%_=&-]*)?";
static REGEX_START:&'static str             = "^";
static REGEX_END:&'static str               = "$";

impl PathUtils {
    fn create_regex (route_path: &str) -> Regex {

        let updated_path = route_path.to_string()
                                     // first mark all double wildcards for replacement. We can't directly replace them
                                     // since the replacement does contain the * symbol as well, which would get overwritten
                                     // with the next replace call
                                     .replace("**", "___DOUBLE_WILDCARD___")
                                     // then replace the regular wildcard symbols (*) with the appropriate regex
                                     .replace("*", VAR_SEQ)
                                     // now replace the previously marked double wild cards (**)
                                     .replace("___DOUBLE_WILDCARD___", VAR_SEQ_WITH_SLASH);

        // then replace the variable symbols (:variable) with the appropriate regex
        let result = REGEX_START.to_string()
                                .append(REGEX_VAR_SEQ.replace_all(updated_path.as_slice(), VAR_SEQ_WITH_CAPTURE).as_slice())
                                .append(REGEX_PARAM_SEQ)
                                .append(REGEX_END);

        match Regex::new(result.as_slice()) {
            Ok(re) => re,
            Err(err) => fail!("{}", err)
        }
    }

    fn get_variable_info (route_path: &str) -> HashMap<String, uint> {
        REGEX_VAR_SEQ.captures_iter(route_path)
             .enumerate()
             .map(|(i, matched)| (matched.at(1).to_string(), i))
             .collect()
    }
}

/// The Router's job is it to hold routes and to resolve them later against
/// concrete URLs. The router is also a regular middleware and needs to be
/// added to the middleware stack with `server.utilize(router)`.

#[deriving(Clone)]
pub struct Router{
    routes: Vec<Route>,
}

impl Router {
    pub fn new () -> Router {
        Router {
            routes: Vec::new()
        }
    }

    /// Registers a handler to be used for a specific GET request.
    /// Handlers are assigned to paths and paths are allowed to contain
    /// variables and wildcards.
    ///
    /// # Example without variables and wildcards
    ///
    /// ```{rust,ignore}
    /// fn handler (request: Request, response: &mut Response) {
    ///     response.send("This matches /user");
    /// };
    /// router.get("/user", handler);
    /// ```
    /// # Example with variables
    ///
    /// ```{rust,ignore}
    /// fn handler (request: Request, response: &mut Response) {
    ///     let text = format!("This is user: {}", request.params.get(&"userid".to_string()));
    ///     response.send(text.as_slice());
    /// };
    /// router.get("/user/:userid", handler);
    /// ```
    /// # Example with simple wildcard
    ///
    /// ```{rust,ignore}
    /// fn handler (request: Request, response: &mut Response) {
    ///     response.send("This matches /user/list/4711 but not /user/extended/list/4711");
    /// };
    /// router.get("/user/*/:userid", handler);
    /// ```
    /// # Example with double wildcard
    ///
    /// ```{rust,ignore}
    /// fn handler (request: Request, response: &mut Response) {
    ///     response.send("This matches /user/list/4711 and also /user/extended/list/4711");
    /// };
    /// router.get("/user/**/:userid", handler);
    /// ```
    pub fn get(&mut self, uri: &str, handler: fn(request: &Request, response: &mut Response)){
        self.add_route(Get, String::from_str(uri), handler);
    }

    /// Registers a handler to be used for a specific POST request.
    ///
    /// # Example
    ///
    /// ```{rust,ignore}
    /// fn handler (request: Request, response: &mut Response) {
    ///     response.send("This matches a POST request to /a/post/request");
    /// };
    /// router.post("/a/post/request", handler);
    /// ```
    /// Take a look at `get()` for a more detailed description.
    pub fn post(&mut self, uri: &str, handler: fn(request: &Request, response: &mut Response)){
        self.add_route(Post, String::from_str(uri), handler);
    }

    /// Registers a handler to be used for a specific PUT request.
    ///
    /// # Example
    ///
    /// ```{rust,ignore}
    /// fn handler (request: Request, response: &mut Response) {
    ///     response.send("This matches a POST request to /a/put/request");
    /// };
    /// router.put("/a/put/request", handler);
    /// ```
    /// Take a look at `get(..)` for a more detailed description.
    pub fn put(&mut self, uri: &str, handler: fn(request: &Request, response: &mut Response)){
        self.add_route(Put, String::from_str(uri), handler);
    }

    /// Registers a handler to be used for a specific DELETE request.
    ///
    /// # Example
    ///
    /// ```{rust,ignore}
    /// fn handler (request: Request, response: &mut Response) {
    ///     response.send("This matches a DELETE request to /a/delete/request");
    /// };
    /// router.delete("/a/delete/request", handler);
    /// ```
    /// Take a look at `get(...)` for a more detailed description.
    pub fn delete(&mut self, uri: &str, handler: fn(request: &Request, response: &mut Response)){
        self.add_route(Delete, String::from_str(uri), handler);
    }

    pub fn add_route (&mut self, method: Method, path: String, handler: fn(request: &Request, response: &mut Response)) -> () {
        let matcher = PathUtils::create_regex(path.as_slice());
        let variable_infos = PathUtils::get_variable_info(path.as_slice());
        let route = Route {
            path: path,
            method: method,
            matcher: matcher,
            handler: handler,
            variables: variable_infos
        };
        self.routes.push(route);
    }

    pub fn match_route(&self, method: Method, path: String) -> Option<RouteResult> {
        self.routes.iter().find(|item| item.method == method && item.matcher.is_match(path.as_slice()))
            .and_then(|route| {
                match route.matcher.captures(path.as_slice()) {
                    Some(captures) => {
                        let mut map = HashMap::new();
                        for (name, pos) in route.variables.iter() {
                            map.insert(name.to_string(), captures.at(pos + 1).to_string());
                        }
                        Some(RouteResult {
                            route: route,
                            params: map
                        })
                    },
                    None => Some(RouteResult{
                        route: route,
                        params: HashMap::new()
                    })
                }
            })
    }
}

impl Middleware for Router {
    fn invoke (&self, req: &mut Request, res: &mut Response) -> Result<Action, NickelError> {
        match &req.origin.request_uri {
            &AbsolutePath(ref url) => {
                match self.match_route(req.origin.method.clone(), url.clone()) {
                    Some(route_result) => {
                        res.origin.status = ::http::status::Ok;
                        req.params = route_result.params.clone();
                        (route_result.route.handler)(req, res);
                        Ok(Halt)
                    },
                    None => Ok(Continue)
                }
            },
            _ => Ok(Continue)
        }
    }
}

#[test]
fn creates_map_with_var_variable_infos () {
    let map = PathUtils::get_variable_info("foo/:uid/bar/:groupid");

    assert_eq!(map.len(), 2);
    assert_eq!(map.get(&"uid".to_string()), &0);
    assert_eq!(map.get(&"groupid".to_string()), &1);
}

#[test]
fn creates_regex_with_captures () {
    let regex = PathUtils::create_regex("foo/:uid/bar/:groupid");
    let caps = regex.captures("foo/4711/bar/5490").unwrap();

    assert_eq!(caps.at(1), "4711");
    assert_eq!(caps.at(2), "5490");

    let regex = PathUtils::create_regex("foo/*/:uid/bar/:groupid");
    let caps = regex.captures("foo/test/4711/bar/5490").unwrap();

    assert_eq!(caps.at(1), "4711");
    assert_eq!(caps.at(2), "5490");

    let regex = PathUtils::create_regex("foo/**/:uid/bar/:groupid");
    let caps = regex.captures("foo/test/another/4711/bar/5490").unwrap();

    assert_eq!(caps.at(1), "4711");
    assert_eq!(caps.at(2), "5490");
}

#[test]
fn creates_valid_regex_for_routes () {
    let regex1 = PathUtils::create_regex("foo/:uid/bar/:groupid");
    let regex2 = PathUtils::create_regex("foo/*/bar");
    let regex3 = PathUtils::create_regex("foo/**/bar");

    assert_eq!(regex1.is_match("foo/4711/bar/5490"), true);
    assert_eq!(regex1.is_match("foo/4711/bar/5490?foo=true&bar=false"), true);
    assert_eq!(regex1.is_match("foo/4711/bar"), false);
    assert_eq!(regex1.is_match("foo/4711/bar?foo=true&bar=false"), false);
    assert_eq!(regex1.is_match("foo/4711/bar/test%20spacing"), true);
    assert_eq!(regex1.is_match("foo/4711/bar/5281?foo=test%20spacing&bar=false"), true);

    assert_eq!(regex2.is_match("foo/4711/bar"), true);
    assert_eq!(regex2.is_match("foo/4711/barr"), false);
    assert_eq!(regex2.is_match("foo/4711/bar?foo=true&bar=false"), true);
    assert_eq!(regex2.is_match("foo/4711/4712/bar"), false);
    assert_eq!(regex2.is_match("foo/4711/4712/bar?foo=true&bar=false"), false);

    assert_eq!(regex3.is_match("foo/4711/bar"), true);
    assert_eq!(regex3.is_match("foo/4711/bar?foo=true&bar=false"), true);
    assert_eq!(regex3.is_match("foo/4711/4712/bar"), true);
    assert_eq!(regex3.is_match("foo/4711/4712/bar?foo=true&bar=false"), true);

    //ensure that this works with commas too
    assert_eq!(regex1.is_match("foo/4711/bar/5490,1234"), true);
    assert_eq!(regex1.is_match("foo/4711/bar/5490,1234?foo=true&bar=false"), true);
    assert_eq!(regex1.is_match("foo/4711/bar"), false);
    assert_eq!(regex1.is_match("foo/4711/bar?foo=1,2,3&bar=false"), false);
}

#[test]
fn can_match_var_routes () {
    let route_store = &mut Router::new();

    fn handler (_request: &Request, response: &mut Response) -> () {
        let _ = response.origin.write("hello from foo".as_bytes());
    };

    route_store.add_route(method::Get, "/foo/:userid".to_string(), handler);
    route_store.add_route(method::Get, "/bar".to_string(), handler);

    let route_result = route_store.match_route(method::Get, "/foo/4711".to_string()).unwrap();
    let route = route_result.route;

    assert_eq!(route_result.params.get(&"userid".to_string()), &"4711".to_string());

    //assert the route has identified the variable
    assert_eq!(route.variables.len(), 1);
    assert_eq!(route.variables.get(&"userid".to_string()), &0);

    let route_result = route_store.match_route(method::Get, "/bar/4711".to_string());
    assert!(route_result.is_none());

    let route_result = route_store.match_route(method::Get, "/foo".to_string());
    assert!(route_result.is_none());

    //ensure that this will work with commas too
    let route_result = route_store.match_route(method::Get, "/foo/123,456".to_string());
    assert!(route_result.is_some());

    let route_result = route_result.unwrap();
    assert_eq!(route_result.params.get(&"userid".to_string()), &"123,456".to_string());

    //ensure that this will work with spacing too
    let route_result = route_store.match_route(method::Get, "/foo/John%20Doe".to_string());
    assert!(route_result.is_some());

    let route_result = route_result.unwrap();
    assert_eq!(route_result.params.get(&"userid".to_string()), &"John%20Doe".to_string());
}
<|endoftext|>


<commit_before>use std::process::Command;
use std::sync::mpsc::{channel, Receiver, Sender};

use threadpool::ThreadPool;

/// Runs child processes and provides a channel to asynchronously wait on
/// their completion.
///
/// This enables us to remain responsive to interruption requests even if
/// the child process does not honor our kill requests.
pub struct Runner {
    pool: ThreadPool,
    tx: Sender<()>,
}

impl Runner {
    pub fn new() -> (Runner, Receiver<()>) {
        let (tx, rx) = channel();
        (Runner {
            pool: ThreadPool::new(1),
            tx: tx,
        },
         rx)
    }

    #[cfg(target_family = "windows")]
    pub fn clear_screen(&self) {
        let _ = Command::new("cls").status();
    }

    #[cfg(target_family = "unix")]
    pub fn clear_screen(&self) {
        let _ = Command::new("clear").status();
    }

    pub fn run_command(&mut self,
                       cmd: &str,
                       updated_paths: Vec<&str>)
                       -> Option<Process> {
        let child = Process::new(cmd, updated_paths);

        if let Some(ref process) = child {
            let tx = self.tx.clone();
            let mut p = process.as_platform_process();

            self.pool.execute(move || {
                p.wait();

                let _ = tx.send(());
            });
        }

        child
    }
}

/// High-level wrapper around a child process
/// Unlike `platform::Process`, `Process` kills the child when it is dropped.
pub struct Process {
    process: platform::Process
}

impl Process {
    pub fn new(cmd: &str, updated_paths: Vec<&str>) -> Option<Process> {
        platform::Process::new(cmd, updated_paths).and_then(|p| {
            Some(Process { process: p })
        })
    }

    fn as_platform_process(&self) -> platform::Process {
        self.process.clone()
    }

    pub fn kill(&mut self) {
        self.process.kill();
    }

    #[allow(dead_code)]
    pub fn wait(&mut self) {
        self.process.wait();
    }
}

impl Drop for Process {
    fn drop(&mut self) {
        self.kill();
    }
}

#[cfg(target_family = "unix")]
mod platform {
    use std::process::Command;

    #[derive(Clone)]
    pub struct Process {
        child_pid: i32,
    }

    #[cfg(target_family = "unix")]
    impl Process {
        pub fn new(cmd: &str, updated_paths: Vec<&str>) -> Option<Process> {
            use libc;
            use std::os::unix::process::CommandExt;

            let mut command = Command::new("sh");
            command.arg("-c").arg(cmd);

            if !updated_paths.is_empty() {
                command.env("WATCHEXEC_UPDATED_PATH", updated_paths[0]);
            }

            let c = command.before_exec(|| unsafe {
                    libc::setpgid(0, 0);
                    Ok(())
                })
                .spawn()
                .ok();

            match c {
                Some(process) => {
                    Some(Process {
                        child_pid: process.id() as i32,
                    })
                }
                None => None,
            }
        }

        pub fn kill(&mut self) {
            use libc;

            extern "C" {
                fn killpg(pgrp: libc::pid_t, sig: libc::c_int) -> libc::c_int;
            }

            unsafe {
                killpg(self.child_pid, libc::SIGTERM);
            }
        }

        pub fn wait(&mut self) {
            use nix::sys::wait::waitpid;

            let _ = waitpid(-self.child_pid, None);
        }
    }
}

#[cfg(target_family = "windows")]
mod platform {
    use std::process::Command;
    use winapi::winnt::HANDLE;

    #[derive(Clone)]
    pub struct Process {
        child_handle: HANDLE,
    }

    unsafe impl Send for Process {}

    #[cfg(target_family = "windows")]
    impl Process {
        pub fn new(cmd: &str, updated_paths: Vec<&str>) -> Option<Process> {
            use std::os::windows::io::AsRawHandle;

            let mut command = Command::new("cmd.exe");
            command.arg("/C").arg(cmd);

            if !updated_paths.is_empty() {
                command.env("WATCHEXEC_UPDATED_PATH", updated_paths[0]);
            }

            match command.spawn().ok() {
                Some(process) => Some(Process { child_handle: process.as_raw_handle() }),
                None => None,
            }
        }

        pub fn kill(&mut self) {
            use kernel32::TerminateProcess;

            unsafe {
                let _ = TerminateProcess(self.child_handle, 0);
            }
        }

        pub fn wait(&mut self) {
            use kernel32::WaitForSingleObject;
            use winapi::winbase::INFINITE;

            unsafe {
                let _ = WaitForSingleObject(self.child_handle, INFINITE);
            }
        }
    }
}

#[cfg(test)]
mod process_tests {
    use std::path::Path;
    use std::thread;
    use std::time::Duration;

    use mktemp::Temp;

    use super::Process;

    fn file_contents(path: &Path) -> String {
        use std::fs::File;
        use std::io::Read;

        let mut f = File::open(path).unwrap();
        let mut s = String::new();
        f.read_to_string(&mut s).unwrap();

        s
    }

    #[test]
    fn test_start() {
        let process = Process::new("echo hi", vec![]);

        assert!(process.is_some());
    }

    #[test]
    fn test_wait() {
        let file = Temp::new_file().unwrap();
        let path = file.to_path_buf();
        let mut process = Process::new(&format!("echo hi > {:?}", path), vec![]).unwrap();
        process.wait();

        assert!(file_contents(&path).starts_with("hi"));
    }

    #[test]
    fn test_kill() {
        let file = Temp::new_file().unwrap();
        let path = file.to_path_buf();

        let mut process = Process::new(&format!("sleep 20; echo hi > {:?}", path), vec![]).unwrap();
        thread::sleep(Duration::from_millis(250));
        process.kill();
        process.wait();

        assert!(file_contents(&path) == "");
    }
}
<commit_msg>Mark process tests as Unix-only for now<commit_after>use std::process::Command;
use std::sync::mpsc::{channel, Receiver, Sender};

use threadpool::ThreadPool;

/// Runs child processes and provides a channel to asynchronously wait on
/// their completion.
///
/// This enables us to remain responsive to interruption requests even if
/// the child process does not honor our kill requests.
pub struct Runner {
    pool: ThreadPool,
    tx: Sender<()>,
}

impl Runner {
    pub fn new() -> (Runner, Receiver<()>) {
        let (tx, rx) = channel();
        (Runner {
            pool: ThreadPool::new(1),
            tx: tx,
        },
         rx)
    }

    #[cfg(target_family = "windows")]
    pub fn clear_screen(&self) {
        let _ = Command::new("cls").status();
    }

    #[cfg(target_family = "unix")]
    pub fn clear_screen(&self) {
        let _ = Command::new("clear").status();
    }

    pub fn run_command(&mut self,
                       cmd: &str,
                       updated_paths: Vec<&str>)
                       -> Option<Process> {
        let child = Process::new(cmd, updated_paths);

        if let Some(ref process) = child {
            let tx = self.tx.clone();
            let mut p = process.as_platform_process();

            self.pool.execute(move || {
                p.wait();

                let _ = tx.send(());
            });
        }

        child
    }
}

/// High-level wrapper around a child process
/// Unlike `platform::Process`, `Process` kills the child when it is dropped.
pub struct Process {
    process: platform::Process
}

impl Process {
    pub fn new(cmd: &str, updated_paths: Vec<&str>) -> Option<Process> {
        platform::Process::new(cmd, updated_paths).and_then(|p| {
            Some(Process { process: p })
        })
    }

    fn as_platform_process(&self) -> platform::Process {
        self.process.clone()
    }

    pub fn kill(&mut self) {
        self.process.kill();
    }

    #[allow(dead_code)]
    pub fn wait(&mut self) {
        self.process.wait();
    }
}

impl Drop for Process {
    fn drop(&mut self) {
        self.kill();
    }
}

#[cfg(target_family = "unix")]
mod platform {
    use std::process::Command;

    #[derive(Clone)]
    pub struct Process {
        child_pid: i32,
    }

    #[cfg(target_family = "unix")]
    impl Process {
        pub fn new(cmd: &str, updated_paths: Vec<&str>) -> Option<Process> {
            use libc;
            use std::os::unix::process::CommandExt;

            let mut command = Command::new("sh");
            command.arg("-c").arg(cmd);

            if !updated_paths.is_empty() {
                command.env("WATCHEXEC_UPDATED_PATH", updated_paths[0]);
            }

            let c = command.before_exec(|| unsafe {
                    libc::setpgid(0, 0);
                    Ok(())
                })
                .spawn()
                .ok();

            match c {
                Some(process) => {
                    Some(Process {
                        child_pid: process.id() as i32,
                    })
                }
                None => None,
            }
        }

        pub fn kill(&mut self) {
            use libc;

            extern "C" {
                fn killpg(pgrp: libc::pid_t, sig: libc::c_int) -> libc::c_int;
            }

            unsafe {
                killpg(self.child_pid, libc::SIGTERM);
            }
        }

        pub fn wait(&mut self) {
            use nix::sys::wait::waitpid;

            let _ = waitpid(-self.child_pid, None);
        }
    }
}

#[cfg(target_family = "windows")]
mod platform {
    use std::process::Command;
    use winapi::winnt::HANDLE;

    #[derive(Clone)]
    pub struct Process {
        child_handle: HANDLE,
    }

    unsafe impl Send for Process {}

    #[cfg(target_family = "windows")]
    impl Process {
        pub fn new(cmd: &str, updated_paths: Vec<&str>) -> Option<Process> {
            use std::os::windows::io::AsRawHandle;

            let mut command = Command::new("cmd.exe");
            command.arg("/C").arg(cmd);

            if !updated_paths.is_empty() {
                command.env("WATCHEXEC_UPDATED_PATH", updated_paths[0]);
            }

            match command.spawn().ok() {
                Some(process) => Some(Process { child_handle: process.as_raw_handle() }),
                None => None,
            }
        }

        pub fn kill(&mut self) {
            use kernel32::TerminateProcess;

            unsafe {
                let _ = TerminateProcess(self.child_handle, 0);
            }
        }

        pub fn wait(&mut self) {
            use kernel32::WaitForSingleObject;
            use winapi::winbase::INFINITE;

            unsafe {
                let _ = WaitForSingleObject(self.child_handle, INFINITE);
            }
        }
    }
}

#[cfg(test)]
mod process_tests {
    use std::path::Path;
    use std::thread;
    use std::time::Duration;

    use mktemp::Temp;

    use super::Process;

    fn file_contents(path: &Path) -> String {
        use std::fs::File;
        use std::io::Read;

        let mut f = File::open(path).unwrap();
        let mut s = String::new();
        f.read_to_string(&mut s).unwrap();

        s
    }

    #[test]
    #[cfg(target_family = "unix")]
    fn test_start() {
        let process = Process::new("echo hi", vec![]);

        assert!(process.is_some());
    }

    #[test]
    #[cfg(target_family = "unix")]
    fn test_wait() {
        let file = Temp::new_file().unwrap();
        let path = file.to_path_buf();
        let mut process = Process::new(&format!("echo hi > {}", path.to_str().unwrap()), vec![]).unwrap();
        process.wait();

        assert!(file_contents(&path).starts_with("hi"));
    }

    #[test]
    #[cfg(target_family = "unix")]
    fn test_kill() {
        let file = Temp::new_file().unwrap();
        let path = file.to_path_buf();

        let mut process = Process::new(&format!("sleep 20; echo hi > {}", path.to_str().unwrap()), vec![]).unwrap();
        thread::sleep(Duration::from_millis(250));
        process.kill();
        process.wait();

        assert!(file_contents(&path) == "");
    }
}
<|endoftext|>


<commit_before>extern crate hyper;
extern crate rustc_serialize;
extern crate rusqlite;
extern crate jmap;

mod db;
mod util;
mod contact;

use std::default::Default;
use std::io::Write;

use hyper::server::{Request, Response};
use hyper::method::Method::Post;
use hyper::status::StatusCode;
use hyper::uri::RequestUri::AbsolutePath;
use hyper::header;

use rustc_serialize::json::{Json,ToJson};

use jmap::util::FromJson;
use jmap::method::{RequestBatch, ResponseBatch};
use jmap::method::RequestMethod::*;

use util::RequestContext;
use contact::ContactHandler;
use db::Db;

fn jmap_handler(batch: RequestBatch) -> ResponseBatch {
    let mut rbatch: ResponseBatch = ResponseBatch::default();

    let r = RequestContext {
        db: Db::open().unwrap(),
    };

    for method in batch.0.into_iter() {
        rbatch.0.push(match method {
            GetContacts(args, client_id) =>
                r.get_contacts(args, client_id),
            GetContactUpdates(args, client_id) =>
                r.get_contact_updates(args, client_id),
            SetContacts(args, client_id) =>
                r.set_contacts(args, client_id),
        });
    }

    rbatch
}

fn finish_response(mut res: Response, code: StatusCode, body: Option<&[u8]>) {
    *res.status_mut() = code;

    match res.start()
        .and_then(|mut res|
                  match body {
                      Some(ref b) => {
                          try!(res.write_all(b));
                          Ok(res)
                      }
                      None => {
                          Ok(res)
                      }
                  })
        .and_then(|res| res.end()) {
            Err(e) => println!("response error: {}", e),
            _      => (),
        };
}

fn http_handler(mut req: Request, mut res: Response) {
    res.headers_mut().set(header::Server("salada/0.0.1".to_string()));

    let uri = req.uri.clone();

    match uri {
        AbsolutePath(ref path) => match (&req.method, &path[..]) {

            (&Post, "/jmap") => {
                match Json::from_reader(&mut req) {
                    Ok(j) => match RequestBatch::from_json(&j) {
                        Ok(b) => {
                            return finish_response(res, StatusCode::Ok, Some(jmap_handler(b).to_json().to_string().as_bytes()))
                        },
                        Err(e) => {
                            println!("jmap parse error: {}", e);
                            return finish_response(res, StatusCode::BadRequest, None)
                        },
                    },
                    Err(e) => {
                        println!("json parse error: {}", e);
                        return finish_response(res, StatusCode::BadRequest, None)
                    },
                }
            },

            (_, "/jmap") => return finish_response(res, StatusCode::MethodNotAllowed, None),
            _            => return finish_response(res, StatusCode::NotFound, None),
        },
        _ => return finish_response(res, StatusCode::BadRequest, None)
    };
}

fn main() {
    let server = hyper::Server::http(http_handler);
    let _listen_guard = server.listen("127.0.0.1:3000").unwrap();
    println!("Listening on http://127.0.0.1:3000/jmap");
}
<commit_msg>Support error returns to methods<commit_after>extern crate hyper;
extern crate rustc_serialize;
extern crate rusqlite;
extern crate jmap;

mod db;
mod util;
mod contact;

use std::default::Default;
use std::io::Write;

use hyper::server::{Request, Response};
use hyper::method::Method::Post;
use hyper::status::StatusCode;
use hyper::uri::RequestUri::AbsolutePath;
use hyper::header;

use rustc_serialize::json::{Json,ToJson};

use jmap::util::FromJson;
use jmap::method::{RequestBatch, ResponseBatch};
use jmap::method::RequestMethod::*;
use jmap::method::ResponseMethod::*;

use util::RequestContext;
use contact::ContactHandler;
use db::Db;

fn jmap_handler(batch: RequestBatch) -> ResponseBatch {
    let mut rbatch: ResponseBatch = ResponseBatch::default();

    let r = RequestContext {
        db: Db::open().unwrap(),
    };

    for method in batch.0.into_iter() {
        rbatch.0.push(match method {
            GetContacts(args, client_id) =>
                r.get_contacts(args, client_id),
            GetContactUpdates(args, client_id) =>
                r.get_contact_updates(args, client_id),
            SetContacts(args, client_id) =>
                r.set_contacts(args, client_id),

            RequestError(args, client_id) =>
                ResponseError(args, client_id),
        });
    }

    rbatch
}

fn finish_response(mut res: Response, code: StatusCode, body: Option<&[u8]>) {
    *res.status_mut() = code;

    match res.start()
        .and_then(|mut res|
                  match body {
                      Some(ref b) => {
                          try!(res.write_all(b));
                          Ok(res)
                      }
                      None => {
                          Ok(res)
                      }
                  })
        .and_then(|res| res.end()) {
            Err(e) => println!("response error: {}", e),
            _      => (),
        };
}

fn http_handler(mut req: Request, mut res: Response) {
    res.headers_mut().set(header::Server("salada/0.0.1".to_string()));

    let uri = req.uri.clone();

    match uri {
        AbsolutePath(ref path) => match (&req.method, &path[..]) {

            (&Post, "/jmap") => {
                match Json::from_reader(&mut req) {
                    Ok(j) => match RequestBatch::from_json(&j) {
                        Ok(b) => {
                            return finish_response(res, StatusCode::Ok, Some(jmap_handler(b).to_json().to_string().as_bytes()))
                        },
                        Err(e) => {
                            println!("jmap parse error: {}", e);
                            return finish_response(res, StatusCode::BadRequest, None)
                        },
                    },
                    Err(e) => {
                        println!("json parse error: {}", e);
                        return finish_response(res, StatusCode::BadRequest, None)
                    },
                }
            },

            (_, "/jmap") => return finish_response(res, StatusCode::MethodNotAllowed, None),
            _            => return finish_response(res, StatusCode::NotFound, None),
        },
        _ => return finish_response(res, StatusCode::BadRequest, None)
    };
}

fn main() {
    let server = hyper::Server::http(http_handler);
    let _listen_guard = server.listen("127.0.0.1:3000").unwrap();
    println!("Listening on http://127.0.0.1:3000/jmap");
}
<|endoftext|>


<commit_before>use std::collections::BTreeMap;

/// top level document
#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct Spec {
    /// version string
    pub swagger: String,
    pub info: Info,
    pub paths: BTreeMap<String, Operations>, // / -> get -> op
    pub definitions: BTreeMap<String, Schema>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub schemes: Option<Vec<String>>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub host: Option<String>,
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="basePath")]
    pub base_path: Option<String>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub consumes: Option<Vec<String>>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub produces: Option<Vec<String>>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub parameters: Option<BTreeMap<String, Parameter>>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub responses: Option<BTreeMap<String, Response>>,
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="securityDefinitions")]
    pub security_definitions: Option<BTreeMap<String, Security>>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub tags: Option<Vec<Tag>>,
}

impl Spec {
    pub fn unfurl(self) -> Self {
        self
    }
}

#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct Tag {
    pub name: String,
    #[serde(skip_serializing_if="Option::is_none")]
    pub description: Option<String>,
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="externalDocs")]
    pub external_docs: Option<Vec<ExternalDoc>>,
}

#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct ExternalDoc {
    pub url: String,
    #[serde(skip_serializing_if="Option::is_none")]
    pub description: Option<String>,
}

#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct Info {
    pub title: String,
    pub version: String,
    #[serde(skip_serializing_if="Option::is_none")]
    pub termsOfService: Option<String>,
}

#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct Operations {
    #[serde(skip_serializing_if="Option::is_none")]
    pub get: Option<Operation>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub post: Option<Operation>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub put: Option<Operation>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub patch: Option<Operation>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub delete: Option<Operation>,
}

#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct Operation {
    #[serde(skip_serializing_if="Option::is_none")]
    pub description: Option<String>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub consumes: Option<Vec<String>>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub produces: Option<Vec<String>>,
    pub schemes: Vec<String>,
    pub tags: Vec<String>,
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="operationId")]
    pub operation_id: Option<String>,
    pub responses: BTreeMap<String, Response>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub parameters: Option<Vec<Parameter>>,
}

#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct Parameter {
    pub name: String,
    #[serde(rename="in")]
    pub location: String,
    #[serde(skip_serializing_if="Option::is_none")]
    pub required: Option<bool>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub schema: Option<Schema>,
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="uniqueItems")]
    pub unique_items: Option<bool>,
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="type")]
    pub param_type: Option<String>,
}

#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct Response {
    pub description: String,
    #[serde(skip_serializing_if="Option::is_none")]
    pub schema: Option<Schema>,
}

#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct Security {
    #[serde(rename="type")]
    pub security_type: String, // todo:
}

#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct Schema {
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="$ref")]
    pub ref_path: Option<String>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub description: Option<String>,
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="type")]
    pub schema_type: Option<String>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub items: Option<Box<Schema>>, // if scheme_type array (box is for recursion)
    #[serde(skip_serializing_if="Option::is_none")]
    pub properties: Option<BTreeMap<String, Schema>>, // implies object
}
<commit_msg>terms of service name casing<commit_after>use std::collections::BTreeMap;

/// top level document
#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct Spec {
    /// version string
    pub swagger: String,
    pub info: Info,
    pub paths: BTreeMap<String, Operations>, // / -> get -> op
    pub definitions: BTreeMap<String, Schema>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub schemes: Option<Vec<String>>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub host: Option<String>,
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="basePath")]
    pub base_path: Option<String>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub consumes: Option<Vec<String>>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub produces: Option<Vec<String>>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub parameters: Option<BTreeMap<String, Parameter>>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub responses: Option<BTreeMap<String, Response>>,
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="securityDefinitions")]
    pub security_definitions: Option<BTreeMap<String, Security>>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub tags: Option<Vec<Tag>>,
}

impl Spec {
    pub fn unfurl(self) -> Self {
        self
    }
}

#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct Tag {
    pub name: String,
    #[serde(skip_serializing_if="Option::is_none")]
    pub description: Option<String>,
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="externalDocs")]
    pub external_docs: Option<Vec<ExternalDoc>>,
}

#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct ExternalDoc {
    pub url: String,
    #[serde(skip_serializing_if="Option::is_none")]
    pub description: Option<String>,
}

#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct Info {
    pub title: String,
    pub version: String,
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="termsOfService")]
    pub terms_of_service: Option<String>,
}

#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct Operations {
    #[serde(skip_serializing_if="Option::is_none")]
    pub get: Option<Operation>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub post: Option<Operation>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub put: Option<Operation>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub patch: Option<Operation>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub delete: Option<Operation>,
}

#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct Operation {
    #[serde(skip_serializing_if="Option::is_none")]
    pub description: Option<String>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub consumes: Option<Vec<String>>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub produces: Option<Vec<String>>,
    pub schemes: Vec<String>,
    pub tags: Vec<String>,
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="operationId")]
    pub operation_id: Option<String>,
    pub responses: BTreeMap<String, Response>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub parameters: Option<Vec<Parameter>>,
}

#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct Parameter {
    pub name: String,
    #[serde(rename="in")]
    pub location: String,
    #[serde(skip_serializing_if="Option::is_none")]
    pub required: Option<bool>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub schema: Option<Schema>,
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="uniqueItems")]
    pub unique_items: Option<bool>,
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="type")]
    pub param_type: Option<String>,
}

#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct Response {
    pub description: String,
    #[serde(skip_serializing_if="Option::is_none")]
    pub schema: Option<Schema>,
}

#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct Security {
    #[serde(rename="type")]
    pub security_type: String, // todo:
}

#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct Schema {
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="$ref")]
    pub ref_path: Option<String>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub description: Option<String>,
    #[serde(skip_serializing_if="Option::is_none")]
    #[serde(rename="type")]
    pub schema_type: Option<String>,
    #[serde(skip_serializing_if="Option::is_none")]
    pub items: Option<Box<Schema>>, // if scheme_type array (box is for recursion)
    #[serde(skip_serializing_if="Option::is_none")]
    pub properties: Option<BTreeMap<String, Schema>>, // implies object
}
<|endoftext|>


<commit_before>use configuration::Configuration;
use std::slice::SliceExt;
use std::cmp::Ordering;
use score::Score;

#[derive(Show)]
pub struct Search {
    pub config: Configuration,
    current: usize,
    pub query: String,
    pub selection: Option<String>,
    pub result: Vec<String>,
    done: bool,
}

impl Search {
    pub fn blank(config: Configuration) -> Search {
        let query = config.initial_search.clone();
        let previous_result = config.choices.clone();
        Search::new(config, query, previous_result, 0, false)
    }

    pub fn is_done(&self) -> bool {
        self.done
    }

    pub fn abort(self) -> Search {
        let mut search = Search::new(self.config, self.query, self.result, self.current, true);
        search.selection = None;
        search
    }

    pub fn done(self) -> Search {
        self.abort()
    }

    fn new(config: Configuration, query: String, result: Vec<String>, index: usize, done: bool) -> Search {

        let selection = Search::select(&result, index);

        Search { config: config,
                 current: index,
                 query: query,
                 selection: selection,
                 result: result,
                 done: done}
    }

    fn new_for_index(self, index: usize) -> Search {
        Search::new(self.config, self.query, self.result, index, self.done)
    }

    fn new_for_query(self, new_query: String) -> Search {
        let new_result = Search::filter(new_query.as_slice(), &self.config.choices);

        Search::new(self.config, new_query, new_result, self.current, self.done)
    }

    fn filter(query: &str, choices: &Vec<String>) -> Vec<String> {
        let mut filtered = choices.iter().filter_map( |choice| {
            let quality = Score::score(choice.as_slice(), query);
            if quality > 0.0 {
                Some((quality, choice.to_string()))
            } else {
                None
            }
        }).collect::<Vec<(f32, String)>>();

        filtered.sort_by( |&(quality_a, _), &(quality_b, _)| {
            quality_a.partial_cmp(&quality_b).unwrap_or(Ordering::Equal).reverse()
        });

        filtered.iter().map( |&(_, ref choice)| choice.to_string() ).collect::<Vec<String>>()
    }

    fn select(result: &Vec<String>, index: usize) -> Option<String> {
        if result.len() > 0 {
            Some(result[index].to_string())
        } else {
            None
        }
    }

    pub fn down(self) -> Search {
        let next_index = self.next_index();
        self.new_for_index(next_index)
    }

    fn up(self) -> Search {
        let next_index = self.prev_index();
        self.new_for_index(next_index)
    }

    pub fn append_to_search(self, input: &str) -> Search {
        let mut new_query = self.query.clone();
        new_query.push_str(input.as_slice());

        self.new_for_query(new_query)
    }

    fn backspace(self) -> Search {
        let mut new_query = self.query.clone();
        new_query.pop();

        self.new_for_query(new_query).new_for_index(0)
    }

    fn next_index(&self) -> usize {
        let next_index = self.current + 1;

        if next_index >= self.config.visible_limit { 0 } else { next_index }
    }

    fn prev_index(&self) -> usize {
        if self.current == 0 { self.config.visible_limit - 1 } else  { self.current - 1 }
    }
}

#[cfg(test)]
fn one_two_three() -> Vec<String> {
    vec!["one".to_string(),
         "two".to_string(),
         "three".to_string()]
}

#[test]
fn it_selects_the_first_choice_by_default() {
    let input =  one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config);
    assert_eq!(search.selection, Some("one".to_string()));
}

#[test]
fn it_selets_the_second_when_down_is_called() {
    let input =  one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config);
    assert_eq!(search.down().selection, Some("two".to_string()));
}

#[test]
fn it_loop_around_when_reaching_end_of_list() {
    let input =  one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config);
    assert_eq!(search.down().down().down().down().selection, Some("two".to_string()));
}

#[test]
fn it_loop_around_when_reaching_top_of_list() {
    let input =  one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config);
    assert_eq!(search.up().up().selection, Some("two".to_string()));
}

#[test]
fn it_loop_around_when_reaching_visible_limit() {
    let input =  one_two_three();

    let config = Configuration::from_inputs(input, None, Some(2));
    let search = Search::blank(config);
    assert_eq!(search.down().down().down().selection, Some("two".to_string()));
}

#[test]
fn it_moves_down_the_filtered_search_results() {
    let input =  one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config);
    assert_eq!(search.append_to_search("t").down().selection, Some("three".to_string()));
}

#[test]
fn it_moves_down_the_filtered_search_results_twice() {
    let input =  one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config);
    assert_eq!(search.append_to_search("t").append_to_search("w").selection, Some("two".to_string()));
}

#[test]
fn it_handles_not_matching_anything() {
    let input = one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config);
    assert_eq!(search.append_to_search("x").selection, None);
}

#[test]
fn up_match_nothing_after_filtering_all_out() {
    let input = one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config).append_to_search("x");

    assert_eq!(search.up().selection, None);
}

#[test]
fn down_match_nothing_after_filtering_all_out() {
    let input = one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config).append_to_search("x");

    assert_eq!(search.down().selection, None);
}

#[test]
fn backspaces_over_characters() {
    let input = one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config).append_to_search("e");

    assert_eq!(search.query, "e");
    assert_eq!(search.backspace().query, "");
}

#[test]
fn resets_the_index() {
    let input = one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config).append_to_search("e");

    assert_eq!(search.down().backspace().current, 0);
}

#[test]
fn previous_results_appear_after_backspace() {
    let input = one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config).append_to_search("t");

    assert_eq!(search.backspace().result.len(), 3);
}

#[test]
fn initial_search_is_not_done() {
    let input = one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config);

    assert!(!search.is_done());
}

#[test]
fn aborted_search_is_done() {
    let input = one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config).abort();

    assert!(search.is_done());
}

#[test]
fn done_search_is_done() {
    let input = one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config).done();

    assert!(search.is_done());
}

#[test]
fn aborted_search_has_no_selection() {
    let input = one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config).abort();

    assert_eq!(search.selection, None);
}
<commit_msg>Add proper implementation for done()<commit_after>use configuration::Configuration;
use std::slice::SliceExt;
use std::cmp::Ordering;
use score::Score;

#[derive(Show)]
pub struct Search {
    pub config: Configuration,
    current: usize,
    pub query: String,
    pub selection: Option<String>,
    pub result: Vec<String>,
    done: bool,
}

impl Search {
    pub fn blank(config: Configuration) -> Search {
        let query = config.initial_search.clone();
        let previous_result = config.choices.clone();
        Search::new(config, query, previous_result, 0, false)
    }

    pub fn is_done(&self) -> bool {
        self.done
    }

    pub fn abort(self) -> Search {
        let mut search = Search::new(self.config, self.query, self.result, self.current, true);
        search.selection = None;
        search
    }

    pub fn done(self) -> Search {
        Search::new(self.config, self.query, self.result, self.current, true)
    }

    fn new(config: Configuration, query: String, result: Vec<String>, index: usize, done: bool) -> Search {

        let selection = Search::select(&result, index);

        Search { config: config,
                 current: index,
                 query: query,
                 selection: selection,
                 result: result,
                 done: done}
    }

    fn new_for_index(self, index: usize) -> Search {
        Search::new(self.config, self.query, self.result, index, self.done)
    }

    fn new_for_query(self, new_query: String) -> Search {
        let new_result = Search::filter(new_query.as_slice(), &self.config.choices);

        Search::new(self.config, new_query, new_result, self.current, self.done)
    }

    fn filter(query: &str, choices: &Vec<String>) -> Vec<String> {
        let mut filtered = choices.iter().filter_map( |choice| {
            let quality = Score::score(choice.as_slice(), query);
            if quality > 0.0 {
                Some((quality, choice.to_string()))
            } else {
                None
            }
        }).collect::<Vec<(f32, String)>>();

        filtered.sort_by( |&(quality_a, _), &(quality_b, _)| {
            quality_a.partial_cmp(&quality_b).unwrap_or(Ordering::Equal).reverse()
        });

        filtered.iter().map( |&(_, ref choice)| choice.to_string() ).collect::<Vec<String>>()
    }

    fn select(result: &Vec<String>, index: usize) -> Option<String> {
        if result.len() > 0 {
            Some(result[index].to_string())
        } else {
            None
        }
    }

    pub fn down(self) -> Search {
        let next_index = self.next_index();
        self.new_for_index(next_index)
    }

    fn up(self) -> Search {
        let next_index = self.prev_index();
        self.new_for_index(next_index)
    }

    pub fn append_to_search(self, input: &str) -> Search {
        let mut new_query = self.query.clone();
        new_query.push_str(input.as_slice());

        self.new_for_query(new_query)
    }

    fn backspace(self) -> Search {
        let mut new_query = self.query.clone();
        new_query.pop();

        self.new_for_query(new_query).new_for_index(0)
    }

    fn next_index(&self) -> usize {
        let next_index = self.current + 1;

        if next_index >= self.config.visible_limit { 0 } else { next_index }
    }

    fn prev_index(&self) -> usize {
        if self.current == 0 { self.config.visible_limit - 1 } else  { self.current - 1 }
    }
}

#[cfg(test)]
fn one_two_three() -> Vec<String> {
    vec!["one".to_string(),
         "two".to_string(),
         "three".to_string()]
}

#[test]
fn it_selects_the_first_choice_by_default() {
    let input =  one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config);
    assert_eq!(search.selection, Some("one".to_string()));
}

#[test]
fn it_selets_the_second_when_down_is_called() {
    let input =  one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config);
    assert_eq!(search.down().selection, Some("two".to_string()));
}

#[test]
fn it_loop_around_when_reaching_end_of_list() {
    let input =  one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config);
    assert_eq!(search.down().down().down().down().selection, Some("two".to_string()));
}

#[test]
fn it_loop_around_when_reaching_top_of_list() {
    let input =  one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config);
    assert_eq!(search.up().up().selection, Some("two".to_string()));
}

#[test]
fn it_loop_around_when_reaching_visible_limit() {
    let input =  one_two_three();

    let config = Configuration::from_inputs(input, None, Some(2));
    let search = Search::blank(config);
    assert_eq!(search.down().down().down().selection, Some("two".to_string()));
}

#[test]
fn it_moves_down_the_filtered_search_results() {
    let input =  one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config);
    assert_eq!(search.append_to_search("t").down().selection, Some("three".to_string()));
}

#[test]
fn it_moves_down_the_filtered_search_results_twice() {
    let input =  one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config);
    assert_eq!(search.append_to_search("t").append_to_search("w").selection, Some("two".to_string()));
}

#[test]
fn it_handles_not_matching_anything() {
    let input = one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config);
    assert_eq!(search.append_to_search("x").selection, None);
}

#[test]
fn up_match_nothing_after_filtering_all_out() {
    let input = one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config).append_to_search("x");

    assert_eq!(search.up().selection, None);
}

#[test]
fn down_match_nothing_after_filtering_all_out() {
    let input = one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config).append_to_search("x");

    assert_eq!(search.down().selection, None);
}

#[test]
fn backspaces_over_characters() {
    let input = one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config).append_to_search("e");

    assert_eq!(search.query, "e");
    assert_eq!(search.backspace().query, "");
}

#[test]
fn resets_the_index() {
    let input = one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config).append_to_search("e");

    assert_eq!(search.down().backspace().current, 0);
}

#[test]
fn previous_results_appear_after_backspace() {
    let input = one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config).append_to_search("t");

    assert_eq!(search.backspace().result.len(), 3);
}

#[test]
fn initial_search_is_not_done() {
    let input = one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config);

    assert!(!search.is_done());
}

#[test]
fn aborted_search_is_done() {
    let input = one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config).abort();

    assert!(search.is_done());
}

#[test]
fn done_search_is_done() {
    let input = one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config).done();

    assert!(search.is_done());
}

#[test]
fn aborted_search_has_no_selection() {
    let input = one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config).abort();

    assert_eq!(search.selection, None);
}

#[test]
fn done_search_has_selection() {
    let input = one_two_three();

    let config = Configuration::from_inputs(input, None, None);
    let search = Search::blank(config).done();

    assert_eq!(search.selection, Some("one".to_string()));
}
<|endoftext|>


<commit_before>#![crate_name = "rustful_macros"]

#![crate_type = "dylib"]

#![doc(html_root_url = "http://ogeon.github.io/rustful/doc/")]

#![feature(macro_rules, plugin_registrar, quote, phase)]

//!This crate provides some helpful macros for rustful, including `router!` and `routes!`.
//!
//!#`router!`
//!The `router!` macro generates a `TreeRouter` containing the provided handlers and routing tree.
//!This can be useful to lower the risk of typing errors, among other things.
//!
//!##Example 1
//!
//!```rust ignore
//!#![feature(phase)]
//!#[phase(plugin)]
//!extern crate rustful_macros;
//!
//!extern crate rustful;
//!
//!...
//!
//!
//!let router = router!{
//!	"/about" => Get: about_us,
//!	"/user/:user" => Get: show_user,
//!	"/product/:name" => Get: show_product,
//!	"/*" => Get: show_error,
//!	"/" => Get: show_welcome
//!};
//!```
//!
//!##Example 2
//!
//!```rust ignore
//!#![feature(phase)]
//!#[phase(plugin)]
//!extern crate rustful_macros;
//!
//!extern crate rustful;
//!
//!...
//!
//!
//!let router = router!{
//!	"/" => Get: show_home,
//!	"home" => Get: show_home,
//!	"user/:username" => {Get: show_user, Post: save_user},
//!	"product" => {
//!		Get: show_all_products,
//!
//!		"json" => Get: send_all_product_data
//!		":id" => {
//!			Get: show_product,
//!			Post | Delete: edit_product,
//!
//!			"json" => Get: send_product_data
//!		}
//!	}
//!};
//!```
//!
//!#`routes!`
//!
//!The `routes!` macro generates a vector of routes, which can be used to create a `TreeRouter`.
//!It has the same syntax as `routes!`.
//!
//!```rust ignore
//!#![feature(phase)]
//!#[phase(plugin)]
//!extern crate rustful_macros;
//!
//!extern crate rustful;
//!
//!...
//!
//!
//!let routes = routes!{
//!	"/" => Get: show_home,
//!	"home" => Get: show_home,
//!	"user/:username" => {Get: show_user, Post: save_user},
//!	"product" => {
//!		Get: show_all_products,
//!
//!		"json" => Get: send_all_product_data
//!		":id" => {
//!			Get: show_product,
//!			Post | Delete: edit_product,
//!
//!			"json" => Get: send_product_data
//!		}
//!	}
//!};
//!
//!let router = TreeRouter::from_routes(&routes);
//!```

extern crate syntax;
extern crate rustc;

use std::path::BytesContainer;

use syntax::{ast, codemap};
use syntax::ext::base::{
	ExtCtxt, MacResult, MacExpr,
	NormalTT, TTMacroExpander
};
use syntax::ext::build::AstBuilder;
use syntax::ext::quote::rt::ExtParseUtils;
use syntax::parse;
use syntax::parse::token;
use syntax::parse::parser;
use syntax::parse::parser::Parser;
use syntax::ptr::P;
//use syntax::print::pprust;

use rustc::plugin::Registry;

#[plugin_registrar]
#[doc(hidden)]
pub fn macro_registrar(reg: &mut Registry) {
	let expander = box expand_router as Box<TTMacroExpander>;
	reg.register_syntax_extension(token::intern("router"), NormalTT(expander, None));

	let expander = box expand_routes as Box<TTMacroExpander>;
	reg.register_syntax_extension(token::intern("routes"), NormalTT(expander, None));
}

fn expand_router<'cx>(cx: &'cx mut ExtCtxt, sp: codemap::Span, tts: &[ast::TokenTree]) -> Box<MacResult + 'cx> {
	let router_ident = cx.ident_of("router");
	let insert_method = cx.ident_of("insert_item");

	let mut calls: Vec<P<ast::Stmt>> = vec!(
		cx.stmt_let(sp, true, router_ident, quote_expr!(&cx, ::rustful::TreeRouter::new()))
	);

	for (path, method, handler) in parse_routes(cx, tts).into_iter() {
		let path_expr = cx.parse_expr(format!("\"{}\"", path));
		let method_expr = cx.expr_path(method);
		calls.push(cx.stmt_expr(
			cx.expr_method_call(sp, cx.expr_ident(sp, router_ident), insert_method, vec!(method_expr, path_expr, handler))
		));
	}

	let block = cx.expr_block(cx.block(sp, calls, Some(cx.expr_ident(sp, router_ident))));

	MacExpr::new(block)
}

fn expand_routes<'cx>(cx: &'cx mut ExtCtxt, sp: codemap::Span, tts: &[ast::TokenTree]) -> Box<MacResult + 'cx> {
	let routes = parse_routes(cx, tts).into_iter().map(|(path, method, handler)| {
		let path_expr = cx.parse_expr(format!("\"{}\"", path));
		let method_expr = cx.expr_path(method);
		mk_tup(sp, vec!(method_expr, path_expr, handler))
	}).collect();

	MacExpr::new(cx.expr_vec(sp, routes))
}

fn parse_routes(cx: &mut ExtCtxt, tts: &[ast::TokenTree]) -> Vec<(String, ast::Path, P<ast::Expr>)> {

	let mut parser = parse::new_parser_from_tts(
		cx.parse_sess(), cx.cfg(), tts.to_vec()
	);

	parse_subroutes("", cx, &mut parser)
}

fn parse_subroutes(base: &str, cx: &mut ExtCtxt, parser: &mut Parser) -> Vec<(String, ast::Path, P<ast::Expr>)> {
	let mut routes = Vec::new();

	while !parser.eat(&token::Eof) {
		match parser.parse_optional_str() {
			Some((ref s, _, _)) => {
				if !parser.eat(&token::FatArrow) {
					parser.expect(&token::FatArrow);
					break;
				}

				let mut new_base = base.to_string();
				match s.container_as_str() {
					Some(s) => {
						new_base.push_str(s.trim_chars('/'));
						new_base.push_str("/");
					},
					None => cx.span_err(parser.span, "invalid path")
				}

				if parser.eat(&token::Eof) {
					cx.span_err(parser.span, "unexpected end of routing tree");
				}

				if parser.eat(&token::OpenDelim(token::Brace)) {
					let subroutes = parse_subroutes(new_base.as_slice(), cx, parser);
					routes.push_all(subroutes.as_slice());

					if parser.eat(&token::CloseDelim(token::Brace)) {
						if !parser.eat(&token::Comma) {
							break;
						}
					} else {
						parser.expect_one_of(&[token::Comma, token::CloseDelim(token::Brace)], &[]);
					}
				} else {
					for (method, handler) in parse_handler(parser).into_iter() {
						routes.push((new_base.clone(), method, handler))
					}

					if !parser.eat(&token::Comma) {
						break;
					}
				}
			},
			None => {
				for (method, handler) in parse_handler(parser).into_iter() {
					routes.push((base.to_string(), method, handler))
				}

				if !parser.eat(&token::Comma) {
					break;
				}
			}
		}
	}

	routes
}

fn parse_handler(parser: &mut Parser) -> Vec<(ast::Path, P<ast::Expr>)> {
	let mut methods = Vec::new();

	loop {
		methods.push(parser.parse_path(parser::NoTypesAllowed));

		if parser.eat(&token::Colon) {
			break;
		}

		if !parser.eat(&token::BinOp(token::Or)) {
			parser.expect_one_of(&[token::Colon, token::BinOp(token::Or)], &[]);
		}
	}

	let handler = parser.parse_expr();

	methods.into_iter().map(|m| (m, handler.clone())).collect()
}

fn mk_tup(sp: codemap::Span, content: Vec<P<ast::Expr>>) -> P<ast::Expr> {
	dummy_expr(sp, ast::ExprTup(content))
}

fn dummy_expr(sp: codemap::Span, e: ast::Expr_) -> P<ast::Expr> {
	P(ast::Expr {
		id: ast::DUMMY_NODE_ID,
		node: e,
		span: sp,
	})
}

/**
A macro for assigning content types.

It takes a main type, a sub type and a parameter list. Instead of this:

```
response.headers.content_type = Some(MediaType {
	type_: String::from_str("text"),
	subtype: String::from_str("html"),
	parameters: vec!((String::from_str("charset"), String::from_str("UTF-8")))
});
```

it can be written like this:

```
response.headers.content_type = content_type!("text", "html", "charset": "UTF-8");
```

The `"charset": "UTF-8"` part defines the parameter list for the content type.
It may contain more than one parameter, or be omitted:

```
response.headers.content_type = content_type!("application", "octet-stream", "type": "image/gif", "padding": "4");
```

```
response.headers.content_type = content_type!("image", "png");
```
**/
#[macro_export]
macro_rules! content_type(
	($main_type:expr, $sub_type:expr) => ({
		::rustful::mime::Mime (
			std::str::FromStr::from_str($main_type).unwrap(),
			std::str::FromStr::from_str($sub_type).unwrap(),
			Vec::new()
		)
	});

	($main_type:expr, $sub_type:expr, $($param:expr: $value:expr),+) => ({
		::rustful::mime::Mime (
			std::str::FromStr::from_str($main_type).unwrap(),
			std::str::FromStr::from_str($sub_type).unwrap(),
			vec!( $( (std::str::FromStr::from_str($param).unwrap(), std::str::FromStr::from_str($value).unwrap()) ),+ )
		)
	});
)

/**
A macro for callig `send` in response and aborting the handle function if it fails.

This macro will print an error to `stdout`.
**/
#[macro_export]
macro_rules! try_send(
	($writer:expr, $content:expr) => (
		match $writer.send($content) {
			Ok(v) => v,
			Err(::rustful::ResponseError::IoError(e)) => {
				println!("IO error: {}", e);
				return;
			},
			Err(::rustful::ResponseError::PluginError(e)) => {
				println!("plugin error: {}", e);
				return;
			}
		}
	);

	($writer:expr, $content:expr while $what:expr) => (
		match $writer.send($content) {
			Ok(v) => v,
			Err(::rustful::ResponseError::IoError(e)) => {
				println!("IO error while {}: {}", $what, e);
				return;
			},
			Err(::rustful::ResponseError::PluginError(e)) => {
				println!("plugin error while {}: {}", $what, e);
				return;
			}
		}
	)
)<commit_msg>RFC 378 made it into rust nightly. Added semicolons for macro invocations 'Parse macro invocations with parentheses or square brackets as expressions no matter the context, and require curly braces or a semicolon following the invocation to invoke a macro as a statement.'<commit_after>#![crate_name = "rustful_macros"]

#![crate_type = "dylib"]

#![doc(html_root_url = "http://ogeon.github.io/rustful/doc/")]

#![feature(macro_rules, plugin_registrar, quote, phase)]

//!This crate provides some helpful macros for rustful, including `router!` and `routes!`.
//!
//!#`router!`
//!The `router!` macro generates a `TreeRouter` containing the provided handlers and routing tree.
//!This can be useful to lower the risk of typing errors, among other things.
//!
//!##Example 1
//!
//!```rust ignore
//!#![feature(phase)]
//!#[phase(plugin)]
//!extern crate rustful_macros;
//!
//!extern crate rustful;
//!
//!...
//!
//!
//!let router = router!{
//!	"/about" => Get: about_us,
//!	"/user/:user" => Get: show_user,
//!	"/product/:name" => Get: show_product,
//!	"/*" => Get: show_error,
//!	"/" => Get: show_welcome
//!};
//!```
//!
//!##Example 2
//!
//!```rust ignore
//!#![feature(phase)]
//!#[phase(plugin)]
//!extern crate rustful_macros;
//!
//!extern crate rustful;
//!
//!...
//!
//!
//!let router = router!{
//!	"/" => Get: show_home,
//!	"home" => Get: show_home,
//!	"user/:username" => {Get: show_user, Post: save_user},
//!	"product" => {
//!		Get: show_all_products,
//!
//!		"json" => Get: send_all_product_data
//!		":id" => {
//!			Get: show_product,
//!			Post | Delete: edit_product,
//!
//!			"json" => Get: send_product_data
//!		}
//!	}
//!};
//!```
//!
//!#`routes!`
//!
//!The `routes!` macro generates a vector of routes, which can be used to create a `TreeRouter`.
//!It has the same syntax as `routes!`.
//!
//!```rust ignore
//!#![feature(phase)]
//!#[phase(plugin)]
//!extern crate rustful_macros;
//!
//!extern crate rustful;
//!
//!...
//!
//!
//!let routes = routes!{
//!	"/" => Get: show_home,
//!	"home" => Get: show_home,
//!	"user/:username" => {Get: show_user, Post: save_user},
//!	"product" => {
//!		Get: show_all_products,
//!
//!		"json" => Get: send_all_product_data
//!		":id" => {
//!			Get: show_product,
//!			Post | Delete: edit_product,
//!
//!			"json" => Get: send_product_data
//!		}
//!	}
//!};
//!
//!let router = TreeRouter::from_routes(&routes);
//!```

extern crate syntax;
extern crate rustc;

use std::path::BytesContainer;

use syntax::{ast, codemap};
use syntax::ext::base::{
	ExtCtxt, MacResult, MacExpr,
	NormalTT, TTMacroExpander
};
use syntax::ext::build::AstBuilder;
use syntax::ext::quote::rt::ExtParseUtils;
use syntax::parse;
use syntax::parse::token;
use syntax::parse::parser;
use syntax::parse::parser::Parser;
use syntax::ptr::P;
//use syntax::print::pprust;

use rustc::plugin::Registry;

#[plugin_registrar]
#[doc(hidden)]
pub fn macro_registrar(reg: &mut Registry) {
	let expander = box expand_router as Box<TTMacroExpander>;
	reg.register_syntax_extension(token::intern("router"), NormalTT(expander, None));

	let expander = box expand_routes as Box<TTMacroExpander>;
	reg.register_syntax_extension(token::intern("routes"), NormalTT(expander, None));
}

fn expand_router<'cx>(cx: &'cx mut ExtCtxt, sp: codemap::Span, tts: &[ast::TokenTree]) -> Box<MacResult + 'cx> {
	let router_ident = cx.ident_of("router");
	let insert_method = cx.ident_of("insert_item");

	let mut calls: Vec<P<ast::Stmt>> = vec!(
		cx.stmt_let(sp, true, router_ident, quote_expr!(&cx, ::rustful::TreeRouter::new()))
	);

	for (path, method, handler) in parse_routes(cx, tts).into_iter() {
		let path_expr = cx.parse_expr(format!("\"{}\"", path));
		let method_expr = cx.expr_path(method);
		calls.push(cx.stmt_expr(
			cx.expr_method_call(sp, cx.expr_ident(sp, router_ident), insert_method, vec!(method_expr, path_expr, handler))
		));
	}

	let block = cx.expr_block(cx.block(sp, calls, Some(cx.expr_ident(sp, router_ident))));

	MacExpr::new(block)
}

fn expand_routes<'cx>(cx: &'cx mut ExtCtxt, sp: codemap::Span, tts: &[ast::TokenTree]) -> Box<MacResult + 'cx> {
	let routes = parse_routes(cx, tts).into_iter().map(|(path, method, handler)| {
		let path_expr = cx.parse_expr(format!("\"{}\"", path));
		let method_expr = cx.expr_path(method);
		mk_tup(sp, vec!(method_expr, path_expr, handler))
	}).collect();

	MacExpr::new(cx.expr_vec(sp, routes))
}

fn parse_routes(cx: &mut ExtCtxt, tts: &[ast::TokenTree]) -> Vec<(String, ast::Path, P<ast::Expr>)> {

	let mut parser = parse::new_parser_from_tts(
		cx.parse_sess(), cx.cfg(), tts.to_vec()
	);

	parse_subroutes("", cx, &mut parser)
}

fn parse_subroutes(base: &str, cx: &mut ExtCtxt, parser: &mut Parser) -> Vec<(String, ast::Path, P<ast::Expr>)> {
	let mut routes = Vec::new();

	while !parser.eat(&token::Eof) {
		match parser.parse_optional_str() {
			Some((ref s, _, _)) => {
				if !parser.eat(&token::FatArrow) {
					parser.expect(&token::FatArrow);
					break;
				}

				let mut new_base = base.to_string();
				match s.container_as_str() {
					Some(s) => {
						new_base.push_str(s.trim_chars('/'));
						new_base.push_str("/");
					},
					None => cx.span_err(parser.span, "invalid path")
				}

				if parser.eat(&token::Eof) {
					cx.span_err(parser.span, "unexpected end of routing tree");
				}

				if parser.eat(&token::OpenDelim(token::Brace)) {
					let subroutes = parse_subroutes(new_base.as_slice(), cx, parser);
					routes.push_all(subroutes.as_slice());

					if parser.eat(&token::CloseDelim(token::Brace)) {
						if !parser.eat(&token::Comma) {
							break;
						}
					} else {
						parser.expect_one_of(&[token::Comma, token::CloseDelim(token::Brace)], &[]);
					}
				} else {
					for (method, handler) in parse_handler(parser).into_iter() {
						routes.push((new_base.clone(), method, handler))
					}

					if !parser.eat(&token::Comma) {
						break;
					}
				}
			},
			None => {
				for (method, handler) in parse_handler(parser).into_iter() {
					routes.push((base.to_string(), method, handler))
				}

				if !parser.eat(&token::Comma) {
					break;
				}
			}
		}
	}

	routes
}

fn parse_handler(parser: &mut Parser) -> Vec<(ast::Path, P<ast::Expr>)> {
	let mut methods = Vec::new();

	loop {
		methods.push(parser.parse_path(parser::NoTypesAllowed));

		if parser.eat(&token::Colon) {
			break;
		}

		if !parser.eat(&token::BinOp(token::Or)) {
			parser.expect_one_of(&[token::Colon, token::BinOp(token::Or)], &[]);
		}
	}

	let handler = parser.parse_expr();

	methods.into_iter().map(|m| (m, handler.clone())).collect()
}

fn mk_tup(sp: codemap::Span, content: Vec<P<ast::Expr>>) -> P<ast::Expr> {
	dummy_expr(sp, ast::ExprTup(content))
}

fn dummy_expr(sp: codemap::Span, e: ast::Expr_) -> P<ast::Expr> {
	P(ast::Expr {
		id: ast::DUMMY_NODE_ID,
		node: e,
		span: sp,
	})
}

/**
A macro for assigning content types.

It takes a main type, a sub type and a parameter list. Instead of this:

```
response.headers.content_type = Some(MediaType {
	type_: String::from_str("text"),
	subtype: String::from_str("html"),
	parameters: vec!((String::from_str("charset"), String::from_str("UTF-8")))
});
```

it can be written like this:

```
response.headers.content_type = content_type!("text", "html", "charset": "UTF-8");
```

The `"charset": "UTF-8"` part defines the parameter list for the content type.
It may contain more than one parameter, or be omitted:

```
response.headers.content_type = content_type!("application", "octet-stream", "type": "image/gif", "padding": "4");
```

```
response.headers.content_type = content_type!("image", "png");
```
**/
#[macro_export]
macro_rules! content_type(
	($main_type:expr, $sub_type:expr) => ({
		::rustful::mime::Mime (
			std::str::FromStr::from_str($main_type).unwrap(),
			std::str::FromStr::from_str($sub_type).unwrap(),
			Vec::new()
		)
	});

	($main_type:expr, $sub_type:expr, $($param:expr: $value:expr),+) => ({
		::rustful::mime::Mime (
			std::str::FromStr::from_str($main_type).unwrap(),
			std::str::FromStr::from_str($sub_type).unwrap(),
			vec!( $( (std::str::FromStr::from_str($param).unwrap(), std::str::FromStr::from_str($value).unwrap()) ),+ )
		)
	});
);

/**
A macro for callig `send` in response and aborting the handle function if it fails.

This macro will print an error to `stdout`.
**/
#[macro_export]
macro_rules! try_send(
	($writer:expr, $content:expr) => (
		match $writer.send($content) {
			Ok(v) => v,
			Err(::rustful::ResponseError::IoError(e)) => {
				println!("IO error: {}", e);
				return;
			},
			Err(::rustful::ResponseError::PluginError(e)) => {
				println!("plugin error: {}", e);
				return;
			}
		}
	);

	($writer:expr, $content:expr while $what:expr) => (
		match $writer.send($content) {
			Ok(v) => v,
			Err(::rustful::ResponseError::IoError(e)) => {
				println!("IO error while {}: {}", $what, e);
				return;
			},
			Err(::rustful::ResponseError::PluginError(e)) => {
				println!("plugin error while {}: {}", $what, e);
				return;
			}
		}
	)
);
<|endoftext|>