path_router/
lib.rs

1//! Routing for paths delimited by a forward slash, with the ability to capture
2//! specified path segments.
3//!
4//! # Example
5//!
6//! ```rust
7//! use path_router::Tree;
8//! let mut routes = Tree::new();
9//! routes.add("GET/user/:username/profile", "profile.html");
10//! assert_eq!(
11//!     routes.find("GET/user/my_name/profile"),
12//!     Some((&"profile.html", vec![("username", "my_name")])));
13//! ```
14
15/// The routing information is stored as a trie.
16///
17/// # Description
18///
19/// Each node is labelled with its path segment. The value is a tuple. The first
20/// element is generic, and is usually a handler function; the second element is
21/// the captured path segments.
22///
23pub struct Tree<'a, T> {
24    label: &'a str,
25    value: Option<(T, Vec<&'a str>)>,
26    branches: Vec<Tree<'a, T>>
27}
28
29impl<'a, T> Tree<'a, T> {
30    /// Constructs a new routing tree.
31    pub fn new<'b>() -> Tree<'b, T> {
32        Tree {
33            label: "",
34            value: None,
35            branches: Vec::new()
36        }
37    }
38    /// Adds a new path and its associated value to the tree. Prefix a segment
39    /// with a colon (:) to enable capturing on the segment.
40    ///
41    /// # Panics
42    ///
43    /// Panics if a duplicate route is added.
44    ///
45    pub fn add(&mut self, key: &'a str, value: T) {
46        let segments = key.split('/').filter(|s| !s.is_empty());
47        let capture_labels = Vec::new();    // Will be filled while iterating
48        self.add_(segments, value, capture_labels);
49    }
50    fn add_<I: Iterator<Item=&'a str>>(
51        &mut self, mut segments: I, value: T,
52        mut capture_labels: Vec<&'a str>) {
53        match segments.next() {
54            None => {
55                if self.value.is_some() {
56                    panic!("Duplicate route!");
57                }
58                self.value = Some((value, capture_labels))
59            },
60            Some(segment) => {
61                if let Some(existing_branch) =
62                    self.branches.iter_mut().find(|t| t.label == segment) {
63                        existing_branch.add_(segments, value, capture_labels);
64                        return;
65                    }
66                if segment.starts_with(':') {
67                    capture_labels.push(&segment[1..]);
68                    if let Some(existing_branch) =
69                        self.branches.iter_mut().find(|t| t.label.is_empty()) {
70                            existing_branch.add_(
71                                segments, value, capture_labels);
72                            return;
73                        }
74                    let mut branch = Tree {
75                        label: "",
76                        value: None,
77                        branches: Vec::new()
78                    };
79                    branch.add_(segments, value, capture_labels);
80                    self.branches.push(branch);
81                } else {
82                    let mut branch = Tree {
83                        label: segment,
84                        value: None,
85                        branches: Vec::new()
86                    };
87                    branch.add_(segments, value, capture_labels);
88                    self.branches.push(branch);
89                }
90            }
91        }
92    }
93    /// Retrieve the value associated with the path, together with the captured
94    /// path segments.
95    pub fn find<'b>(
96        &self,
97        key: &'b str
98        ) -> Option<(&T, Vec<(&'a str, &'b str)>)> {
99        let segments: Vec<&str> = key.split('/')
100            .filter(|s| !s.is_empty())
101            .collect();
102        let mut captured = Vec::new();  // Will be filled while iterating
103        self.find_(segments.as_slice(), &mut captured)
104            .map(|&(ref v, ref labels)| {
105                (v, labels.iter().cloned().zip(captured).collect())
106            })
107    }
108    fn find_<'b>(
109        &self,
110        segments: &[&'b str],
111        captured: &mut Vec<&'b str>
112        ) -> Option<&(T, Vec<&'a str>)> {
113        match segments.split_first() {
114            None => self.value.as_ref(),
115            Some((&segment, remaining)) => self.branches.iter().filter_map(|t| {
116                if t.label == segment {
117                    t.find_(remaining, captured)
118                } else if t.label == "" {
119                    captured.push(segment);
120                    let result = t.find_(remaining, captured);
121                    if result.is_none() {
122                        captured.pop();
123                    }
124                    result
125                } else {
126                    None
127                }
128            }).next()
129        }
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use Tree;
136    #[test]
137    fn can_add_and_find() {
138        let mut routes = Tree::new();
139        routes.add("/", 0);
140        routes.add("/var", 1);
141        routes.add("/var/www", 11);
142        routes.add("/var/log", 12);
143        assert_eq!(routes.find("/vax"), None);
144        assert_eq!(routes.find("/var/something"), None);
145        assert_eq!(
146            routes.find("////"),
147            Some((&0, vec![])));
148        assert_eq!(
149            routes.find("//var//"),
150            Some((&1, vec![])));
151        assert_eq!(
152            routes.find("/var/www/"),
153            Some((&11, vec![])));
154        assert_eq!(
155            routes.find("/var/log/"),
156            Some((&12, vec![])));
157    }
158    #[test]
159    fn can_add_and_capture_and_find() {
160        let mut routes = Tree::new();
161        routes.add("/user/:username", 11);
162        routes.add("/user/:username/:intent/show", 111);
163        routes.add("/user/:username/profile", 112);
164        assert_eq!(routes.find("/user/myname/delete"), None);
165        assert_eq!(routes.find("/user/myname/cook/throw"), None);
166        assert_eq!(
167            routes.find("/user/myname"),
168            Some((&11, vec![("username", "myname")])));
169        assert_eq!(
170            routes.find("/user/myname/profile"),
171            Some((&112, vec![("username", "myname")])));
172        assert_eq!(
173            routes.find("/user/myname/cook/show"),
174            Some((&111, vec![
175                  ("username", "myname"),
176                  ("intent", "cook")
177            ])));
178    }
179    #[test]
180    fn can_add_and_capture_and_find_handlers() {
181        let mut routes = Tree::new();
182        let handler = |captured: Vec<(&str, &str)>| {
183            assert_eq!(captured.len(), 2);
184            assert_eq!(captured[0].0, "folder");
185            assert_eq!(captured[0].1, "myfolder");
186            assert_eq!(captured[1].0, "file");
187            assert_eq!(captured[1].1, "myfile");
188        };
189        routes.add("home/:folder/:file", handler);
190        match routes.find("/home/myfolder/myfile") {
191            None => assert!(false),
192            Some((fx, captured)) => fx(captured)
193        }
194    }
195}