1use crate::validation::{Rule, ValidationError};
4use serde_json::Value;
5use std::collections::HashMap;
6
7pub struct Validator<'a> {
31 data: &'a Value,
32 rules: HashMap<String, Vec<Box<dyn Rule>>>,
33 custom_messages: HashMap<String, String>,
34 custom_attributes: HashMap<String, String>,
35 stop_on_first_failure: bool,
36 pre_errors: Vec<(String, String)>,
38}
39
40impl<'a> Validator<'a> {
41 pub fn new(data: &'a Value) -> Self {
43 Self {
44 data,
45 rules: HashMap::new(),
46 custom_messages: HashMap::new(),
47 custom_attributes: HashMap::new(),
48 stop_on_first_failure: false,
49 pre_errors: Vec::new(),
50 }
51 }
52
53 pub fn with_error(mut self, field: impl Into<String>, message: impl Into<String>) -> Self {
72 self.pre_errors.push((field.into(), message.into()));
73 self
74 }
75
76 pub fn rule<R: Rule + 'static>(mut self, field: impl Into<String>, rule: R) -> Self {
78 let field = field.into();
79 self.rules
80 .entry(field)
81 .or_default()
82 .push(Box::new(rule) as Box<dyn Rule>);
83 self
84 }
85
86 pub fn rules(mut self, field: impl Into<String>, rules: Vec<Box<dyn Rule>>) -> Self {
99 self.rules.insert(field.into(), rules);
100 self
101 }
102
103 pub fn boxed_rules(mut self, field: impl Into<String>, rules: Vec<Box<dyn Rule>>) -> Self {
105 self.rules.insert(field.into(), rules);
106 self
107 }
108
109 pub fn message(mut self, key: impl Into<String>, message: impl Into<String>) -> Self {
120 self.custom_messages.insert(key.into(), message.into());
121 self
122 }
123
124 pub fn messages(mut self, messages: HashMap<String, String>) -> Self {
126 self.custom_messages.extend(messages);
127 self
128 }
129
130 pub fn attribute(mut self, field: impl Into<String>, name: impl Into<String>) -> Self {
141 self.custom_attributes.insert(field.into(), name.into());
142 self
143 }
144
145 pub fn attributes(mut self, attributes: HashMap<String, String>) -> Self {
147 self.custom_attributes.extend(attributes);
148 self
149 }
150
151 pub fn stop_on_first_failure(mut self) -> Self {
153 self.stop_on_first_failure = true;
154 self
155 }
156
157 pub fn validate_or_redirect(
172 self,
173 url: impl Into<String>,
174 ) -> Result<(), crate::http::action::ActionError> {
175 let data: &Value = self.data;
177 self.validate()
178 .map_err(|e| e.with_old_input(data).into_action_error(url))
179 }
180
181 pub fn validate(self) -> Result<(), ValidationError> {
183 let mut errors = ValidationError::new();
184
185 for (field, message) in &self.pre_errors {
187 errors.add(field, message.clone());
188 }
189
190 for (field, rules) in &self.rules {
191 let value = self.get_value(field);
192 let display_field = self.get_display_field(field);
193
194 let has_nullable = rules.iter().any(|r| r.name() == "nullable");
196 if has_nullable && value.is_null() {
197 continue;
198 }
199
200 for rule in rules {
201 if rule.name() == "nullable" {
203 continue;
204 }
205
206 if let Err(default_message) = rule.validate(&display_field, &value, self.data) {
207 let message_key = format!("{}.{}", field, rule.name());
209 let message = self
210 .custom_messages
211 .get(&message_key)
212 .cloned()
213 .unwrap_or(default_message);
214
215 errors.add(field, message);
216 }
217 }
218
219 if self.stop_on_first_failure && errors.has(field) {
220 break;
221 }
222 }
223
224 if errors.is_empty() {
225 Ok(())
226 } else {
227 Err(errors)
228 }
229 }
230
231 pub fn passes(&self) -> bool {
233 let mut errors = ValidationError::new();
234
235 for (field, message) in &self.pre_errors {
237 errors.add(field, message.clone());
238 }
239
240 for (field, rules) in &self.rules {
241 let value = self.get_value(field);
242 let display_field = self.get_display_field(field);
243
244 let has_nullable = rules.iter().any(|r| r.name() == "nullable");
245 if has_nullable && value.is_null() {
246 continue;
247 }
248
249 for rule in rules {
250 if rule.name() == "nullable" {
251 continue;
252 }
253
254 if rule.validate(&display_field, &value, self.data).is_err() {
255 errors.add(field, "failed");
256 }
257 }
258 }
259
260 errors.is_empty()
261 }
262
263 pub fn fails(&self) -> bool {
265 !self.passes()
266 }
267
268 fn get_value(&self, field: &str) -> Value {
270 get_nested_value(self.data, field)
271 .cloned()
272 .unwrap_or(Value::Null)
273 }
274
275 fn get_display_field(&self, field: &str) -> String {
277 self.custom_attributes
278 .get(field)
279 .cloned()
280 .unwrap_or_else(|| {
281 field.split('_').collect::<Vec<_>>().join(" ")
283 })
284 }
285}
286
287fn get_nested_value<'a>(data: &'a Value, path: &str) -> Option<&'a Value> {
289 let parts: Vec<&str> = path.split('.').collect();
290 let mut current = data;
291
292 for part in parts {
293 if let Value::Object(map) = current {
295 current = map.get(part)?;
296 }
297 else if let Value::Array(arr) = current {
299 let index: usize = part.parse().ok()?;
300 current = arr.get(index)?;
301 } else {
302 return None;
303 }
304 }
305
306 Some(current)
307}
308
309pub fn validate<I, F>(data: &Value, rules: I) -> Result<(), ValidationError>
324where
325 I: IntoIterator<Item = (F, Vec<Box<dyn Rule>>)>,
326 F: Into<String>,
327{
328 let mut validator = Validator::new(data);
329 for (field, field_rules) in rules {
330 validator = validator.rules(field, field_rules);
331 }
332 validator.validate()
333}
334
335#[cfg(test)]
336mod tests {
337 use super::*;
338 use crate::rules;
339 use crate::validation::rules::*;
340 use serde_json::json;
341
342 #[test]
343 fn test_validator_passes() {
344 let data = json!({
345 "email": "test@example.com",
346 "name": "John Doe"
347 });
348
349 let result = Validator::new(&data)
350 .rules("email", rules![required(), email()])
351 .rules("name", rules![required(), string()])
352 .validate();
353
354 assert!(result.is_ok());
355 }
356
357 #[test]
358 fn test_validator_fails() {
359 let data = json!({
360 "email": "invalid-email",
361 "name": ""
362 });
363
364 let result = Validator::new(&data)
365 .rules("email", rules![required(), email()])
366 .rules("name", rules![required()])
367 .validate();
368
369 assert!(result.is_err());
370 let errors = result.unwrap_err();
371 assert!(errors.has("email"));
372 assert!(errors.has("name"));
373 }
374
375 #[test]
376 fn test_validator_custom_message() {
377 let data = json!({"email": ""});
378
379 let result = Validator::new(&data)
380 .rules("email", rules![required()])
381 .message("email.required", "We need your email!")
382 .validate();
383
384 let errors = result.unwrap_err();
385 assert_eq!(
386 errors.first("email"),
387 Some(&"We need your email!".to_string())
388 );
389 }
390
391 #[test]
392 fn test_validator_custom_attribute() {
393 let data = json!({"user_email": ""});
394
395 let validator = Validator::new(&data)
397 .rules("user_email", rules![required()])
398 .attribute("user_email", "email address");
399
400 let result = validator.validate();
402 assert!(result.is_err());
403 let errors = result.unwrap_err();
404 assert!(
405 errors.first("user_email").is_some(),
406 "Expected error for 'user_email'"
407 );
408
409 }
414
415 #[test]
416 fn test_validator_nullable() {
417 let data = json!({"nickname": null});
418
419 let result = Validator::new(&data)
420 .rules("nickname", rules![nullable(), string(), min(3)])
421 .validate();
422
423 assert!(result.is_ok());
424 }
425
426 #[test]
427 fn test_nested_value() {
428 let data = json!({
429 "user": {
430 "profile": {
431 "email": "test@example.com"
432 }
433 }
434 });
435
436 let value = get_nested_value(&data, "user.profile.email");
437 assert_eq!(value, Some(&json!("test@example.com")));
438 }
439
440 #[test]
441 fn test_validate_function() {
442 let data = json!({"email": "test@example.com"});
443
444 let result = validate(&data, vec![("email", rules![required(), email()])]);
445
446 assert!(result.is_ok());
447 }
448
449 #[test]
450 fn test_passes_and_fails() {
451 let data = json!({"email": "invalid"});
452
453 let validator = Validator::new(&data).rules("email", rules![email()]);
454
455 assert!(validator.fails());
456 }
457
458 #[test]
461 fn test_validate_or_redirect_ok_when_all_rules_pass() {
462 let data = json!({"name": "Alice"});
463 let result = Validator::new(&data)
464 .rules("name", rules![required(), string()])
465 .validate_or_redirect("/form");
466 assert!(result.is_ok());
467 }
468
469 #[test]
470 fn test_validate_or_redirect_err_when_rule_fails() {
471 let data = json!({"name": ""});
472 let result = Validator::new(&data)
473 .rules("name", rules![required()])
474 .validate_or_redirect("/form");
475 assert!(result.is_err());
476 }
477
478 #[test]
479 fn test_validate_or_redirect_chains_with_old_input() {
480 let data = json!({"name": ""});
483
484 let result_via_helper = Validator::new(&data)
485 .rules("name", rules![required()])
486 .validate_or_redirect("/form");
487
488 let result_manual = Validator::new(&data)
490 .rules("name", rules![required()])
491 .validate()
492 .map_err(|e| e.with_old_input(&data).into_action_error("/form"));
493
494 assert!(result_via_helper.is_err());
496 assert!(result_manual.is_err());
497 }
498}