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 serde::Deserialize;
12use serde::Serialize;
13use sha2::{Digest, Sha256};
14
15#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Display)]
16pub 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 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 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}