rusdantic_core/rules/
pattern.rs1use crate::error::{PathSegment, ValidationError, ValidationErrors};
8use crate::rules::AsStr;
9
10pub fn anchor_pattern(pattern: &str) -> String {
26 let starts = pattern.starts_with('^');
27 let ends = pattern.ends_with('$');
28 match (starts, ends) {
29 (true, true) => pattern.to_string(),
30 (true, false) => format!("{}$", pattern),
31 (false, true) => format!("^{}", pattern),
32 (false, false) => format!("^(?:{})$", pattern),
33 }
34}
35
36pub fn validate_pattern<T: AsStr>(
37 value: &T,
38 regex: ®ex::Regex,
39 pattern_str: &str,
40 path: &[PathSegment],
41 errors: &mut ValidationErrors,
42) {
43 let s = value.as_str_ref();
44
45 if !regex.is_match(s) {
46 errors.add(
47 ValidationError::new(
48 "pattern",
49 format!("must match pattern '{}'", pattern_str),
50 )
51 .with_path(path.to_vec())
52 .with_param("pattern", serde_json::json!(pattern_str)),
53 );
54 }
55}
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60 use regex::Regex;
61
62 fn path(name: &str) -> Vec<PathSegment> {
63 vec![PathSegment::Field(name.to_string())]
64 }
65
66 #[test]
67 fn test_pattern_matches() {
68 let re = Regex::new(r"^[a-z]+$").unwrap();
69 let mut errors = ValidationErrors::new();
70 validate_pattern(
71 &"hello".to_string(),
72 &re,
73 "^[a-z]+$",
74 &path("f"),
75 &mut errors,
76 );
77 assert!(errors.is_empty());
78 }
79
80 #[test]
81 fn test_pattern_no_match() {
82 let re = Regex::new(r"^[a-z]+$").unwrap();
83 let mut errors = ValidationErrors::new();
84 validate_pattern(
85 &"Hello123".to_string(),
86 &re,
87 "^[a-z]+$",
88 &path("f"),
89 &mut errors,
90 );
91 assert_eq!(errors.len(), 1);
92 assert_eq!(errors.errors()[0].code, "pattern");
93 assert_eq!(errors.errors()[0].params["pattern"], "^[a-z]+$");
94 }
95
96 #[test]
97 fn test_pattern_with_digits() {
98 let re = Regex::new(r"^\d{3}-\d{4}$").unwrap();
99 let mut errors = ValidationErrors::new();
100 validate_pattern(
101 &"123-4567".to_string(),
102 &re,
103 r"^\d{3}-\d{4}$",
104 &path("phone"),
105 &mut errors,
106 );
107 assert!(errors.is_empty());
108 }
109
110 #[test]
111 fn test_pattern_empty_string() {
112 let re = Regex::new(r"^.+$").unwrap();
113 let mut errors = ValidationErrors::new();
114 validate_pattern(&"".to_string(), &re, "^.+$", &path("f"), &mut errors);
115 assert_eq!(errors.len(), 1);
116 }
117
118 #[test]
119 fn test_pattern_unicode() {
120 let re = Regex::new(r"^\p{L}+$").unwrap();
121 let mut errors = ValidationErrors::new();
122 validate_pattern(
123 &"héllo".to_string(),
124 &re,
125 r"^\p{L}+$",
126 &path("f"),
127 &mut errors,
128 );
129 assert!(errors.is_empty());
130 }
131}