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