1use core::{error, fmt, ops, str};
18
19use alloc::{
20 borrow::Cow,
21 string::{String, ToString},
22 vec::Vec,
23};
24
25use crate::{prop::IcalProp, value::owned};
26
27#[derive(Debug)]
29pub struct ParseIcalComponentKindError(
30 String,
32);
33
34impl fmt::Display for ParseIcalComponentKindError {
35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36 write!(f, "Cannot parse iCalendar component `{}`", self.0)
37 }
38}
39
40impl error::Error for ParseIcalComponentKindError {}
41
42#[derive(Clone, Debug, PartialEq, Eq)]
44pub struct IcalComponent<'a> {
45 pub name: IcalComponentName<'a>,
47 pub props: Vec<IcalProp<'a>>,
49 pub components: Vec<IcalComponent<'a>>,
51}
52
53#[derive(Clone, Debug, PartialEq, Eq)]
55pub enum IcalComponentName<'a> {
56 Kind(IcalComponentKind),
58 Unknown(Cow<'a, str>),
60}
61
62impl ops::Deref for IcalComponentName<'_> {
63 type Target = str;
64
65 fn deref(&self) -> &Self::Target {
68 match self {
69 Self::Kind(kind) => kind,
70 Self::Unknown(name) => name,
71 }
72 }
73}
74
75impl From<IcalComponentKind> for IcalComponentName<'_> {
76 fn from(kind: IcalComponentKind) -> Self {
77 Self::Kind(kind)
78 }
79}
80
81impl<'a> From<Cow<'a, str>> for IcalComponentName<'a> {
82 fn from(name: Cow<'a, str>) -> Self {
83 match name.parse().ok() {
84 Some(kind) => Self::Kind(kind),
85 None => Self::Unknown(name),
86 }
87 }
88}
89
90impl<'a> From<&'a str> for IcalComponentName<'a> {
91 fn from(name: &'a str) -> Self {
92 Cow::Borrowed(name).into()
93 }
94}
95
96impl IcalComponentName<'_> {
97 pub fn into_owned(self) -> IcalComponentName<'static> {
100 match self {
101 Self::Kind(kind) => IcalComponentName::Kind(kind),
102 Self::Unknown(name) => IcalComponentName::Unknown(owned(name)),
103 }
104 }
105}
106
107impl IcalComponent<'_> {
108 pub fn into_owned(self) -> IcalComponent<'static> {
112 IcalComponent {
113 name: self.name.into_owned(),
114 props: self.props.into_iter().map(IcalProp::into_owned).collect(),
115 components: self
116 .components
117 .into_iter()
118 .map(IcalComponent::into_owned)
119 .collect(),
120 }
121 }
122}
123
124#[derive(Clone, Copy, Debug, PartialEq, Eq)]
129pub enum IcalComponentKind {
130 VCalendar,
132 VEvent,
134 VTodo,
136 VJournal,
138 VFreeBusy,
140 VTimezone,
142 Standard,
144 Daylight,
146 VAlarm,
148 Participant,
150 VLocation,
152 VResource,
154 VAvailability,
156 Available,
158}
159
160impl IcalComponentKind {
161 pub const ALL: [Self; 14] = [
163 Self::VCalendar,
164 Self::VEvent,
165 Self::VTodo,
166 Self::VJournal,
167 Self::VFreeBusy,
168 Self::VTimezone,
169 Self::Standard,
170 Self::Daylight,
171 Self::VAlarm,
172 Self::Participant,
173 Self::VLocation,
174 Self::VResource,
175 Self::VAvailability,
176 Self::Available,
177 ];
178}
179
180impl str::FromStr for IcalComponentKind {
181 type Err = ParseIcalComponentKindError;
182
183 fn from_str(kind: &str) -> Result<Self, Self::Err> {
185 let kind = match kind {
186 kind if kind.eq_ignore_ascii_case("VCALENDAR") => Self::VCalendar,
187 kind if kind.eq_ignore_ascii_case("VEVENT") => Self::VEvent,
188 kind if kind.eq_ignore_ascii_case("VTODO") => Self::VTodo,
189 kind if kind.eq_ignore_ascii_case("VJOURNAL") => Self::VJournal,
190 kind if kind.eq_ignore_ascii_case("VFREEBUSY") => Self::VFreeBusy,
191 kind if kind.eq_ignore_ascii_case("VTIMEZONE") => Self::VTimezone,
192 kind if kind.eq_ignore_ascii_case("STANDARD") => Self::Standard,
193 kind if kind.eq_ignore_ascii_case("DAYLIGHT") => Self::Daylight,
194 kind if kind.eq_ignore_ascii_case("VALARM") => Self::VAlarm,
195 kind if kind.eq_ignore_ascii_case("PARTICIPANT") => Self::Participant,
196 kind if kind.eq_ignore_ascii_case("VLOCATION") => Self::VLocation,
197 kind if kind.eq_ignore_ascii_case("VRESOURCE") => Self::VResource,
198 kind if kind.eq_ignore_ascii_case("VAVAILABILITY") => Self::VAvailability,
199 kind if kind.eq_ignore_ascii_case("AVAILABLE") => Self::Available,
200 _ => return Err(ParseIcalComponentKindError(kind.to_string())),
201 };
202
203 Ok(kind)
204 }
205}
206
207impl ops::Deref for IcalComponentKind {
208 type Target = str;
209
210 fn deref(&self) -> &Self::Target {
211 match self {
212 Self::VCalendar => "VCALENDAR",
213 Self::VEvent => "VEVENT",
214 Self::VTodo => "VTODO",
215 Self::VJournal => "VJOURNAL",
216 Self::VFreeBusy => "VFREEBUSY",
217 Self::VTimezone => "VTIMEZONE",
218 Self::Standard => "STANDARD",
219 Self::Daylight => "DAYLIGHT",
220 Self::VAlarm => "VALARM",
221 Self::Participant => "PARTICIPANT",
222 Self::VLocation => "VLOCATION",
223 Self::VResource => "VRESOURCE",
224 Self::VAvailability => "VAVAILABILITY",
225 Self::Available => "AVAILABLE",
226 }
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use core::str::FromStr;
233
234 use crate::component::IcalComponentKind;
235
236 #[test]
237 fn round_trips_every_kind_through_its_wire_name() {
238 for kind in IcalComponentKind::ALL {
239 assert_eq!(IcalComponentKind::from_str(&kind).ok(), Some(kind));
240 }
241 assert_eq!(
242 IcalComponentKind::from_str("vevent").ok(),
243 Some(IcalComponentKind::VEvent),
244 );
245 assert!(IcalComponentKind::from_str("VUNKNOWN").is_err());
246 }
247}