1use crate::errors::{HttpError, HttpResult};
4
5pub trait Validate {
7 fn validate(&self) -> HttpResult<()>;
8}
9
10pub fn validate_required<T>(field: &Option<T>, field_name: &str) -> HttpResult<()> {
12 if field.is_none() {
13 return Err(HttpError::bad_request(format!(
14 "{} is required",
15 field_name
16 )));
17 }
18 Ok(())
19}
20
21pub fn validate_min_length(value: &str, min: usize, field_name: &str) -> HttpResult<()> {
22 if value.len() < min {
23 return Err(HttpError::bad_request(format!(
24 "{} must be at least {} characters long",
25 field_name, min
26 )));
27 }
28 Ok(())
29}
30
31pub fn validate_max_length(value: &str, max: usize, field_name: &str) -> HttpResult<()> {
32 if value.len() > max {
33 return Err(HttpError::bad_request(format!(
34 "{} must be at most {} characters long",
35 field_name, max
36 )));
37 }
38 Ok(())
39}
40
41pub fn validate_email(email: &str, field_name: &str) -> HttpResult<()> {
42 if !email.contains('@') || !email.contains('.') {
44 return Err(HttpError::bad_request(format!(
45 "{} must be a valid email address",
46 field_name
47 )));
48 }
49
50 let at_pos = email.find('@').unwrap();
52 if at_pos == 0 {
53 return Err(HttpError::bad_request(format!(
54 "{} must be a valid email address",
55 field_name
56 )));
57 }
58
59 let domain_part = &email[at_pos + 1..];
61 if domain_part.is_empty() || !domain_part.contains('.') {
62 return Err(HttpError::bad_request(format!(
63 "{} must be a valid email address",
64 field_name
65 )));
66 }
67
68 let last_dot_pos = domain_part.rfind('.').unwrap();
70 if last_dot_pos == domain_part.len() - 1 {
71 return Err(HttpError::bad_request(format!(
72 "{} must be a valid email address",
73 field_name
74 )));
75 }
76
77 Ok(())
78}
79
80pub fn validate_range<T: PartialOrd>(value: T, min: T, max: T, field_name: &str) -> HttpResult<()> {
81 if value < min || value > max {
82 return Err(HttpError::bad_request(format!(
83 "{} must be between {} and {}",
84 field_name,
85 std::any::type_name::<T>(),
86 std::any::type_name::<T>()
87 )));
88 }
89 Ok(())
90}
91
92pub fn validate_pattern(value: &str, pattern: &str, field_name: &str) -> HttpResult<()> {
93 if !regex_simple_match(value, pattern) {
94 return Err(HttpError::bad_request(format!(
95 "{} does not match required pattern",
96 field_name
97 )));
98 }
99 Ok(())
100}
101
102fn regex_simple_match(value: &str, pattern: &str) -> bool {
104 match pattern {
105 "alphanumeric" => value.chars().all(char::is_alphanumeric),
106 "numeric" => value.chars().all(char::is_numeric),
107 "alpha" => value.chars().all(char::is_alphabetic),
108 _ => true, }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn test_validate_required_success() {
118 let value = Some("test");
119 let result = validate_required(&value, "test_field");
120 assert!(result.is_ok());
121 }
122
123 #[test]
124 fn test_validate_required_failure() {
125 let value: Option<String> = None;
126 let result = validate_required(&value, "test_field");
127 assert!(result.is_err());
128
129 if let Err(HttpError::BadRequest { message }) = result {
130 assert!(message.contains("test_field is required"));
131 } else {
132 panic!("Expected BadRequest error");
133 }
134 }
135
136 #[test]
137 fn test_validate_min_length_success() {
138 let result = validate_min_length("hello world", 5, "message");
139 assert!(result.is_ok());
140 }
141
142 #[test]
143 fn test_validate_min_length_failure() {
144 let result = validate_min_length("hi", 5, "message");
145 assert!(result.is_err());
146
147 if let Err(HttpError::BadRequest { message }) = result {
148 assert!(message.contains("message must be at least 5 characters long"));
149 } else {
150 panic!("Expected BadRequest error");
151 }
152 }
153
154 #[test]
155 fn test_validate_min_length_exact() {
156 let result = validate_min_length("exact", 5, "message");
157 assert!(result.is_ok());
158 }
159
160 #[test]
161 fn test_validate_max_length_success() {
162 let result = validate_max_length("short", 10, "message");
163 assert!(result.is_ok());
164 }
165
166 #[test]
167 fn test_validate_max_length_failure() {
168 let result = validate_max_length("this is a very long message", 10, "message");
169 assert!(result.is_err());
170
171 if let Err(HttpError::BadRequest { message }) = result {
172 assert!(message.contains("message must be at most 10 characters long"));
173 } else {
174 panic!("Expected BadRequest error");
175 }
176 }
177
178 #[test]
179 fn test_validate_max_length_exact() {
180 let result = validate_max_length("exactly10c", 10, "message");
181 assert!(result.is_ok());
182 }
183
184 #[test]
185 fn test_validate_email_valid() {
186 let valid_emails = [
187 "user@example.com",
188 "test.email@domain.org",
189 "user+tag@example.co.uk",
190 ];
191
192 for email in valid_emails {
193 let result = validate_email(email, "email");
194 assert!(result.is_ok(), "Failed to validate email: {}", email);
195 }
196 }
197
198 #[test]
199 fn test_validate_email_invalid() {
200 let invalid_emails = [
201 "invalid",
202 "no-at-sign.com",
203 "no-domain@",
204 "@no-user.com",
205 "no.dot@domain",
206 ];
207
208 for email in invalid_emails {
209 let result = validate_email(email, "email");
210 assert!(
211 result.is_err(),
212 "Should have failed to validate email: {}",
213 email
214 );
215
216 if let Err(HttpError::BadRequest { message }) = result {
217 assert!(message.contains("email must be a valid email address"));
218 } else {
219 panic!("Expected BadRequest error");
220 }
221 }
222 }
223
224 #[test]
225 fn test_validate_range_success() {
226 let result = validate_range(5, 1, 10, "number");
227 assert!(result.is_ok());
228
229 let result = validate_range(1, 1, 10, "number");
230 assert!(result.is_ok());
231
232 let result = validate_range(10, 1, 10, "number");
233 assert!(result.is_ok());
234 }
235
236 #[test]
237 fn test_validate_range_failure() {
238 let result = validate_range(0, 1, 10, "number");
239 assert!(result.is_err());
240
241 let result = validate_range(11, 1, 10, "number");
242 assert!(result.is_err());
243 }
244
245 #[test]
246 fn test_validate_pattern_alphanumeric() {
247 let result = validate_pattern("test123", "alphanumeric", "username");
248 assert!(result.is_ok());
249
250 let result = validate_pattern("test-123", "alphanumeric", "username");
251 assert!(result.is_err());
252 }
253
254 #[test]
255 fn test_validate_pattern_numeric() {
256 let result = validate_pattern("12345", "numeric", "id");
257 assert!(result.is_ok());
258
259 let result = validate_pattern("123a5", "numeric", "id");
260 assert!(result.is_err());
261 }
262
263 #[test]
264 fn test_validate_pattern_alpha() {
265 let result = validate_pattern("hello", "alpha", "name");
266 assert!(result.is_ok());
267
268 let result = validate_pattern("hello123", "alpha", "name");
269 assert!(result.is_err());
270 }
271
272 #[test]
273 fn test_validate_pattern_unknown() {
274 let result = validate_pattern("anything", "unknown_pattern", "field");
276 assert!(result.is_ok());
277 }
278
279 #[test]
280 fn test_validate_trait_implementation() {
281 struct TestStruct {
282 name: Option<String>,
283 email: String,
284 age: u32,
285 }
286
287 impl Validate for TestStruct {
288 fn validate(&self) -> HttpResult<()> {
289 validate_required(&self.name, "name")?;
290 validate_email(&self.email, "email")?;
291 validate_range(self.age, 0, 120, "age")?;
292 Ok(())
293 }
294 }
295
296 let valid_struct = TestStruct {
297 name: Some("John".to_string()),
298 email: "john@example.com".to_string(),
299 age: 25,
300 };
301 assert!(valid_struct.validate().is_ok());
302
303 let invalid_struct = TestStruct {
304 name: None,
305 email: "invalid-email".to_string(),
306 age: 150,
307 };
308 assert!(invalid_struct.validate().is_err());
309 }
310
311 #[test]
312 fn test_chained_validations() {
313 fn validate_user_input(name: &Option<String>, email: &str, age: u32) -> HttpResult<()> {
314 validate_required(name, "name")?;
315 if let Some(name_value) = name {
316 validate_min_length(name_value, 2, "name")?;
317 validate_max_length(name_value, 50, "name")?;
318 }
319 validate_email(email, "email")?;
320 validate_range(age, 13, 120, "age")?;
321 Ok(())
322 }
323
324 let result = validate_user_input(&Some("John".to_string()), "john@example.com", 25);
325 assert!(result.is_ok());
326
327 let result = validate_user_input(&None, "john@example.com", 25);
328 assert!(result.is_err());
329
330 let result = validate_user_input(
331 &Some("J".to_string()), "john@example.com",
333 25,
334 );
335 assert!(result.is_err());
336 }
337}