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