1pub 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 pub fn new<'b>() -> Tree<'b, T> {
32 Tree {
33 label: "",
34 value: None,
35 branches: Vec::new()
36 }
37 }
38 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(); 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 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(); 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}