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
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}