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