1use logos;
2use logos::Logos;
3use std::collections::HashMap;
4use std::convert::TryFrom;
5
6#[derive(Logos, Debug, PartialEq)]
7pub enum ReqToken<'a> {
8 #[token("/")]
10 Slash,
11
12 #[regex("[a-zA-Z0-9[.]-_~!$&'()*+,;=:@]+")]
14 PathSegment(&'a str),
15 #[error]
18 #[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 #[token("/")]
51 Slash,
52
53 #[regex("[{]([^{}/]+)[}]")]
54 Arg(&'a str),
55
56 #[regex(r"[a-zA-Z0-9[.]-_~!$&'()*+,;=:@]+")]
58 PathSegment(&'a str),
59 #[error]
62 #[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 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 }
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}