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

            fn format(value: &$type) -> String {
                value.to_string()
            }
        }

        impl structform::ParseAndFormat<Option<$type>> for $numeric_input<Option<$type>> {
            fn parse(value: &str) -> Result<Option<$type>, structform::ParseError> {
                use std::convert::TryFrom;

                let trimmed = value.trim();
                if trimmed.is_empty() {
                    Ok(None)
                } else {
                    trimmed
                        .parse::<$underlying_numeric_type>()
                        .map_err(|_e| structform::ParseError::NumberOutOfRange {
                            required_type: $type_name.to_string(),
                            min: $min.to_string(),
                            max: $max.to_string(),
                        })
                        .and_then(|via| {
                            <$type>::try_from(via)
                                .map_err(|e| structform::ParseError::FromStrError(e.to_string()))
                        })
                        .map(Option::Some)
                }
            }

            fn format(value: &Option<$type>) -> String {
                match value {
                    None => "".to_string(),
                    Some(inner) => inner.to_string(),
                }
            }
        }
    };
}

/// Implements `ParseAndFormat<$type> for $numeric_input<$type>`.
///
/// This works the same as `impl_numeric_input_with_stringops`, except if input
/// string is empty after trimming, then parse will return $type::default().
#[macro_export]
macro_rules! impl_numeric_input_with_default_with_stringops {
    ($numeric_input: ident, $type_name: literal, $type: ty, $underlying_numeric_type: ty) => {
        impl_numeric_input_with_default_with_stringops!(
            $numeric_input,
            $type_name,
            $type,
            $underlying_numeric_type,
            <$type>::MIN,
            <$type>::MAX
        );
    };
    ($numeric_input: ident, $type_name: literal, $type: ty, $underlying_numeric_type: ty, $min: expr, $max: expr) => {
        impl structform::ParseAndFormat<$type> for $numeric_input<$type> {
            fn parse(value: &str) -> Result<$type, ParseError> {
                use std::convert::TryFrom;
                let trimmed = value.trim();
                if trimmed.is_empty() {
                    Ok(<$type>::default())
                } else {
                    trimmed
                        .parse::<$underlying_numeric_type>()
                        .map_err(|_e| ParseError::NumberOutOfRange {
                            required_type: $type_name.to_string(),
                            min: $min.to_string(),
                            max: $max.to_string(),
                        })
                        .and_then(|via| {
                            <$type>::try_from(via)
                                .map_err(|e| ParseError::FromStrError(e.to_string()))
                        })
                }
            }

            fn format(value: &$type) -> String {
                value.to_string()
            }
        }
    };
}