Skip to main content

darpi_route/
lib.rs

1use logos;
2use logos::Logos;
3use std::collections::HashMap;
4use std::convert::TryFrom;
5
6#[derive(Logos, Debug, PartialEq)]
7pub enum ReqToken<'a> {
8    // Tokens can be literal strings, of any length.
9    #[token("/")]
10    Slash,
11
12    // Or regular expressions.
13    #[regex("[a-zA-Z0-9[.]-_~!$&'()*+,;=:@]+")]
14    PathSegment(&'a str),
15    // Logos requires one token variant to handle errors,
16    // it can be named anything you wish.
17    #[error]
18    // We can also use this variant to define whitespace,
19    // or any other matches we wish to skip.
20    #[regex(r"[ \t\n\f]+", logos::skip)]
21    Error,
22}
23
24#[derive(Debug)]
25pub struct ReqRoute<'a> {
26    pub values: Vec<ReqToken<'a>>,
27}
28
29impl<'a> TryFrom<&'a str> for ReqRoute<'a> {
30    type Error = String;
31
32    fn try_from(s: &'a str) -> Result<Self, Self::Error> {
33        let mut lex = ReqToken::lexer(s);
34        let mut values: Vec<ReqToken<'a>> = vec![];
35
36        while let Some(next) = lex.next() {
37            match next {
38                ReqToken::Error => return Err("invalid ReqRoute".to_string()),
39                _ => values.push(next),
40            }
41        }
42
43        Ok(Self { values })
44    }
45}
46
47#[derive(Logos, Debug, PartialEq)]
48pub enum Token<'a> {
49    // Tokens can be literal strings, of any length.
50    #[token("/")]
51    Slash,
52
53    #[regex("[{]([^{}/]+)[}]")]
54    Arg(&'a str),
55
56    // Or regular expressions.
57    #[regex(r"[a-zA-Z0-9[.]-_~!$&'()*+,;=:@]+")]
58    PathSegment(&'a str),
59    // Logos requires one token variant to handle errors,
60    // it can be named anything you wish.
61    #[error]
62    // We can also use this variant to define whitespace,
63    // or any other matches we wish to skip.
64    #[regex(r"[ \t\n\f]+", logos::skip)]
65    Error,
66}
67
68#[derive(Debug)]
69pub struct Route<'a> {
70    pub values: Vec<Token<'a>>,
71}
72
73impl<'a> TryFrom<&'a str> for Route<'a> {
74    type Error = String;
75
76    fn try_from(s: &'a str) -> Result<Self, Self::Error> {
77        let mut lex = Token::lexer(s);
78        let mut values: Vec<Token<'a>> = vec![];
79
80        while let Some(next) = lex.next() {
81            match next {
82                Token::Error => return Err("invalid route".to_string()),
83                _ => values.push(next),
84            }
85        }
86
87        Ok(Self { values })
88    }
89}
90
91impl<'a> PartialEq<ReqRoute<'a>> for Route<'a> {
92    fn eq(&self, other: &ReqRoute) -> bool {
93        if self.values.len() != other.values.len() {
94            return false;
95        }
96
97        for (i, tt) in self.values.iter().enumerate() {
98            match (tt, &other.values[i]) {
99                (Token::PathSegment(left), ReqToken::PathSegment(right)) => {
100                    if left != right {
101                        return false;
102                    }
103                }
104                _ => {}
105            }
106        }
107
108        true
109    }
110}
111
112impl<'a> ReqRoute<'a> {
113    pub fn extract_args(&self, route: &Route<'a>) -> Result<HashMap<&'a str, &'a str>, String> {
114        if route != self {
115            return Err("routes are not matching".to_string());
116        }
117
118        let mut args = HashMap::new();
119        for (i, tok) in route.values.iter().enumerate() {
120            match (tok, &self.values[i]) {
121                (Token::Arg(key), ReqToken::PathSegment(value)) => {
122                    args.insert(&key[1..key.len() - 1], *value);
123                }
124                _ => {}
125            }
126        }
127        Ok(args)
128    }
129}
130
131#[test]
132fn route_to_string() {
133    let def_route = Route::try_from("/user/id/{article}").unwrap();
134    //let def_route = Route::try_from("/user/{id}/{article}").unwrap();
135    let req_route = ReqRoute::try_from("/user/id/1").unwrap();
136    assert_eq!(def_route, req_route);
137    let args = req_route.extract_args(&def_route).unwrap();
138    panic!("{:#?}", args);
139
140    // asd("/user/{name}");
141    // asd("/user/article");
142    // let mut v = vec![
143    //     "/user/{name}",
144    //     "/user/article",
145    //     "user/{id}/article",
146    //     "user/id/article",
147    //     "user/id/{article}",
148    // ];
149    // v.sort_by(|left, right| {
150    //     let left_matches: Vec<usize> = left.match_indices('{').map(|t| t.0).collect();
151    //     let left_count = left_matches.iter().fold(0, |acc, a| acc + a);
152    //
153    //     if left_matches.len() == 0 {
154    //         return Ordering::Less;
155    //     }
156    //
157    //     let right_matches: Vec<usize> = right.match_indices('{').map(|t| t.0).collect();
158    //     let right_count = right_matches.iter().fold(0, |acc, a| acc + a);
159    //
160    //     if right_matches.len() == 0 {
161    //         return Ordering::Greater;
162    //     }
163    //
164    //     if left_matches.len() + left_count > right_matches.len() + right_count {
165    //         return Ordering::Less;
166    //     }
167    //
168    //     Ordering::Greater
169    // });
170    //println!("{:#?}", v);
171}
172
173impl<'a> ToString for Route<'a> {
174    fn to_string(&self) -> String {
175        let mut s = String::new();
176
177        for tt in &self.values {
178            let fragment = match tt {
179                Token::Arg(s) => s,
180                Token::PathSegment(s) => s,
181                Token::Slash => "/",
182                _ => "",
183            };
184            s.push_str(fragment);
185        }
186        s
187    }
188}
189
190#[test]
191fn are_equal() {
192    let left: Route = Route::try_from("/user/{id}/{article}").unwrap();
193    let right: ReqRoute = ReqRoute::try_from("/user/1/2").unwrap();
194    assert_eq!(left, right);
195
196    let args = right.extract_args(&left).unwrap();
197    assert_eq!(args.len(), 2);
198    assert_eq!(args.get("id"), Some(&"1"));
199    assert_eq!(args.get("article"), Some(&"2"));
200
201    let left: Route = Route::try_from("/user/{id}/article/{article}").unwrap();
202    let right: ReqRoute = ReqRoute::try_from("/user/1/article/2").unwrap();
203    assert_eq!(left, right);
204
205    let args = right.extract_args(&left).unwrap();
206    assert_eq!(args.len(), 2);
207    assert_eq!(args.get("id"), Some(&"1"));
208    assert_eq!(args.get("article"), Some(&"2"));
209
210    let left: Route = Route::try_from("/user/{name}").unwrap();
211    let right: ReqRoute = ReqRoute::try_from("/user/petar-asd").unwrap();
212    assert_eq!(left, right);
213
214    let args = right.extract_args(&left).unwrap();
215    assert_eq!(args.len(), 1);
216    assert_eq!(args.get("name"), Some(&"petar-asd"));
217}
218
219#[test]
220fn are_not_equal() {
221    let left: Route = Route::try_from("/user/{id}/article/{article}").unwrap();
222    let right: ReqRoute = ReqRoute::try_from("/qwe-zxc").unwrap();
223    assert_ne!(left, right);
224
225    let args = right.extract_args(&left);
226    assert_eq!(args, Err("routes are not matching".to_string()));
227}