Skip to main content

rfc5545_types/
set.rs

1//! Types representing the members of finite sets.
2//!
3//! These are the "known value" enums for RFC 5545 property values and parameters.
4//! All extensible enums are `#[non_exhaustive]` — callers that need to handle unknown
5//! values should wrap them with a discriminated union (e.g. `Token<T, S>`).
6
7use strum::{Display, EnumString};
8
9/// An iTIP method (RFC 5546 §1.4).
10#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, EnumString, Display)]
11#[non_exhaustive]
12#[strum(ascii_case_insensitive)]
13pub enum Method {
14    #[strum(serialize = "PUBLISH")]
15    Publish,
16    #[strum(serialize = "REQUEST")]
17    Request,
18    #[strum(serialize = "REPLY")]
19    Reply,
20    #[strum(serialize = "ADD")]
21    Add,
22    #[strum(serialize = "CANCEL")]
23    Cancel,
24    #[strum(serialize = "REFRESH")]
25    Refresh,
26    #[strum(serialize = "COUNTER")]
27    Counter,
28    #[strum(serialize = "DECLINECOUNTER")]
29    DeclineCounter,
30}
31
32/// An unsigned integer in the range `0..=100` (RFC 5545 §3.8.1.8).
33#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
34pub struct Percent(u8);
35
36impl Percent {
37    /// The minimum percentage value (0).
38    pub const MIN: Self = Percent(0);
39    /// The maximum percentage value (100).
40    pub const MAX: Self = Percent(100);
41
42    /// Returns the raw percentage value.
43    #[inline(always)]
44    pub const fn get(self) -> u8 {
45        self.0
46    }
47
48    /// Creates a `Percent` from a raw `u8`, returning `None` if greater than 100.
49    #[inline(always)]
50    pub const fn new(value: u8) -> Option<Self> {
51        match value {
52            0..=100 => Some(Self(value)),
53            _ => None,
54        }
55    }
56}
57
58/// A priority value in the range `0..=9` (RFC 5545 §3.8.1.9).
59#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
60pub enum Priority {
61    #[default]
62    Zero,
63    A1,
64    A2,
65    A3,
66    B1,
67    B2,
68    B3,
69    C1,
70    C2,
71    C3,
72}
73
74/// The broad classification of a [`Priority`] value.
75#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
76pub enum PriorityClass {
77    /// Low priority (values 6–9).
78    Low,
79    /// Medium priority (value 5).
80    Medium,
81    /// High priority (values 1–4).
82    High,
83}
84
85impl PartialOrd for Priority {
86    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
87        if matches!(self, Self::Zero) || matches!(other, Self::Zero) {
88            None
89        } else {
90            let a = (*self) as u8;
91            let b = (*other) as u8;
92
93            match a.cmp(&b) {
94                std::cmp::Ordering::Less => Some(std::cmp::Ordering::Greater),
95                std::cmp::Ordering::Equal => Some(std::cmp::Ordering::Equal),
96                std::cmp::Ordering::Greater => Some(std::cmp::Ordering::Less),
97            }
98        }
99    }
100}
101
102impl Priority {
103    /// Returns `true` if this is a low priority (values 6–9).
104    pub const fn is_low(self) -> bool {
105        matches!(self.into_class(), Some(PriorityClass::Low))
106    }
107
108    /// Returns `true` if this is a medium priority (value 5).
109    pub const fn is_medium(self) -> bool {
110        matches!(self.into_class(), Some(PriorityClass::Medium))
111    }
112
113    /// Returns `true` if this is a high priority (values 1–4).
114    pub const fn is_high(self) -> bool {
115        matches!(self.into_class(), Some(PriorityClass::High))
116    }
117
118    /// Returns the [`PriorityClass`] of this priority, or `None` for `Zero` (undefined).
119    pub const fn into_class(self) -> Option<PriorityClass> {
120        match self {
121            Self::Zero => None,
122            Self::A1 | Self::A2 | Self::A3 | Self::B1 => Some(PriorityClass::High),
123            Self::B2 => Some(PriorityClass::Medium),
124            Self::B3 | Self::C1 | Self::C2 | Self::C3 => Some(PriorityClass::Low),
125        }
126    }
127}
128
129// ============================================================================
130// Version / Gregorian
131// ============================================================================
132
133/// The iCalendar version (RFC 5545 §3.7.4).
134#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
135pub enum Version {
136    /// iCalendar version 2.0.
137    V2_0,
138}
139
140/// The single allowed value of the CALSCALE property (RFC 5545 §3.7.1).
141#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
142pub struct Gregorian;
143
144// ============================================================================
145// Encoding
146// ============================================================================
147
148/// The possible values of the ENCODING parameter (RFC 5545 §3.2.7).
149#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumString, Display)]
150#[non_exhaustive]
151#[strum(ascii_case_insensitive)]
152pub enum Encoding {
153    /// The `8BIT` text encoding (RFC 2045).
154    #[strum(serialize = "8BIT")]
155    Bit8,
156    /// The `BASE64` binary encoding (RFC 4648).
157    #[strum(serialize = "BASE64")]
158    Base64,
159}
160
161// ============================================================================
162// TimeTransparency
163// ============================================================================
164
165/// The value of the TRANSP property (RFC 5545 §3.8.2.7).
166#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, EnumString, Display)]
167#[non_exhaustive]
168#[strum(ascii_case_insensitive)]
169pub enum TimeTransparency {
170    /// The event is opaque (consumes time on the calendar).
171    #[default]
172    #[strum(serialize = "OPAQUE")]
173    Opaque,
174    /// The event is transparent (does not consume time).
175    #[strum(serialize = "TRANSPARENT")]
176    Transparent,
177}
178
179// ============================================================================
180// Status enums
181// ============================================================================
182
183/// The status of a VEVENT component (RFC 5545 §3.8.1.11).
184#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumString, Display)]
185#[non_exhaustive]
186#[strum(ascii_case_insensitive)]
187pub enum EventStatus {
188    #[strum(serialize = "TENTATIVE")]
189    Tentative,
190    #[strum(serialize = "CONFIRMED")]
191    Confirmed,
192    #[strum(serialize = "CANCELLED")]
193    Cancelled,
194}
195
196/// The status of a VTODO component (RFC 5545 §3.8.1.11).
197#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumString, Display)]
198#[non_exhaustive]
199#[strum(ascii_case_insensitive)]
200pub enum TodoStatus {
201    #[strum(serialize = "NEEDS-ACTION")]
202    NeedsAction,
203    #[strum(serialize = "COMPLETED")]
204    Completed,
205    #[strum(serialize = "IN-PROCESS")]
206    InProcess,
207    #[strum(serialize = "CANCELLED")]
208    Cancelled,
209}
210
211/// The status of a VJOURNAL component (RFC 5545 §3.8.1.11).
212#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumString, Display)]
213#[non_exhaustive]
214#[strum(ascii_case_insensitive)]
215pub enum JournalStatus {
216    #[strum(serialize = "DRAFT")]
217    Draft,
218    #[strum(serialize = "FINAL")]
219    Final,
220    #[strum(serialize = "CANCELLED")]
221    Cancelled,
222}
223
224// ============================================================================
225// CLASS property value
226// ============================================================================
227
228/// The value of the CLASS property (RFC 5545 §3.8.1.3).
229#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, EnumString, Display)]
230#[non_exhaustive]
231#[strum(ascii_case_insensitive)]
232pub enum ClassValue {
233    #[default]
234    #[strum(serialize = "PUBLIC")]
235    Public,
236    #[strum(serialize = "PRIVATE")]
237    Private,
238    #[strum(serialize = "CONFIDENTIAL")]
239    Confidential,
240}
241
242// ============================================================================
243// Parameter value enums
244// ============================================================================
245
246/// The CUTYPE parameter value (RFC 5545 §3.2.3).
247#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, EnumString, Display)]
248#[non_exhaustive]
249#[strum(ascii_case_insensitive)]
250pub enum CalendarUserType {
251    #[default]
252    #[strum(serialize = "INDIVIDUAL")]
253    Individual,
254    #[strum(serialize = "GROUP")]
255    Group,
256    #[strum(serialize = "RESOURCE")]
257    Resource,
258    #[strum(serialize = "ROOM")]
259    Room,
260    #[strum(serialize = "UNKNOWN")]
261    Unknown,
262}
263
264/// The ROLE parameter value (RFC 5545 §3.2.16).
265#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, EnumString, Display)]
266#[non_exhaustive]
267#[strum(ascii_case_insensitive)]
268pub enum ParticipationRole {
269    #[strum(serialize = "CHAIR")]
270    Chair,
271    #[default]
272    #[strum(serialize = "REQ-PARTICIPANT")]
273    ReqParticipant,
274    #[strum(serialize = "OPT-PARTICIPANT")]
275    OptParticipant,
276    #[strum(serialize = "NON-PARTICIPANT")]
277    NonParticipant,
278}
279
280/// The PARTSTAT parameter value (RFC 5545 §3.2.12).
281#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, EnumString, Display)]
282#[non_exhaustive]
283#[strum(ascii_case_insensitive)]
284pub enum ParticipationStatus {
285    #[default]
286    #[strum(serialize = "NEEDS-ACTION")]
287    NeedsAction,
288    #[strum(serialize = "ACCEPTED")]
289    Accepted,
290    #[strum(serialize = "DECLINED")]
291    Declined,
292    #[strum(serialize = "TENTATIVE")]
293    Tentative,
294    #[strum(serialize = "DELEGATED")]
295    Delegated,
296    #[strum(serialize = "COMPLETED")]
297    Completed,
298    #[strum(serialize = "IN-PROCESS")]
299    InProcess,
300}
301
302/// The FBTYPE parameter value (RFC 5545 §3.2.9).
303#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumString, Display)]
304#[non_exhaustive]
305#[strum(ascii_case_insensitive)]
306pub enum FreeBusyType {
307    #[strum(serialize = "FREE")]
308    Free,
309    #[strum(serialize = "BUSY")]
310    Busy,
311    #[strum(serialize = "BUSY-UNAVAILABLE")]
312    BusyUnavailable,
313    #[strum(serialize = "BUSY-TENTATIVE")]
314    BusyTentative,
315}
316
317/// The RELTYPE parameter value (RFC 5545 §3.2.15).
318#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumString, Display)]
319#[non_exhaustive]
320#[strum(ascii_case_insensitive)]
321pub enum RelationshipType {
322    #[strum(serialize = "PARENT")]
323    Parent,
324    #[strum(serialize = "CHILD")]
325    Child,
326    #[strum(serialize = "SIBLING")]
327    Sibling,
328    /// RFC 9074 §7.1.
329    #[strum(serialize = "SNOOZE")]
330    Snooze,
331}
332
333/// The ACTION property value for alarms (RFC 5545 §3.8.6.1).
334#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumString, Display)]
335#[non_exhaustive]
336#[strum(ascii_case_insensitive)]
337pub enum AlarmAction {
338    #[strum(serialize = "AUDIO")]
339    Audio,
340    #[strum(serialize = "DISPLAY")]
341    Display,
342    #[strum(serialize = "EMAIL")]
343    Email,
344}
345
346/// The RELATED parameter value for TRIGGER (RFC 5545 §3.8.6.3).
347#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumString, Display)]
348#[non_exhaustive]
349#[strum(ascii_case_insensitive)]
350pub enum TriggerRelation {
351    #[strum(serialize = "START")]
352    Start,
353    #[strum(serialize = "END")]
354    End,
355}
356
357/// The VALUE parameter value (RFC 5545 §3.2.20).
358#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumString, Display)]
359#[non_exhaustive]
360#[strum(ascii_case_insensitive)]
361pub enum ValueType {
362    #[strum(serialize = "BINARY")]
363    Binary,
364    #[strum(serialize = "BOOLEAN")]
365    Boolean,
366    #[strum(serialize = "CAL-ADDRESS")]
367    CalAddress,
368    #[strum(serialize = "DATE")]
369    Date,
370    #[strum(serialize = "DATE-TIME")]
371    DateTime,
372    #[strum(serialize = "DURATION")]
373    Duration,
374    #[strum(serialize = "FLOAT")]
375    Float,
376    #[strum(serialize = "INTEGER")]
377    Integer,
378    #[strum(serialize = "PERIOD")]
379    Period,
380    #[strum(serialize = "RECUR")]
381    Recur,
382    #[strum(serialize = "TEXT")]
383    Text,
384    #[strum(serialize = "TIME")]
385    Time,
386    #[strum(serialize = "URI")]
387    Uri,
388    #[strum(serialize = "UTC-OFFSET")]
389    UtcOffset,
390}
391
392/// The only possible value of the RANGE parameter (RFC 5545 §3.2.13).
393#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
394pub struct ThisAndFuture;
395
396// ============================================================================
397// RFC 7986 enums
398// ============================================================================
399
400/// The DISPLAY parameter value (RFC 7986 §6.1).
401#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumString, Display)]
402#[non_exhaustive]
403#[strum(ascii_case_insensitive)]
404pub enum DisplayType {
405    #[strum(serialize = "BADGE")]
406    Badge,
407    #[strum(serialize = "GRAPHIC")]
408    Graphic,
409    #[strum(serialize = "FULLSIZE")]
410    Fullsize,
411    #[strum(serialize = "THUMBNAIL")]
412    Thumbnail,
413}
414
415/// The FEATURE parameter value (RFC 7986 §6.3).
416#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumString, Display)]
417#[non_exhaustive]
418#[strum(ascii_case_insensitive)]
419pub enum FeatureType {
420    #[strum(serialize = "AUDIO")]
421    Audio,
422    #[strum(serialize = "CHAT")]
423    Chat,
424    #[strum(serialize = "FEED")]
425    Feed,
426    #[strum(serialize = "MODERATOR")]
427    Moderator,
428    #[strum(serialize = "PHONE")]
429    Phone,
430    #[strum(serialize = "SCREEN")]
431    Screen,
432    #[strum(serialize = "VIDEO")]
433    Video,
434}
435
436// ============================================================================
437// RFC 9073 enums
438// ============================================================================
439
440/// The RESOURCE-TYPE property value (RFC 9073 §6.3).
441#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumString, Display)]
442#[non_exhaustive]
443#[strum(ascii_case_insensitive)]
444pub enum ResourceType {
445    #[strum(serialize = "ROOM")]
446    Room,
447    #[strum(serialize = "PROJECTOR")]
448    Projector,
449    #[strum(serialize = "REMOTE-CONFERENCE-AUDIO")]
450    RemoteConferenceAudio,
451    #[strum(serialize = "REMOTE-CONFERENCE-VIDEO")]
452    RemoteConferenceVideo,
453}
454
455/// The PARTICIPANT-TYPE property value (RFC 9073 §6.2).
456#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumString, Display)]
457#[non_exhaustive]
458#[strum(ascii_case_insensitive)]
459pub enum ParticipantType {
460    #[strum(serialize = "ACTIVE")]
461    Active,
462    #[strum(serialize = "INACTIVE")]
463    Inactive,
464    #[strum(serialize = "SPONSOR")]
465    Sponsor,
466    #[strum(serialize = "CONTACT")]
467    Contact,
468    #[strum(serialize = "BOOKING-CONTACT")]
469    BookingContact,
470    #[strum(serialize = "EMERGENCY-CONTACT")]
471    EmergencyContact,
472    #[strum(serialize = "PUBLICITY-CONTACT")]
473    PublicityContact,
474    #[strum(serialize = "PLANNER-CONTACT")]
475    PlannerContact,
476    #[strum(serialize = "PERFORMER")]
477    Performer,
478    #[strum(serialize = "SPEAKER")]
479    Speaker,
480}
481
482// ============================================================================
483// RFC 9074 enums
484// ============================================================================
485
486/// A proximity value (RFC 9074 §8.1).
487#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumString, Display)]
488#[non_exhaustive]
489#[strum(ascii_case_insensitive)]
490pub enum ProximityValue {
491    #[strum(serialize = "ARRIVE")]
492    Arrive,
493    #[strum(serialize = "DEPART")]
494    Depart,
495    #[strum(serialize = "CONNECT")]
496    Connect,
497    #[strum(serialize = "DISCONNECT")]
498    Disconnect,
499}
500
501// ============================================================================
502// Unified status
503// ============================================================================
504
505/// A unified status value covering events, todos, and journals (RFC 5545 §3.8.1.11).
506#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, EnumString, Display)]
507#[non_exhaustive]
508#[strum(ascii_case_insensitive)]
509pub enum Status {
510    #[strum(serialize = "TENTATIVE")]
511    Tentative,
512    #[strum(serialize = "CONFIRMED")]
513    Confirmed,
514    #[strum(serialize = "CANCELLED")]
515    Cancelled,
516    #[strum(serialize = "NEEDS-ACTION")]
517    NeedsAction,
518    #[strum(serialize = "COMPLETED")]
519    Completed,
520    #[strum(serialize = "IN-PROCESS")]
521    InProcess,
522    #[strum(serialize = "DRAFT")]
523    Draft,
524    #[strum(serialize = "FINAL")]
525    Final,
526}
527
528// ============================================================================
529// Alarm action markers
530// ============================================================================
531
532/// The AUDIO alarm action marker.
533#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
534pub struct AudioAction;
535
536/// The DISPLAY alarm action marker.
537#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
538pub struct DisplayAction;
539
540/// The EMAIL alarm action marker.
541#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Hash)]
542pub struct EmailAction;
543
544/// An unknown alarm action, either IANA-registered or experimental.
545#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
546pub enum UnknownAction<S> {
547    /// An IANA-registered action name.
548    Iana(S),
549    /// An experimental (X-) action name.
550    X(S),
551}
552
553impl<S> UnknownAction<S> {
554    /// Borrows the inner value.
555    pub const fn as_ref(&self) -> UnknownAction<&S> {
556        match self {
557            UnknownAction::Iana(action) => UnknownAction::Iana(action),
558            UnknownAction::X(action) => UnknownAction::X(action),
559        }
560    }
561
562    /// Returns whether this is an IANA or X- action.
563    pub const fn kind(&self) -> crate::string::NameKind {
564        match self {
565            UnknownAction::Iana(_) => crate::string::NameKind::Iana,
566            UnknownAction::X(_) => crate::string::NameKind::X,
567        }
568    }
569
570    /// Consumes the `UnknownAction` and returns the inner value.
571    pub fn into_inner(self) -> S {
572        match self {
573            UnknownAction::Iana(action) | UnknownAction::X(action) => action,
574        }
575    }
576}
577
578#[cfg(test)]
579mod tests {
580    use super::*;
581
582    #[test]
583    #[allow(clippy::neg_cmp_op_on_partial_ord)]
584    fn priority_partial_ord() {
585        assert_eq!(Priority::Zero.partial_cmp(&Priority::Zero), None);
586        assert_eq!(Priority::Zero.partial_cmp(&Priority::A1), None);
587        assert_eq!(Priority::A1.partial_cmp(&Priority::Zero), None);
588
589        assert!(Priority::A1 > Priority::A2);
590        assert!(Priority::A2 > Priority::A3);
591        assert!(Priority::A1 > Priority::A3);
592
593        assert!(Priority::A1 > Priority::B1);
594        assert!(Priority::B1 > Priority::C1);
595        assert!(Priority::A1 > Priority::C1);
596
597        assert!(!(Priority::Zero > Priority::C2));
598        assert!(!(Priority::Zero < Priority::C2));
599        assert!(Priority::Zero != Priority::C2);
600    }
601
602    #[test]
603    fn priority_class_predicates() {
604        assert!(!Priority::Zero.is_low());
605        assert!(!Priority::Zero.is_medium());
606        assert!(!Priority::Zero.is_high());
607
608        assert!(Priority::A1.is_high());
609        assert!(Priority::A2.is_high());
610        assert!(Priority::A3.is_high());
611        assert!(Priority::B1.is_high());
612
613        assert!(!Priority::B2.is_high());
614        assert!(Priority::B2.is_medium());
615        assert!(!Priority::B2.is_low());
616
617        assert!(Priority::B3.is_low());
618        assert!(Priority::C1.is_low());
619        assert!(Priority::C2.is_low());
620        assert!(Priority::C3.is_low());
621    }
622
623    #[test]
624    fn priority_class_projection() {
625        assert_eq!(Priority::Zero.into_class(), None);
626
627        assert_eq!(Priority::A1.into_class(), Some(PriorityClass::High));
628        assert_eq!(Priority::A2.into_class(), Some(PriorityClass::High));
629        assert_eq!(Priority::A3.into_class(), Some(PriorityClass::High));
630        assert_eq!(Priority::B1.into_class(), Some(PriorityClass::High));
631
632        assert_eq!(Priority::B2.into_class(), Some(PriorityClass::Medium));
633
634        assert_eq!(Priority::B3.into_class(), Some(PriorityClass::Low));
635        assert_eq!(Priority::C1.into_class(), Some(PriorityClass::Low));
636        assert_eq!(Priority::C2.into_class(), Some(PriorityClass::Low));
637        assert_eq!(Priority::C3.into_class(), Some(PriorityClass::Low));
638    }
639}