Skip to main content

ruled_router/parser/
types.rs

1//! 类型转换实现
2//!
3//! 为基本类型实现 FromParam 和 ToParam trait
4
5use crate::error::ParseError;
6use crate::traits::{FromParam, ToParam};
7
8/// 为基本数字类型实现 FromParam 和 ToParam
9macro_rules! impl_from_to_param_for_numbers {
10    ($($t:ty),*) => {
11        $(
12            impl FromParam for $t {
13                fn from_param(param: &str) -> Result<Self, ParseError> {
14                    param.parse().map_err(|_| {
15                        ParseError::type_conversion(format!(
16                            "Cannot convert '{}' to {}", param, stringify!($t)
17                        ))
18                    })
19                }
20            }
21
22            impl ToParam for $t {
23                fn to_param(&self) -> String {
24                    self.to_string()
25                }
26            }
27        )*
28    };
29}
30
31// 实现所有基本数字类型
32impl_from_to_param_for_numbers!(u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, f32, f64);
33
34/// String 的实现
35impl FromParam for String {
36  fn from_param(param: &str) -> Result<Self, ParseError> {
37    Ok(param.to_string())
38  }
39}
40
41impl ToParam for String {
42  fn to_param(&self) -> String {
43    self.clone()
44  }
45}
46
47/// &str 的实现
48impl ToParam for &str {
49  fn to_param(&self) -> String {
50    self.to_string()
51  }
52}
53
54/// bool 的实现
55impl FromParam for bool {
56  fn from_param(param: &str) -> Result<Self, ParseError> {
57    match param.to_lowercase().as_str() {
58      "true" | "1" | "yes" | "on" => Ok(true),
59      "false" | "0" | "no" | "off" => Ok(false),
60      _ => Err(ParseError::type_conversion(format!(
61        "Cannot convert '{param}' to bool. Valid values: true/false, 1/0, yes/no, on/off"
62      ))),
63    }
64  }
65}
66
67impl ToParam for bool {
68  fn to_param(&self) -> String {
69    self.to_string()
70  }
71}
72
73/// Option<T> 的实现
74impl<T: FromParam> FromParam for Option<T> {
75  fn from_param(param: &str) -> Result<Self, ParseError> {
76    if param.is_empty() {
77      Ok(None)
78    } else {
79      T::from_param(param).map(Some)
80    }
81  }
82}
83
84impl<T: ToParam> ToParam for Option<T> {
85  fn to_param(&self) -> String {
86    match self {
87      Some(value) => value.to_param(),
88      None => String::new(),
89    }
90  }
91}
92
93/// Vec<T> 的实现(用于多值参数)
94impl<T: FromParam> FromParam for Vec<T> {
95  fn from_param(param: &str) -> Result<Self, ParseError> {
96    if param.is_empty() {
97      Ok(Vec::new())
98    } else {
99      // 假设多个值用逗号分隔
100      param.split(',').map(|s| T::from_param(s.trim())).collect()
101    }
102  }
103}
104
105impl<T: ToParam> ToParam for Vec<T> {
106  fn to_param(&self) -> String {
107    self.iter().map(|item| item.to_param()).collect::<Vec<_>>().join(",")
108  }
109}
110
111/// char 的实现
112impl FromParam for char {
113  fn from_param(param: &str) -> Result<Self, ParseError> {
114    let mut chars = param.chars();
115    match (chars.next(), chars.next()) {
116      (Some(c), None) => Ok(c),
117      _ => Err(ParseError::type_conversion(format!(
118        "Cannot convert '{param}' to char. Expected exactly one character"
119      ))),
120    }
121  }
122}
123
124impl ToParam for char {
125  fn to_param(&self) -> String {
126    self.to_string()
127  }
128}
129
130/// 自定义类型示例:UserId
131///
132/// 展示如何为自定义类型实现 FromParam 和 ToParam
133#[derive(Debug, Clone, PartialEq, Eq)]
134pub struct UserId(pub u32);
135
136impl FromParam for UserId {
137  fn from_param(param: &str) -> Result<Self, ParseError> {
138    u32::from_param(param).map(UserId)
139  }
140}
141
142impl ToParam for UserId {
143  fn to_param(&self) -> String {
144    self.0.to_string()
145  }
146}
147
148/// 自定义类型示例:Slug
149///
150/// URL 友好的字符串标识符
151#[derive(Debug, Clone, PartialEq, Eq)]
152pub struct Slug(pub String);
153
154impl FromParam for Slug {
155  fn from_param(param: &str) -> Result<Self, ParseError> {
156    // 验证 slug 格式:只允许字母、数字、连字符和下划线
157    if param.chars().all(|c| c.is_alphanumeric() || c == '-' || c == '_') {
158      Ok(Slug(param.to_string()))
159    } else {
160      Err(ParseError::type_conversion(format!(
161        "Invalid slug '{param}'. Slugs can only contain letters, numbers, hyphens, and underscores"
162      )))
163    }
164  }
165}
166
167impl ToParam for Slug {
168  fn to_param(&self) -> String {
169    self.0.clone()
170  }
171}
172
173/// 自定义类型示例:Email
174///
175/// 简单的邮箱地址验证
176#[derive(Debug, Clone, PartialEq, Eq)]
177pub struct Email(pub String);
178
179impl FromParam for Email {
180  fn from_param(param: &str) -> Result<Self, ParseError> {
181    // 简单的邮箱验证:包含 @ 符号且两边都有内容
182    if param.contains('@') && param.split('@').count() == 2 {
183      let parts: Vec<&str> = param.split('@').collect();
184      if !parts[0].is_empty() && !parts[1].is_empty() && parts[1].contains('.') {
185        return Ok(Email(param.to_string()));
186      }
187    }
188    Err(ParseError::type_conversion(format!("Invalid email address: {param}")))
189  }
190}
191
192impl ToParam for Email {
193  fn to_param(&self) -> String {
194    self.0.clone()
195  }
196}
197
198/// 自定义类型示例:Version
199///
200/// 语义化版本号
201#[derive(Debug, Clone, PartialEq, Eq)]
202pub struct Version {
203  pub major: u32,
204  pub minor: u32,
205  pub patch: u32,
206}
207
208impl FromParam for Version {
209  fn from_param(param: &str) -> Result<Self, ParseError> {
210    let parts: Vec<&str> = param.split('.').collect();
211    if parts.len() != 3 {
212      return Err(ParseError::type_conversion(format!(
213        "Invalid version format '{param}'. Expected format: major.minor.patch"
214      )));
215    }
216
217    let major = parts[0]
218      .parse()
219      .map_err(|_| ParseError::type_conversion(format!("Invalid major version: {}", parts[0])))?;
220
221    let minor = parts[1]
222      .parse()
223      .map_err(|_| ParseError::type_conversion(format!("Invalid minor version: {}", parts[1])))?;
224
225    let patch = parts[2]
226      .parse()
227      .map_err(|_| ParseError::type_conversion(format!("Invalid patch version: {}", parts[2])))?;
228
229    Ok(Version { major, minor, patch })
230  }
231}
232
233impl ToParam for Version {
234  fn to_param(&self) -> String {
235    format!("{}.{}.{}", self.major, self.minor, self.patch)
236  }
237}
238
239#[cfg(test)]
240mod tests {
241  use super::*;
242
243  #[test]
244  fn test_number_conversions() {
245    assert_eq!(u32::from_param("123").unwrap(), 123);
246    assert_eq!(i32::from_param("-456").unwrap(), -456);
247    assert_eq!(f64::from_param(&std::f64::consts::PI.to_string()).unwrap(), std::f64::consts::PI);
248
249    assert!(u32::from_param("abc").is_err());
250    assert!(i32::from_param("12.34").is_err());
251
252    assert_eq!(123u32.to_param(), "123");
253    assert_eq!((-456i32).to_param(), "-456");
254    assert_eq!(std::f64::consts::PI.to_param(), "3.141592653589793");
255  }
256
257  #[test]
258  fn test_string_conversions() {
259    assert_eq!(String::from_param("hello").unwrap(), "hello");
260    assert_eq!("world".to_param(), "world");
261    assert_eq!("test".to_string().to_param(), "test");
262  }
263
264  #[test]
265  fn test_bool_conversions() {
266    assert!(bool::from_param("true").unwrap());
267    assert!(!bool::from_param("false").unwrap());
268    assert!(bool::from_param("1").unwrap());
269    assert!(!bool::from_param("0").unwrap());
270    assert!(bool::from_param("yes").unwrap());
271    assert!(!bool::from_param("no").unwrap());
272    assert!(bool::from_param("on").unwrap());
273    assert!(!bool::from_param("off").unwrap());
274
275    assert!(bool::from_param("maybe").is_err());
276
277    assert_eq!(true.to_param(), "true");
278    assert_eq!(false.to_param(), "false");
279  }
280
281  #[test]
282  fn test_option_conversions() {
283    assert_eq!(Option::<u32>::from_param("").unwrap(), None);
284    assert_eq!(Option::<u32>::from_param("123").unwrap(), Some(123));
285
286    assert_eq!(Some(123u32).to_param(), "123");
287    assert_eq!(None::<u32>.to_param(), "");
288  }
289
290  #[test]
291  fn test_vec_conversions() {
292    assert_eq!(Vec::<u32>::from_param("").unwrap(), Vec::<u32>::new());
293    assert_eq!(Vec::<u32>::from_param("1,2,3").unwrap(), vec![1, 2, 3]);
294    assert_eq!(Vec::<String>::from_param("a,b,c").unwrap(), vec!["a", "b", "c"]);
295
296    assert_eq!(vec![1u32, 2, 3].to_param(), "1,2,3");
297    assert_eq!(vec!["a".to_string(), "b".to_string()].to_param(), "a,b");
298  }
299
300  #[test]
301  fn test_char_conversions() {
302    assert_eq!(char::from_param("a").unwrap(), 'a');
303    assert_eq!(char::from_param("中").unwrap(), '中');
304
305    assert!(char::from_param("").is_err());
306    assert!(char::from_param("ab").is_err());
307
308    assert_eq!('x'.to_param(), "x");
309  }
310
311  #[test]
312  fn test_user_id() {
313    assert_eq!(UserId::from_param("123").unwrap(), UserId(123));
314    assert!(UserId::from_param("abc").is_err());
315
316    assert_eq!(UserId(456).to_param(), "456");
317  }
318
319  #[test]
320  fn test_slug() {
321    assert_eq!(Slug::from_param("hello-world_123").unwrap(), Slug("hello-world_123".to_string()));
322    assert!(Slug::from_param("hello world").is_err()); // 空格不允许
323    assert!(Slug::from_param("hello@world").is_err()); // @ 不允许
324
325    assert_eq!(Slug("test-slug".to_string()).to_param(), "test-slug");
326  }
327
328  #[test]
329  fn test_email() {
330    assert_eq!(
331      Email::from_param("user@example.com").unwrap(),
332      Email("user@example.com".to_string())
333    );
334    assert!(Email::from_param("invalid-email").is_err());
335    assert!(Email::from_param("@example.com").is_err());
336    assert!(Email::from_param("user@").is_err());
337
338    assert_eq!(Email("test@example.com".to_string()).to_param(), "test@example.com");
339  }
340
341  #[test]
342  fn test_version() {
343    assert_eq!(
344      Version::from_param("1.2.3").unwrap(),
345      Version {
346        major: 1,
347        minor: 2,
348        patch: 3
349      }
350    );
351    assert!(Version::from_param("1.2").is_err());
352    assert!(Version::from_param("1.2.3.4").is_err());
353    assert!(Version::from_param("a.b.c").is_err());
354
355    assert_eq!(
356      Version {
357        major: 2,
358        minor: 1,
359        patch: 0
360      }
361      .to_param(),
362      "2.1.0"
363    );
364  }
365}