Skip to main content

tanzim_validate/
string.rs

1use crate::error::{Error, ErrorKind};
2use crate::{Meta, Validator};
3use tanzim_value::{Value, ValueType};
4
5/// (`string` feature) Accepts a string, with optional length bounds and (with the `regex` feature) a
6/// pattern. No coercion: non-string values are rejected.
7#[derive(Debug, Clone, Default)]
8pub struct Str {
9    meta: Meta,
10    min_chars: Option<usize>,
11    max_chars: Option<usize>,
12    #[cfg(feature = "regex")]
13    pattern: Option<regex::Regex>,
14}
15
16impl Str {
17    /// Attach human-facing metadata (name, description, examples, default, output conversion).
18    pub fn with_meta(mut self, meta: Meta) -> Self {
19        self.meta = meta;
20        self
21    }
22
23    pub fn new() -> Self {
24        Self::default()
25    }
26
27    pub fn min_chars(mut self, min: usize) -> Self {
28        self.min_chars = Some(min);
29        self
30    }
31
32    pub fn max_chars(mut self, max: usize) -> Self {
33        self.max_chars = Some(max);
34        self
35    }
36
37    /// Require the string to match `pattern`.
38    ///
39    /// Returns `Err` with the compiler message if `pattern` is not a valid regular
40    /// expression, so the caller must `?` or unwrap it.
41    #[cfg(feature = "regex")]
42    pub fn regex(mut self, pattern: impl Into<String>) -> Result<Self, String> {
43        let pattern = pattern.into();
44        match regex::Regex::new(&pattern) {
45            Ok(compiled) => {
46                self.pattern = Some(compiled);
47                Ok(self)
48            }
49            Err(error) => Err(error.to_string()),
50        }
51    }
52}
53
54impl Validator for Str {
55    fn meta(&self) -> &Meta {
56        &self.meta
57    }
58
59    fn meta_mut(&mut self) -> &mut Meta {
60        &mut self.meta
61    }
62
63    fn check(&self, value: &mut Value) -> Result<(), Error> {
64        let text = match value {
65            Value::String(text) => text,
66            other => {
67                return Err(Error::new(ErrorKind::Type {
68                    expected: ValueType::String,
69                    found: other.type_name(),
70                }));
71            }
72        };
73
74        let length = text.chars().count();
75        if let Some(min) = self.min_chars
76            && length < min
77        {
78            return Err(Error::new(ErrorKind::TooShort { len: length, min }));
79        }
80        if let Some(max) = self.max_chars
81            && length > max
82        {
83            return Err(Error::new(ErrorKind::TooLong { len: length, max }));
84        }
85
86        #[cfg(feature = "regex")]
87        if let Some(pattern) = &self.pattern
88            && !pattern.is_match(text)
89        {
90            return Err(Error::new(ErrorKind::PatternMismatch {
91                pattern: pattern.as_str().to_string(),
92            }));
93        }
94
95        Ok(())
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn accepts_string() {
105        let mut value = Value::String("hi".into());
106        assert!(Str::new().validate(&mut value).is_ok());
107    }
108
109    #[test]
110    fn rejects_non_string() {
111        let mut value = Value::Int(1);
112        let error = Str::new().validate(&mut value).unwrap_err();
113        assert!(matches!(error.kind, ErrorKind::Type { .. }));
114    }
115
116    #[test]
117    fn enforces_min_chars() {
118        let mut value = Value::String("".into());
119        let error = Str::new().min_chars(1).validate(&mut value).unwrap_err();
120        assert!(matches!(error.kind, ErrorKind::TooShort { .. }));
121    }
122
123    #[test]
124    fn enforces_max_chars() {
125        let mut value = Value::String("toolong".into());
126        let error = Str::new().max_chars(3).validate(&mut value).unwrap_err();
127        assert!(matches!(error.kind, ErrorKind::TooLong { .. }));
128    }
129
130    #[cfg(feature = "regex")]
131    #[test]
132    fn regex_matches_and_rejects() {
133        let validator = Str::new().regex("^[a-z]+$").unwrap();
134        let mut ok = Value::String("abc".into());
135        assert!(validator.validate(&mut ok).is_ok());
136        let mut bad = Value::String("abc1".into());
137        let error = validator.validate(&mut bad).unwrap_err();
138        assert!(matches!(error.kind, ErrorKind::PatternMismatch { .. }));
139    }
140}