use std::{collections::HashMap, ops::RangeInclusive};
use bc_envelope::prelude::*;
use crate::{
Pattern,
pattern::{Matcher, Path, compile_as_atomic, leaf::LeafPattern, vm::Instr},
};
#[derive(Debug, Clone)]
pub struct DatePattern(dcbor_pattern::DatePattern);
impl PartialEq for DatePattern {
fn eq(&self, other: &Self) -> bool { self.0 == other.0 }
}
impl Eq for DatePattern {}
impl std::hash::Hash for DatePattern {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) { self.0.hash(state); }
}
impl DatePattern {
pub fn any() -> Self { Self(dcbor_pattern::DatePattern::any()) }
pub fn value(date: Date) -> Self {
Self(dcbor_pattern::DatePattern::value(date))
}
pub fn range(range: RangeInclusive<Date>) -> Self {
Self(dcbor_pattern::DatePattern::range(range))
}
pub fn earliest(date: Date) -> Self {
Self(dcbor_pattern::DatePattern::earliest(date))
}
pub fn latest(date: Date) -> Self {
Self(dcbor_pattern::DatePattern::latest(date))
}
pub fn string(iso_string: impl Into<String>) -> Self {
Self(dcbor_pattern::DatePattern::string(iso_string))
}
pub fn regex(regex: regex::Regex) -> Self {
Self(dcbor_pattern::DatePattern::regex(regex))
}
pub fn from_dcbor_pattern(
dcbor_pattern: dcbor_pattern::DatePattern,
) -> Self {
Self(dcbor_pattern)
}
}
impl Matcher for DatePattern {
fn paths_with_captures(
&self,
haystack: &Envelope,
) -> (Vec<Path>, HashMap<String, Vec<Path>>) {
if let Some(cbor) = haystack.subject().as_leaf() {
let dcbor_paths = dcbor_pattern::Matcher::paths(&self.0, &cbor);
if !dcbor_paths.is_empty() {
let envelope_paths = vec![vec![haystack.clone()]];
let envelope_captures = HashMap::new(); (envelope_paths, envelope_captures)
} else {
(vec![], HashMap::new())
}
} else {
(vec![], HashMap::new())
}
}
fn compile(
&self,
code: &mut Vec<Instr>,
literals: &mut Vec<Pattern>,
captures: &mut Vec<String>,
) {
compile_as_atomic(
&Pattern::Leaf(LeafPattern::Date(self.clone())),
code,
literals,
captures,
);
}
}
impl std::fmt::Display for DatePattern {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[cfg(test)]
mod tests {
use dcbor_parse::parse_dcbor_item;
use super::*;
#[test]
fn test_date_pattern_any() {
let date = Date::from_ymd(2023, 12, 25);
let envelope = Envelope::new(date);
let pattern = DatePattern::any();
let paths = pattern.paths(&envelope);
assert_eq!(paths.len(), 1);
assert_eq!(paths[0], vec![envelope.clone()]);
let text_envelope = Envelope::new("test");
let paths = pattern.paths(&text_envelope);
assert!(paths.is_empty());
}
#[test]
fn test_date_pattern_specific() {
let date = Date::from_ymd(2023, 12, 25);
let envelope = Envelope::new(date);
let pattern = DatePattern::value(date);
let paths = pattern.paths(&envelope);
assert_eq!(paths.len(), 1);
let different_date = Date::from_ymd(2023, 12, 24);
let pattern = DatePattern::value(different_date);
let paths = pattern.paths(&envelope);
assert!(paths.is_empty());
}
#[test]
fn test_date_pattern_range() {
let date = Date::from_ymd(2023, 12, 25);
let envelope = Envelope::new(date);
let start = Date::from_ymd(2023, 12, 20);
let end = Date::from_ymd(2023, 12, 30);
let pattern = DatePattern::range(start..=end);
let paths = pattern.paths(&envelope);
assert_eq!(paths.len(), 1);
let start = Date::from_ymd(2023, 12, 26);
let end = Date::from_ymd(2023, 12, 30);
let pattern = DatePattern::range(start..=end);
let paths = pattern.paths(&envelope);
assert!(paths.is_empty());
let start = Date::from_ymd(2023, 12, 25);
let end = Date::from_ymd(2023, 12, 25);
let pattern = DatePattern::range(start..=end);
let paths = pattern.paths(&envelope);
assert_eq!(paths.len(), 1);
}
#[test]
fn test_date_pattern_iso8601() {
let date = Date::from_ymd(2023, 12, 25);
let envelope = Envelope::new(date);
let pattern = DatePattern::string("2023-12-25");
let paths = pattern.paths(&envelope);
assert_eq!(paths.len(), 1);
let pattern = DatePattern::string("2023-12-24");
let paths = pattern.paths(&envelope);
assert!(paths.is_empty());
let date_with_time = Date::from_ymd_hms(2023, 12, 25, 15, 30, 45);
let envelope_with_time = Envelope::new(date_with_time);
let pattern = DatePattern::string("2023-12-25T15:30:45Z");
let paths = pattern.paths(&envelope_with_time);
assert_eq!(paths.len(), 1);
}
#[test]
fn test_date_pattern_regex() {
let date = Date::from_ymd(2023, 12, 25);
let envelope = Envelope::new(date);
let regex = regex::Regex::new(r"^2023-.*").unwrap();
let pattern = DatePattern::regex(regex);
let paths = pattern.paths(&envelope);
assert_eq!(paths.len(), 1);
let regex = regex::Regex::new(r".*-12-.*").unwrap();
let pattern = DatePattern::regex(regex);
let paths = pattern.paths(&envelope);
assert_eq!(paths.len(), 1);
let regex = regex::Regex::new(r"^2024-.*").unwrap();
let pattern = DatePattern::regex(regex);
let paths = pattern.paths(&envelope);
assert!(paths.is_empty());
let date_with_time = Date::from_ymd_hms(2023, 12, 25, 15, 30, 45);
let envelope_with_time = Envelope::new(date_with_time);
let regex = regex::Regex::new(r".*T15:30:45Z$").unwrap();
let pattern = DatePattern::regex(regex);
let paths = pattern.paths(&envelope_with_time);
assert_eq!(paths.len(), 1);
}
#[test]
fn test_date_pattern_with_non_date_tagged_cbor() {
let tagged_cbor = CBOR::to_tagged_value(100, "not a date");
let envelope = Envelope::new(tagged_cbor);
let pattern = DatePattern::any();
let paths = pattern.paths(&envelope);
assert!(paths.is_empty());
}
#[test]
fn test_date_pattern_display() {
let pattern = DatePattern::any();
assert_eq!(pattern.to_string(), "date");
let pattern = DatePattern::value(Date::from_ymd(2023, 12, 25));
assert_eq!(pattern.to_string(), "date'2023-12-25'");
let pattern = DatePattern::range(
Date::from_ymd(2023, 12, 20)..=Date::from_ymd(2023, 12, 30),
);
assert_eq!(pattern.to_string(), "date'2023-12-20...2023-12-30'");
let pattern = DatePattern::earliest(Date::from_ymd(2023, 12, 25));
assert_eq!(pattern.to_string(), "date'2023-12-25...'");
let pattern = DatePattern::latest(Date::from_ymd(2023, 12, 25));
assert_eq!(pattern.to_string(), "date'...2023-12-25'");
let pattern = DatePattern::string("2023-12-25");
assert_eq!(pattern.to_string(), "date'2023-12-25'");
let pattern =
DatePattern::regex(regex::Regex::new(r"^2023-.*").unwrap());
assert_eq!(pattern.to_string(), "date'/^2023-.*/'");
}
#[test]
fn test_date_pattern_dcbor_integration() {
let date = Date::from_ymd(2023, 12, 25);
let date_envelope = Envelope::new(date);
let text_envelope = Envelope::new("2023-12-25");
let number_envelope = Envelope::new(42);
let any_pattern = DatePattern::any();
assert!(any_pattern.matches(&date_envelope));
assert!(!any_pattern.matches(&text_envelope)); assert!(!any_pattern.matches(&number_envelope));
let exact_pattern = DatePattern::value(date);
assert!(exact_pattern.matches(&date_envelope));
assert!(!exact_pattern.matches(&text_envelope));
assert!(!exact_pattern.matches(&number_envelope));
let different_pattern =
DatePattern::value(Date::from_ymd(2023, 12, 24));
assert!(!different_pattern.matches(&date_envelope));
let start = Date::from_ymd(2023, 12, 20);
let end = Date::from_ymd(2023, 12, 30);
let range_pattern = DatePattern::range(start..=end);
assert!(range_pattern.matches(&date_envelope));
assert!(!range_pattern.matches(&text_envelope));
assert!(!range_pattern.matches(&number_envelope));
let iso_pattern = DatePattern::string("2023-12-25");
assert!(iso_pattern.matches(&date_envelope));
assert!(!iso_pattern.matches(&text_envelope));
let regex_pattern =
DatePattern::regex(regex::Regex::new(r"^2023-.*").unwrap());
assert!(regex_pattern.matches(&date_envelope));
assert!(!regex_pattern.matches(&text_envelope));
let paths = exact_pattern.paths(&date_envelope);
assert_eq!(paths.len(), 1);
assert_eq!(paths[0], vec![date_envelope.clone()]);
let no_paths = exact_pattern.paths(&text_envelope);
assert_eq!(no_paths.len(), 0);
}
#[test]
fn test_date_pattern_paths_with_captures() {
let date = Date::from_ymd(2023, 12, 25);
let date_envelope = Envelope::new(date);
let pattern = DatePattern::value(date);
let (paths, captures) = pattern.paths_with_captures(&date_envelope);
assert_eq!(paths.len(), 1);
assert_eq!(paths[0], vec![date_envelope.clone()]);
assert_eq!(captures.len(), 0); }
#[test]
fn test_date_pattern_with_non_date_envelope() {
let envelope = Envelope::new_assertion("key", "value");
let pattern = DatePattern::any();
let paths = pattern.paths(&envelope);
assert_eq!(paths.len(), 0); }
#[test]
fn test_date_pattern_with_direct_cbor_values() {
let date_cbor = parse_dcbor_item("1(1640995200)").unwrap(); let text_cbor = parse_dcbor_item("\"2023-12-25\"").unwrap();
let date_envelope = Envelope::new(date_cbor);
let text_envelope = Envelope::new(text_cbor);
let any_pattern = DatePattern::any();
assert!(any_pattern.matches(&date_envelope));
assert!(!any_pattern.matches(&text_envelope));
if let Ok(parsed_date) =
Date::try_from(parse_dcbor_item("1(1640995200)").unwrap())
{
let specific_pattern = DatePattern::value(parsed_date);
assert!(specific_pattern.matches(&date_envelope));
assert!(!specific_pattern.matches(&text_envelope));
}
}
#[test]
fn test_date_pattern_earliest_latest() {
let date = Date::from_ymd(2023, 12, 25);
let envelope = Envelope::new(date);
let earlier_date = Date::from_ymd(2023, 12, 20);
let earliest_pattern = DatePattern::earliest(earlier_date);
assert!(earliest_pattern.matches(&envelope));
let later_date = Date::from_ymd(2023, 12, 30);
let earliest_pattern = DatePattern::earliest(later_date);
assert!(!earliest_pattern.matches(&envelope));
let later_date = Date::from_ymd(2023, 12, 30);
let latest_pattern = DatePattern::latest(later_date);
assert!(latest_pattern.matches(&envelope));
let earlier_date = Date::from_ymd(2023, 12, 20);
let latest_pattern = DatePattern::latest(earlier_date);
assert!(!latest_pattern.matches(&envelope));
}
}