1pub mod binary;
20pub mod boolean;
21pub mod cal_address;
22pub mod datetime;
23pub mod duration;
24pub mod float;
25pub mod geo;
26pub mod integer;
27pub mod period;
28pub mod recur;
29pub mod request_status;
30pub mod text;
31pub mod uri;
32pub mod utc_offset;
33
34use core::{error, fmt, ops, str};
35
36use alloc::{
37 borrow::Cow,
38 string::{String, ToString},
39 vec::Vec,
40};
41
42use crate::value::{
43 binary::IcalBinary,
44 boolean::IcalBoolean,
45 cal_address::IcalCalAddress,
46 datetime::{IcalDate, IcalDateTime, IcalDateTimeList, IcalTime},
47 duration::IcalDuration,
48 float::IcalFloat,
49 geo::IcalGeo,
50 integer::IcalInteger,
51 period::IcalPeriod,
52 recur::IcalRecur,
53 request_status::IcalRequestStatus,
54 text::{IcalText, IcalTextList},
55 uri::IcalUri,
56 utc_offset::IcalUtcOffset,
57};
58
59#[derive(Debug)]
61pub struct ParseIcalValueKindError(
62 String,
64);
65
66impl fmt::Display for ParseIcalValueKindError {
67 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68 write!(f, "Cannot parse iCalendar value type `{}`", self.0)
69 }
70}
71
72impl error::Error for ParseIcalValueKindError {}
73
74#[derive(Clone, Copy, Debug, PartialEq, Eq)]
79pub enum IcalValueKind {
80 Binary,
82 Boolean,
84 CalAddress,
86 Date,
88 DateTime,
90 DateTimeList,
93 Duration,
95 Float,
97 Geo,
99 Integer,
101 Period,
103 Recur,
105 RequestStatus,
107 Text,
109 TextList,
111 Time,
113 Uri,
115 UtcOffset,
117}
118
119impl IcalValueKind {
120 pub const ALL: [Self; 18] = [
124 Self::Binary,
125 Self::Boolean,
126 Self::CalAddress,
127 Self::Date,
128 Self::DateTime,
129 Self::DateTimeList,
130 Self::Duration,
131 Self::Float,
132 Self::Geo,
133 Self::Integer,
134 Self::Period,
135 Self::Recur,
136 Self::RequestStatus,
137 Self::Text,
138 Self::TextList,
139 Self::Time,
140 Self::Uri,
141 Self::UtcOffset,
142 ];
143}
144
145impl str::FromStr for IcalValueKind {
146 type Err = ParseIcalValueKindError;
147
148 fn from_str(kind: &str) -> Result<Self, Self::Err> {
152 match kind {
153 kind if kind.eq_ignore_ascii_case("BINARY") => Ok(Self::Binary),
154 kind if kind.eq_ignore_ascii_case("BOOLEAN") => Ok(Self::Boolean),
155 kind if kind.eq_ignore_ascii_case("CAL-ADDRESS") => Ok(Self::CalAddress),
156 kind if kind.eq_ignore_ascii_case("DATE") => Ok(Self::Date),
157 kind if kind.eq_ignore_ascii_case("DATE-TIME") => Ok(Self::DateTime),
158 kind if kind.eq_ignore_ascii_case("DATE-TIME-LIST") => Ok(Self::DateTimeList),
159 kind if kind.eq_ignore_ascii_case("DURATION") => Ok(Self::Duration),
160 kind if kind.eq_ignore_ascii_case("FLOAT") => Ok(Self::Float),
161 kind if kind.eq_ignore_ascii_case("GEO") => Ok(Self::Geo),
162 kind if kind.eq_ignore_ascii_case("INTEGER") => Ok(Self::Integer),
163 kind if kind.eq_ignore_ascii_case("PERIOD") => Ok(Self::Period),
164 kind if kind.eq_ignore_ascii_case("RECUR") => Ok(Self::Recur),
165 kind if kind.eq_ignore_ascii_case("REQUEST-STATUS") => Ok(Self::RequestStatus),
166 kind if kind.eq_ignore_ascii_case("TEXT") => Ok(Self::Text),
167 kind if kind.eq_ignore_ascii_case("TEXT-LIST") => Ok(Self::TextList),
168 kind if kind.eq_ignore_ascii_case("TIME") => Ok(Self::Time),
169 kind if kind.eq_ignore_ascii_case("URI") => Ok(Self::Uri),
170 kind if kind.eq_ignore_ascii_case("UTC-OFFSET") => Ok(Self::UtcOffset),
171 _ => Err(ParseIcalValueKindError(kind.to_string())),
172 }
173 }
174}
175
176impl ops::Deref for IcalValueKind {
177 type Target = str;
178
179 fn deref(&self) -> &Self::Target {
180 match self {
181 Self::Binary => "BINARY",
182 Self::Boolean => "BOOLEAN",
183 Self::CalAddress => "CAL-ADDRESS",
184 Self::Date => "DATE",
185 Self::DateTime => "DATE-TIME",
186 Self::DateTimeList => "DATE-TIME-LIST",
187 Self::Duration => "DURATION",
188 Self::Float => "FLOAT",
189 Self::Geo => "GEO",
190 Self::Integer => "INTEGER",
191 Self::Period => "PERIOD",
192 Self::Recur => "RECUR",
193 Self::RequestStatus => "REQUEST-STATUS",
194 Self::Text => "TEXT",
195 Self::TextList => "TEXT-LIST",
196 Self::Time => "TIME",
197 Self::Uri => "URI",
198 Self::UtcOffset => "UTC-OFFSET",
199 }
200 }
201}
202
203#[derive(Clone, Debug, PartialEq, Eq)]
206pub enum IcalValue<'a> {
207 Binary(IcalBinary<'a>),
209 Boolean(IcalBoolean<'a>),
211 CalAddress(IcalCalAddress<'a>),
213 Date(IcalDate<'a>),
215 DateTime(IcalDateTime<'a>),
217 DateTimeList(IcalDateTimeList<'a>),
219 Duration(IcalDuration<'a>),
221 Float(IcalFloat<'a>),
223 Geo(IcalGeo<'a>),
225 Integer(IcalInteger<'a>),
227 Period(IcalPeriod<'a>),
229 Recur(IcalRecur<'a>),
231 RequestStatus(IcalRequestStatus<'a>),
233 Text(IcalText<'a>),
235 TextList(IcalTextList<'a>),
237 Time(IcalTime<'a>),
239 Uri(IcalUri<'a>),
241 UtcOffset(IcalUtcOffset<'a>),
243 Unknown(IcalUnknownValue<'a>),
246}
247
248impl IcalValue<'_> {
249 pub fn kind(&self) -> Option<IcalValueKind> {
252 match self {
253 Self::Binary(_) => Some(IcalValueKind::Binary),
254 Self::Boolean(_) => Some(IcalValueKind::Boolean),
255 Self::CalAddress(_) => Some(IcalValueKind::CalAddress),
256 Self::Date(_) => Some(IcalValueKind::Date),
257 Self::DateTime(_) => Some(IcalValueKind::DateTime),
258 Self::DateTimeList(_) => Some(IcalValueKind::DateTimeList),
259 Self::Duration(_) => Some(IcalValueKind::Duration),
260 Self::Float(_) => Some(IcalValueKind::Float),
261 Self::Geo(_) => Some(IcalValueKind::Geo),
262 Self::Integer(_) => Some(IcalValueKind::Integer),
263 Self::Period(_) => Some(IcalValueKind::Period),
264 Self::Recur(_) => Some(IcalValueKind::Recur),
265 Self::RequestStatus(_) => Some(IcalValueKind::RequestStatus),
266 Self::Text(_) => Some(IcalValueKind::Text),
267 Self::TextList(_) => Some(IcalValueKind::TextList),
268 Self::Time(_) => Some(IcalValueKind::Time),
269 Self::Uri(_) => Some(IcalValueKind::Uri),
270 Self::UtcOffset(_) => Some(IcalValueKind::UtcOffset),
271 Self::Unknown(_) => None,
272 }
273 }
274
275 pub fn into_owned(self) -> IcalValue<'static> {
281 match self {
282 Self::Binary(IcalBinary::Uri(value)) => {
283 IcalValue::Binary(IcalBinary::Uri(owned(value)))
284 }
285 Self::Binary(IcalBinary::Base64(value)) => {
286 IcalValue::Binary(IcalBinary::Base64(owned(value)))
287 }
288 Self::Boolean(value) => IcalValue::Boolean(IcalBoolean(owned(value.0))),
289 Self::CalAddress(value) => IcalValue::CalAddress(IcalCalAddress(owned(value.0))),
290 Self::Date(value) => IcalValue::Date(IcalDate(owned(value.0))),
291 Self::DateTime(value) => IcalValue::DateTime(IcalDateTime(owned(value.0))),
292 Self::DateTimeList(value) => {
293 IcalValue::DateTimeList(IcalDateTimeList(value.0.into_iter().map(owned).collect()))
294 }
295 Self::Duration(value) => IcalValue::Duration(IcalDuration(owned(value.0))),
296 Self::Float(value) => IcalValue::Float(IcalFloat(owned(value.0))),
297 Self::Geo(value) => IcalValue::Geo(IcalGeo {
298 latitude: owned(value.latitude),
299 longitude: owned(value.longitude),
300 }),
301 Self::Integer(value) => IcalValue::Integer(IcalInteger(owned(value.0))),
302 Self::Period(value) => IcalValue::Period(IcalPeriod(owned(value.0))),
303 Self::Recur(value) => IcalValue::Recur(IcalRecur(owned(value.0))),
304 Self::RequestStatus(value) => IcalValue::RequestStatus(IcalRequestStatus {
305 code: owned(value.code),
306 description: owned(value.description),
307 extra: owned(value.extra),
308 }),
309 Self::Text(value) => IcalValue::Text(IcalText(owned(value.0))),
310 Self::TextList(value) => {
311 IcalValue::TextList(IcalTextList(value.0.into_iter().map(owned).collect()))
312 }
313 Self::Time(value) => IcalValue::Time(IcalTime(owned(value.0))),
314 Self::Uri(value) => IcalValue::Uri(IcalUri(owned(value.0))),
315 Self::UtcOffset(value) => IcalValue::UtcOffset(IcalUtcOffset(owned(value.0))),
316 Self::Unknown(value) => IcalValue::Unknown(IcalUnknownValue {
317 components: value
318 .components
319 .into_iter()
320 .map(|component| component.into_iter().map(owned).collect())
321 .collect(),
322 }),
323 }
324 }
325}
326
327pub(crate) fn owned(text: Cow<'_, str>) -> Cow<'static, str> {
330 Cow::Owned(text.into_owned())
331}
332
333#[derive(Clone, Debug, Default, PartialEq, Eq)]
336pub struct IcalUnknownValue<'a> {
337 pub components: Vec<Vec<Cow<'a, str>>>,
339}
340
341#[cfg(test)]
342mod tests {
343 use core::str::FromStr;
344
345 use crate::value::{IcalUnknownValue, IcalValue, IcalValueKind, text::IcalText};
346
347 #[test]
348 fn reports_the_kind_of_a_value_and_none_for_unknown() {
349 assert_eq!(
350 IcalValue::Text(IcalText::default()).kind(),
351 Some(IcalValueKind::Text),
352 );
353 assert_eq!(IcalValue::Unknown(IcalUnknownValue::default()).kind(), None);
354 }
355
356 #[test]
357 fn maps_value_param_strings_liberally_and_case_insensitively() {
358 assert_eq!("URI".parse().ok(), Some(IcalValueKind::Uri));
359 assert_eq!("date-time".parse().ok(), Some(IcalValueKind::DateTime));
360 assert!(IcalValueKind::from_str("bogus").is_err());
361 }
362}