1pub mod available;
26pub mod daylight;
27pub mod participant;
28pub mod spec;
29pub mod standard;
30pub mod valarm;
31pub mod vavailability;
32pub mod vcalendar;
33pub mod vevent;
34pub mod vfreebusy;
35pub mod vjournal;
36pub mod vlocation;
37pub mod vresource;
38pub mod vtimezone;
39pub mod vtodo;
40
41use core::{error, fmt, ops, str};
42
43use alloc::{
44 borrow::Cow,
45 string::{String, ToString},
46 vec::Vec,
47};
48
49use crate::{prop::IcalProp, value::owned};
50
51#[derive(Debug)]
53pub struct ParseIcalComponentKindError(
54 String,
56);
57
58impl fmt::Display for ParseIcalComponentKindError {
59 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60 write!(f, "Cannot parse iCalendar component `{}`", self.0)
61 }
62}
63
64impl error::Error for ParseIcalComponentKindError {}
65
66#[derive(Clone, Debug, PartialEq, Eq)]
68pub struct IcalComponent<'a> {
69 pub name: IcalComponentName<'a>,
71 pub props: Vec<IcalProp<'a>>,
73 pub components: Vec<IcalComponent<'a>>,
75}
76
77#[derive(Clone, Debug, PartialEq, Eq)]
79pub enum IcalComponentName<'a> {
80 Kind(IcalComponentKind),
82 Unknown(Cow<'a, str>),
84}
85
86impl ops::Deref for IcalComponentName<'_> {
87 type Target = str;
88
89 fn deref(&self) -> &Self::Target {
92 match self {
93 Self::Kind(kind) => kind,
94 Self::Unknown(name) => name,
95 }
96 }
97}
98
99impl From<IcalComponentKind> for IcalComponentName<'_> {
100 fn from(kind: IcalComponentKind) -> Self {
101 Self::Kind(kind)
102 }
103}
104
105impl<'a> From<Cow<'a, str>> for IcalComponentName<'a> {
106 fn from(name: Cow<'a, str>) -> Self {
107 match name.parse().ok() {
108 Some(kind) => Self::Kind(kind),
109 None => Self::Unknown(name),
110 }
111 }
112}
113
114impl<'a> From<&'a str> for IcalComponentName<'a> {
115 fn from(name: &'a str) -> Self {
116 Cow::Borrowed(name).into()
117 }
118}
119
120impl IcalComponentName<'_> {
121 pub fn into_owned(self) -> IcalComponentName<'static> {
124 match self {
125 Self::Kind(kind) => IcalComponentName::Kind(kind),
126 Self::Unknown(name) => IcalComponentName::Unknown(owned(name)),
127 }
128 }
129}
130
131impl IcalComponent<'_> {
132 pub fn into_owned(self) -> IcalComponent<'static> {
136 IcalComponent {
137 name: self.name.into_owned(),
138 props: self.props.into_iter().map(IcalProp::into_owned).collect(),
139 components: self
140 .components
141 .into_iter()
142 .map(IcalComponent::into_owned)
143 .collect(),
144 }
145 }
146}
147
148#[derive(Clone, Copy, Debug, PartialEq, Eq)]
153pub enum IcalComponentKind {
154 VCalendar,
156 VEvent,
158 VTodo,
160 VJournal,
162 VFreeBusy,
164 VTimezone,
166 Standard,
168 Daylight,
170 VAlarm,
172 Participant,
174 VLocation,
176 VResource,
178 VAvailability,
180 Available,
182}
183
184impl IcalComponentKind {
185 pub const ALL: [Self; 14] = [
187 Self::VCalendar,
188 Self::VEvent,
189 Self::VTodo,
190 Self::VJournal,
191 Self::VFreeBusy,
192 Self::VTimezone,
193 Self::Standard,
194 Self::Daylight,
195 Self::VAlarm,
196 Self::Participant,
197 Self::VLocation,
198 Self::VResource,
199 Self::VAvailability,
200 Self::Available,
201 ];
202}
203
204impl str::FromStr for IcalComponentKind {
205 type Err = ParseIcalComponentKindError;
206
207 fn from_str(kind: &str) -> Result<Self, Self::Err> {
209 let kind = match kind {
210 kind if kind.eq_ignore_ascii_case("VCALENDAR") => Self::VCalendar,
211 kind if kind.eq_ignore_ascii_case("VEVENT") => Self::VEvent,
212 kind if kind.eq_ignore_ascii_case("VTODO") => Self::VTodo,
213 kind if kind.eq_ignore_ascii_case("VJOURNAL") => Self::VJournal,
214 kind if kind.eq_ignore_ascii_case("VFREEBUSY") => Self::VFreeBusy,
215 kind if kind.eq_ignore_ascii_case("VTIMEZONE") => Self::VTimezone,
216 kind if kind.eq_ignore_ascii_case("STANDARD") => Self::Standard,
217 kind if kind.eq_ignore_ascii_case("DAYLIGHT") => Self::Daylight,
218 kind if kind.eq_ignore_ascii_case("VALARM") => Self::VAlarm,
219 kind if kind.eq_ignore_ascii_case("PARTICIPANT") => Self::Participant,
220 kind if kind.eq_ignore_ascii_case("VLOCATION") => Self::VLocation,
221 kind if kind.eq_ignore_ascii_case("VRESOURCE") => Self::VResource,
222 kind if kind.eq_ignore_ascii_case("VAVAILABILITY") => Self::VAvailability,
223 kind if kind.eq_ignore_ascii_case("AVAILABLE") => Self::Available,
224 _ => return Err(ParseIcalComponentKindError(kind.to_string())),
225 };
226
227 Ok(kind)
228 }
229}
230
231impl ops::Deref for IcalComponentKind {
232 type Target = str;
233
234 fn deref(&self) -> &Self::Target {
235 match self {
236 Self::VCalendar => "VCALENDAR",
237 Self::VEvent => "VEVENT",
238 Self::VTodo => "VTODO",
239 Self::VJournal => "VJOURNAL",
240 Self::VFreeBusy => "VFREEBUSY",
241 Self::VTimezone => "VTIMEZONE",
242 Self::Standard => "STANDARD",
243 Self::Daylight => "DAYLIGHT",
244 Self::VAlarm => "VALARM",
245 Self::Participant => "PARTICIPANT",
246 Self::VLocation => "VLOCATION",
247 Self::VResource => "VRESOURCE",
248 Self::VAvailability => "VAVAILABILITY",
249 Self::Available => "AVAILABLE",
250 }
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use core::str::FromStr;
257
258 use crate::component::IcalComponentKind;
259
260 #[test]
261 fn round_trips_every_kind_through_its_wire_name() {
262 for kind in IcalComponentKind::ALL {
263 assert_eq!(IcalComponentKind::from_str(&kind).ok(), Some(kind));
264 }
265 assert_eq!(
266 IcalComponentKind::from_str("vevent").ok(),
267 Some(IcalComponentKind::VEvent),
268 );
269 assert!(IcalComponentKind::from_str("VUNKNOWN").is_err());
270 }
271}