ruled_router/
formatter.rs1use crate::error::ParseError;
6use crate::parser::{PathParser, QueryParser};
7use crate::traits::ToParam;
8use crate::utils::{format_query_string, normalize_path};
9use std::collections::HashMap;
10
11#[derive(Debug, Clone)]
15pub struct PathFormatter {
16 parser: PathParser,
17}
18
19impl PathFormatter {
20 pub fn new(pattern: &str) -> Result<Self, ParseError> {
22 Ok(Self {
23 parser: PathParser::new(pattern)?,
24 })
25 }
26
27 pub fn format(&self, params: &HashMap<String, String>) -> Result<String, ParseError> {
52 self.parser.format_path(params)
53 }
54
55 pub fn format_typed<T: ToParam>(&self, typed_params: &HashMap<String, T>) -> Result<String, ParseError> {
65 let string_params: HashMap<String, String> = typed_params.iter().map(|(k, v)| (k.clone(), v.to_param())).collect();
66 self.format(&string_params)
67 }
68}
69
70#[derive(Debug, Clone, Default)]
74pub struct QueryFormatter {
75 params: HashMap<String, Vec<String>>,
76}
77
78impl QueryFormatter {
79 pub fn new() -> Self {
81 Self::default()
82 }
83
84 pub fn from_parser(parser: &QueryParser) -> Self {
86 Self {
87 params: parser.params().clone(),
88 }
89 }
90
91 pub fn set<T: ToParam>(&mut self, key: &str, value: T) -> &mut Self {
98 self.params.insert(key.to_string(), vec![value.to_param()]);
99 self
100 }
101
102 pub fn add<T: ToParam>(&mut self, key: &str, value: T) -> &mut Self {
109 self.params.entry(key.to_string()).or_default().push(value.to_param());
110 self
111 }
112
113 pub fn set_multiple<T: ToParam>(&mut self, key: &str, values: &[T]) -> &mut Self {
120 let string_values: Vec<String> = values.iter().map(|v| v.to_param()).collect();
121 self.params.insert(key.to_string(), string_values);
122 self
123 }
124
125 pub fn remove(&mut self, key: &str) -> &mut Self {
131 self.params.remove(key);
132 self
133 }
134
135 pub fn clear(&mut self) -> &mut Self {
137 self.params.clear();
138 self
139 }
140
141 pub fn format(&self) -> String {
162 format_query_string(&self.params)
163 }
164
165 pub fn format_with_prefix(&self) -> String {
171 let query = self.format();
172 if query.is_empty() {
173 String::new()
174 } else {
175 format!("?{query}")
176 }
177 }
178
179 pub fn is_empty(&self) -> bool {
181 self.params.is_empty()
182 }
183
184 pub fn len(&self) -> usize {
186 self.params.len()
187 }
188
189 pub fn params(&self) -> &HashMap<String, Vec<String>> {
191 &self.params
192 }
193}
194
195#[derive(Debug, Clone)]
199pub struct UrlFormatter {
200 path_formatter: PathFormatter,
201 query_formatter: QueryFormatter,
202}
203
204impl UrlFormatter {
205 pub fn new(path_pattern: &str) -> Result<Self, ParseError> {
211 Ok(Self {
212 path_formatter: PathFormatter::new(path_pattern)?,
213 query_formatter: QueryFormatter::new(),
214 })
215 }
216
217 pub fn path_formatter(&self) -> &PathFormatter {
219 &self.path_formatter
220 }
221
222 pub fn query_formatter_mut(&mut self) -> &mut QueryFormatter {
224 &mut self.query_formatter
225 }
226
227 pub fn query_formatter(&self) -> &QueryFormatter {
229 &self.query_formatter
230 }
231
232 pub fn format(&self, path_params: &HashMap<String, String>) -> Result<String, ParseError> {
263 let path = self.path_formatter.format(path_params)?;
264 let query = self.query_formatter.format_with_prefix();
265 Ok(format!("{}{}", normalize_path(&path), query))
266 }
267
268 pub fn format_typed<T: ToParam>(&self, path_params: &HashMap<String, T>) -> Result<String, ParseError> {
270 let path = self.path_formatter.format_typed(path_params)?;
271 let query = self.query_formatter.format_with_prefix();
272 Ok(format!("{}{}", normalize_path(&path), query))
273 }
274}
275
276#[cfg(test)]
280mod tests {
281 use super::*;
282 use std::collections::HashMap;
283
284 #[test]
285 fn test_path_formatter() {
286 let formatter = PathFormatter::new("/users/:id/posts/:post_id").unwrap();
287
288 let mut params = HashMap::new();
289 params.insert("id".to_string(), "123".to_string());
290 params.insert("post_id".to_string(), "456".to_string());
291
292 let path = formatter.format(¶ms).unwrap();
293 assert_eq!(path, "/users/123/posts/456");
294 }
295
296 #[test]
297 fn test_path_formatter_typed() {
298 let formatter = PathFormatter::new("/users/:id").unwrap();
299
300 let mut params = HashMap::new();
301 params.insert("id".to_string(), 123u32);
302
303 let path = formatter.format_typed(¶ms).unwrap();
304 assert_eq!(path, "/users/123");
305 }
306
307 #[test]
308 fn test_query_formatter() {
309 let mut formatter = QueryFormatter::new();
310
311 formatter.set("page", 1).set("size", 20).add("tags", "rust").add("tags", "web");
312
313 let query = formatter.format();
314
315 assert!(query.contains("page=1"));
317 assert!(query.contains("size=20"));
318 assert!(query.contains("tags=rust"));
319 assert!(query.contains("tags=web"));
320 }
321
322 #[test]
323 fn test_query_formatter_with_prefix() {
324 let mut formatter = QueryFormatter::new();
325 formatter.set("test", "value");
326
327 let query = formatter.format_with_prefix();
328 assert_eq!(query, "?test=value");
329
330 let empty_formatter = QueryFormatter::new();
332 let empty_query = empty_formatter.format_with_prefix();
333 assert_eq!(empty_query, "");
334 }
335
336 #[test]
337 fn test_query_formatter_multiple_values() {
338 let mut formatter = QueryFormatter::new();
339
340 formatter.set_multiple("colors", &["red", "green", "blue"]);
341
342 let query = formatter.format();
343 assert!(query.contains("colors=red"));
344 assert!(query.contains("colors=green"));
345 assert!(query.contains("colors=blue"));
346 }
347
348 #[test]
349 fn test_url_formatter() {
350 let mut formatter = UrlFormatter::new("/users/:id").unwrap();
351
352 formatter.query_formatter_mut().set("page", 1).set("size", 20);
353
354 let mut path_params = HashMap::new();
355 path_params.insert("id".to_string(), "123".to_string());
356
357 let url = formatter.format(&path_params).unwrap();
358
359 assert!(url.starts_with("/users/123?"));
360 assert!(url.contains("page=1"));
361 assert!(url.contains("size=20"));
362 }
363
364 #[test]
365 fn test_query_formatter_operations() {
366 let mut formatter = QueryFormatter::new();
367
368 formatter.set("key1", "value1").add("key2", "value2a").add("key2", "value2b");
370
371 assert_eq!(formatter.len(), 2);
372 assert!(!formatter.is_empty());
373
374 formatter.remove("key1");
376 assert_eq!(formatter.len(), 1);
377
378 formatter.clear();
380 assert_eq!(formatter.len(), 0);
381 assert!(formatter.is_empty());
382 }
383}