structform/
numeric_input.rs

1/// Implements `ParseAndFormat<$type> for $numeric_input<$type>`, and also
2/// implements `ParseAndFormat<Option<$type>>> for $numeric_input<Option<$type>>`.
3///
4/// This will parse by first converting the string input to an
5/// $underlying_numeric_type, which should be something like u32 or
6/// i64, then calling
7/// `std::convert::TryFrom<$underlying_numeric_type>`. If the input
8/// string is empty after trimming, then parse will return a
9/// `ParseError::Required` for the `ParseAndFormat<$type>` case, and
10/// return `None` for the `ParseAndFormat<Option<$type>>` case.
11///
12/// Formatting is done using `std::string::ToString`.
13#[macro_export]
14macro_rules! impl_numeric_input_with_stringops {
15    ($numeric_input: ident, $type_name: literal, $type: ty, $underlying_numeric_type: ty) => {
16        impl_numeric_input_with_stringops!(
17            $numeric_input,
18            $type_name,
19            $type,
20            $underlying_numeric_type,
21            <$type>::MIN,
22            <$type>::MAX
23        );
24    };
25    ($numeric_input: ident, $type_name: literal, $type: ty, $underlying_numeric_type: ty, $min: expr, $max: expr) => {
26        impl structform::ParseAndFormat<$type> for $numeric_input<$type> {
27            fn parse(value: &str) -> Result<$type, ParseError> {
28                use std::convert::TryFrom;
29                let trimmed = value.trim();
30                if trimmed.is_empty() {
31                    Err(ParseError::Required)
32                } else {
33                    trimmed
34                        .parse::<$underlying_numeric_type>()
35                        .map_err(|_e| ParseError::NumberOutOfRange {
36                            required_type: $type_name.to_string(),
37                            min: $min.to_string(),
38                            max: $max.to_string(),
39                        })
40                        .and_then(|via| {
41                            <$type>::try_from(via)
42                                .map_err(|e| ParseError::FromStrError(e.to_string()))
43                        })
44                }
45            }
46
47            fn format(value: &$type) -> String {
48                value.to_string()
49            }
50        }
51
52        impl structform::ParseAndFormat<Option<$type>> for $numeric_input<Option<$type>> {
53            fn parse(value: &str) -> Result<Option<$type>, structform::ParseError> {
54                use std::convert::TryFrom;
55
56                let trimmed = value.trim();
57                if trimmed.is_empty() {
58                    Ok(None)
59                } else {
60                    trimmed
61                        .parse::<$underlying_numeric_type>()
62                        .map_err(|_e| structform::ParseError::NumberOutOfRange {
63                            required_type: $type_name.to_string(),
64                            min: $min.to_string(),
65                            max: $max.to_string(),
66                        })
67                        .and_then(|via| {
68                            <$type>::try_from(via)
69                                .map_err(|e| structform::ParseError::FromStrError(e.to_string()))
70                        })
71                        .map(Option::Some)
72                }
73            }
74
75            fn format(value: &Option<$type>) -> String {
76                match value {
77                    None => "".to_string(),
78                    Some(inner) => inner.to_string(),
79                }
80            }
81        }
82    };
83}
84
85/// Implements `ParseAndFormat<$type> for $numeric_input<$type>`.
86///
87/// This works the same as `impl_numeric_input_with_stringops`, except if input
88/// string is empty after trimming, then parse will return $type::default().
89#[macro_export]
90macro_rules! impl_numeric_input_with_default_with_stringops {
91    ($numeric_input: ident, $type_name: literal, $type: ty, $underlying_numeric_type: ty) => {
92        impl_numeric_input_with_default_with_stringops!(
93            $numeric_input,
94            $type_name,
95            $type,
96            $underlying_numeric_type,
97            <$type>::MIN,
98            <$type>::MAX
99        );
100    };
101    ($numeric_input: ident, $type_name: literal, $type: ty, $underlying_numeric_type: ty, $min: expr, $max: expr) => {
102        impl structform::ParseAndFormat<$type> for $numeric_input<$type> {
103            fn parse(value: &str) -> Result<$type, ParseError> {
104                use std::convert::TryFrom;
105                let trimmed = value.trim();
106                if trimmed.is_empty() {
107                    Ok(<$type>::default())
108                } else {
109                    trimmed
110                        .parse::<$underlying_numeric_type>()
111                        .map_err(|_e| ParseError::NumberOutOfRange {
112                            required_type: $type_name.to_string(),
113                            min: $min.to_string(),
114                            max: $max.to_string(),
115                        })
116                        .and_then(|via| {
117                            <$type>::try_from(via)
118                                .map_err(|e| ParseError::FromStrError(e.to_string()))
119                        })
120                }
121            }
122
123            fn format(value: &$type) -> String {
124                value.to_string()
125            }
126        }
127    };
128}