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
/// Implements `ParseAndFormat<$type> for $text_input<$type>`, and also
/// implements `ParseAndFormat<Option<$type>>> for $text_input<Option<$type>>`.
///
/// This will parse by trimming the string input and then calling
/// `str::parse`. 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_text_input_with_stringops {
    ($text_input: ident, $type_name: literal, $type: ty) => {
        impl_text_input_with_stringops!(
            $text_input,
            |_e| structform::ParseError::InvalidFormat {
                required_type: $type_name.to_string()
            },
            $type
        );
    };
    ($text_input: ident, $type: ty) => {
        impl_text_input_with_stringops!(
            $text_input,
            |e| structform::ParseError::FromStrError(e.to_string()),
            $type
        );
    };
    ($text_input: ident, $handle_error: expr, $type: ty) => {
        impl structform::ParseAndFormat<$type> for $text_input<$type> {
            fn parse(value: &str) -> Result<$type, structform::ParseError> {
                let trimmed = value.trim();
                if trimmed.is_empty() {
                    Err(structform::ParseError::Required)
                } else {
                    trimmed.parse::<$type>().map_err($handle_error)
                }
            }

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

        impl structform::ParseAndFormat<Option<$type>> for $text_input<Option<$type>> {
            fn parse(value: &str) -> Result<Option<$type>, structform::ParseError> {
                let trimmed = value.trim();
                if trimmed.is_empty() {
                    Ok(None)
                } else {
                    trimmed
                        .parse::<$type>()
                        .map(Option::Some)
                        .map_err(|e| structform::ParseError::FromStrError(e.to_string()))
                }
            }

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

/// Implements `ParseAndFormat<Vec<$type>> for $text_input<Vec<$type>>`.
///
/// This will parse by splitting the string on commas, and
/// individually parsing each split using `str::parse`. Empty strings
/// will result in an empty `Vec`.
///
/// Formatting is done using `std::string::ToString` on each element
/// of the `Vec` and then joining them with a comma.
///
/// Note: This is not a good idea of your value might contain commas.
#[macro_export]
macro_rules! impl_vec_text_input_with_stringops {
    ($text_input: ident, $type_name: literal, $type: ty) => {
        impl_vec_text_input_with_stringops!(
            $text_input,
            |_e| structform::ParseError::InvalidFormat {
                required_type: $type_name.to_string()
            },
            $type
        );
    };
    ($text_input: ident, $type: ty) => {
        impl_vec_text_input_with_stringops!(
            $text_input,
            |e| structform::ParseError::FromStrError(e.to_string()),
            $type
        );
    };
    ($text_input: ident, $handle_error: expr, $type: ty) => {
        impl structform::ParseAndFormat<Vec<$type>> for $text_input<Vec<$type>> {
            fn parse(value: &str) -> Result<Vec<$type>, structform::ParseError> {
                value
                    .trim()
                    .split(',')
                    .map(|s| s.trim())
                    .filter(|s| !s.is_empty())
                    .map(|trimmed| trimmed.parse::<$type>().map_err($handle_error))
                    .collect()
            }

            fn format(value: &Vec<$type>) -> String {
                value
                    .iter()
                    .map(|value| value.to_string())
                    .collect::<Vec<_>>()
                    .join(", ")
            }
        }
    };
}