1use crate::validation::translate_validation;
4use crate::validation::Rule;
5use regex::Regex;
6use serde_json::Value;
7use std::sync::LazyLock;
8
9pub struct Required;
15
16pub const fn required() -> Required {
17 Required
18}
19
20impl Rule for Required {
21 fn validate(&self, field: &str, value: &Value, _data: &Value) -> Result<(), String> {
22 let is_empty = match value {
23 Value::Null => true,
24 Value::String(s) => s.trim().is_empty(),
25 Value::Array(a) => a.is_empty(),
26 Value::Object(o) => o.is_empty(),
27 _ => false,
28 };
29
30 if is_empty {
31 Err(
32 translate_validation("validation.required", &[("attribute", field)])
33 .unwrap_or_else(|| format!("The {field} field is required.")),
34 )
35 } else {
36 Ok(())
37 }
38 }
39
40 fn name(&self) -> &'static str {
41 "required"
42 }
43}
44
45pub struct RequiredIf {
47 other: String,
48 value: Value,
49}
50
51pub fn required_if(other: impl Into<String>, value: impl Into<Value>) -> RequiredIf {
52 RequiredIf {
53 other: other.into(),
54 value: value.into(),
55 }
56}
57
58impl Rule for RequiredIf {
59 fn validate(&self, field: &str, value: &Value, data: &Value) -> Result<(), String> {
60 let other_value = data.get(&self.other);
61 if other_value == Some(&self.value) {
62 Required.validate(field, value, data)
63 } else {
64 Ok(())
65 }
66 }
67
68 fn name(&self) -> &'static str {
69 "required_if"
70 }
71}
72
73pub struct IsString;
79
80pub const fn string() -> IsString {
81 IsString
82}
83
84impl Rule for IsString {
85 fn validate(&self, field: &str, value: &Value, _data: &Value) -> Result<(), String> {
86 if value.is_null() || value.is_string() {
87 Ok(())
88 } else {
89 Err(
90 translate_validation("validation.string", &[("attribute", field)])
91 .unwrap_or_else(|| format!("The {field} field must be a string.")),
92 )
93 }
94 }
95
96 fn name(&self) -> &'static str {
97 "string"
98 }
99}
100
101pub struct IsInteger;
103
104pub const fn integer() -> IsInteger {
105 IsInteger
106}
107
108impl Rule for IsInteger {
109 fn validate(&self, field: &str, value: &Value, _data: &Value) -> Result<(), String> {
110 if value.is_null() || value.is_i64() || value.is_u64() {
111 Ok(())
112 } else if let Some(s) = value.as_str() {
113 if s.parse::<i64>().is_ok() {
114 Ok(())
115 } else {
116 Err(
117 translate_validation("validation.integer", &[("attribute", field)])
118 .unwrap_or_else(|| format!("The {field} field must be an integer.")),
119 )
120 }
121 } else {
122 Err(
123 translate_validation("validation.integer", &[("attribute", field)])
124 .unwrap_or_else(|| format!("The {field} field must be an integer.")),
125 )
126 }
127 }
128
129 fn name(&self) -> &'static str {
130 "integer"
131 }
132}
133
134pub struct Numeric;
136
137pub const fn numeric() -> Numeric {
138 Numeric
139}
140
141impl Rule for Numeric {
142 fn validate(&self, field: &str, value: &Value, _data: &Value) -> Result<(), String> {
143 if value.is_null() || value.is_number() {
144 Ok(())
145 } else if let Some(s) = value.as_str() {
146 if s.parse::<f64>().is_ok() {
147 Ok(())
148 } else {
149 Err(
150 translate_validation("validation.numeric", &[("attribute", field)])
151 .unwrap_or_else(|| format!("The {field} field must be a number.")),
152 )
153 }
154 } else {
155 Err(
156 translate_validation("validation.numeric", &[("attribute", field)])
157 .unwrap_or_else(|| format!("The {field} field must be a number.")),
158 )
159 }
160 }
161
162 fn name(&self) -> &'static str {
163 "numeric"
164 }
165}
166
167pub struct IsBoolean;
169
170pub const fn boolean() -> IsBoolean {
171 IsBoolean
172}
173
174impl Rule for IsBoolean {
175 fn validate(&self, field: &str, value: &Value, _data: &Value) -> Result<(), String> {
176 if value.is_null() || value.is_boolean() {
177 Ok(())
178 } else if let Some(s) = value.as_str() {
179 match s.to_lowercase().as_str() {
180 "true" | "false" | "1" | "0" | "yes" | "no" => Ok(()),
181 _ => Err(
182 translate_validation("validation.boolean", &[("attribute", field)])
183 .unwrap_or_else(|| format!("The {field} field must be true or false.")),
184 ),
185 }
186 } else if let Some(n) = value.as_i64() {
187 if n == 0 || n == 1 {
188 Ok(())
189 } else {
190 Err(
191 translate_validation("validation.boolean", &[("attribute", field)])
192 .unwrap_or_else(|| format!("The {field} field must be true or false.")),
193 )
194 }
195 } else {
196 Err(
197 translate_validation("validation.boolean", &[("attribute", field)])
198 .unwrap_or_else(|| format!("The {field} field must be true or false.")),
199 )
200 }
201 }
202
203 fn name(&self) -> &'static str {
204 "boolean"
205 }
206}
207
208pub struct IsArray;
210
211pub const fn array() -> IsArray {
212 IsArray
213}
214
215impl Rule for IsArray {
216 fn validate(&self, field: &str, value: &Value, _data: &Value) -> Result<(), String> {
217 if value.is_null() || value.is_array() {
218 Ok(())
219 } else {
220 Err(
221 translate_validation("validation.array", &[("attribute", field)])
222 .unwrap_or_else(|| format!("The {field} field must be an array.")),
223 )
224 }
225 }
226
227 fn name(&self) -> &'static str {
228 "array"
229 }
230}
231
232pub struct Min {
238 min: f64,
239}
240
241pub fn min(min: impl Into<f64>) -> Min {
242 Min { min: min.into() }
243}
244
245impl Rule for Min {
246 fn validate(&self, field: &str, value: &Value, _data: &Value) -> Result<(), String> {
247 if value.is_null() {
248 return Ok(());
249 }
250
251 let size = get_size(value);
252 if size < self.min {
253 let unit = get_size_unit(value);
254 let type_key = get_size_type_key("min", value);
255 let min_str = format!("{}", self.min as i64);
256 Err(
257 translate_validation(&type_key, &[("attribute", field), ("min", &min_str)])
258 .unwrap_or_else(|| {
259 format!(
260 "The {} field must be at least {} {}.",
261 field, self.min as i64, unit
262 )
263 }),
264 )
265 } else {
266 Ok(())
267 }
268 }
269
270 fn name(&self) -> &'static str {
271 "min"
272 }
273}
274
275pub struct Max {
277 max: f64,
278}
279
280pub fn max(max: impl Into<f64>) -> Max {
281 Max { max: max.into() }
282}
283
284impl Rule for Max {
285 fn validate(&self, field: &str, value: &Value, _data: &Value) -> Result<(), String> {
286 if value.is_null() {
287 return Ok(());
288 }
289
290 let size = get_size(value);
291 if size > self.max {
292 let unit = get_size_unit(value);
293 let type_key = get_size_type_key("max", value);
294 let max_str = format!("{}", self.max as i64);
295 Err(
296 translate_validation(&type_key, &[("attribute", field), ("max", &max_str)])
297 .unwrap_or_else(|| {
298 format!(
299 "The {} field must not be greater than {} {}.",
300 field, self.max as i64, unit
301 )
302 }),
303 )
304 } else {
305 Ok(())
306 }
307 }
308
309 fn name(&self) -> &'static str {
310 "max"
311 }
312}
313
314pub struct Between {
316 min: f64,
317 max: f64,
318}
319
320pub fn between(min: impl Into<f64>, max: impl Into<f64>) -> Between {
321 Between {
322 min: min.into(),
323 max: max.into(),
324 }
325}
326
327impl Rule for Between {
328 fn validate(&self, field: &str, value: &Value, _data: &Value) -> Result<(), String> {
329 if value.is_null() {
330 return Ok(());
331 }
332
333 let size = get_size(value);
334 if size < self.min || size > self.max {
335 let unit = get_size_unit(value);
336 let type_key = get_size_type_key("between", value);
337 let min_str = format!("{}", self.min as i64);
338 let max_str = format!("{}", self.max as i64);
339 Err(translate_validation(
340 &type_key,
341 &[("attribute", field), ("min", &min_str), ("max", &max_str)],
342 )
343 .unwrap_or_else(|| {
344 format!(
345 "The {} field must be between {} and {} {}.",
346 field, self.min as i64, self.max as i64, unit
347 )
348 }))
349 } else {
350 Ok(())
351 }
352 }
353
354 fn name(&self) -> &'static str {
355 "between"
356 }
357}
358
359static EMAIL_REGEX: LazyLock<Regex> =
364 LazyLock::new(|| Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap());
365
366pub struct Email;
368
369pub const fn email() -> Email {
370 Email
371}
372
373impl Rule for Email {
374 fn validate(&self, field: &str, value: &Value, _data: &Value) -> Result<(), String> {
375 if value.is_null() {
376 return Ok(());
377 }
378
379 match value.as_str() {
380 Some(s) if EMAIL_REGEX.is_match(s) => Ok(()),
381 _ => Err(
382 translate_validation("validation.email", &[("attribute", field)])
383 .unwrap_or_else(|| format!("The {field} field must be a valid email address.")),
384 ),
385 }
386 }
387
388 fn name(&self) -> &'static str {
389 "email"
390 }
391}
392
393static URL_REGEX: LazyLock<Regex> =
394 LazyLock::new(|| Regex::new(r"^https?://[^\s/$.?#].[^\s]*$").unwrap());
395
396pub struct Url;
398
399pub const fn url() -> Url {
400 Url
401}
402
403impl Rule for Url {
404 fn validate(&self, field: &str, value: &Value, _data: &Value) -> Result<(), String> {
405 if value.is_null() {
406 return Ok(());
407 }
408
409 match value.as_str() {
410 Some(s) if URL_REGEX.is_match(s) => Ok(()),
411 _ => Err(
412 translate_validation("validation.url", &[("attribute", field)])
413 .unwrap_or_else(|| format!("The {field} field must be a valid URL.")),
414 ),
415 }
416 }
417
418 fn name(&self) -> &'static str {
419 "url"
420 }
421}
422
423pub struct Regex_ {
425 pattern: Regex,
426}
427
428pub fn regex(pattern: &str) -> Regex_ {
429 Regex_ {
430 pattern: Regex::new(pattern).expect("Invalid regex pattern"),
431 }
432}
433
434impl Rule for Regex_ {
435 fn validate(&self, field: &str, value: &Value, _data: &Value) -> Result<(), String> {
436 if value.is_null() {
437 return Ok(());
438 }
439
440 match value.as_str() {
441 Some(s) if self.pattern.is_match(s) => Ok(()),
442 _ => Err(
443 translate_validation("validation.regex", &[("attribute", field)])
444 .unwrap_or_else(|| format!("The {field} field format is invalid.")),
445 ),
446 }
447 }
448
449 fn name(&self) -> &'static str {
450 "regex"
451 }
452}
453
454static ALPHA_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^[a-zA-Z]+$").unwrap());
455
456pub struct Alpha;
458
459pub const fn alpha() -> Alpha {
460 Alpha
461}
462
463impl Rule for Alpha {
464 fn validate(&self, field: &str, value: &Value, _data: &Value) -> Result<(), String> {
465 if value.is_null() {
466 return Ok(());
467 }
468
469 match value.as_str() {
470 Some(s) if ALPHA_REGEX.is_match(s) => Ok(()),
471 _ => Err(
472 translate_validation("validation.alpha", &[("attribute", field)])
473 .unwrap_or_else(|| format!("The {field} field must only contain letters.")),
474 ),
475 }
476 }
477
478 fn name(&self) -> &'static str {
479 "alpha"
480 }
481}
482
483static ALPHA_NUM_REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^[a-zA-Z0-9]+$").unwrap());
484
485pub struct AlphaNum;
487
488pub const fn alpha_num() -> AlphaNum {
489 AlphaNum
490}
491
492impl Rule for AlphaNum {
493 fn validate(&self, field: &str, value: &Value, _data: &Value) -> Result<(), String> {
494 if value.is_null() {
495 return Ok(());
496 }
497
498 match value.as_str() {
499 Some(s) if ALPHA_NUM_REGEX.is_match(s) => Ok(()),
500 _ => Err(
501 translate_validation("validation.alpha_num", &[("attribute", field)])
502 .unwrap_or_else(|| {
503 format!("The {field} field must only contain letters and numbers.")
504 }),
505 ),
506 }
507 }
508
509 fn name(&self) -> &'static str {
510 "alpha_num"
511 }
512}
513
514static ALPHA_DASH_REGEX: LazyLock<Regex> =
515 LazyLock::new(|| Regex::new(r"^[a-zA-Z0-9_-]+$").unwrap());
516
517pub struct AlphaDash;
519
520pub const fn alpha_dash() -> AlphaDash {
521 AlphaDash
522}
523
524impl Rule for AlphaDash {
525 fn validate(&self, field: &str, value: &Value, _data: &Value) -> Result<(), String> {
526 if value.is_null() {
527 return Ok(());
528 }
529
530 match value.as_str() {
531 Some(s) if ALPHA_DASH_REGEX.is_match(s) => Ok(()),
532 _ => Err(
533 translate_validation("validation.alpha_dash", &[("attribute", field)])
534 .unwrap_or_else(|| {
535 format!(
536 "The {field} field must only contain letters, numbers, dashes, and underscores."
537 )
538 }),
539 ),
540 }
541 }
542
543 fn name(&self) -> &'static str {
544 "alpha_dash"
545 }
546}
547
548pub struct Confirmed {
554 confirmation_field: String,
555}
556
557pub fn confirmed() -> Confirmed {
558 Confirmed {
559 confirmation_field: String::new(), }
561}
562
563impl Rule for Confirmed {
564 fn validate(&self, field: &str, value: &Value, data: &Value) -> Result<(), String> {
565 if value.is_null() {
566 return Ok(());
567 }
568
569 let confirmation_field = if self.confirmation_field.is_empty() {
570 format!("{field}_confirmation")
571 } else {
572 self.confirmation_field.clone()
573 };
574
575 let confirmation_value = data.get(&confirmation_field);
576 if confirmation_value == Some(value) {
577 Ok(())
578 } else {
579 Err(
580 translate_validation("validation.confirmed", &[("attribute", field)])
581 .unwrap_or_else(|| format!("The {field} confirmation does not match.")),
582 )
583 }
584 }
585
586 fn name(&self) -> &'static str {
587 "confirmed"
588 }
589}
590
591pub struct In {
593 values: Vec<Value>,
594}
595
596pub fn in_array<I, V>(values: I) -> In
597where
598 I: IntoIterator<Item = V>,
599 V: Into<Value>,
600{
601 In {
602 values: values.into_iter().map(|v| v.into()).collect(),
603 }
604}
605
606impl Rule for In {
607 fn validate(&self, field: &str, value: &Value, _data: &Value) -> Result<(), String> {
608 if value.is_null() {
609 return Ok(());
610 }
611
612 if self.values.contains(value) {
613 Ok(())
614 } else {
615 Err(
616 translate_validation("validation.in", &[("attribute", field)])
617 .unwrap_or_else(|| format!("The selected {field} is invalid.")),
618 )
619 }
620 }
621
622 fn name(&self) -> &'static str {
623 "in"
624 }
625}
626
627pub struct NotIn {
629 values: Vec<Value>,
630}
631
632pub fn not_in<I, V>(values: I) -> NotIn
633where
634 I: IntoIterator<Item = V>,
635 V: Into<Value>,
636{
637 NotIn {
638 values: values.into_iter().map(|v| v.into()).collect(),
639 }
640}
641
642impl Rule for NotIn {
643 fn validate(&self, field: &str, value: &Value, _data: &Value) -> Result<(), String> {
644 if value.is_null() {
645 return Ok(());
646 }
647
648 if self.values.contains(value) {
649 Err(
650 translate_validation("validation.not_in", &[("attribute", field)])
651 .unwrap_or_else(|| format!("The selected {field} is invalid.")),
652 )
653 } else {
654 Ok(())
655 }
656 }
657
658 fn name(&self) -> &'static str {
659 "not_in"
660 }
661}
662
663pub struct Different {
665 other: String,
666}
667
668pub fn different(other: impl Into<String>) -> Different {
669 Different {
670 other: other.into(),
671 }
672}
673
674impl Rule for Different {
675 fn validate(&self, field: &str, value: &Value, data: &Value) -> Result<(), String> {
676 if value.is_null() {
677 return Ok(());
678 }
679
680 let other_value = data.get(&self.other);
681 if other_value == Some(value) {
682 Err(translate_validation(
683 "validation.different",
684 &[("attribute", field), ("other", &self.other)],
685 )
686 .unwrap_or_else(|| {
687 format!("The {} and {} fields must be different.", field, self.other)
688 }))
689 } else {
690 Ok(())
691 }
692 }
693
694 fn name(&self) -> &'static str {
695 "different"
696 }
697}
698
699pub struct Same {
701 other: String,
702}
703
704pub fn same(other: impl Into<String>) -> Same {
705 Same {
706 other: other.into(),
707 }
708}
709
710impl Rule for Same {
711 fn validate(&self, field: &str, value: &Value, data: &Value) -> Result<(), String> {
712 if value.is_null() {
713 return Ok(());
714 }
715
716 let other_value = data.get(&self.other);
717 if other_value != Some(value) {
718 Err(translate_validation(
719 "validation.same",
720 &[("attribute", field), ("other", &self.other)],
721 )
722 .unwrap_or_else(|| format!("The {} and {} fields must match.", field, self.other)))
723 } else {
724 Ok(())
725 }
726 }
727
728 fn name(&self) -> &'static str {
729 "same"
730 }
731}
732
733pub struct Date;
739
740pub const fn date() -> Date {
741 Date
742}
743
744impl Rule for Date {
745 fn validate(&self, field: &str, value: &Value, _data: &Value) -> Result<(), String> {
746 if value.is_null() {
747 return Ok(());
748 }
749
750 if let Some(s) = value.as_str() {
751 if chrono::NaiveDate::parse_from_str(s, "%Y-%m-%d").is_ok()
753 || chrono::NaiveDate::parse_from_str(s, "%d/%m/%Y").is_ok()
754 || chrono::NaiveDate::parse_from_str(s, "%m/%d/%Y").is_ok()
755 || chrono::DateTime::parse_from_rfc3339(s).is_ok()
756 {
757 return Ok(());
758 }
759 }
760
761 Err(
762 translate_validation("validation.date", &[("attribute", field)])
763 .unwrap_or_else(|| format!("The {field} field must be a valid date.")),
764 )
765 }
766
767 fn name(&self) -> &'static str {
768 "date"
769 }
770}
771
772pub struct Nullable;
778
779pub const fn nullable() -> Nullable {
780 Nullable
781}
782
783impl Rule for Nullable {
784 fn validate(&self, _field: &str, _value: &Value, _data: &Value) -> Result<(), String> {
785 Ok(())
787 }
788
789 fn name(&self) -> &'static str {
790 "nullable"
791 }
792}
793
794pub struct Accepted;
796
797pub const fn accepted() -> Accepted {
798 Accepted
799}
800
801impl Rule for Accepted {
802 fn validate(&self, field: &str, value: &Value, _data: &Value) -> Result<(), String> {
803 let accepted = match value {
804 Value::Bool(true) => true,
805 Value::Number(n) => n.as_i64() == Some(1),
806 Value::String(s) => {
807 matches!(s.to_lowercase().as_str(), "yes" | "on" | "1" | "true")
808 }
809 _ => false,
810 };
811
812 if accepted {
813 Ok(())
814 } else {
815 Err(
816 translate_validation("validation.accepted", &[("attribute", field)])
817 .unwrap_or_else(|| format!("The {field} field must be accepted.")),
818 )
819 }
820 }
821
822 fn name(&self) -> &'static str {
823 "accepted"
824 }
825}
826
827fn get_size(value: &Value) -> f64 {
833 match value {
834 Value::String(s) => s.chars().count() as f64,
835 Value::Array(a) => a.len() as f64,
836 Value::Object(o) => o.len() as f64,
837 Value::Number(n) => n.as_f64().unwrap_or(0.0),
838 _ => 0.0,
839 }
840}
841
842fn get_size_unit(value: &Value) -> &'static str {
844 match value {
845 Value::String(_) => "characters",
846 Value::Array(_) => "items",
847 Value::Object(_) => "items",
848 _ => "",
849 }
850}
851
852fn get_size_type_key(rule: &str, value: &Value) -> String {
854 let suffix = match value {
855 Value::String(_) => "string",
856 Value::Array(_) | Value::Object(_) => "array",
857 _ => "numeric",
858 };
859 format!("validation.{rule}.{suffix}")
860}
861
862#[cfg(test)]
863mod tests {
864 use super::*;
865 use serde_json::json;
866
867 #[test]
868 fn test_required() {
869 let rule = required();
870 let data = json!({});
871
872 assert!(rule.validate("name", &json!("John"), &data).is_ok());
873 assert!(rule.validate("name", &json!(null), &data).is_err());
874 assert!(rule.validate("name", &json!(""), &data).is_err());
875 assert!(rule.validate("name", &json!(" "), &data).is_err());
876 }
877
878 #[test]
879 fn test_email() {
880 let rule = email();
881 let data = json!({});
882
883 assert!(rule
884 .validate("email", &json!("test@example.com"), &data)
885 .is_ok());
886 assert!(rule.validate("email", &json!("invalid"), &data).is_err());
887 assert!(rule.validate("email", &json!(null), &data).is_ok());
888 }
889
890 #[test]
891 fn test_min_max() {
892 let data = json!({});
893
894 assert!(min(3).validate("name", &json!("John"), &data).is_ok());
896 assert!(min(5).validate("name", &json!("John"), &data).is_err());
897
898 assert!(max(5).validate("name", &json!("John"), &data).is_ok());
899 assert!(max(2).validate("name", &json!("John"), &data).is_err());
900
901 assert!(min(18).validate("age", &json!(25), &data).is_ok());
903 assert!(min(18).validate("age", &json!(15), &data).is_err());
904 }
905
906 #[test]
907 fn test_between() {
908 let rule = between(1, 10);
909 let data = json!({});
910
911 assert!(rule.validate("count", &json!(5), &data).is_ok());
912 assert!(rule.validate("count", &json!(0), &data).is_err());
913 assert!(rule.validate("count", &json!(11), &data).is_err());
914 }
915
916 #[test]
917 fn test_in_array() {
918 let rule = in_array(["active", "inactive", "pending"]);
919 let data = json!({});
920
921 assert!(rule.validate("status", &json!("active"), &data).is_ok());
922 assert!(rule.validate("status", &json!("unknown"), &data).is_err());
923 }
924
925 #[test]
926 fn test_confirmed() {
927 let rule = confirmed();
928 let data = json!({
929 "password": "secret123",
930 "password_confirmation": "secret123"
931 });
932
933 assert!(rule
934 .validate("password", &json!("secret123"), &data)
935 .is_ok());
936
937 let bad_data = json!({
938 "password": "secret123",
939 "password_confirmation": "different"
940 });
941 assert!(rule
942 .validate("password", &json!("secret123"), &bad_data)
943 .is_err());
944 }
945
946 #[test]
947 fn test_url() {
948 let rule = url();
949 let data = json!({});
950
951 assert!(rule
952 .validate("website", &json!("https://example.com"), &data)
953 .is_ok());
954 assert!(rule
955 .validate("website", &json!("http://example.com/path"), &data)
956 .is_ok());
957 assert!(rule
958 .validate("website", &json!("not-a-url"), &data)
959 .is_err());
960 }
961
962 #[test]
963 fn test_rules_call_translate_validation() {
964 fn mock(key: &str, params: &[(&str, &str)]) -> Option<String> {
965 let attr = params
966 .iter()
967 .find(|(k, _)| *k == "attribute")
968 .map(|(_, v)| *v)
969 .unwrap_or("?");
970 Some(format!("[translated] {key}: attr={attr}"))
971 }
972
973 let was_set = crate::validation::bridge::VALIDATION_TRANSLATOR
975 .set(mock as crate::validation::TranslatorFn)
976 .is_ok();
977
978 let result = required().validate("email", &json!(null), &json!({}));
979 assert!(result.is_err());
980
981 if was_set {
982 let msg = result.unwrap_err();
983 assert!(
984 msg.contains("[translated]"),
985 "Expected translated message, got: {msg}"
986 );
987 assert!(
988 msg.contains("validation.required"),
989 "Expected key in message, got: {msg}"
990 );
991 }
992 }
993
994 #[test]
995 fn test_string() {
996 let rule = string();
997 let data = json!({});
998
999 assert!(rule.validate("name", &json!("hello"), &data).is_ok());
1000 assert!(rule.validate("name", &json!(""), &data).is_ok());
1001 assert!(rule.validate("name", &json!(42), &data).is_err());
1002 assert!(rule.validate("name", &json!(true), &data).is_err());
1003 assert!(rule.validate("name", &json!([1, 2]), &data).is_err());
1004 assert!(rule.validate("name", &json!(null), &data).is_ok());
1006 }
1007
1008 #[test]
1009 fn test_integer() {
1010 let rule = integer();
1011 let data = json!({});
1012
1013 assert!(rule.validate("age", &json!(42), &data).is_ok());
1014 assert!(rule.validate("age", &json!(0), &data).is_ok());
1015 assert!(rule.validate("age", &json!(-5), &data).is_ok());
1016 assert!(rule.validate("age", &json!("123"), &data).is_ok());
1018 assert!(rule.validate("age", &json!(3.17), &data).is_err());
1020 assert!(rule.validate("age", &json!("hello"), &data).is_err());
1021 assert!(rule.validate("age", &json!(true), &data).is_err());
1022 assert!(rule.validate("age", &json!(null), &data).is_ok());
1024 }
1025
1026 #[test]
1027 fn test_numeric() {
1028 let rule = numeric();
1029 let data = json!({});
1030
1031 assert!(rule.validate("price", &json!(42), &data).is_ok());
1032 assert!(rule.validate("price", &json!(3.17), &data).is_ok());
1033 assert!(rule.validate("price", &json!(-10), &data).is_ok());
1034 assert!(rule.validate("price", &json!("42.5"), &data).is_ok());
1036 assert!(rule.validate("price", &json!("hello"), &data).is_err());
1037 assert!(rule.validate("price", &json!(true), &data).is_err());
1038 assert!(rule.validate("price", &json!(null), &data).is_ok());
1040 }
1041
1042 #[test]
1043 fn test_boolean() {
1044 let rule = boolean();
1045 let data = json!({});
1046
1047 assert!(rule.validate("active", &json!(true), &data).is_ok());
1048 assert!(rule.validate("active", &json!(false), &data).is_ok());
1049 assert!(rule.validate("active", &json!("true"), &data).is_ok());
1051 assert!(rule.validate("active", &json!("false"), &data).is_ok());
1052 assert!(rule.validate("active", &json!("yes"), &data).is_ok());
1053 assert!(rule.validate("active", &json!("no"), &data).is_ok());
1054 assert!(rule.validate("active", &json!("1"), &data).is_ok());
1055 assert!(rule.validate("active", &json!("0"), &data).is_ok());
1056 assert!(rule.validate("active", &json!(1), &data).is_ok());
1058 assert!(rule.validate("active", &json!(0), &data).is_ok());
1059 assert!(rule.validate("active", &json!("maybe"), &data).is_err());
1061 assert!(rule.validate("active", &json!(42), &data).is_err());
1063 assert!(rule.validate("active", &json!(null), &data).is_ok());
1065 }
1066
1067 #[test]
1068 fn test_array() {
1069 let rule = array();
1070 let data = json!({});
1071
1072 assert!(rule.validate("items", &json!([1, 2, 3]), &data).is_ok());
1073 assert!(rule.validate("items", &json!([]), &data).is_ok());
1074 assert!(rule.validate("items", &json!(["a", "b"]), &data).is_ok());
1075 assert!(rule.validate("items", &json!("not array"), &data).is_err());
1076 assert!(rule.validate("items", &json!(42), &data).is_err());
1077 assert!(rule.validate("items", &json!(true), &data).is_err());
1078 assert!(rule.validate("items", &json!(null), &data).is_ok());
1080 }
1081
1082 #[test]
1083 fn test_required_if() {
1084 let data = json!({"role": "admin"});
1085
1086 assert!(required_if("role", "admin")
1088 .validate("name", &json!(null), &data)
1089 .is_err());
1090 assert!(required_if("role", "admin")
1091 .validate("name", &json!(""), &data)
1092 .is_err());
1093 assert!(required_if("role", "admin")
1094 .validate("name", &json!("Alice"), &data)
1095 .is_ok());
1096
1097 assert!(required_if("role", "user")
1099 .validate("name", &json!(null), &data)
1100 .is_ok());
1101 assert!(required_if("role", "user")
1102 .validate("name", &json!(""), &data)
1103 .is_ok());
1104 }
1105
1106 #[test]
1107 fn test_different() {
1108 let data = json!({"other_field": "b"});
1109
1110 assert!(different("other_field")
1112 .validate("field", &json!("a"), &data)
1113 .is_ok());
1114 assert!(different("other_field")
1116 .validate("field", &json!("b"), &data)
1117 .is_err());
1118 assert!(different("other_field")
1120 .validate("field", &json!(null), &data)
1121 .is_ok());
1122 }
1123
1124 #[test]
1125 fn test_same() {
1126 let data = json!({"other_field": "a"});
1127
1128 assert!(same("other_field")
1130 .validate("field", &json!("a"), &data)
1131 .is_ok());
1132 assert!(same("other_field")
1134 .validate("field", &json!("b"), &data)
1135 .is_err());
1136 assert!(same("other_field")
1138 .validate("field", &json!(null), &data)
1139 .is_ok());
1140 }
1141
1142 #[test]
1143 fn test_regex() {
1144 let rule = regex(r"^\d{3}-\d{4}$");
1145 let data = json!({});
1146
1147 assert!(rule.validate("phone", &json!("123-4567"), &data).is_ok());
1148 assert!(rule.validate("phone", &json!("abc"), &data).is_err());
1149 assert!(rule.validate("phone", &json!("12-345"), &data).is_err());
1150 assert!(rule.validate("phone", &json!(null), &data).is_ok());
1152 }
1153
1154 #[test]
1155 fn test_alpha() {
1156 let rule = alpha();
1157 let data = json!({});
1158
1159 assert!(rule.validate("name", &json!("Hello"), &data).is_ok());
1160 assert!(rule.validate("name", &json!("abc"), &data).is_ok());
1161 assert!(rule.validate("name", &json!("Hello123"), &data).is_err());
1162 assert!(rule.validate("name", &json!("hello world"), &data).is_err());
1163 assert!(rule.validate("name", &json!(""), &data).is_err());
1165 assert!(rule.validate("name", &json!(null), &data).is_ok());
1167 }
1168
1169 #[test]
1170 fn test_alpha_num() {
1171 let rule = alpha_num();
1172 let data = json!({});
1173
1174 assert!(rule.validate("code", &json!("Hello123"), &data).is_ok());
1175 assert!(rule.validate("code", &json!("abc"), &data).is_ok());
1176 assert!(rule.validate("code", &json!("123"), &data).is_ok());
1177 assert!(rule.validate("code", &json!("Hello!@#"), &data).is_err());
1178 assert!(rule.validate("code", &json!("hello world"), &data).is_err());
1179 assert!(rule.validate("code", &json!(null), &data).is_ok());
1181 }
1182
1183 #[test]
1184 fn test_alpha_dash() {
1185 let rule = alpha_dash();
1186 let data = json!({});
1187
1188 assert!(rule
1189 .validate("slug", &json!("hello-world_123"), &data)
1190 .is_ok());
1191 assert!(rule.validate("slug", &json!("abc"), &data).is_ok());
1192 assert!(rule.validate("slug", &json!("a-b_c"), &data).is_ok());
1193 assert!(rule.validate("slug", &json!("hello world"), &data).is_err());
1194 assert!(rule.validate("slug", &json!("hello!@#"), &data).is_err());
1195 assert!(rule.validate("slug", &json!(null), &data).is_ok());
1197 }
1198
1199 #[test]
1200 fn test_not_in() {
1201 let rule = not_in(["banned", "blocked"]);
1202 let data = json!({});
1203
1204 assert!(rule.validate("status", &json!("active"), &data).is_ok());
1205 assert!(rule.validate("status", &json!("approved"), &data).is_ok());
1206 assert!(rule.validate("status", &json!("banned"), &data).is_err());
1207 assert!(rule.validate("status", &json!("blocked"), &data).is_err());
1208 assert!(rule.validate("status", &json!(null), &data).is_ok());
1210 }
1211
1212 #[test]
1213 fn test_date() {
1214 let rule = date();
1215 let data = json!({});
1216
1217 assert!(rule
1219 .validate("birthday", &json!("2024-01-15"), &data)
1220 .is_ok());
1221 assert!(rule
1223 .validate("created", &json!("2024-01-15T10:30:00Z"), &data)
1224 .is_ok());
1225 assert!(rule
1227 .validate("birthday", &json!("not-a-date"), &data)
1228 .is_err());
1229 assert!(rule
1230 .validate("birthday", &json!("2024-13-01"), &data)
1231 .is_err());
1232 assert!(rule.validate("birthday", &json!(42), &data).is_err());
1234 assert!(rule.validate("birthday", &json!(null), &data).is_ok());
1236 }
1237
1238 #[test]
1239 fn test_nullable() {
1240 let rule = nullable();
1241 let data = json!({});
1242
1243 assert!(rule.validate("field", &json!(null), &data).is_ok());
1245 assert!(rule.validate("field", &json!("value"), &data).is_ok());
1246 assert!(rule.validate("field", &json!(42), &data).is_ok());
1247 assert!(rule.validate("field", &json!(true), &data).is_ok());
1248 }
1249
1250 #[test]
1251 fn test_accepted() {
1252 let rule = accepted();
1253 let data = json!({});
1254
1255 assert!(rule.validate("terms", &json!(true), &data).is_ok());
1257 assert!(rule.validate("terms", &json!("yes"), &data).is_ok());
1258 assert!(rule.validate("terms", &json!("on"), &data).is_ok());
1259 assert!(rule.validate("terms", &json!("1"), &data).is_ok());
1260 assert!(rule.validate("terms", &json!("true"), &data).is_ok());
1261 assert!(rule.validate("terms", &json!(1), &data).is_ok());
1262
1263 assert!(rule.validate("terms", &json!(false), &data).is_err());
1265 assert!(rule.validate("terms", &json!("no"), &data).is_err());
1266 assert!(rule.validate("terms", &json!("off"), &data).is_err());
1267 assert!(rule.validate("terms", &json!(0), &data).is_err());
1268 assert!(rule.validate("terms", &json!(null), &data).is_err());
1269 assert!(rule.validate("terms", &json!("false"), &data).is_err());
1270 }
1271
1272 #[test]
1273 fn test_size_type_key_selection() {
1274 assert_eq!(
1276 get_size_type_key("min", &json!("hello")),
1277 "validation.min.string"
1278 );
1279 assert_eq!(
1280 get_size_type_key("max", &json!("hello")),
1281 "validation.max.string"
1282 );
1283 assert_eq!(
1284 get_size_type_key("between", &json!("hello")),
1285 "validation.between.string"
1286 );
1287
1288 assert_eq!(
1290 get_size_type_key("min", &json!(42)),
1291 "validation.min.numeric"
1292 );
1293 assert_eq!(
1294 get_size_type_key("max", &json!(42)),
1295 "validation.max.numeric"
1296 );
1297 assert_eq!(
1298 get_size_type_key("between", &json!(42)),
1299 "validation.between.numeric"
1300 );
1301
1302 assert_eq!(
1304 get_size_type_key("min", &json!([1, 2, 3])),
1305 "validation.min.array"
1306 );
1307 assert_eq!(
1308 get_size_type_key("max", &json!([1, 2, 3])),
1309 "validation.max.array"
1310 );
1311 assert_eq!(
1312 get_size_type_key("between", &json!([1, 2, 3])),
1313 "validation.between.array"
1314 );
1315 }
1316}