dcbor_pattern/pattern/value/
text_pattern.rs1use dcbor::prelude::*;
2
3use crate::pattern::{Matcher, Path, Pattern, vm::Instr};
4
5#[derive(Debug, Clone)]
7pub enum TextPattern {
8 Any,
10 Value(String),
12 Regex(regex::Regex),
14}
15
16impl PartialEq for TextPattern {
17 fn eq(&self, other: &Self) -> bool {
18 match (self, other) {
19 (TextPattern::Any, TextPattern::Any) => true,
20 (TextPattern::Value(a), TextPattern::Value(b)) => a == b,
21 (TextPattern::Regex(a), TextPattern::Regex(b)) => {
22 a.as_str() == b.as_str()
23 }
24 _ => false,
25 }
26 }
27}
28
29impl Eq for TextPattern {}
30
31impl std::hash::Hash for TextPattern {
32 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
33 match self {
34 TextPattern::Any => {
35 0u8.hash(state);
36 }
37 TextPattern::Value(s) => {
38 1u8.hash(state);
39 s.hash(state);
40 }
41 TextPattern::Regex(regex) => {
42 2u8.hash(state);
43 regex.as_str().hash(state);
45 }
46 }
47 }
48}
49
50impl TextPattern {
51 pub fn any() -> Self { TextPattern::Any }
53
54 pub fn value<T: Into<String>>(value: T) -> Self {
56 TextPattern::Value(value.into())
57 }
58
59 pub fn regex(regex: regex::Regex) -> Self { TextPattern::Regex(regex) }
61}
62
63impl Matcher for TextPattern {
64 fn paths(&self, haystack: &CBOR) -> Vec<Path> {
65 let is_hit = haystack.as_text().is_some_and(|value| match self {
66 TextPattern::Any => true,
67 TextPattern::Value(want) => value == *want,
68 TextPattern::Regex(regex) => regex.is_match(value),
69 });
70
71 if is_hit {
72 vec![vec![haystack.clone()]]
73 } else {
74 vec![]
75 }
76 }
77
78 fn compile(
79 &self,
80 code: &mut Vec<Instr>,
81 literals: &mut Vec<Pattern>,
82 _captures: &mut Vec<String>,
83 ) {
84 let idx = literals.len();
85 literals.push(Pattern::Value(crate::pattern::ValuePattern::Text(
86 self.clone(),
87 )));
88 code.push(Instr::MatchPredicate(idx));
89 }
90}
91
92impl std::fmt::Display for TextPattern {
93 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94 match self {
95 TextPattern::Any => write!(f, "text"),
96 TextPattern::Value(value) => {
97 let escaped = value.replace("\\", "\\\\").replace("\"", "\\\"");
98 write!(f, "\"{}\"", escaped)
99 }
100 TextPattern::Regex(regex) => write!(f, "/{}/", regex),
101 }
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[test]
110 fn test_text_pattern_display() {
111 assert_eq!(TextPattern::any().to_string(), "text");
112 assert_eq!(TextPattern::value("Hello").to_string(), "\"Hello\"");
113 assert_eq!(
114 TextPattern::regex(regex::Regex::new(r"^\d+$").unwrap())
115 .to_string(),
116 "/^\\d+$/"
117 );
118 }
119
120 #[test]
121 fn test_text_pattern_matching() {
122 let hello_cbor = "Hello".to_cbor();
123 let world_cbor = "World".to_cbor();
124 let digits_cbor = "12345".to_cbor();
125 let mixed_cbor = "Hello123".to_cbor();
126 let number_cbor = 42.to_cbor();
127
128 let any_pattern = TextPattern::any();
130 assert!(any_pattern.matches(&hello_cbor));
131 assert!(any_pattern.matches(&world_cbor));
132 assert!(any_pattern.matches(&digits_cbor));
133 assert!(any_pattern.matches(&mixed_cbor));
134 assert!(!any_pattern.matches(&number_cbor));
135
136 let hello_pattern = TextPattern::value("Hello");
138 assert!(hello_pattern.matches(&hello_cbor));
139 assert!(!hello_pattern.matches(&world_cbor));
140 assert!(!hello_pattern.matches(&number_cbor));
141
142 let digits_regex = regex::Regex::new(r"^\d+$").unwrap();
144 let digits_pattern = TextPattern::regex(digits_regex);
145 assert!(!digits_pattern.matches(&hello_cbor));
146 assert!(!digits_pattern.matches(&world_cbor));
147 assert!(digits_pattern.matches(&digits_cbor));
148 assert!(!digits_pattern.matches(&mixed_cbor));
149 assert!(!digits_pattern.matches(&number_cbor));
150
151 let word_regex = regex::Regex::new(r"^[A-Za-z]+$").unwrap();
152 let word_pattern = TextPattern::regex(word_regex);
153 assert!(word_pattern.matches(&hello_cbor));
154 assert!(word_pattern.matches(&world_cbor));
155 assert!(!word_pattern.matches(&digits_cbor));
156 assert!(!word_pattern.matches(&mixed_cbor));
157 assert!(!word_pattern.matches(&number_cbor));
158 }
159
160 #[test]
161 fn test_text_pattern_paths() {
162 let hello_cbor = "Hello".to_cbor();
163 let number_cbor = 42.to_cbor();
164
165 let any_pattern = TextPattern::any();
166 let hello_paths = any_pattern.paths(&hello_cbor);
167 assert_eq!(hello_paths.len(), 1);
168 assert_eq!(hello_paths[0].len(), 1);
169 assert_eq!(hello_paths[0][0], hello_cbor);
170
171 let number_paths = any_pattern.paths(&number_cbor);
172 assert_eq!(number_paths.len(), 0);
173
174 let hello_pattern = TextPattern::value("Hello");
175 let paths = hello_pattern.paths(&hello_cbor);
176 assert_eq!(paths.len(), 1);
177 assert_eq!(paths[0].len(), 1);
178 assert_eq!(paths[0][0], hello_cbor);
179
180 let no_match_paths = hello_pattern.paths(&number_cbor);
181 assert_eq!(no_match_paths.len(), 0);
182 }
183
184 #[test]
185 fn test_text_pattern_equality() {
186 let any1 = TextPattern::any();
187 let any2 = TextPattern::any();
188 let value1 = TextPattern::value("test");
189 let value2 = TextPattern::value("test");
190 let value3 = TextPattern::value("different");
191 let regex1 = TextPattern::regex(regex::Regex::new(r"\d+").unwrap());
192 let regex2 = TextPattern::regex(regex::Regex::new(r"\d+").unwrap());
193 let regex3 = TextPattern::regex(regex::Regex::new(r"[a-z]+").unwrap());
194
195 assert_eq!(any1, any2);
197 assert_eq!(value1, value2);
198 assert_eq!(regex1, regex2);
199
200 assert_ne!(any1, value1);
202 assert_ne!(value1, value3);
203 assert_ne!(regex1, regex3);
204 assert_ne!(value1, regex1);
205 }
206
207 #[test]
208 fn test_text_pattern_regex_complex() {
209 let email_cbor = "test@example.com".to_cbor();
210 let not_email_cbor = "not_an_email".to_cbor();
211
212 let email_regex = regex::Regex::new(r"^[^@]+@[^@]+\.[^@]+$").unwrap();
214 let email_pattern = TextPattern::regex(email_regex);
215
216 assert!(email_pattern.matches(&email_cbor));
217 assert!(!email_pattern.matches(¬_email_cbor));
218 }
219}