1pub mod aalarm;
25pub mod acknowledged;
26pub mod action;
27pub mod attach;
28pub mod attendee;
29pub mod busytype;
30pub mod calendar_address;
31pub mod calscale;
32pub mod cardinality;
33pub mod categories;
34pub mod class;
35pub mod color;
36pub mod comment;
37pub mod completed;
38pub mod concept;
39pub mod conference;
40pub mod contact;
41pub mod created;
42pub mod dalarm;
43pub mod description;
44pub mod dtend;
45pub mod dtstamp;
46pub mod dtstart;
47pub mod due;
48pub mod duration;
49pub mod exdate;
50pub mod exrule;
51pub mod freebusy;
52pub mod geo;
53pub mod image;
54pub mod last_modified;
55pub mod link;
56pub mod location;
57pub mod location_type;
58pub mod malarm;
59pub mod method;
60pub mod name;
61pub mod organizer;
62pub mod palarm;
63pub mod participant_type;
64pub mod percent_complete;
65pub mod priority;
66pub mod prodid;
67pub mod proximity;
68pub mod rdate;
69pub mod recurrence_id;
70pub mod refid;
71pub mod refresh_interval;
72pub mod related_to;
73pub mod repeat;
74pub mod request_status;
75pub mod resource_type;
76pub mod resources;
77pub mod rnum;
78pub mod rrule;
79pub mod sequence;
80pub mod source;
81pub mod spec;
82pub mod status;
83pub mod structured_data;
84pub mod styled_description;
85pub mod summary;
86pub mod transp;
87pub mod trigger;
88pub mod tz;
89pub mod tzid;
90pub mod tzname;
91pub mod tzoffsetfrom;
92pub mod tzoffsetto;
93pub mod tzurl;
94pub mod uid;
95pub mod url;
96
97use core::{error, fmt, ops, str};
98
99use alloc::{
100 borrow::Cow,
101 string::{String, ToString},
102 vec::Vec,
103};
104
105use crate::{
106 param::IcalParam,
107 value::{IcalValue, owned},
108};
109
110#[derive(Debug)]
112pub struct ParseIcalPropKindError(
113 String,
115);
116
117impl fmt::Display for ParseIcalPropKindError {
118 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119 write!(f, "Cannot parse iCalendar property `{}`", self.0)
120 }
121}
122
123impl error::Error for ParseIcalPropKindError {}
124
125#[derive(Clone, Debug, PartialEq, Eq)]
127pub struct IcalProp<'a> {
128 pub name: IcalPropName<'a>,
130 pub params: Vec<IcalParam<'a>>,
132 pub value: IcalValue<'a>,
134}
135
136#[derive(Clone, Debug, PartialEq, Eq)]
141pub enum IcalPropName<'a> {
142 Kind(IcalPropKind),
144 Unknown(Cow<'a, str>),
146}
147
148impl ops::Deref for IcalPropName<'_> {
149 type Target = str;
150
151 fn deref(&self) -> &Self::Target {
154 match self {
155 Self::Kind(kind) => kind,
156 Self::Unknown(name) => name,
157 }
158 }
159}
160
161impl From<IcalPropKind> for IcalPropName<'_> {
162 fn from(kind: IcalPropKind) -> Self {
163 Self::Kind(kind)
164 }
165}
166
167impl From<&IcalPropKind> for IcalPropName<'_> {
168 fn from(kind: &IcalPropKind) -> Self {
169 Self::Kind(*kind)
170 }
171}
172
173impl<'a> From<Cow<'a, str>> for IcalPropName<'a> {
174 fn from(name: Cow<'a, str>) -> Self {
175 match name.parse().ok() {
176 Some(kind) => Self::Kind(kind),
177 None => Self::Unknown(name),
178 }
179 }
180}
181
182impl<'a> From<&'a str> for IcalPropName<'a> {
183 fn from(name: &'a str) -> Self {
184 Cow::Borrowed(name).into()
185 }
186}
187
188impl IcalPropName<'_> {
189 pub fn into_owned(self) -> IcalPropName<'static> {
192 match self {
193 Self::Kind(kind) => IcalPropName::Kind(kind),
194 Self::Unknown(name) => IcalPropName::Unknown(owned(name)),
195 }
196 }
197}
198
199impl IcalProp<'_> {
200 pub fn into_owned(self) -> IcalProp<'static> {
204 IcalProp {
205 name: self.name.into_owned(),
206 params: self.params.into_iter().map(IcalParam::into_owned).collect(),
207 value: self.value.into_owned(),
208 }
209 }
210}
211
212#[derive(Clone, Copy, Debug, PartialEq, Eq)]
219pub enum IcalPropKind {
220 CalScale,
222 Method,
224 ProdId,
226 Attach,
228 Categories,
230 Class,
232 Comment,
234 Description,
236 Geo,
238 Location,
240 PercentComplete,
242 Priority,
244 Resources,
246 Status,
248 Summary,
250 Completed,
252 DtEnd,
254 Due,
256 DtStart,
258 Duration,
260 FreeBusy,
262 Transp,
264 TzId,
266 TzName,
268 TzOffsetFrom,
270 TzOffsetTo,
272 TzUrl,
274 Attendee,
276 Contact,
278 Organizer,
280 RecurrenceId,
282 RelatedTo,
284 Url,
286 Uid,
288 ExDate,
290 RDate,
292 RRule,
294 ExRule,
296 Action,
298 Repeat,
300 Trigger,
302 Created,
304 DtStamp,
306 LastModified,
308 Sequence,
310 RequestStatus,
312 Name,
314 RefreshInterval,
316 Source,
318 Color,
320 Image,
322 Conference,
324 ParticipantType,
326 ResourceType,
328 CalendarAddress,
330 LocationType,
332 StructuredData,
334 Link,
336 Refid,
338 Concept,
340 BusyType,
342 StyledDescription,
344 Acknowledged,
346 Proximity,
348 Tz,
350 AAlarm,
352 DAlarm,
354 MAlarm,
356 PAlarm,
358 RNum,
360}
361
362impl IcalPropKind {
363 pub const ALL: [Self; 70] = [
366 Self::CalScale,
367 Self::Method,
368 Self::ProdId,
369 Self::Attach,
370 Self::Categories,
371 Self::Class,
372 Self::Comment,
373 Self::Description,
374 Self::Geo,
375 Self::Location,
376 Self::PercentComplete,
377 Self::Priority,
378 Self::Resources,
379 Self::Status,
380 Self::Summary,
381 Self::Completed,
382 Self::DtEnd,
383 Self::Due,
384 Self::DtStart,
385 Self::Duration,
386 Self::FreeBusy,
387 Self::Transp,
388 Self::TzId,
389 Self::TzName,
390 Self::TzOffsetFrom,
391 Self::TzOffsetTo,
392 Self::TzUrl,
393 Self::Attendee,
394 Self::Contact,
395 Self::Organizer,
396 Self::RecurrenceId,
397 Self::RelatedTo,
398 Self::Url,
399 Self::Uid,
400 Self::ExDate,
401 Self::RDate,
402 Self::RRule,
403 Self::ExRule,
404 Self::Action,
405 Self::Repeat,
406 Self::Trigger,
407 Self::Created,
408 Self::DtStamp,
409 Self::LastModified,
410 Self::Sequence,
411 Self::RequestStatus,
412 Self::Name,
413 Self::RefreshInterval,
414 Self::Source,
415 Self::Color,
416 Self::Image,
417 Self::Conference,
418 Self::ParticipantType,
419 Self::ResourceType,
420 Self::CalendarAddress,
421 Self::LocationType,
422 Self::StructuredData,
423 Self::Link,
424 Self::Refid,
425 Self::Concept,
426 Self::BusyType,
427 Self::StyledDescription,
428 Self::Acknowledged,
429 Self::Proximity,
430 Self::Tz,
431 Self::AAlarm,
432 Self::DAlarm,
433 Self::MAlarm,
434 Self::PAlarm,
435 Self::RNum,
436 ];
437}
438
439impl str::FromStr for IcalPropKind {
440 type Err = ParseIcalPropKindError;
441
442 fn from_str(kind: &str) -> Result<Self, Self::Err> {
444 let kind = match kind {
445 kind if kind.eq_ignore_ascii_case("CALSCALE") => Self::CalScale,
446 kind if kind.eq_ignore_ascii_case("METHOD") => Self::Method,
447 kind if kind.eq_ignore_ascii_case("PRODID") => Self::ProdId,
448 kind if kind.eq_ignore_ascii_case("ATTACH") => Self::Attach,
449 kind if kind.eq_ignore_ascii_case("CATEGORIES") => Self::Categories,
450 kind if kind.eq_ignore_ascii_case("CLASS") => Self::Class,
451 kind if kind.eq_ignore_ascii_case("COMMENT") => Self::Comment,
452 kind if kind.eq_ignore_ascii_case("DESCRIPTION") => Self::Description,
453 kind if kind.eq_ignore_ascii_case("GEO") => Self::Geo,
454 kind if kind.eq_ignore_ascii_case("LOCATION") => Self::Location,
455 kind if kind.eq_ignore_ascii_case("PERCENT-COMPLETE") => Self::PercentComplete,
456 kind if kind.eq_ignore_ascii_case("PRIORITY") => Self::Priority,
457 kind if kind.eq_ignore_ascii_case("RESOURCES") => Self::Resources,
458 kind if kind.eq_ignore_ascii_case("STATUS") => Self::Status,
459 kind if kind.eq_ignore_ascii_case("SUMMARY") => Self::Summary,
460 kind if kind.eq_ignore_ascii_case("COMPLETED") => Self::Completed,
461 kind if kind.eq_ignore_ascii_case("DTEND") => Self::DtEnd,
462 kind if kind.eq_ignore_ascii_case("DUE") => Self::Due,
463 kind if kind.eq_ignore_ascii_case("DTSTART") => Self::DtStart,
464 kind if kind.eq_ignore_ascii_case("DURATION") => Self::Duration,
465 kind if kind.eq_ignore_ascii_case("FREEBUSY") => Self::FreeBusy,
466 kind if kind.eq_ignore_ascii_case("TRANSP") => Self::Transp,
467 kind if kind.eq_ignore_ascii_case("TZID") => Self::TzId,
468 kind if kind.eq_ignore_ascii_case("TZNAME") => Self::TzName,
469 kind if kind.eq_ignore_ascii_case("TZOFFSETFROM") => Self::TzOffsetFrom,
470 kind if kind.eq_ignore_ascii_case("TZOFFSETTO") => Self::TzOffsetTo,
471 kind if kind.eq_ignore_ascii_case("TZURL") => Self::TzUrl,
472 kind if kind.eq_ignore_ascii_case("ATTENDEE") => Self::Attendee,
473 kind if kind.eq_ignore_ascii_case("CONTACT") => Self::Contact,
474 kind if kind.eq_ignore_ascii_case("ORGANIZER") => Self::Organizer,
475 kind if kind.eq_ignore_ascii_case("RECURRENCE-ID") => Self::RecurrenceId,
476 kind if kind.eq_ignore_ascii_case("RELATED-TO") => Self::RelatedTo,
477 kind if kind.eq_ignore_ascii_case("URL") => Self::Url,
478 kind if kind.eq_ignore_ascii_case("UID") => Self::Uid,
479 kind if kind.eq_ignore_ascii_case("EXDATE") => Self::ExDate,
480 kind if kind.eq_ignore_ascii_case("RDATE") => Self::RDate,
481 kind if kind.eq_ignore_ascii_case("RRULE") => Self::RRule,
482 kind if kind.eq_ignore_ascii_case("EXRULE") => Self::ExRule,
483 kind if kind.eq_ignore_ascii_case("ACTION") => Self::Action,
484 kind if kind.eq_ignore_ascii_case("REPEAT") => Self::Repeat,
485 kind if kind.eq_ignore_ascii_case("TRIGGER") => Self::Trigger,
486 kind if kind.eq_ignore_ascii_case("CREATED") => Self::Created,
487 kind if kind.eq_ignore_ascii_case("DTSTAMP") => Self::DtStamp,
488 kind if kind.eq_ignore_ascii_case("LAST-MODIFIED") => Self::LastModified,
489 kind if kind.eq_ignore_ascii_case("SEQUENCE") => Self::Sequence,
490 kind if kind.eq_ignore_ascii_case("REQUEST-STATUS") => Self::RequestStatus,
491 kind if kind.eq_ignore_ascii_case("NAME") => Self::Name,
492 kind if kind.eq_ignore_ascii_case("REFRESH-INTERVAL") => Self::RefreshInterval,
493 kind if kind.eq_ignore_ascii_case("SOURCE") => Self::Source,
494 kind if kind.eq_ignore_ascii_case("COLOR") => Self::Color,
495 kind if kind.eq_ignore_ascii_case("IMAGE") => Self::Image,
496 kind if kind.eq_ignore_ascii_case("CONFERENCE") => Self::Conference,
497 kind if kind.eq_ignore_ascii_case("PARTICIPANT-TYPE") => Self::ParticipantType,
498 kind if kind.eq_ignore_ascii_case("RESOURCE-TYPE") => Self::ResourceType,
499 kind if kind.eq_ignore_ascii_case("CALENDAR-ADDRESS") => Self::CalendarAddress,
500 kind if kind.eq_ignore_ascii_case("LOCATION-TYPE") => Self::LocationType,
501 kind if kind.eq_ignore_ascii_case("STRUCTURED-DATA") => Self::StructuredData,
502 kind if kind.eq_ignore_ascii_case("LINK") => Self::Link,
503 kind if kind.eq_ignore_ascii_case("REFID") => Self::Refid,
504 kind if kind.eq_ignore_ascii_case("CONCEPT") => Self::Concept,
505 kind if kind.eq_ignore_ascii_case("BUSYTYPE") => Self::BusyType,
506 kind if kind.eq_ignore_ascii_case("STYLED-DESCRIPTION") => Self::StyledDescription,
507 kind if kind.eq_ignore_ascii_case("ACKNOWLEDGED") => Self::Acknowledged,
508 kind if kind.eq_ignore_ascii_case("PROXIMITY") => Self::Proximity,
509 kind if kind.eq_ignore_ascii_case("TZ") => Self::Tz,
510 kind if kind.eq_ignore_ascii_case("AALARM") => Self::AAlarm,
511 kind if kind.eq_ignore_ascii_case("DALARM") => Self::DAlarm,
512 kind if kind.eq_ignore_ascii_case("MALARM") => Self::MAlarm,
513 kind if kind.eq_ignore_ascii_case("PALARM") => Self::PAlarm,
514 kind if kind.eq_ignore_ascii_case("RNUM") => Self::RNum,
515 _ => return Err(ParseIcalPropKindError(kind.to_string())),
516 };
517
518 Ok(kind)
519 }
520}
521
522impl ops::Deref for IcalPropKind {
523 type Target = str;
524
525 fn deref(&self) -> &Self::Target {
526 match self {
527 Self::CalScale => "CALSCALE",
528 Self::Method => "METHOD",
529 Self::ProdId => "PRODID",
530 Self::Attach => "ATTACH",
531 Self::Categories => "CATEGORIES",
532 Self::Class => "CLASS",
533 Self::Comment => "COMMENT",
534 Self::Description => "DESCRIPTION",
535 Self::Geo => "GEO",
536 Self::Location => "LOCATION",
537 Self::PercentComplete => "PERCENT-COMPLETE",
538 Self::Priority => "PRIORITY",
539 Self::Resources => "RESOURCES",
540 Self::Status => "STATUS",
541 Self::Summary => "SUMMARY",
542 Self::Completed => "COMPLETED",
543 Self::DtEnd => "DTEND",
544 Self::Due => "DUE",
545 Self::DtStart => "DTSTART",
546 Self::Duration => "DURATION",
547 Self::FreeBusy => "FREEBUSY",
548 Self::Transp => "TRANSP",
549 Self::TzId => "TZID",
550 Self::TzName => "TZNAME",
551 Self::TzOffsetFrom => "TZOFFSETFROM",
552 Self::TzOffsetTo => "TZOFFSETTO",
553 Self::TzUrl => "TZURL",
554 Self::Attendee => "ATTENDEE",
555 Self::Contact => "CONTACT",
556 Self::Organizer => "ORGANIZER",
557 Self::RecurrenceId => "RECURRENCE-ID",
558 Self::RelatedTo => "RELATED-TO",
559 Self::Url => "URL",
560 Self::Uid => "UID",
561 Self::ExDate => "EXDATE",
562 Self::RDate => "RDATE",
563 Self::RRule => "RRULE",
564 Self::ExRule => "EXRULE",
565 Self::Action => "ACTION",
566 Self::Repeat => "REPEAT",
567 Self::Trigger => "TRIGGER",
568 Self::Created => "CREATED",
569 Self::DtStamp => "DTSTAMP",
570 Self::LastModified => "LAST-MODIFIED",
571 Self::Sequence => "SEQUENCE",
572 Self::RequestStatus => "REQUEST-STATUS",
573 Self::Name => "NAME",
574 Self::RefreshInterval => "REFRESH-INTERVAL",
575 Self::Source => "SOURCE",
576 Self::Color => "COLOR",
577 Self::Image => "IMAGE",
578 Self::Conference => "CONFERENCE",
579 Self::ParticipantType => "PARTICIPANT-TYPE",
580 Self::ResourceType => "RESOURCE-TYPE",
581 Self::CalendarAddress => "CALENDAR-ADDRESS",
582 Self::LocationType => "LOCATION-TYPE",
583 Self::StructuredData => "STRUCTURED-DATA",
584 Self::Link => "LINK",
585 Self::Refid => "REFID",
586 Self::Concept => "CONCEPT",
587 Self::BusyType => "BUSYTYPE",
588 Self::StyledDescription => "STYLED-DESCRIPTION",
589 Self::Acknowledged => "ACKNOWLEDGED",
590 Self::Proximity => "PROXIMITY",
591 Self::Tz => "TZ",
592 Self::AAlarm => "AALARM",
593 Self::DAlarm => "DALARM",
594 Self::MAlarm => "MALARM",
595 Self::PAlarm => "PALARM",
596 Self::RNum => "RNUM",
597 }
598 }
599}
600
601#[cfg(test)]
602mod tests {
603 use core::str::FromStr;
604
605 use alloc::borrow::Cow;
606
607 use crate::{
608 param::IcalParam,
609 prop::{IcalProp, IcalPropKind, IcalPropName},
610 value::{IcalValue, text::IcalText},
611 };
612
613 #[test]
614 fn names_the_property_and_wraps_the_value() {
615 let prop = IcalProp {
616 name: IcalPropKind::Summary.into(),
617 params: [].into(),
618 value: IcalValue::Text(IcalText(Cow::Borrowed("Lunch"))),
619 };
620 assert_eq!(prop.name, IcalPropName::Kind(IcalPropKind::Summary));
621 assert_eq!(&*prop.name, "SUMMARY");
622 assert!(prop.params.is_empty());
623 }
624
625 #[test]
626 fn carries_the_given_parameters() {
627 let prop = IcalProp {
628 name: IcalPropKind::Attendee.into(),
629 params: [IcalParam::Role(Cow::Borrowed("CHAIR"))].into(),
630 value: IcalValue::CalAddress("mailto:a@b.example".into()),
631 };
632 assert_eq!(&*prop.name, "ATTENDEE");
633 assert_eq!(prop.params.len(), 1);
634 }
635
636 #[test]
637 fn round_trips_every_kind_through_its_wire_name() {
638 for kind in IcalPropKind::ALL {
639 assert_eq!(IcalPropKind::from_str(&kind).ok(), Some(kind));
640 }
641 assert_eq!(
642 IcalPropKind::from_str("summary").ok(),
643 Some(IcalPropKind::Summary),
644 );
645 assert!(IcalPropKind::from_str("X-CUSTOM").is_err());
646 }
647}