rust-easy-router 0.1.1

Simple router framework for Rust's Iron framework.
Documentation
/*! # Rust Easy Router

 Library to add "matched" routing to the Rust web framework Iron.
 This can be used to build REST APIs with relative ease, and high stability.

 # Example Code:

 ```
 extern crate rust-easy-router;

 use rust-easy-router::*;

 fn test_handle(vars: HashMap<String, String>, body: &mut Body) -> IronResult<Response>
 {
     let mut string = "Vars:".to_owned();

     for (x, y) in &vars {
         string.push_str(&format!("\n{} -> {}", x, y));
     }

     string.push_str("\n");

     /* Get Body */
     let mut buf: String = "".to_owned();
     let res = body.read_to_string(&mut buf);
     string.push_str(&buf[..]);

     Ok (
         Response::with((status::Ok, string))
     )

 }

 fn main()
 {
     /* Creates new Router instance */
     let mut router = Router::new();

     /* Add routes */
     router.get("/get_image/:user/:album/:image", test_handle);

     /* Closure for Iron */
     let handle = move |_req: &mut Request| -> IronResult<Response> {
         return router.handle(_req);
     };

     /* Start server */
     let server = Iron::new(handle).http("localhost:3000").unwrap();
     println!("Server ready on port 3000.");

 }

 ```

 */


extern crate iron;

use iron::prelude::*;
use iron::status;
use iron::request::Body;
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use std::cmp::{PartialEq, Eq};
use std::option::Option::None;


#[cfg(test)]
mod tests {

    use Router;
    use std::collections::HashMap;
    use iron::prelude::*;
    use iron::status;
    use iron::request::Body;
    use std::io::Read;

    fn test_handle(vars: HashMap<String, String>, body: &mut Body) -> IronResult<Response>
    {
        let mut string = "Vars:".to_owned();

        for (x, y) in &vars {
            string.push_str(&format!("\n{} -> {}", x, y));
        }

        string.push_str("\n");

        /* Get Body */
        let mut buf: String = "".to_owned();
        let res = body.read_to_string(&mut buf);
        string.push_str(&buf[..]);

        Ok (
            Response::with((status::Ok, string))
        )

    }

    #[test]
    fn test_router()
    {

        let mut r = Router::new();

        r.get("/id/:album", test_handle);

        let handle = move |_req: &mut Request| -> IronResult<Response> {
            return r.handle(_req);
        };

        //let server = Iron::new(handle).http("localhost:3000").unwrap();
        //println!("Server ready on 3000.");

    }
}



pub struct MatchableUrlResult {

    matches: bool,
    variables: Option<HashMap<String, String>>

}

pub struct MatchableUrl {
    url: String,
    components: Vec<(String, bool)>
}

impl Hash for MatchableUrl {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.url.hash(state);
        self.components.hash(state);
    }
}

impl PartialEq for MatchableUrl {
    fn eq(&self, other: &MatchableUrl) -> bool {
        return self.url == other.url;
    }
}

impl Eq for MatchableUrl {}

impl MatchableUrl {

    /// Creates a new MatchableUrl given a "route."
    ///
    /// # Example:
    ///
    /// ```
    /// let m = MatchableUrl::new("/user/:id");
    /// ```
    pub fn new(url: &str) -> MatchableUrl
    {

        let split = url[1..].split("/");
        let mut comp : Vec<(String, bool)> = Vec::new();

        for s in split {
            if s.find(":") == None {
                comp.push((s.to_owned(), true));
            } else {
                comp.push((s.to_owned(), false));
            }
        }

        return MatchableUrl { url: url.to_owned(), components: comp };

    }

    /// Attempts to match a request with a MatchableUrl.
    /// If this succeeds, it will also return a list of the in-url parameters.
    pub fn _match(&self, request_path: Vec<&str>) -> MatchableUrlResult
    {

        /* Check size first */
        let size = request_path.len();

        if size != self.components.len() {
            return MatchableUrlResult { matches: false, variables: None }
        }

        let mut index = 0;
        let mut vars : HashMap<String, String> = HashMap::new();

        for s in request_path {
            let (ref name, ref exac) = self.components[index];

            if *exac {
                if name != s {
                    return MatchableUrlResult { matches: false, variables: None }
                }
            } else {
                vars.insert(name[1..].to_owned(), s.to_owned());
            }

            index += 1;
        }

        return MatchableUrlResult { matches: true, variables: Some(vars) };

    }

}

pub struct Router {

    routes: HashMap<MatchableUrl, fn(HashMap<String, String>, &mut Body) -> IronResult<Response>>

}

impl Router {

    /// Creates a new Router.
    pub fn new() -> Router
    {
        return Router { routes: HashMap::new() };
    }

    /// The method to take in each Iron Request.
    /// Attempts to match its path with the Router's routes.
    pub fn handle(&self, _req: &mut Request) -> IronResult<Response>
    {

        for (m_url, f) in &self.routes {
            let path = _req.url.path();
            let res = m_url._match(path);
            let b = &mut _req.body;
            if res.matches {
                return f(res.variables.unwrap(), b);
            }
        }

        Ok(
            Response::with((status:: Ok, "No match."))
        )

    }

    /// Function to add a new route to the Router.
    ///
    /// # Example:
    ///
    /// ```
    /// router.get("/user/:id/:album/:image");
    /// ```
    pub fn get(&mut self, path: &str, f: fn(HashMap<String, String>, &mut Body) -> IronResult<Response>)
    {
        let url = MatchableUrl::new(path);
        self.routes.insert(url, f);
    }

}