rust_easy_router/
lib.rs

1/*! # Rust Easy Router
2
3 Library to add "matched" routing to the Rust web framework Iron.
4 This can be used to build REST APIs with relative ease, and high stability.
5
6 # Example Code:
7
8 ```
9 extern crate rust-easy-router;
10
11 use rust-easy-router::*;
12
13 fn test_handle(vars: HashMap<String, String>, body: &mut Body) -> IronResult<Response>
14 {
15     let mut string = "Vars:".to_owned();
16
17     for (x, y) in &vars {
18         string.push_str(&format!("\n{} -> {}", x, y));
19     }
20
21     string.push_str("\n");
22
23     /* Get Body */
24     let mut buf: String = "".to_owned();
25     let res = body.read_to_string(&mut buf);
26     string.push_str(&buf[..]);
27
28     Ok (
29         Response::with((status::Ok, string))
30     )
31
32 }
33
34 fn main()
35 {
36     /* Creates new Router instance */
37     let mut router = Router::new();
38
39     /* Add routes */
40     router.get("/get_image/:user/:album/:image", test_handle);
41
42     /* Closure for Iron */
43     let handle = move |_req: &mut Request| -> IronResult<Response> {
44         return router.handle(_req);
45     };
46
47     /* Start server */
48     let server = Iron::new(handle).http("localhost:3000").unwrap();
49     println!("Server ready on port 3000.");
50
51 }
52
53 ```
54
55 */
56
57
58extern crate iron;
59
60use iron::prelude::*;
61use iron::status;
62use iron::request::Body;
63use std::collections::HashMap;
64use std::hash::{Hash, Hasher};
65use std::cmp::{PartialEq, Eq};
66use std::option::Option::None;
67
68
69#[cfg(test)]
70mod tests {
71
72    use Router;
73    use std::collections::HashMap;
74    use iron::prelude::*;
75    use iron::status;
76    use iron::request::Body;
77    use std::io::Read;
78
79    fn test_handle(vars: HashMap<String, String>, body: &mut Body) -> IronResult<Response>
80    {
81        let mut string = "Vars:".to_owned();
82
83        for (x, y) in &vars {
84            string.push_str(&format!("\n{} -> {}", x, y));
85        }
86
87        string.push_str("\n");
88
89        /* Get Body */
90        let mut buf: String = "".to_owned();
91        let res = body.read_to_string(&mut buf);
92        string.push_str(&buf[..]);
93
94        Ok (
95            Response::with((status::Ok, string))
96        )
97
98    }
99
100    #[test]
101    fn test_router()
102    {
103
104        let mut r = Router::new();
105
106        r.get("/id/:album", test_handle);
107
108        let handle = move |_req: &mut Request| -> IronResult<Response> {
109            return r.handle(_req);
110        };
111
112        //let server = Iron::new(handle).http("localhost:3000").unwrap();
113        //println!("Server ready on 3000.");
114
115    }
116}
117
118
119
120pub struct MatchableUrlResult {
121
122    matches: bool,
123    variables: Option<HashMap<String, String>>
124
125}
126
127pub struct MatchableUrl {
128    url: String,
129    components: Vec<(String, bool)>
130}
131
132impl Hash for MatchableUrl {
133    fn hash<H: Hasher>(&self, state: &mut H) {
134        self.url.hash(state);
135        self.components.hash(state);
136    }
137}
138
139impl PartialEq for MatchableUrl {
140    fn eq(&self, other: &MatchableUrl) -> bool {
141        return self.url == other.url;
142    }
143}
144
145impl Eq for MatchableUrl {}
146
147impl MatchableUrl {
148
149    /// Creates a new MatchableUrl given a "route."
150    ///
151    /// # Example:
152    ///
153    /// ```
154    /// let m = MatchableUrl::new("/user/:id");
155    /// ```
156    pub fn new(url: &str) -> MatchableUrl
157    {
158
159        let split = url[1..].split("/");
160        let mut comp : Vec<(String, bool)> = Vec::new();
161
162        for s in split {
163            if s.find(":") == None {
164                comp.push((s.to_owned(), true));
165            } else {
166                comp.push((s.to_owned(), false));
167            }
168        }
169
170        return MatchableUrl { url: url.to_owned(), components: comp };
171
172    }
173
174    /// Attempts to match a request with a MatchableUrl.
175    /// If this succeeds, it will also return a list of the in-url parameters.
176    pub fn _match(&self, request_path: Vec<&str>) -> MatchableUrlResult
177    {
178
179        /* Check size first */
180        let size = request_path.len();
181
182        if size != self.components.len() {
183            return MatchableUrlResult { matches: false, variables: None }
184        }
185
186        let mut index = 0;
187        let mut vars : HashMap<String, String> = HashMap::new();
188
189        for s in request_path {
190            let (ref name, ref exac) = self.components[index];
191
192            if *exac {
193                if name != s {
194                    return MatchableUrlResult { matches: false, variables: None }
195                }
196            } else {
197                vars.insert(name[1..].to_owned(), s.to_owned());
198            }
199
200            index += 1;
201        }
202
203        return MatchableUrlResult { matches: true, variables: Some(vars) };
204
205    }
206
207}
208
209pub struct Router {
210
211    routes: HashMap<MatchableUrl, fn(HashMap<String, String>, &mut Body) -> IronResult<Response>>
212
213}
214
215impl Router {
216
217    /// Creates a new Router.
218    pub fn new() -> Router
219    {
220        return Router { routes: HashMap::new() };
221    }
222
223    /// The method to take in each Iron Request.
224    /// Attempts to match its path with the Router's routes.
225    pub fn handle(&self, _req: &mut Request) -> IronResult<Response>
226    {
227
228        for (m_url, f) in &self.routes {
229            let path = _req.url.path();
230            let res = m_url._match(path);
231            let b = &mut _req.body;
232            if res.matches {
233                return f(res.variables.unwrap(), b);
234            }
235        }
236
237        Ok(
238            Response::with((status:: Ok, "No match."))
239        )
240
241    }
242
243    /// Function to add a new route to the Router.
244    ///
245    /// # Example:
246    ///
247    /// ```
248    /// router.get("/user/:id/:album/:image");
249    /// ```
250    pub fn get(&mut self, path: &str, f: fn(HashMap<String, String>, &mut Body) -> IronResult<Response>)
251    {
252        let url = MatchableUrl::new(path);
253        self.routes.insert(url, f);
254    }
255
256}