ferrum_router/router/
mod.rs

1use std::collections::HashMap;
2use std::error::Error;
3use std::fmt;
4use std::sync::Arc;
5
6use ferrum::{Request, Response, Handler, FerrumResult, FerrumError};
7use ferrum::{header, Method, StatusCode};
8use ferrum::typemap::Key;
9
10use recognizer::{Glob, GlobTypes, Recognizer, Recognize, RouteMatch, Params};
11
12pub mod id;
13pub use self::id::*;
14
15pub struct RouterInner {
16    /// The routers, specialized by method.
17    pub routers: HashMap<Method, Vec<Arc<Recognizer>>>,
18
19    /// Routes that accept any method.
20    pub wildcard: Vec<Arc<Recognizer>>,
21
22    /// Used in URI generation.
23    pub route_ids: HashMap<Id, (String, Arc<Recognizer>)>,
24}
25
26/// `Router` provides an interface for creating complex routes as middleware
27/// for the Ferrum framework.
28pub struct Router {
29    inner: Arc<RouterInner>
30}
31
32impl Router {
33    /// Construct a new, empty `Router`.
34    ///
35    /// ```
36    /// use ferrum_router::Router;
37    /// let router = Router::new();
38    /// ```
39    pub fn new() -> Router {
40        Router {
41            inner: Arc::new(RouterInner {
42                routers: HashMap::new(),
43                wildcard: Vec::new(),
44                route_ids: HashMap::new(),
45            })
46        }
47    }
48
49    fn mut_inner(&mut self) -> &mut RouterInner {
50        Arc::get_mut(&mut self.inner).expect("Cannot modify router at this point.")
51    }
52
53    /// Add a new route to a `Router`, matching both a method and glob pattern.
54    ///
55    /// `route` supports glob patterns based on the rust regex and uses `{name}` (`{name: typename}`,
56    /// `{name: pattern}`) for matching storing named segment of the request url in the `Params`
57    /// object, which is stored in the request `extensions`.
58    ///
59    /// For instance, to route `Get` requests on any route matching
60    /// `/users/{userid:[0-9]+}/{friendid:[0-9]+}` and store `userid` and `friend` in
61    /// the exposed Params object:
62    ///
63    /// ```ignore
64    /// let mut router = Router::new();
65    /// router.route(Method::Get, "/users/{userid:[0-9]+}/{friendid:[0-9]+}", controller, "user_friend");
66    /// ```
67    ///
68    /// `route_id` is a optional unique name for your route, and is used when generating an URI with
69    /// `url_for`.
70    ///
71    /// The controller provided to route can be any `Handler`, which allows
72    /// extreme flexibility when handling routes. For instance, you could provide
73    /// a `Chain`, a `Handler`, which contains an authorization middleware and
74    /// a controller function, so that you can confirm that the request is
75    /// authorized for this route before handling it.
76    pub fn route<G, H, S, T>(&mut self, method: Method, glob: G, handler: H, route_id: Option<Id>) -> &mut Router
77        where G: Into<Glob<S, T>>,
78              H: Handler,
79              S: AsRef<[u8]>,
80              T: GlobTypes,
81    {
82        let glob = glob.into();
83        let types = glob.types().map(|types| types.store());
84        let recognizer = Arc::new(
85            Recognizer::new(glob.path(), Box::new(handler), types).unwrap()
86        );
87
88        if let Some(route_id) = route_id {
89            self.route_id(route_id, glob.path(), recognizer.clone());
90        }
91
92        self.mut_inner().routers
93            .entry(method)
94            .or_insert(Vec::new())
95            .push(recognizer);
96        self
97    }
98
99    fn route_id(&mut self, id: Id, glob_path: &[u8], recognizer: Arc<Recognizer>) {
100        let inner = self.mut_inner();
101        let ref mut route_ids = inner.route_ids;
102
103        match route_ids.get(&id) {
104            Some(&(ref other_glob_path, _)) if glob_path != other_glob_path.as_bytes() =>
105                panic!("Duplicate route_id: {}", id),
106            _ => ()
107        };
108
109        route_ids.insert(id, (String::from_utf8_lossy(glob_path).to_string(), recognizer));
110    }
111
112    /// Like route, but specialized to the `Get` method.
113    pub fn get<G, H, S, T>(&mut self, glob: G, handler: H, route_id: Option<Id>) -> &mut Router
114        where G: Into<Glob<S, T>>,
115              H: Handler,
116              S: AsRef<[u8]>,
117              T: GlobTypes,
118    {
119        self.route(Method::Get, glob, handler, route_id)
120    }
121
122    /// Like route, but specialized to the `Post` method.
123    pub fn post<G, H, S, T>(&mut self, glob: G, handler: H, route_id: Option<Id>) -> &mut Router
124        where G: Into<Glob<S, T>>,
125              H: Handler,
126              S: AsRef<[u8]>,
127              T: GlobTypes,
128    {
129        self.route(Method::Post, glob, handler, route_id)
130    }
131
132    /// Like route, but specialized to the `Put` method.
133    pub fn put<G, H, S, T>(&mut self, glob: G, handler: H, route_id: Option<Id>) -> &mut Router
134        where G: Into<Glob<S, T>>,
135              H: Handler,
136              S: AsRef<[u8]>,
137              T: GlobTypes,
138    {
139        self.route(Method::Put, glob, handler, route_id)
140    }
141
142    /// Like route, but specialized to the `Delete` method.
143    pub fn delete<G, H, S, T>(&mut self, glob: G, handler: H, route_id: Option<Id>) -> &mut Router
144        where G: Into<Glob<S, T>>,
145              H: Handler,
146              S: AsRef<[u8]>,
147              T: GlobTypes,
148    {
149        self.route(Method::Delete, glob, handler, route_id)
150    }
151
152    /// Like route, but specialized to the `Head` method.
153    pub fn head<G, H, S, T>(&mut self, glob: G, handler: H, route_id: Option<Id>) -> &mut Router
154        where G: Into<Glob<S, T>>,
155              H: Handler,
156              S: AsRef<[u8]>,
157              T: GlobTypes,
158    {
159        self.route(Method::Head, glob, handler, route_id)
160    }
161
162    /// Like route, but specialized to the `Patch` method.
163    pub fn patch<G, H, S, T>(&mut self, glob: G, handler: H, route_id: Option<Id>) -> &mut Router
164        where G: Into<Glob<S, T>>,
165              H: Handler,
166              S: AsRef<[u8]>,
167              T: GlobTypes,
168    {
169        self.route(Method::Patch, glob, handler, route_id)
170    }
171
172    /// Like route, but specialized to the `Options` method.
173    pub fn options<G, H, S, T>(&mut self, glob: G, handler: H, route_id: Option<Id>) -> &mut Router
174        where G: Into<Glob<S, T>>,
175              H: Handler,
176              S: AsRef<[u8]>,
177              T: GlobTypes,
178    {
179        self.route(Method::Options, glob, handler, route_id)
180    }
181
182    /// Route will match any method, including gibberish.
183    /// In case of ambiguity, handlers specific to methods will be preferred.
184    pub fn any<G, H, S, T>(&mut self, glob: G, handler: H, route_id: Option<Id>) -> &mut Router
185        where G: Into<Glob<S, T>>,
186              H: Handler,
187              S: AsRef<[u8]>,
188              T: GlobTypes,
189    {
190        let glob = glob.into();
191        let types = glob.types().map(|types| types.store());
192        let recognizer = Arc::new(
193            Recognizer::new(glob.path(), Box::new(handler), types).unwrap()
194        );
195
196        if let Some(route_id) = route_id {
197            self.route_id(route_id, glob.path(), recognizer.clone());
198        }
199
200        self.mut_inner().wildcard.push(recognizer);
201        self
202    }
203
204    fn recognize(&self, method: &Method, path: &str) -> Option<RouteMatch> {
205        self.inner.routers
206            .get(method)
207            .and_then(|recognizers| recognizers.recognize(path))
208            .or(self.inner.wildcard.recognize(path))
209    }
210
211    fn handle_options(&self, path: &str) -> Response {
212        static METHODS: &'static [Method] = &[
213            Method::Get,
214            Method::Post,
215            Method::Put,
216            Method::Delete,
217            Method::Head,
218            Method::Patch
219        ];
220
221        // Get all the available methods and return them.
222        let mut options = vec![];
223
224        for method in METHODS.iter() {
225            self.inner.routers.get(method).map(|recognizers| {
226                if let Some(_) = recognizers.recognize(path) {
227                    options.push(method.clone());
228                }
229            });
230        }
231        // If GET is there, HEAD is also there.
232        if options.contains(&Method::Get) && !options.contains(&Method::Head) {
233            options.push(Method::Head);
234        }
235
236        let mut response = Response::new().with_status(StatusCode::Ok);
237        response.headers.set(header::Allow(options));
238        response
239    }
240
241    fn handle_method(&self, request: &mut Request) -> Option<FerrumResult<Response>> {
242        if let Some(matched) = self.recognize(&request.method, request.uri.path()) {
243            request.extensions.insert::<Router>(matched.params);
244            request.extensions.insert::<RouterInner>(self.inner.clone());
245            Some(matched.handler.handle(request))
246        } else {
247            None
248        }
249    }
250}
251
252impl Key for Router {
253    type Value = Params;
254}
255
256impl Key for RouterInner {
257    type Value = Arc<RouterInner>;
258}
259
260impl Handler for Router {
261    fn handle(&self, request: &mut Request) -> FerrumResult<Response> {
262        self.handle_method(request).unwrap_or_else(||
263            match request.method {
264                Method::Options => Ok(self.handle_options(request.uri.path())),
265                // For HEAD, fall back to GET. Hyper ensures no response body is written.
266                Method::Head => {
267                    request.method = Method::Get;
268                    self.handle_method(request).unwrap_or(
269                        Err(
270                            FerrumError::new(
271                                NoRoute,
272                                Some(Response::new()
273                                    .with_status(StatusCode::NotFound))
274                            )
275                        )
276                    )
277                }
278                _ => Err(
279                    FerrumError::new(
280                        NoRoute,
281                        Some(Response::new()
282                            .with_status(StatusCode::NotFound))
283                    )
284                )
285            }
286        )
287    }
288}
289
290/// The error thrown by router if there is no matching route,
291/// it is always accompanied by a NotFound response.
292#[derive(Debug, PartialEq, Eq)]
293pub struct NoRoute;
294
295impl fmt::Display for NoRoute {
296    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
297        f.write_str("No matching route found.")
298    }
299}
300
301impl Error for NoRoute {
302    fn description(&self) -> &str { "No Route" }
303}
304
305#[cfg(test)]
306mod tests;