rusty_pipe/
router.rs

1use std::collections::HashMap;
2use std::error::Error;
3use std::fmt;
4use std::sync::Arc;
5
6use iron::{Request, Response, BeforeMiddleware, Handler, IronResult, IronError};
7use iron::{status, method, headers};
8use iron::typemap::Key;
9use iron::modifiers::Redirect;
10
11use route_recognizer::Router as Recognizer;
12use route_recognizer::{Match, Params};
13
14#[derive(Clone)]
15pub struct MetaRouter {
16    routers: HashMap<method::Method, Recognizer<Box<String>>>,
17    wildcard: Recognizer<Box<String>>,
18}
19
20pub struct RouterInner {
21    // The routers, specialized by method.
22    pub routers: HashMap<method::Method, Recognizer<Box<Handler>>>,
23    // Routes that accept any method.
24    pub wildcard: Recognizer<Box<Handler>>,
25    // Used in URL generation.
26    pub route_ids: HashMap<String, String>,
27    // Used in metadata access.
28    pub meta_router: MetaRouter,
29}
30
31/// `Router` provides an interface for creating complex routes as middleware
32/// for the Iron framework.
33pub struct Router {
34    inner: Arc<RouterInner>
35}
36
37pub struct RouteMatch {
38    pub params: Params,
39    pub route_id: String
40}
41
42impl MetaRouter {
43    fn recognize(&self, method: &method::Method, path: &str)
44                     -> Option<Match<&Box<String>>> {
45        self.routers.get(method).and_then(|router| router.recognize(path).ok())
46            .or(self.wildcard.recognize(path).ok())
47    }
48
49    pub fn analyse_method(&self, req: &mut Request, path: &str) -> Option<RouteMatch> {
50        if let Some(matched) = self.recognize(&req.method, path) {
51            let ctx = RouteMatch {
52                params: matched.params,
53                route_id: matched.route_id
54            };
55            Some(ctx)
56        } else {
57            None
58        }
59    }
60}
61
62impl Router {
63    /// Construct a new, empty `Router`.
64    ///
65    /// ```
66    /// # use rusty_pipe::Router;
67    /// let router = Router::new();
68    /// ```
69    pub fn new() -> Router {
70        Router {
71            inner: Arc::new(RouterInner {
72                routers: HashMap::new(),
73                wildcard: Recognizer::new(),
74                route_ids: HashMap::new(),
75                meta_router: MetaRouter {
76                    routers: HashMap::new(),
77                    wildcard: Recognizer::new()
78                }
79            })
80        }
81    }
82
83    fn mut_inner(&mut self) -> &mut RouterInner {
84        Arc::get_mut(&mut self.inner).expect("Cannot modify router at this point.")
85    }
86
87    /// Add a new route to a `Router`, matching both a method and glob pattern.
88    ///
89    /// `route` supports glob patterns: `*` for a single wildcard segment and
90    /// `:param` for matching storing that segment of the request url in the `Params`
91    /// object, which is stored in the request `extensions`.
92    ///
93    /// For instance, to route `Get` requests on any route matching
94    /// `/users/:userid/:friend` and store `userid` and `friend` in
95    /// the exposed Params object:
96    ///
97    /// ```ignore
98    /// let mut router = Router::new();
99    /// router.route(method::Get, "/users/:userid/:friendid", controller, "user_friend");
100    /// ```
101    ///
102    /// `route_id` is a unique name for your route, and is used when generating an URL with
103    /// `url_for`.
104    ///
105    /// The controller provided to route can be any `Handler`, which allows
106    /// extreme flexibility when handling routes. For instance, you could provide
107    /// a `Chain`, a `Handler`, which contains an authorization middleware and
108    /// a controller function, so that you can confirm that the request is
109    /// authorized for this route before handling it.
110    pub fn route<S: AsRef<str>, H: Handler>(&mut self, method: method::Method, glob: S, handler: H, route_id: &str) -> &mut Router {
111        self.mut_inner().routers
112            .entry(method.clone())
113            .or_insert(Recognizer::new())
114            .add(glob.as_ref(), route_id, Box::new(handler));
115        self.route_id(route_id.as_ref(), glob.as_ref());
116        self.mut_inner().meta_router.routers
117            .entry(method)
118            .or_insert(Recognizer::new())
119            .add(glob.as_ref(), route_id, Box::new("".to_string()));
120        self
121    }
122
123    fn route_id(&mut self, id: &str, glob: &str) {
124        let inner = self.mut_inner();
125        let ref mut route_ids = inner.route_ids;
126
127        match route_ids.get(id) {
128            Some(other_glob) if glob != other_glob => panic!("Duplicate route_id: {}", id),
129            _ => ()
130        };
131
132        route_ids.insert(id.to_owned(), glob.to_owned());
133    }
134
135    /// Like route, but specialized to the `Get` method.
136    pub fn get<S: AsRef<str>, H: Handler>(&mut self, glob: S, handler: H, route_id: &str) -> &mut Router {
137        self.route(method::Get, glob, handler, route_id)
138    }
139
140    /// Like route, but specialized to the `Post` method.
141    pub fn post<S: AsRef<str>, H: Handler>(&mut self, glob: S, handler: H, route_id: &str) -> &mut Router {
142        self.route(method::Post, glob, handler, route_id)
143    }
144
145    /// Like route, but specialized to the `Put` method.
146    pub fn put<S: AsRef<str>, H: Handler>(&mut self, glob: S, handler: H, route_id: &str) -> &mut Router {
147        self.route(method::Put, glob, handler, route_id)
148    }
149
150    /// Like route, but specialized to the `Delete` method.
151    pub fn delete<S: AsRef<str>, H: Handler>(&mut self, glob: S, handler: H, route_id: &str) -> &mut Router {
152        self.route(method::Delete, glob, handler, route_id)
153    }
154
155    /// Like route, but specialized to the `Head` method.
156    pub fn head<S: AsRef<str>, H: Handler>(&mut self, glob: S, handler: H, route_id: &str) -> &mut Router {
157        self.route(method::Head, glob, handler, route_id)
158    }
159
160    /// Like route, but specialized to the `Patch` method.
161    pub fn patch<S: AsRef<str>, H: Handler>(&mut self, glob: S, handler: H, route_id: &str) -> &mut Router {
162        self.route(method::Patch, glob, handler, route_id)
163    }
164
165    /// Like route, but specialized to the `Options` method.
166    pub fn options<S: AsRef<str>, H: Handler>(&mut self, glob: S, handler: H, route_id: &str) -> &mut Router {
167        self.route(method::Options, glob, handler, route_id)
168    }
169
170    /// Route will match any method, including gibberish.
171    /// In case of ambiguity, handlers specific to methods will be preferred.
172    pub fn any<S: AsRef<str>, H: Handler>(&mut self, glob: S, handler: H, route_id: &str) -> &mut Router {
173        self.mut_inner().wildcard.add(glob.as_ref(), route_id, Box::new(handler));
174        self.route_id(route_id.as_ref(), glob.as_ref());
175        self
176    }
177
178    fn recognize(&self, method: &method::Method, path: &str)
179                     -> Option<Match<&Box<Handler>>> {
180        self.inner.routers.get(method).and_then(|router| router.recognize(path).ok())
181            .or(self.inner.wildcard.recognize(path).ok())
182    }
183
184    fn handle_options(&self, path: &str) -> Response {
185        static METHODS: &'static [method::Method] =
186            &[method::Get, method::Post, method::Put,
187              method::Delete, method::Head, method::Patch];
188
189        // Get all the available methods and return them.
190        let mut options = vec![];
191
192        for method in METHODS.iter() {
193            self.inner.routers.get(method).map(|router| {
194                if let Some(_) = router.recognize(path).ok() {
195                    options.push(method.clone());
196                }
197            });
198        }
199        // If GET is there, HEAD is also there.
200        if options.contains(&method::Get) && !options.contains(&method::Head) {
201            options.push(method::Head);
202        }
203
204        let mut res = Response::with(status::Ok);
205        res.headers.set(headers::Allow(options));
206        res
207    }
208
209    // Tests for a match by adding or removing a trailing slash.
210    fn redirect_slash(&self, req : &Request) -> Option<IronError> {
211        let mut url = req.url.clone();
212        let mut path = url.path().join("/");
213
214        if let Some(last_char) = path.chars().last() {
215            {
216                let mut path_segments = url.as_mut().path_segments_mut().unwrap();
217                if last_char == '/' {
218                    // We didn't recognize anything without a trailing slash; try again with one appended.
219                    path.pop();
220                    path_segments.pop();
221                } else {
222                    // We didn't recognize anything with a trailing slash; try again without it.
223                    path.push('/');
224                    path_segments.push("");
225                }
226            }
227        }
228
229        self.recognize(&req.method, &path).and(
230            Some(IronError::new(TrailingSlash,
231                                (status::MovedPermanently, Redirect(url))))
232        )
233    }
234
235    fn handle_method(&self, req: &mut Request, path: &str) -> Option<IronResult<Response>> {
236        if let Some(matched) = self.recognize(&req.method, path) {
237            let ctx = RouteMatch {
238                params: matched.params,
239                route_id: matched.route_id
240            };
241            req.extensions.insert::<Router>(ctx);
242            req.extensions.insert::<RouterInner>(self.inner.clone());
243            Some(matched.handler.handle(req))
244        } else { self.redirect_slash(req).and_then(|redirect| Some(Err(redirect))) }
245    }
246
247    pub fn to_meta(&self) -> MetaRouter {
248        self.inner.meta_router.clone()
249    }
250}
251
252impl Key for Router { type Value = RouteMatch; }
253
254impl Key for RouterInner { type Value = Arc<RouterInner>; }
255
256impl Key for MetaRouter { type Value = RouteMatch; }
257
258impl Handler for Router {
259    fn handle(&self, req: &mut Request) -> IronResult<Response> {
260        let path = req.url.path().join("/");
261
262        self.handle_method(req, &path).unwrap_or_else(||
263            match req.method {
264                method::Options => Ok(self.handle_options(&path)),
265                // For HEAD, fall back to GET. Hyper ensures no response body is written.
266                method::Head => {
267                    req.method = method::Get;
268                    self.handle_method(req, &path).unwrap_or(
269                        Err(IronError::new(NoRoute, status::NotFound))
270                    )
271                }
272                _ => Err(IronError::new(NoRoute, status::NotFound))
273            }
274        )
275    }
276}
277
278impl BeforeMiddleware for MetaRouter {
279    fn before(&self, req: &mut Request) -> IronResult<()> {
280        let path = req.url.path().join("/");
281
282        match self.analyse_method(req, &path) {
283            None => Ok(()),
284            Some(ctx) => {
285                req.extensions.insert::<MetaRouter>(ctx);
286                Ok(())
287            }
288        }
289    }
290}
291
292/// The error thrown by router if there is no matching route,
293/// it is always accompanied by a NotFound response.
294#[derive(Debug, PartialEq, Eq)]
295pub struct NoRoute;
296
297impl fmt::Display for NoRoute {
298    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
299        f.write_str("No matching route found.")
300    }
301}
302
303impl Error for NoRoute {
304    fn description(&self) -> &str { "No Route" }
305}
306
307/// The error thrown by router if a request was redirected
308/// by adding or removing a trailing slash.
309#[derive(Debug, PartialEq, Eq)]
310pub struct TrailingSlash;
311
312impl fmt::Display for TrailingSlash {
313    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
314        f.write_str("The request had a trailing slash.")
315    }
316}
317
318impl Error for TrailingSlash {
319    fn description(&self) -> &str { "Trailing Slash" }
320}
321
322#[cfg(test)]
323mod test {
324    use super::Router;
325    use iron::{headers, method, status, Request, Response};
326
327    #[test]
328    fn test_route_id_correct() {
329        let mut router = Router::new();
330        router.post("/api/xyz", |_: &mut Request| {
331            Ok(Response::with((status::Ok, "")))
332        }, "a");
333
334        let mut router = Router::new();
335        router.post("/api/v2/xyz", |_: &mut Request| {
336            Ok(Response::with((status::Ok, "")))
337        }, "b");
338
339        let res = router.recognize(&method::Method::Post, "api/v2/xyz");
340        assert!(res.is_some());
341        let res = res.unwrap();
342        assert_eq!(res.route_id, "b");
343    }
344
345    #[test]
346    fn test_meta_route_id_correct() {
347        let mut router = Router::new();
348        router.get("/api/xyz", |_: &mut Request| {
349            Ok(Response::with((status::Ok, "")))
350        }, "a");
351
352        let mut router = Router::new();
353        router.get("/api/v2/xyz", |_: &mut Request| {
354            Ok(Response::with((status::Ok, "")))
355        }, "b");
356
357        let meta = router.to_meta();
358
359        let res = meta.recognize(&method::Method::Get, "api/v2/xyz");
360        assert!(res.is_some());
361        let res = res.unwrap();
362        assert_eq!(res.route_id, "b");
363    }
364
365    #[test]
366    fn test_handle_options_post() {
367        let mut router = Router::new();
368        router.post("/", |_: &mut Request| {
369            Ok(Response::with((status::Ok, "")))
370        }, "");
371        let resp = router.handle_options("/");
372        let headers = resp.headers.get::<headers::Allow>().unwrap();
373        let expected = headers::Allow(vec![method::Method::Post]);
374        assert_eq!(&expected, headers);
375    }
376
377    #[test]
378    fn test_handle_options_get_head() {
379        let mut router = Router::new();
380        router.get("/", |_: &mut Request| {
381            Ok(Response::with((status::Ok, "")))
382        }, "");
383        let resp = router.handle_options("/");
384        let headers = resp.headers.get::<headers::Allow>().unwrap();
385        let expected = headers::Allow(vec![method::Method::Get, method::Method::Head]);
386        assert_eq!(&expected, headers);
387    }
388
389    #[test]
390    fn test_handle_any_ok() {
391        let mut router = Router::new();
392        router.post("/post", |_: &mut Request| {
393            Ok(Response::with((status::Ok, "")))
394        }, "");
395        router.any("/post", |_: &mut Request| {
396            Ok(Response::with((status::Ok, "")))
397        }, "");
398        router.put("/post", |_: &mut Request| {
399            Ok(Response::with((status::Ok, "")))
400        }, "");
401        router.any("/get", |_: &mut Request| {
402            Ok(Response::with((status::Ok, "")))
403        }, "any");
404
405        assert!(router.recognize(&method::Get, "/post").is_some());
406        assert!(router.recognize(&method::Get, "/get").is_some());
407    }
408
409    #[test]
410    fn test_request() {
411        let mut router = Router::new();
412        router.post("/post", |_: &mut Request| {
413            Ok(Response::with((status::Ok, "")))
414        }, "");
415        router.get("/post", |_: &mut Request| {
416            Ok(Response::with((status::Ok, "")))
417        }, "");
418
419        assert!(router.recognize(&method::Post, "/post").is_some());
420        assert!(router.recognize(&method::Get, "/post").is_some());
421        assert!(router.recognize(&method::Put, "/post").is_none());
422        assert!(router.recognize(&method::Get, "/post/").is_none());
423    }
424
425    #[test]
426    fn test_not_found() {
427        let mut router = Router::new();
428        router.put("/put", |_: &mut Request| {
429            Ok(Response::with((status::Ok, "")))
430        }, "");
431        assert!(router.recognize(&method::Patch, "/patch").is_none());
432    }
433
434    #[test]
435    #[should_panic]
436    fn test_same_route_id() {
437        let mut router = Router::new();
438        router.put("/put", |_: &mut Request| {
439            Ok(Response::with((status::Ok, "")))
440        }, "my_route_id");
441        router.get("/get", |_: &mut Request| {
442            Ok(Response::with((status::Ok, "")))
443        }, "my_route_id");
444    }
445
446    #[test]
447    fn test_wildcard_regression() {
448        let mut router = Router::new();
449        router.options("*", |_: &mut Request| Ok(Response::with((status::Ok, ""))), "id1");
450        router.put("/upload/*filename", |_: &mut Request| Ok(Response::with((status::Ok, ""))), "id2");
451        assert!(router.recognize(&method::Options, "/foo").is_some());
452        assert!(router.recognize(&method::Put, "/foo").is_none());
453        assert!(router.recognize(&method::Put, "/upload/foo").is_some());
454    }
455}