Skip to main content

rustical_ical/
calendar_object.rs

1use std::sync::OnceLock;
2
3use crate::Error;
4use caldata::{
5    IcalObjectParser,
6    component::{CalendarInnerData, IcalCalendarObject},
7    generator::Emitter,
8    parser::ParserOptions,
9};
10use derive_more::Display;
11use hex::ToHex;
12use serde::Deserialize;
13use serde::Serialize;
14use sha2::{Digest, Sha256};
15
16#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Display)]
17// specified in https://datatracker.ietf.org/doc/html/rfc5545#section-3.6
18pub enum CalendarObjectType {
19    #[serde(rename = "VEVENT")]
20    Event = 0,
21    #[serde(rename = "VTODO")]
22    Todo = 1,
23    #[serde(rename = "VJOURNAL")]
24    Journal = 2,
25}
26
27impl From<&IcalCalendarObject> for CalendarObjectType {
28    fn from(value: &IcalCalendarObject) -> Self {
29        match value.get_inner() {
30            CalendarInnerData::Event(_, _) => Self::Event,
31            CalendarInnerData::Todo(_, _) => Self::Todo,
32            CalendarInnerData::Journal(_, _) => Self::Journal,
33        }
34    }
35}
36
37impl CalendarObjectType {
38    #[must_use]
39    pub const fn as_str(&self) -> &'static str {
40        match self {
41            Self::Event => "VEVENT",
42            Self::Todo => "VTODO",
43            Self::Journal => "VJOURNAL",
44        }
45    }
46}
47
48impl rustical_xml::ValueSerialize for CalendarObjectType {
49    fn serialize(&self) -> String {
50        self.as_str().to_owned()
51    }
52}
53
54impl rustical_xml::ValueDeserialize for CalendarObjectType {
55    fn deserialize(val: &str) -> std::result::Result<Self, rustical_xml::XmlError> {
56        match <String as rustical_xml::ValueDeserialize>::deserialize(val)?.as_str() {
57            "VEVENT" => Ok(Self::Event),
58            "VTODO" => Ok(Self::Todo),
59            "VJOURNAL" => Ok(Self::Journal),
60            _ => Err(rustical_xml::XmlError::InvalidValue(
61                rustical_xml::ParseValueError::Other(format!(
62                    "Invalid value '{val}', must be VEVENT, VTODO, or VJOURNAL"
63                )),
64            )),
65        }
66    }
67}
68
69#[derive(Debug, Clone)]
70pub struct CalendarObject {
71    inner: IcalCalendarObject,
72    ics: OnceLock<String>,
73}
74
75impl CalendarObject {
76    // This function parses iCalendar data but doesn't cache it
77    // This is meant for iCalendar data coming from outside that might need to be normalised.
78    // For example if timezones are omitted this can be fixed by this function.
79    pub fn import(ics: &str, options: Option<ParserOptions>) -> Result<Self, Error> {
80        let parser =
81            IcalObjectParser::from_slice(ics.as_bytes()).with_options(options.unwrap_or_default());
82        let inner = parser.expect_one()?;
83
84        Ok(Self {
85            inner,
86            ics: OnceLock::new(),
87        })
88    }
89
90    // This function parses iCalendar data and then caches the parsed iCalendar data.
91    // This function is only meant for loading data from a data store where we know the iCalendar
92    // is already in the desired form.
93    pub fn from_ics(ics: String) -> Result<Self, Error> {
94        let parser = IcalObjectParser::from_slice(ics.as_bytes());
95        let inner = parser.expect_one()?;
96
97        Ok(Self {
98            inner,
99            ics: ics.into(),
100        })
101    }
102
103    #[must_use]
104    pub const fn get_inner(&self) -> &IcalCalendarObject {
105        &self.inner
106    }
107
108    #[must_use]
109    pub fn get_uid(&self) -> &str {
110        self.inner.get_uid()
111    }
112
113    #[must_use]
114    pub fn get_etag(&self) -> String {
115        let mut hasher = Sha256::new();
116        hasher.update(self.get_uid());
117        hasher.update(self.get_ics());
118        format!(
119            "\"{}\"",
120            hasher.finalize().as_slice().encode_hex::<String>()
121        )
122    }
123
124    #[must_use]
125    pub fn get_ics(&self) -> &str {
126        self.ics.get_or_init(|| self.inner.generate())
127    }
128
129    #[must_use]
130    pub fn get_object_type(&self) -> CalendarObjectType {
131        (&self.inner).into()
132    }
133}
134
135impl From<CalendarObject> for IcalCalendarObject {
136    fn from(value: CalendarObject) -> Self {
137        value.inner
138    }
139}
140
141impl From<IcalCalendarObject> for CalendarObject {
142    fn from(value: IcalCalendarObject) -> Self {
143        Self {
144            ics: value.generate().into(),
145            inner: value,
146        }
147    }
148}