tanzim_validate/
string.rs1use crate::error::{Error, ErrorKind};
2use crate::{Meta, Validator};
3use tanzim_value::{Value, ValueType};
4
5#[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 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 #[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
54crate::impl_meta_methods!(Str);
55
56impl Validator for Str {
57 fn meta(&self) -> &Meta {
58 &self.meta
59 }
60
61 fn meta_mut(&mut self) -> &mut Meta {
62 &mut self.meta
63 }
64
65 fn check(&self, value: &mut Value) -> Result<(), Error> {
66 let text = match value {
67 Value::String(text) => text,
68 other => {
69 return Err(Error::new(ErrorKind::Type {
70 expected: ValueType::String,
71 found: other.type_name(),
72 }));
73 }
74 };
75
76 let length = text.chars().count();
77 if let Some(min) = self.min_chars
78 && length < min
79 {
80 return Err(Error::new(ErrorKind::TooShort { len: length, min }));
81 }
82 if let Some(max) = self.max_chars
83 && length > max
84 {
85 return Err(Error::new(ErrorKind::TooLong { len: length, max }));
86 }
87
88 #[cfg(feature = "regex")]
89 if let Some(pattern) = &self.pattern
90 && !pattern.is_match(text)
91 {
92 return Err(Error::new(ErrorKind::PatternMismatch {
93 pattern: pattern.as_str().to_string(),
94 }));
95 }
96
97 Ok(())
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn accepts_string() {
107 let mut value = Value::String("hi".into());
108 assert!(Str::new().validate(&mut value).is_ok());
109 }
110
111 #[test]
112 fn rejects_non_string() {
113 let mut value = Value::Int(1);
114 let error = Str::new().validate(&mut value).unwrap_err();
115 assert!(matches!(error.kind, ErrorKind::Type { .. }));
116 }
117
118 #[test]
119 fn enforces_min_chars() {
120 let mut value = Value::String("".into());
121 let error = Str::new().min_chars(1).validate(&mut value).unwrap_err();
122 assert!(matches!(error.kind, ErrorKind::TooShort { .. }));
123 }
124
125 #[test]
126 fn enforces_max_chars() {
127 let mut value = Value::String("toolong".into());
128 let error = Str::new().max_chars(3).validate(&mut value).unwrap_err();
129 assert!(matches!(error.kind, ErrorKind::TooLong { .. }));
130 }
131
132 #[cfg(feature = "regex")]
133 #[test]
134 fn regex_matches_and_rejects() {
135 let validator = Str::new().regex("^[a-z]+$").unwrap();
136 let mut ok = Value::String("abc".into());
137 assert!(validator.validate(&mut ok).is_ok());
138 let mut bad = Value::String("abc1".into());
139 let error = validator.validate(&mut bad).unwrap_err();
140 assert!(matches!(error.kind, ErrorKind::PatternMismatch { .. }));
141 }
142}