summaryrefslogtreecommitdiff
path: root/src/router.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/router.rs')
-rw-r--r--src/router.rs162
1 files changed, 162 insertions, 0 deletions
diff --git a/src/router.rs b/src/router.rs
new file mode 100644
index 0000000..18ed429
--- /dev/null
+++ b/src/router.rs
@@ -0,0 +1,162 @@
+use std::collections::VecDeque;
+
+#[derive(Clone, Copy)]
+enum TablePointer<R> where R: Clone + Copy {
+ Link(usize),
+ Route(R),
+ RouteWithLink(usize, R),
+ NotFound,
+}
+
+pub struct NoMatchingRoute;
+
+pub type RouteMatch<'a, R> = Result<(R, &'a str), NoMatchingRoute>;
+
+#[derive(Clone)]
+pub struct RoutingTable<R> where R: Copy {
+ tables: Vec<[TablePointer<R>; 128]>,
+}
+
+impl<R> RoutingTable<R> where R: Copy {
+ pub fn new() -> RoutingTable<R> {
+ let zero_table = [TablePointer::NotFound; 128];
+ RoutingTable {
+ tables: vec![zero_table],
+ }
+ }
+
+ pub fn insert(&mut self, path: &str, route: R) {
+ assert!(!path.is_empty());
+ let mut index: usize = 0;
+ let p: Vec<usize> = path.as_bytes().iter().map(|i| usize::from(*i)).collect();
+ let mut path_queue = VecDeque::from(p);
+
+ while path_queue.len() > 1 {
+ let ch = path_queue.pop_front().unwrap() as usize % 128;
+ match self.tables[index][ch] {
+ TablePointer::NotFound => {
+ self.tables.push([TablePointer::NotFound; 128]);
+ let i_other_table = self.tables.len() - 1;
+ self.tables[index][ch] = TablePointer::Link(i_other_table);
+ path_queue.push_front(ch);
+ }
+ TablePointer::Link(i_other_table) => {
+ index = i_other_table as usize;
+ }
+ TablePointer::RouteWithLink(i_other_table, _) => {
+ index = i_other_table as usize;
+ path_queue.push_front(ch);
+ }
+ TablePointer::Route(route) => {
+ self.tables.push([TablePointer::NotFound; 128]);
+ let i_other_table = self.tables.len() - 1;
+ self.tables[index][ch] = TablePointer::RouteWithLink(i_other_table, route);
+ path_queue.push_front(ch);
+ }
+ }
+ }
+ // last character element in path
+ let ch = path_queue.pop_front().unwrap() % 128;
+ match self.tables[index][ch] {
+ TablePointer::NotFound => {
+ // slot is empty, just place the Route
+ self.tables[index][ch] = TablePointer::Route(route)
+ }
+ TablePointer::Link(i) => {
+ // slot is filled with link to longer path. Replace by RouteWithLink
+ self.tables[index][ch] = TablePointer::RouteWithLink(i, route);
+ }
+ _ => {
+ panic!("Not expected here, maybe duplicate")
+ }
+ }
+ }
+
+ pub fn match_path<'a>(&self, path: &'a str) -> RouteMatch<'a, R> {
+ let mut table: &[TablePointer<R>; 128] = &self.tables[0];
+ let path_bytes = path.as_bytes();
+ let path_max_i = path_bytes.len() - 1;
+
+ let mut i = 0;
+ while i <= path_max_i {
+ let ch = path_bytes[i] as usize % 128;
+ match table[ch] {
+ TablePointer::NotFound => {
+ return Err(NoMatchingRoute);
+ }
+ TablePointer::Link(i_other_table) => {
+ table = &self.tables[i_other_table];
+ i += 1;
+ }
+ TablePointer::RouteWithLink(i_other_table, route) => {
+ if i == path_max_i {
+ return Ok((route, Default::default()));
+ } else {
+ table = &self.tables[i_other_table];
+ }
+ }
+ TablePointer::Route(route) => {
+ return Ok((route, path.get(i + 1..=path_max_i).unwrap()));
+ }
+ }
+ }
+ Err(NoMatchingRoute)
+ }
+}
+
+#[cfg(test)]
+mod test1 {
+ use super::*;
+ use test::Bencher;
+
+ #[derive(Clone, Copy)]
+ enum Route {
+ Login,
+ Logout,
+ Info,
+ Check
+ }
+
+ #[bench]
+ fn bench_1(b: &mut Bencher) {
+ let mut r = RoutingTable::new();
+ r.insert("/login", Route::Login);
+ r.insert("/info", Route::Info);
+ r.insert("/logout", Route::Logout);
+ r.insert("/logout2", Route::Info);
+ r.insert("/check", Route::Check);
+
+ match r.tables[0][47] {
+ TablePointer::Link(n) => assert!(n == 1),
+ _ => panic!("Wrong"),
+ }
+
+ b.iter(|| {
+ match r.match_path("/login") {
+ RouteMatch::Match(Route::Login, rest) => assert_eq!(rest, ""),
+ _ => panic!("Wrong")
+ }
+ match r.match_path("/logout") {
+ RouteMatch::Match(Route::Logout, rest) => assert_eq!(rest, ""),
+ _ => panic!("Wrong")
+ }
+ match r.match_path("/info") {
+ RouteMatch::Match(Route::Info, rest) => assert_eq!(rest, ""),
+ _ => panic!("Wrong")
+ }
+ match r.match_path("/logout2") {
+ RouteMatch::Match(Route::Info, rest) => assert_eq!(rest, ""),
+ _ => panic!("Wrong")
+ }
+ match r.match_path("/asdasdasd") {
+ RouteMatch::None => (),
+ _ => panic!("Wrong")
+ }
+
+ match r.match_path("/login/foo/bar") {
+ RouteMatch::Match(Route::Login, rest) => assert_eq!(rest, "/foo/bar"),
+ _ => panic!("Wrong")
+ }
+ })
+ }
+} \ No newline at end of file