1use crate::error::ParseError;
6use crate::traits::{FromParam, ToParam};
7
8macro_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
31impl_from_to_param_for_numbers!(u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, f32, f64);
33
34impl 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
47impl ToParam for &str {
49 fn to_param(&self) -> String {
50 self.to_string()
51 }
52}
53
54impl 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
73impl<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
93impl<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 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
111impl 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#[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#[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 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#[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 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#[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()); assert!(Slug::from_param("hello@world").is_err()); 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}