karbon_framework/validation/constraints/string/
length.rs1use crate::validation::constraints::{Constraint, ConstraintResult, ConstraintViolation};
2
3pub struct Length {
7 pub min: Option<usize>,
8 pub max: Option<usize>,
9 pub exact: Option<usize>,
10 pub min_message: String,
11 pub max_message: String,
12 pub exact_message: String,
13}
14
15impl Default for Length {
16 fn default() -> Self {
17 Self {
18 min: None,
19 max: None,
20 exact: None,
21 min_message: "This value is too short. It should have {{ limit }} characters or more."
22 .to_string(),
23 max_message: "This value is too long. It should have {{ limit }} characters or less."
24 .to_string(),
25 exact_message: "This value should have exactly {{ limit }} characters.".to_string(),
26 }
27 }
28}
29
30impl Length {
31 pub fn new() -> Self {
32 Self::default()
33 }
34
35 pub fn min(mut self, min: usize) -> Self {
36 self.min = Some(min);
37 self
38 }
39
40 pub fn max(mut self, max: usize) -> Self {
41 self.max = Some(max);
42 self
43 }
44
45 pub fn exact(mut self, exact: usize) -> Self {
46 self.exact = Some(exact);
47 self
48 }
49
50 pub fn with_min_message(mut self, message: impl Into<String>) -> Self {
51 self.min_message = message.into();
52 self
53 }
54
55 pub fn with_max_message(mut self, message: impl Into<String>) -> Self {
56 self.max_message = message.into();
57 self
58 }
59
60 pub fn with_exact_message(mut self, message: impl Into<String>) -> Self {
61 self.exact_message = message.into();
62 self
63 }
64
65 fn format_message(&self, template: &str, limit: usize) -> String {
66 template.replace("{{ limit }}", &limit.to_string())
67 }
68}
69
70impl Constraint for Length {
71 fn validate(&self, value: &str) -> ConstraintResult {
72 let len = value.chars().count();
73
74 if let Some(exact) = self.exact {
75 if len != exact {
76 return Err(ConstraintViolation::new(
77 self.name(),
78 self.format_message(&self.exact_message, exact),
79 value,
80 ));
81 }
82 return Ok(());
83 }
84
85 if let Some(min) = self.min {
86 if len < min {
87 return Err(ConstraintViolation::new(
88 self.name(),
89 self.format_message(&self.min_message, min),
90 value,
91 ));
92 }
93 }
94
95 if let Some(max) = self.max {
96 if len > max {
97 return Err(ConstraintViolation::new(
98 self.name(),
99 self.format_message(&self.max_message, max),
100 value,
101 ));
102 }
103 }
104
105 Ok(())
106 }
107
108 fn name(&self) -> &'static str {
109 "Length"
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 #[test]
118 fn test_min_length() {
119 let constraint = Length::new().min(3);
120 assert!(constraint.validate("abc").is_ok());
121 assert!(constraint.validate("abcd").is_ok());
122 assert!(constraint.validate("ab").is_err());
123 assert!(constraint.validate("").is_err());
124 }
125
126 #[test]
127 fn test_max_length() {
128 let constraint = Length::new().max(5);
129 assert!(constraint.validate("abc").is_ok());
130 assert!(constraint.validate("abcde").is_ok());
131 assert!(constraint.validate("abcdef").is_err());
132 }
133
134 #[test]
135 fn test_min_max_range() {
136 let constraint = Length::new().min(2).max(5);
137 assert!(constraint.validate("ab").is_ok());
138 assert!(constraint.validate("abcde").is_ok());
139 assert!(constraint.validate("a").is_err());
140 assert!(constraint.validate("abcdef").is_err());
141 }
142
143 #[test]
144 fn test_exact_length() {
145 let constraint = Length::new().exact(4);
146 assert!(constraint.validate("abcd").is_ok());
147 assert!(constraint.validate("abc").is_err());
148 assert!(constraint.validate("abcde").is_err());
149 }
150
151 #[test]
152 fn test_unicode_characters() {
153 let constraint = Length::new().min(2).max(5);
154 assert!(constraint.validate("café").is_ok()); assert!(constraint.validate("é").is_err()); }
157
158 #[test]
159 fn test_error_message_formatting() {
160 let constraint = Length::new().min(3);
161 let err = constraint.validate("ab").unwrap_err();
162 assert!(err.message.contains("3"));
163 }
164}