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