titan_router/
router.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
use std::collections::HashMap;

use crate::routekey::{FindSegmentResult, Segments};

#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)]
pub(crate) struct RouteId(usize);

#[derive(Clone)]
pub struct Router<V>
where
  V: Clone,
{
  routes: Vec<(Segments, V)>,
  lookup_cache: HashMap<Segments, RouteId>,
}

impl<V> Default for Router<V>
where
  V: Clone,
{
  fn default() -> Self {
    Self { routes: Vec::default(), lookup_cache: HashMap::default() }
  }
}

#[derive(Debug, PartialEq)]
pub struct Match<V> {
  pub value: V,
  pub params: HashMap<String, String>,
}

impl<V> Router<V>
where
  V: Clone,
{
  pub fn at(&mut self, route: &str, handler: V) {
    let segments: Segments = route.to_string().into();

    let index = self.routes.len(); // (self.routes.len() - 1) + 1

    let route_id = RouteId(index);

    self.routes.push((segments.clone(), handler));

    if segments.num_params() == 0 {
      self.lookup_cache.insert(segments, route_id);
    }
  }

  pub fn find(&mut self, route: &str) -> Option<Match<&V>> {
    let segments = Segments::from(route.to_string());

    if let Some(RouteId(route_index)) = self.lookup_cache.get(&segments) {
      let (_, value) = &self.routes[*route_index];
      return Some(Match { value, params: HashMap::default() });
    };

    for (index, (key, value)) in self.routes.iter().enumerate() {
      if let FindSegmentResult::Match(params) = key.find(&segments.0) {
        if params.is_empty() {
          self.lookup_cache.insert(segments, RouteId(index));
        }
        return Some(Match { value, params });
      };
    }
    None
  }

  pub fn lookup(&self, route: &str) -> Option<Match<&V>> {
    let from_request = Segments::from(route.to_string());

    if let Some(RouteId(route_index)) = self.lookup_cache.get(&from_request) {
      let (_, value) = &self.routes[*route_index];
      return Some(Match { value, params: HashMap::default() });
    };

    for (contract, value) in &self.routes {
      if let FindSegmentResult::Match(params) = contract.find(&from_request.0) {
        return Some(Match { value, params });
      };
    }
    None
  }
  pub fn lookup_mut(&mut self, route: &str) -> Option<Match<&mut V>> {
    let segments = Segments::from(route.to_string());

    if let Some(RouteId(route_index)) = self.lookup_cache.get_mut(&segments) {
      let (_, value) = &mut self.routes[*route_index];
      return Some(Match { value, params: HashMap::default() });
    };

    for (index, (key, value)) in self.routes.iter_mut().enumerate() {
      if let FindSegmentResult::Match(params) = key.find(&segments.0) {
        if params.is_empty() {
          self.lookup_cache.insert(segments, RouteId(index));
        }
        return Some(Match { value, params });
      };
    }
    None
  }
}

#[cfg(test)]
mod lib_tests {
  use super::*;
  #[test]
  fn test() {
    let mut router = Router::default();

    let nice = "nice".to_string();

    router.at("/test", nice.clone());
    assert_eq!(router.lookup("/te"), None); // BIG PROBLEM FATAL TODO:
    router.at("/test/:var/:fdshj", nice.clone());
    assert!(router.lookup("/test/test").is_none()); // BIG PROBLEM FATAL TODO:
    router.at("/test/:var", nice.clone());
    router.at("/test2", nice.clone());

    router.at("/nice", "nice2".to_string());

    assert!(router.lookup("/ni").is_none()); // BIG PROBLEM FATAL TODO:
  }
}