1pub mod binary;
21pub mod boolean;
22pub mod cal_address;
23pub mod datetime;
24pub mod duration;
25pub mod float;
26pub mod geo;
27pub mod integer;
28pub mod period;
29pub mod recur;
30pub mod request_status;
31pub mod text;
32pub mod uri;
33pub mod utc_offset;
34
35use core::{error, fmt, ops, str};
36
37use alloc::{
38 borrow::Cow,
39 string::{String, ToString},
40 vec::Vec,
41};
42
43use crate::value::{
44 binary::IcalBinary,
45 boolean::IcalBoolean,
46 cal_address::IcalCalAddress,
47 datetime::{IcalDate, IcalDateTime, IcalDateTimeList, IcalTime},
48 duration::IcalDuration,
49 float::IcalFloat,
50 geo::IcalGeo,
51 integer::IcalInteger,
52 period::IcalPeriod,
53 recur::IcalRecur,
54 request_status::IcalRequestStatus,
55 text::{IcalText, IcalTextList},
56 uri::IcalUri,
57 utc_offset::IcalUtcOffset,
58};
59
60#[derive(Debug)]
62pub struct ParseIcalValueKindError(
63 String,
65);
66
67impl fmt::Display for ParseIcalValueKindError {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 write!(f, "Cannot parse iCalendar value type `{}`", self.0)
70 }
71}
72
73impl error::Error for ParseIcalValueKindError {}
74
75#[derive(Clone, Copy, Debug, PartialEq, Eq)]
80pub enum IcalValueKind {
81 Binary,
83 Boolean,
85 CalAddress,
87 Date,
89 DateTime,
91 DateTimeList,
94 Duration,
96 Float,
98 Geo,
100 Integer,
102 Period,
104 Recur,
106 RequestStatus,
108 Text,
110 TextList,
112 Time,
114 Uri,
116 UtcOffset,
118}
119
120impl IcalValueKind {
121 pub const ALL: [Self; 18] = [
125 Self::Binary,
126 Self::Boolean,
127 Self::CalAddress,
128 Self::Date,
129 Self::DateTime,
130 Self::DateTimeList,
131 Self::Duration,
132 Self::Float,
133 Self::Geo,
134 Self::Integer,
135 Self::Period,
136 Self::Recur,
137 Self::RequestStatus,
138 Self::Text,
139 Self::TextList,
140 Self::Time,
141 Self::Uri,
142 Self::UtcOffset,
143 ];
144}
145
146impl str::FromStr for IcalValueKind {
147 type Err = ParseIcalValueKindError;
148
149 fn from_str(kind: &str) -> Result<Self, Self::Err> {
153 match kind {
154 kind if kind.eq_ignore_ascii_case("BINARY") => Ok(Self::Binary),
155 kind if kind.eq_ignore_ascii_case("BOOLEAN") => Ok(Self::Boolean),
156 kind if kind.eq_ignore_ascii_case("CAL-ADDRESS") => Ok(Self::CalAddress),
157 kind if kind.eq_ignore_ascii_case("DATE") => Ok(Self::Date),
158 kind if kind.eq_ignore_ascii_case("DATE-TIME") => Ok(Self::DateTime),
159 kind if kind.eq_ignore_ascii_case("DATE-TIME-LIST") => Ok(Self::DateTimeList),
160 kind if kind.eq_ignore_ascii_case("DURATION") => Ok(Self::Duration),
161 kind if kind.eq_ignore_ascii_case("FLOAT") => Ok(Self::Float),
162 kind if kind.eq_ignore_ascii_case("GEO") => Ok(Self::Geo),
163 kind if kind.eq_ignore_ascii_case("INTEGER") => Ok(Self::Integer),
164 kind if kind.eq_ignore_ascii_case("PERIOD") => Ok(Self::Period),
165 kind if kind.eq_ignore_ascii_case("RECUR") => Ok(Self::Recur),
166 kind if kind.eq_ignore_ascii_case("REQUEST-STATUS") => Ok(Self::RequestStatus),
167 kind if kind.eq_ignore_ascii_case("TEXT") => Ok(Self::Text),
168 kind if kind.eq_ignore_ascii_case("TEXT-LIST") => Ok(Self::TextList),
169 kind if kind.eq_ignore_ascii_case("TIME") => Ok(Self::Time),
170 kind if kind.eq_ignore_ascii_case("URI") => Ok(Self::Uri),
171 kind if kind.eq_ignore_ascii_case("UTC-OFFSET") => Ok(Self::UtcOffset),
172 _ => Err(ParseIcalValueKindError(kind.to_string())),
173 }
174 }
175}
176
177impl ops::Deref for IcalValueKind {
178 type Target = str;
179
180 fn deref(&self) -> &Self::Target {
181 match self {
182 Self::Binary => "BINARY",
183 Self::Boolean => "BOOLEAN",
184 Self::CalAddress => "CAL-ADDRESS",
185 Self::Date => "DATE",
186 Self::DateTime => "DATE-TIME",
187 Self::DateTimeList => "DATE-TIME-LIST",
188 Self::Duration => "DURATION",
189 Self::Float => "FLOAT",
190 Self::Geo => "GEO",
191 Self::Integer => "INTEGER",
192 Self::Period => "PERIOD",
193 Self::Recur => "RECUR",
194 Self::RequestStatus => "REQUEST-STATUS",
195 Self::Text => "TEXT",
196 Self::TextList => "TEXT-LIST",
197 Self::Time => "TIME",
198 Self::Uri => "URI",
199 Self::UtcOffset => "UTC-OFFSET",
200 }
201 }
202}
203
204#[derive(Clone, Debug, PartialEq, Eq)]
207pub enum IcalValue<'a> {
208 Binary(IcalBinary<'a>),
210 Boolean(IcalBoolean<'a>),
212 CalAddress(IcalCalAddress<'a>),
214 Date(IcalDate<'a>),
216 DateTime(IcalDateTime<'a>),
218 DateTimeList(IcalDateTimeList<'a>),
220 Duration(IcalDuration<'a>),
222 Float(IcalFloat<'a>),
224 Geo(IcalGeo<'a>),
226 Integer(IcalInteger<'a>),
228 Period(IcalPeriod<'a>),
230 Recur(IcalRecur<'a>),
232 RequestStatus(IcalRequestStatus<'a>),
234 Text(IcalText<'a>),
236 TextList(IcalTextList<'a>),
238 Time(IcalTime<'a>),
240 Uri(IcalUri<'a>),
242 UtcOffset(IcalUtcOffset<'a>),
244
245 Unknown(IcalUnknownValue<'a>),
248}
249
250impl IcalValue<'_> {
251 pub fn kind(&self) -> Option<IcalValueKind> {
254 match self {
255 Self::Binary(_) => Some(IcalValueKind::Binary),
256 Self::Boolean(_) => Some(IcalValueKind::Boolean),
257 Self::CalAddress(_) => Some(IcalValueKind::CalAddress),
258 Self::Date(_) => Some(IcalValueKind::Date),
259 Self::DateTime(_) => Some(IcalValueKind::DateTime),
260 Self::DateTimeList(_) => Some(IcalValueKind::DateTimeList),
261 Self::Duration(_) => Some(IcalValueKind::Duration),
262 Self::Float(_) => Some(IcalValueKind::Float),
263 Self::Geo(_) => Some(IcalValueKind::Geo),
264 Self::Integer(_) => Some(IcalValueKind::Integer),
265 Self::Period(_) => Some(IcalValueKind::Period),
266 Self::Recur(_) => Some(IcalValueKind::Recur),
267 Self::RequestStatus(_) => Some(IcalValueKind::RequestStatus),
268 Self::Text(_) => Some(IcalValueKind::Text),
269 Self::TextList(_) => Some(IcalValueKind::TextList),
270 Self::Time(_) => Some(IcalValueKind::Time),
271 Self::Uri(_) => Some(IcalValueKind::Uri),
272 Self::UtcOffset(_) => Some(IcalValueKind::UtcOffset),
273 Self::Unknown(_) => None,
274 }
275 }
276
277 pub fn into_owned(self) -> IcalValue<'static> {
285 match self {
286 Self::Binary(IcalBinary::Uri(value)) => {
287 IcalValue::Binary(IcalBinary::Uri(owned(value)))
288 }
289 Self::Binary(IcalBinary::Base64(value)) => {
290 IcalValue::Binary(IcalBinary::Base64(owned(value)))
291 }
292 Self::Boolean(value) => IcalValue::Boolean(IcalBoolean(owned(value.0))),
293 Self::CalAddress(value) => IcalValue::CalAddress(IcalCalAddress(owned(value.0))),
294 Self::Date(value) => IcalValue::Date(IcalDate(owned(value.0))),
295 Self::DateTime(value) => IcalValue::DateTime(IcalDateTime(owned(value.0))),
296 Self::DateTimeList(value) => {
297 IcalValue::DateTimeList(IcalDateTimeList(value.0.into_iter().map(owned).collect()))
298 }
299 Self::Duration(value) => IcalValue::Duration(IcalDuration(owned(value.0))),
300 Self::Float(value) => IcalValue::Float(IcalFloat(owned(value.0))),
301 Self::Geo(value) => IcalValue::Geo(IcalGeo {
302 latitude: owned(value.latitude),
303 longitude: owned(value.longitude),
304 }),
305 Self::Integer(value) => IcalValue::Integer(IcalInteger(owned(value.0))),
306 Self::Period(value) => IcalValue::Period(IcalPeriod(owned(value.0))),
307 Self::Recur(value) => IcalValue::Recur(IcalRecur(owned(value.0))),
308 Self::RequestStatus(value) => IcalValue::RequestStatus(IcalRequestStatus {
309 code: owned(value.code),
310 description: owned(value.description),
311 extra: owned(value.extra),
312 }),
313 Self::Text(value) => IcalValue::Text(IcalText(owned(value.0))),
314 Self::TextList(value) => {
315 IcalValue::TextList(IcalTextList(value.0.into_iter().map(owned).collect()))
316 }
317 Self::Time(value) => IcalValue::Time(IcalTime(owned(value.0))),
318 Self::Uri(value) => IcalValue::Uri(IcalUri(owned(value.0))),
319 Self::UtcOffset(value) => IcalValue::UtcOffset(IcalUtcOffset(owned(value.0))),
320 Self::Unknown(value) => IcalValue::Unknown(IcalUnknownValue {
321 components: value
322 .components
323 .into_iter()
324 .map(|component| component.into_iter().map(owned).collect())
325 .collect(),
326 }),
327 }
328 }
329}
330
331pub(crate) fn owned(text: Cow<'_, str>) -> Cow<'static, str> {
334 Cow::Owned(text.into_owned())
335}
336
337#[derive(Clone, Debug, Default, PartialEq, Eq)]
340pub struct IcalUnknownValue<'a> {
341 pub components: Vec<Vec<Cow<'a, str>>>,
343}
344
345#[cfg(test)]
346mod tests {
347 use core::str::FromStr;
348
349 use crate::value::{IcalUnknownValue, IcalValue, IcalValueKind, text::IcalText};
350
351 #[test]
352 fn reports_the_kind_of_a_value_and_none_for_unknown() {
353 assert_eq!(
354 IcalValue::Text(IcalText::default()).kind(),
355 Some(IcalValueKind::Text),
356 );
357 assert_eq!(IcalValue::Unknown(IcalUnknownValue::default()).kind(), None);
358 }
359
360 #[test]
361 fn maps_value_param_strings_liberally_and_case_insensitively() {
362 assert_eq!("URI".parse().ok(), Some(IcalValueKind::Uri));
363 assert_eq!("date-time".parse().ok(), Some(IcalValueKind::DateTime));
364 assert!(IcalValueKind::from_str("bogus").is_err());
365 }
366}