rustical_ical/
calendar_object.rs1use 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)]
17pub 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 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 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}