use std::ops::RangeInclusive;
use dcbor::{Date, prelude::*};
use crate::pattern::{Matcher, Path, Pattern, vm::Instr};
#[derive(Debug, Clone)]
pub enum DatePattern {
Any,
Value(Date),
Range(RangeInclusive<Date>),
Earliest(Date),
Latest(Date),
String(String),
Regex(regex::Regex),
}
impl PartialEq for DatePattern {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(DatePattern::Any, DatePattern::Any) => true,
(DatePattern::Value(a), DatePattern::Value(b)) => a == b,
(DatePattern::Range(a), DatePattern::Range(b)) => a == b,
(DatePattern::Earliest(a), DatePattern::Earliest(b)) => a == b,
(DatePattern::Latest(a), DatePattern::Latest(b)) => a == b,
(DatePattern::String(a), DatePattern::String(b)) => a == b,
(DatePattern::Regex(a), DatePattern::Regex(b)) => {
a.as_str() == b.as_str()
}
_ => false,
}
}
}
impl Eq for DatePattern {}
impl std::hash::Hash for DatePattern {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
match self {
DatePattern::Any => {
0u8.hash(state);
}
DatePattern::Value(date) => {
1u8.hash(state);
date.hash(state);
}
DatePattern::Range(range) => {
2u8.hash(state);
range.start().hash(state);
range.end().hash(state);
}
DatePattern::Earliest(date) => {
3u8.hash(state);
date.hash(state);
}
DatePattern::Latest(date) => {
4u8.hash(state);
date.hash(state);
}
DatePattern::String(iso_string) => {
5u8.hash(state);
iso_string.hash(state);
}
DatePattern::Regex(regex) => {
6u8.hash(state);
regex.as_str().hash(state);
}
}
}
}
impl DatePattern {
pub fn any() -> Self { DatePattern::Any }
pub fn value(date: Date) -> Self { DatePattern::Value(date) }
pub fn range(range: RangeInclusive<Date>) -> Self {
DatePattern::Range(range)
}
pub fn earliest(date: Date) -> Self { DatePattern::Earliest(date) }
pub fn latest(date: Date) -> Self { DatePattern::Latest(date) }
pub fn string(iso_string: impl Into<String>) -> Self {
DatePattern::String(iso_string.into())
}
pub fn regex(regex: regex::Regex) -> Self { DatePattern::Regex(regex) }
}
impl Matcher for DatePattern {
fn paths(&self, haystack: &CBOR) -> Vec<Path> {
if let CBORCase::Tagged(tag, _) = haystack.as_case() {
if tag.value() == 1 {
if let Ok(date) = Date::try_from(haystack.clone()) {
let is_hit = match self {
DatePattern::Any => true,
DatePattern::Value(expected_date) => {
date == *expected_date
}
DatePattern::Range(range) => range.contains(&date),
DatePattern::Earliest(earliest) => date >= *earliest,
DatePattern::Latest(latest) => date <= *latest,
DatePattern::String(expected_string) => {
date.to_string() == *expected_string
}
DatePattern::Regex(regex) => {
regex.is_match(&date.to_string())
}
};
if is_hit {
vec![vec![haystack.clone()]]
} else {
vec![]
}
} else {
vec![]
}
} else {
vec![]
}
} else {
vec![]
}
}
fn compile(
&self,
code: &mut Vec<Instr>,
literals: &mut Vec<Pattern>,
_captures: &mut Vec<String>,
) {
let idx = literals.len();
literals.push(Pattern::Value(crate::pattern::ValuePattern::Date(
self.clone(),
)));
code.push(Instr::MatchPredicate(idx));
}
}
impl std::fmt::Display for DatePattern {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DatePattern::Any => write!(f, "date"),
DatePattern::Value(date) => write!(f, "date'{}'", date),
DatePattern::Range(range) => {
write!(f, "date'{}...{}'", range.start(), range.end())
}
DatePattern::Earliest(date) => write!(f, "date'{}...'", date),
DatePattern::Latest(date) => write!(f, "date'...{}'", date),
DatePattern::String(iso_string) => {
write!(f, "date'{}'", iso_string)
}
DatePattern::Regex(regex) => {
write!(f, "date'/{}/'", regex.as_str())
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_date_pattern_any() {
let date = Date::from_ymd(2023, 12, 25);
let cbor = CBOR::from(date);
let pattern = DatePattern::any();
let paths = pattern.paths(&cbor);
assert_eq!(paths.len(), 1);
assert_eq!(paths[0], vec![cbor.clone()]);
let text_cbor = CBOR::from("test");
let paths = pattern.paths(&text_cbor);
assert!(paths.is_empty());
}
#[test]
fn test_date_pattern_value() {
let date = Date::from_ymd(2023, 12, 25);
let cbor = CBOR::from(date);
let pattern = DatePattern::value(date);
let paths = pattern.paths(&cbor);
assert_eq!(paths.len(), 1);
assert_eq!(paths[0], vec![cbor.clone()]);
let other_date = Date::from_ymd(2024, 1, 1);
let pattern = DatePattern::value(other_date);
let paths = pattern.paths(&cbor);
assert!(paths.is_empty());
}
#[test]
fn test_date_pattern_range() {
let date1 = Date::from_ymd(2023, 12, 20);
let date2 = Date::from_ymd(2023, 12, 25);
let date3 = Date::from_ymd(2023, 12, 30);
let test_date = date2;
let cbor = CBOR::from(test_date);
let pattern = DatePattern::range(date1..=date3);
let paths = pattern.paths(&cbor);
assert_eq!(paths.len(), 1);
let early_date = Date::from_ymd(2023, 12, 10);
let _late_date = Date::from_ymd(2024, 1, 10);
let pattern = DatePattern::range(early_date..=date1);
let paths = pattern.paths(&cbor);
assert!(paths.is_empty());
}
#[test]
fn test_date_pattern_earliest() {
let early_date = Date::from_ymd(2023, 12, 20);
let test_date = Date::from_ymd(2023, 12, 25);
let cbor = CBOR::from(test_date);
let pattern = DatePattern::earliest(early_date);
let paths = pattern.paths(&cbor);
assert_eq!(paths.len(), 1);
let late_date = Date::from_ymd(2023, 12, 30);
let pattern = DatePattern::earliest(late_date);
let paths = pattern.paths(&cbor);
assert!(paths.is_empty());
}
#[test]
fn test_date_pattern_latest() {
let late_date = Date::from_ymd(2023, 12, 30);
let test_date = Date::from_ymd(2023, 12, 25);
let cbor = CBOR::from(test_date);
let pattern = DatePattern::latest(late_date);
let paths = pattern.paths(&cbor);
assert_eq!(paths.len(), 1);
let early_date = Date::from_ymd(2023, 12, 20);
let pattern = DatePattern::latest(early_date);
let paths = pattern.paths(&cbor);
assert!(paths.is_empty());
}
#[test]
fn test_date_pattern_iso8601() {
let date = Date::from_ymd(2023, 12, 25);
let cbor = CBOR::from(date);
let iso_string = date.to_string();
let pattern = DatePattern::string(iso_string.clone());
let paths = pattern.paths(&cbor);
assert_eq!(paths.len(), 1);
let pattern = DatePattern::string("2024-01-01T00:00:00Z");
let paths = pattern.paths(&cbor);
assert!(paths.is_empty());
}
#[test]
fn test_date_pattern_regex() {
let date = Date::from_ymd(2023, 12, 25);
let cbor = CBOR::from(date);
let regex = regex::Regex::new(r"2023-.*").unwrap();
let pattern = DatePattern::regex(regex);
let paths = pattern.paths(&cbor);
assert_eq!(paths.len(), 1);
let regex = regex::Regex::new(r"2024-.*").unwrap();
let pattern = DatePattern::regex(regex);
let paths = pattern.paths(&cbor);
assert!(paths.is_empty());
}
#[test]
fn test_date_pattern_display() {
assert_eq!(DatePattern::any().to_string(), "date");
let date = Date::from_ymd(2023, 12, 25);
assert_eq!(
DatePattern::value(date).to_string(),
format!("date'{}'", date)
);
let date1 = Date::from_ymd(2023, 12, 20);
let date2 = Date::from_ymd(2023, 12, 30);
assert_eq!(
DatePattern::range(date1..=date2).to_string(),
format!("date'{}...{}'", date1, date2)
);
assert_eq!(
DatePattern::earliest(date).to_string(),
format!("date'{}...'", date)
);
assert_eq!(
DatePattern::latest(date).to_string(),
format!("date'...{}'", date)
);
assert_eq!(
DatePattern::string("2023-12-25T00:00:00Z").to_string(),
"date'2023-12-25T00:00:00Z'"
);
let regex = regex::Regex::new(r"2023-.*").unwrap();
assert_eq!(DatePattern::regex(regex).to_string(), "date'/2023-.*/'");
}
}