Skip to main content

caldir_core/event/
x_property.rs

1use icalendar::Property;
2
3#[derive(Debug, Clone, Eq)]
4pub struct XProperty {
5    pub name: String,
6    pub value: String,
7    pub params: Vec<(String, String)>,
8}
9
10// Exclude attributes from the x-properties when comparing them
11// (for backwards-compatibility reasons)
12impl PartialEq for XProperty {
13    fn eq(&self, other: &Self) -> bool {
14        self.name == other.name && self.value == other.value
15    }
16}
17
18impl XProperty {
19    pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
20        XProperty {
21            name: name.into(),
22            value: value.into(),
23            params: Vec::new(),
24        }
25    }
26}
27
28impl From<&XProperty> for Property {
29    fn from(value: &XProperty) -> Self {
30        let mut prop = Property::new(&value.name, &value.value);
31        for (k, v) in &value.params {
32            prop.add_parameter(k, v);
33        }
34        prop.done()
35    }
36}
37
38impl From<&Property> for XProperty {
39    fn from(value: &Property) -> Self {
40        let params = value
41            .params()
42            .values()
43            .map(|p| (p.key().to_string(), p.value().to_string()))
44            .collect();
45        XProperty {
46            name: value.key().to_string(),
47            value: value.value().to_string(),
48            params,
49        }
50    }
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56
57    #[test]
58    fn parses_name_and_value() {
59        let prop = Property::new("X-HOOLI-EVENT-ID", "abc123").done();
60
61        let x = XProperty::from(&prop);
62
63        assert_eq!(x.name, "X-HOOLI-EVENT-ID");
64        assert_eq!(x.value, "abc123");
65        assert!(x.params.is_empty());
66    }
67
68    #[test]
69    fn parses_params() {
70        let mut prop = Property::new("X-ALT-DESC", "<html>...</html>");
71        prop.add_parameter("FMTTYPE", "text/html");
72
73        let x = XProperty::from(&prop.done());
74
75        assert_eq!(
76            x.params,
77            vec![("FMTTYPE".to_string(), "text/html".to_string())]
78        );
79    }
80
81    #[test]
82    fn writes_name_and_value() {
83        let x = XProperty::new("X-HOOLI-EVENT-ID", "abc123");
84
85        let prop = Property::from(&x);
86
87        assert_eq!(prop.key(), "X-HOOLI-EVENT-ID");
88        assert_eq!(prop.value(), "abc123");
89        assert!(prop.params().is_empty());
90    }
91
92    #[test]
93    fn writes_params() {
94        let x = XProperty {
95            name: "X-ALT-DESC".to_string(),
96            value: "<html>...</html>".to_string(),
97            params: vec![("FMTTYPE".to_string(), "text/html".to_string())],
98        };
99
100        let prop = Property::from(&x);
101
102        assert_eq!(
103            prop.params().get("FMTTYPE").map(|p| p.value()),
104            Some("text/html")
105        );
106    }
107
108    #[test]
109    fn eq_ignores_param_drift() {
110        let bare = XProperty::new("X-APPLE-STRUCTURED-LOCATION", "geo:51.47,-0.45");
111        let with_params = XProperty {
112            name: "X-APPLE-STRUCTURED-LOCATION".to_string(),
113            value: "geo:51.47,-0.45".to_string(),
114            params: vec![
115                ("VALUE".to_string(), "URI".to_string()),
116                ("X-TITLE".to_string(), "London Heathrow".to_string()),
117            ],
118        };
119
120        assert_eq!(bare, with_params);
121    }
122
123    #[test]
124    fn eq_distinguishes_value_changes() {
125        let a = XProperty::new("X-APPLE-STRUCTURED-LOCATION", "geo:51.47,-0.45");
126        let b = XProperty::new("X-APPLE-STRUCTURED-LOCATION", "geo:1.0,2.0");
127
128        assert_ne!(a, b);
129    }
130}