ruled_router/
formatter.rs

1//! 路由格式化器
2//!
3//! 提供将结构化数据格式化为路径和查询字符串的功能
4
5use 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/// 路径格式化器
12///
13/// 用于将路由结构体格式化为 URL 路径
14#[derive(Debug, Clone)]
15pub struct PathFormatter {
16  parser: PathParser,
17}
18
19impl PathFormatter {
20  /// 创建新的路径格式化器
21  pub fn new(pattern: &str) -> Result<Self, ParseError> {
22    Ok(Self {
23      parser: PathParser::new(pattern)?,
24    })
25  }
26
27  /// 格式化路径
28  ///
29  /// # 参数
30  ///
31  /// * `params` - 路径参数映射
32  ///
33  /// # 返回值
34  ///
35  /// 格式化后的路径字符串
36  ///
37  /// # 示例
38  ///
39  /// ```rust
40  /// use ruled_router::formatter::PathFormatter;
41  /// use std::collections::HashMap;
42  ///
43  /// let formatter = PathFormatter::new("/users/:id/posts/:post_id").unwrap();
44  /// let mut params = HashMap::new();
45  /// params.insert("id".to_string(), "123".to_string());
46  /// params.insert("post_id".to_string(), "456".to_string());
47  ///
48  /// let path = formatter.format(&params).unwrap();
49  /// assert_eq!(path, "/users/123/posts/456");
50  /// ```
51  pub fn format(&self, params: &HashMap<String, String>) -> Result<String, ParseError> {
52    self.parser.format_path(params)
53  }
54
55  /// 格式化路径(使用类型安全的参数)
56  ///
57  /// # 参数
58  ///
59  /// * `typed_params` - 类型化的参数映射
60  ///
61  /// # 返回值
62  ///
63  /// 格式化后的路径字符串
64  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/// 查询格式化器
71///
72/// 用于将查询结构体格式化为查询字符串
73#[derive(Debug, Clone, Default)]
74pub struct QueryFormatter {
75  params: HashMap<String, Vec<String>>,
76}
77
78impl QueryFormatter {
79  /// 创建新的查询格式化器
80  pub fn new() -> Self {
81    Self::default()
82  }
83
84  /// 从查询解析器创建格式化器
85  pub fn from_parser(parser: &QueryParser) -> Self {
86    Self {
87      params: parser.params().clone(),
88    }
89  }
90
91  /// 设置参数值
92  ///
93  /// # 参数
94  ///
95  /// * `key` - 参数名
96  /// * `value` - 参数值
97  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  /// 添加参数值(支持多值)
103  ///
104  /// # 参数
105  ///
106  /// * `key` - 参数名
107  /// * `value` - 参数值
108  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  /// 设置多个值
114  ///
115  /// # 参数
116  ///
117  /// * `key` - 参数名
118  /// * `values` - 参数值列表
119  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  /// 移除参数
126  ///
127  /// # 参数
128  ///
129  /// * `key` - 参数名
130  pub fn remove(&mut self, key: &str) -> &mut Self {
131    self.params.remove(key);
132    self
133  }
134
135  /// 清空所有参数
136  pub fn clear(&mut self) -> &mut Self {
137    self.params.clear();
138    self
139  }
140
141  /// 格式化为查询字符串
142  ///
143  /// # 返回值
144  ///
145  /// 格式化后的查询字符串(不包含 '?' 前缀)
146  ///
147  /// # 示例
148  ///
149  /// ```rust
150  /// use ruled_router::formatter::QueryFormatter;
151  ///
152  /// let mut formatter = QueryFormatter::new();
153  /// formatter.set("page", 1)
154  ///          .set("size", 20)
155  ///          .add("tags", "rust")
156  ///          .add("tags", "web");
157  ///
158  /// let query = formatter.format();
159  /// // 结果类似: "page=1&size=20&tags=rust&tags=web"
160  /// ```
161  pub fn format(&self) -> String {
162    format_query_string(&self.params)
163  }
164
165  /// 格式化为完整的查询字符串(包含 '?' 前缀)
166  ///
167  /// # 返回值
168  ///
169  /// 格式化后的查询字符串,如果没有参数则返回空字符串
170  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  /// 检查是否为空
180  pub fn is_empty(&self) -> bool {
181    self.params.is_empty()
182  }
183
184  /// 获取参数数量
185  pub fn len(&self) -> usize {
186    self.params.len()
187  }
188
189  /// 获取所有参数的引用
190  pub fn params(&self) -> &HashMap<String, Vec<String>> {
191    &self.params
192  }
193}
194
195/// URL 格式化器
196///
197/// 组合路径和查询参数格式化功能
198#[derive(Debug, Clone)]
199pub struct UrlFormatter {
200  path_formatter: PathFormatter,
201  query_formatter: QueryFormatter,
202}
203
204impl UrlFormatter {
205  /// 创建新的 URL 格式化器
206  ///
207  /// # 参数
208  ///
209  /// * `path_pattern` - 路径模式
210  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  /// 获取路径格式化器的可变引用
218  pub fn path_formatter(&self) -> &PathFormatter {
219    &self.path_formatter
220  }
221
222  /// 获取查询格式化器的可变引用
223  pub fn query_formatter_mut(&mut self) -> &mut QueryFormatter {
224    &mut self.query_formatter
225  }
226
227  /// 获取查询格式化器的引用
228  pub fn query_formatter(&self) -> &QueryFormatter {
229    &self.query_formatter
230  }
231
232  /// 格式化完整的 URL
233  ///
234  /// # 参数
235  ///
236  /// * `path_params` - 路径参数
237  ///
238  /// # 返回值
239  ///
240  /// 完整的 URL 字符串
241  ///
242  /// # 示例
243  ///
244  /// ```rust
245  /// use ruled_router::formatter::UrlFormatter;
246  /// use std::collections::HashMap;
247  ///
248  /// let mut formatter = UrlFormatter::new("/users/:id").unwrap();
249  ///
250  /// // 设置查询参数
251  /// formatter.query_formatter_mut()
252  ///          .set("page", 1)
253  ///          .set("size", 20);
254  ///
255  /// // 设置路径参数
256  /// let mut path_params = HashMap::new();
257  /// path_params.insert("id".to_string(), "123".to_string());
258  ///
259  /// let url = formatter.format(&path_params).unwrap();
260  /// // 结果: "/users/123?page=1&size=20"
261  /// ```
262  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  /// 格式化完整的 URL(使用类型安全的路径参数)
269  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// 注意:RouterFormatter 和 QueryFormatter_ trait 将在宏实现时提供
277// 这些 trait 需要访问结构体的内部字段,所以会通过派生宏自动生成
278
279#[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(&params).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(&params).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    // 查询参数的顺序可能不同,所以我们检查包含关系
316    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    // 测试空查询
331    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    // 测试设置和添加
369    formatter.set("key1", "value1").add("key2", "value2a").add("key2", "value2b");
370
371    assert_eq!(formatter.len(), 2);
372    assert!(!formatter.is_empty());
373
374    // 测试移除
375    formatter.remove("key1");
376    assert_eq!(formatter.len(), 1);
377
378    // 测试清空
379    formatter.clear();
380    assert_eq!(formatter.len(), 0);
381    assert!(formatter.is_empty());
382  }
383}