dcbor_pattern/pattern/value/
date_pattern.rs1use std::ops::RangeInclusive;
2
3use dcbor::{Date, prelude::*};
4
5use crate::pattern::{Matcher, Path, Pattern, vm::Instr};
6
7#[derive(Debug, Clone)]
9pub enum DatePattern {
10 Any,
12 Value(Date),
14 Range(RangeInclusive<Date>),
16 Earliest(Date),
18 Latest(Date),
20 String(String),
22 Regex(regex::Regex),
25}
26
27impl PartialEq for DatePattern {
28 fn eq(&self, other: &Self) -> bool {
29 match (self, other) {
30 (DatePattern::Any, DatePattern::Any) => true,
31 (DatePattern::Value(a), DatePattern::Value(b)) => a == b,
32 (DatePattern::Range(a), DatePattern::Range(b)) => a == b,
33 (DatePattern::Earliest(a), DatePattern::Earliest(b)) => a == b,
34 (DatePattern::Latest(a), DatePattern::Latest(b)) => a == b,
35 (DatePattern::String(a), DatePattern::String(b)) => a == b,
36 (DatePattern::Regex(a), DatePattern::Regex(b)) => {
37 a.as_str() == b.as_str()
38 }
39 _ => false,
40 }
41 }
42}
43
44impl Eq for DatePattern {}
45
46impl std::hash::Hash for DatePattern {
47 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
48 match self {
49 DatePattern::Any => {
50 0u8.hash(state);
51 }
52 DatePattern::Value(date) => {
53 1u8.hash(state);
54 date.hash(state);
55 }
56 DatePattern::Range(range) => {
57 2u8.hash(state);
58 range.start().hash(state);
59 range.end().hash(state);
60 }
61 DatePattern::Earliest(date) => {
62 3u8.hash(state);
63 date.hash(state);
64 }
65 DatePattern::Latest(date) => {
66 4u8.hash(state);
67 date.hash(state);
68 }
69 DatePattern::String(iso_string) => {
70 5u8.hash(state);
71 iso_string.hash(state);
72 }
73 DatePattern::Regex(regex) => {
74 6u8.hash(state);
75 regex.as_str().hash(state);
77 }
78 }
79 }
80}
81
82impl DatePattern {
83 pub fn any() -> Self {
85 DatePattern::Any
86 }
87
88 pub fn value(date: Date) -> Self {
90 DatePattern::Value(date)
91 }
92
93 pub fn range(range: RangeInclusive<Date>) -> Self {
96 DatePattern::Range(range)
97 }
98
99 pub fn earliest(date: Date) -> Self {
102 DatePattern::Earliest(date)
103 }
104
105 pub fn latest(date: Date) -> Self {
108 DatePattern::Latest(date)
109 }
110
111 pub fn string(iso_string: impl Into<String>) -> Self {
114 DatePattern::String(iso_string.into())
115 }
116
117 pub fn regex(regex: regex::Regex) -> Self {
120 DatePattern::Regex(regex)
121 }
122}
123
124impl Matcher for DatePattern {
125 fn paths(&self, haystack: &CBOR) -> Vec<Path> {
126 if let CBORCase::Tagged(tag, _) = haystack.as_case() {
128 if tag.value() == 1 {
130 if let Ok(date) = Date::try_from(haystack.clone()) {
132 let is_hit = match self {
133 DatePattern::Any => true,
134 DatePattern::Value(expected_date) => {
135 date == *expected_date
136 }
137 DatePattern::Range(range) => range.contains(&date),
138 DatePattern::Earliest(earliest) => date >= *earliest,
139 DatePattern::Latest(latest) => date <= *latest,
140 DatePattern::String(expected_string) => {
141 date.to_string() == *expected_string
142 }
143 DatePattern::Regex(regex) => {
144 regex.is_match(&date.to_string())
145 }
146 };
147
148 if is_hit {
149 vec![vec![haystack.clone()]]
150 } else {
151 vec![]
152 }
153 } else {
154 vec![]
156 }
157 } else {
158 vec![]
160 }
161 } else {
162 vec![]
164 }
165 }
166
167 fn compile(
168 &self,
169 code: &mut Vec<Instr>,
170 literals: &mut Vec<Pattern>,
171 _captures: &mut Vec<String>,
172 ) {
173 let idx = literals.len();
174 literals.push(Pattern::Value(crate::pattern::ValuePattern::Date(
175 self.clone(),
176 )));
177 code.push(Instr::MatchPredicate(idx));
178 }
179}
180
181impl std::fmt::Display for DatePattern {
182 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183 match self {
184 DatePattern::Any => write!(f, "date"),
185 DatePattern::Value(date) => write!(f, "date'{}'", date),
186 DatePattern::Range(range) => {
187 write!(f, "date'{}...{}'", range.start(), range.end())
188 }
189 DatePattern::Earliest(date) => write!(f, "date'{}...'", date),
190 DatePattern::Latest(date) => write!(f, "date'...{}'", date),
191 DatePattern::String(iso_string) => {
192 write!(f, "date'{}'", iso_string)
193 }
194 DatePattern::Regex(regex) => {
195 write!(f, "date'/{}/'", regex.as_str())
196 }
197 }
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204
205 #[test]
206 fn test_date_pattern_any() {
207 let date = Date::from_ymd(2023, 12, 25);
209 let cbor = CBOR::from(date);
210
211 let pattern = DatePattern::any();
212 let paths = pattern.paths(&cbor);
213 assert_eq!(paths.len(), 1);
214 assert_eq!(paths[0], vec![cbor.clone()]);
215
216 let text_cbor = CBOR::from("test");
218 let paths = pattern.paths(&text_cbor);
219 assert!(paths.is_empty());
220 }
221
222 #[test]
223 fn test_date_pattern_value() {
224 let date = Date::from_ymd(2023, 12, 25);
225 let cbor = CBOR::from(date.clone());
226
227 let pattern = DatePattern::value(date.clone());
229 let paths = pattern.paths(&cbor);
230 assert_eq!(paths.len(), 1);
231 assert_eq!(paths[0], vec![cbor.clone()]);
232
233 let other_date = Date::from_ymd(2024, 1, 1);
235 let pattern = DatePattern::value(other_date);
236 let paths = pattern.paths(&cbor);
237 assert!(paths.is_empty());
238 }
239
240 #[test]
241 fn test_date_pattern_range() {
242 let date1 = Date::from_ymd(2023, 12, 20);
243 let date2 = Date::from_ymd(2023, 12, 25);
244 let date3 = Date::from_ymd(2023, 12, 30);
245
246 let test_date = date2.clone();
247 let cbor = CBOR::from(test_date);
248
249 let pattern = DatePattern::range(date1.clone()..=date3.clone());
251 let paths = pattern.paths(&cbor);
252 assert_eq!(paths.len(), 1);
253
254 let early_date = Date::from_ymd(2023, 12, 10);
256 let _late_date = Date::from_ymd(2024, 1, 10);
257 let pattern = DatePattern::range(early_date..=date1);
258 let paths = pattern.paths(&cbor);
259 assert!(paths.is_empty());
260 }
261
262 #[test]
263 fn test_date_pattern_earliest() {
264 let early_date = Date::from_ymd(2023, 12, 20);
265 let test_date = Date::from_ymd(2023, 12, 25);
266 let cbor = CBOR::from(test_date);
267
268 let pattern = DatePattern::earliest(early_date);
270 let paths = pattern.paths(&cbor);
271 assert_eq!(paths.len(), 1);
272
273 let late_date = Date::from_ymd(2023, 12, 30);
275 let pattern = DatePattern::earliest(late_date);
276 let paths = pattern.paths(&cbor);
277 assert!(paths.is_empty());
278 }
279
280 #[test]
281 fn test_date_pattern_latest() {
282 let late_date = Date::from_ymd(2023, 12, 30);
283 let test_date = Date::from_ymd(2023, 12, 25);
284 let cbor = CBOR::from(test_date);
285
286 let pattern = DatePattern::latest(late_date);
288 let paths = pattern.paths(&cbor);
289 assert_eq!(paths.len(), 1);
290
291 let early_date = Date::from_ymd(2023, 12, 20);
293 let pattern = DatePattern::latest(early_date);
294 let paths = pattern.paths(&cbor);
295 assert!(paths.is_empty());
296 }
297
298 #[test]
299 fn test_date_pattern_iso8601() {
300 let date = Date::from_ymd(2023, 12, 25);
301 let cbor = CBOR::from(date.clone());
302 let iso_string = date.to_string();
303
304 let pattern = DatePattern::string(iso_string.clone());
306 let paths = pattern.paths(&cbor);
307 assert_eq!(paths.len(), 1);
308
309 let pattern = DatePattern::string("2024-01-01T00:00:00Z");
311 let paths = pattern.paths(&cbor);
312 assert!(paths.is_empty());
313 }
314
315 #[test]
316 fn test_date_pattern_regex() {
317 let date = Date::from_ymd(2023, 12, 25);
318 let cbor = CBOR::from(date);
319
320 let regex = regex::Regex::new(r"2023-.*").unwrap();
322 let pattern = DatePattern::regex(regex);
323 let paths = pattern.paths(&cbor);
324 assert_eq!(paths.len(), 1);
325
326 let regex = regex::Regex::new(r"2024-.*").unwrap();
328 let pattern = DatePattern::regex(regex);
329 let paths = pattern.paths(&cbor);
330 assert!(paths.is_empty());
331 }
332
333 #[test]
334 fn test_date_pattern_display() {
335 assert_eq!(DatePattern::any().to_string(), "date");
336
337 let date = Date::from_ymd(2023, 12, 25);
338 assert_eq!(
339 DatePattern::value(date.clone()).to_string(),
340 format!("date'{}'", date)
341 );
342
343 let date1 = Date::from_ymd(2023, 12, 20);
344 let date2 = Date::from_ymd(2023, 12, 30);
345 assert_eq!(
346 DatePattern::range(date1.clone()..=date2.clone()).to_string(),
347 format!("date'{}...{}'", date1, date2)
348 );
349
350 assert_eq!(
351 DatePattern::earliest(date.clone()).to_string(),
352 format!("date'{}...'", date)
353 );
354 assert_eq!(
355 DatePattern::latest(date.clone()).to_string(),
356 format!("date'...{}'", date)
357 );
358
359 assert_eq!(
360 DatePattern::string("2023-12-25T00:00:00Z").to_string(),
361 "date'2023-12-25T00:00:00Z'"
362 );
363
364 let regex = regex::Regex::new(r"2023-.*").unwrap();
365 assert_eq!(DatePattern::regex(regex).to_string(), "date'/2023-.*/'");
366 }
367}