Skip to main content

triplets_core/
metadata.rs

1use chrono::NaiveDate;
2use std::fmt::Display;
3
4pub use crate::constants::metadata::{META_FIELD_DATE, METADATA_DELIMITER};
5
6/// Canonical identifier for metadata fields.
7#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub struct MetadataKey {
9    name: &'static str,
10}
11
12impl MetadataKey {
13    /// Create a metadata key with a canonical static name.
14    pub const fn new(name: &'static str) -> Self {
15        Self { name }
16    }
17
18    /// Return the raw key name.
19    pub const fn as_str(&self) -> &'static str {
20        self.name
21    }
22
23    /// Encode a value using the shared delimiter (e.g., "date=2025-01-01").
24    pub fn encode(&self, value: impl Display) -> String {
25        format!("{}{}{}", self.name, METADATA_DELIMITER, value)
26    }
27
28    /// Strip the field prefix from a serialized metadata entry.
29    pub fn strip<'a>(&self, entry: &'a str) -> Option<&'a str> {
30        entry
31            .strip_prefix(self.name)
32            .and_then(|rest| rest.strip_prefix(METADATA_DELIMITER))
33    }
34}
35
36// TODO: This could be more extensible for non-U.S formats; it's a bit too hardcoded
37/// Build common date string variants for metadata prefixes.
38pub fn build_date_meta_values(date: &NaiveDate) -> Vec<crate::types::MetaValue> {
39    let mut vals = vec![
40        date.format("%Y-%m-%d").to_string(),    // 2024-10-15 (ISO)
41        date.format("%b. %-d, %Y").to_string(), // Oct. 15, 2024
42        date.format("%B %-d, %Y").to_string(),  // October 15, 2024
43        date.format("%d.%m.%Y").to_string(),    // 15.10.2024
44        date.format("%m/%d/%Y").to_string(),    // 02/25/2025
45        date.format("%b %-d, %Y").to_string(),  // Oct 15, 2024 (no dot)
46    ];
47    vals.sort();
48    vals.dedup();
49    vals
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    #[test]
57    fn metadata_key_encodes_and_strips_values() {
58        let encoded = META_FIELD_DATE.encode("2025-02-23");
59        assert_eq!(encoded, "date=2025-02-23");
60        assert_eq!(META_FIELD_DATE.strip(&encoded), Some("2025-02-23"));
61        assert_eq!(META_FIELD_DATE.strip("other=2025-02-23"), None);
62    }
63
64    #[test]
65    fn date_meta_values_are_deduped_and_include_expected_formats() {
66        let date = NaiveDate::from_ymd_opt(2025, 2, 25).unwrap();
67        let values = build_date_meta_values(&date);
68
69        assert!(values.contains(&"2025-02-25".to_string()));
70        assert!(values.contains(&"02/25/2025".to_string()));
71        assert!(values.contains(&"25.02.2025".to_string()));
72
73        let mut uniq = values.clone();
74        uniq.sort();
75        uniq.dedup();
76        assert_eq!(uniq.len(), values.len());
77    }
78
79    #[test]
80    fn metadata_key_new_and_as_str_work() {
81        const CUSTOM: MetadataKey = MetadataKey::new("custom");
82        assert_eq!(CUSTOM.as_str(), "custom");
83        assert_eq!(CUSTOM.encode(42), "custom=42");
84        assert_eq!(CUSTOM.strip("custom=42"), Some("42"));
85        assert_eq!(CUSTOM.strip("custom42"), None);
86
87        let runtime_key = MetadataKey::new("runtime");
88        assert_eq!(runtime_key.as_str(), "runtime");
89    }
90}