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}