Skip to main content

ical/
param.rs

1//! # Parameters
2//!
3//! A decoded parameter and the iCalendar parameter-name vocabulary.
4//!
5//! [`IcalParam`] is a closed set of the parameters RFC 5545 and its extensions
6//! define, one variant each, plus an [`Unknown`](IcalParam::Unknown) arm so
7//! anything else round-trips. Parameters are few and simple (a text or a small
8//! list), so unlike properties each variant carries its value directly rather
9//! than through a shared value type; the variant itself names the parameter. A
10//! known name is the closed [`IcalParamKind`], reached through `FromStr` and
11//! `Deref`. Pure model, no [`crate::tree`] dependency.
12
13use core::{error, fmt, ops, str};
14
15use alloc::{
16    borrow::Cow,
17    string::{String, ToString},
18    vec::Vec,
19};
20
21use crate::value::owned;
22
23/// Parse iCalendar parameter kind error.
24#[derive(Debug)]
25pub struct ParseIcalParamKindError(
26    /// The iCalendar parameter that cannot be parsed.
27    String,
28);
29
30impl fmt::Display for ParseIcalParamKindError {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        write!(f, "Cannot parse iCalendar parameter `{}`", self.0)
33    }
34}
35
36impl error::Error for ParseIcalParamKindError {}
37
38/// The closed iCalendar parameter-name vocabulary, one fieldless variant per
39/// known parameter. An identity for dispatch and allowed-sets; the open
40/// counterpart that carries the value (and unknown names) is [`IcalParam`].
41#[derive(Clone, Copy, Debug, PartialEq, Eq)]
42pub enum IcalParamKind {
43    /// `ALTREP`: alternate text representation URI (RFC 5545 3.2.1).
44    AltRep,
45    /// `CN`: common name of a calendar user (RFC 5545 3.2.2).
46    Cn,
47    /// `CUTYPE`: calendar user type (RFC 5545 3.2.3).
48    CuType,
49    /// `DELEGATED-FROM`: delegators of an attendee (RFC 5545 3.2.4).
50    DelegatedFrom,
51    /// `DELEGATED-TO`: delegatees of an attendee (RFC 5545 3.2.5).
52    DelegatedTo,
53    /// `DIR`: directory-entry reference URI (RFC 5545 3.2.6).
54    Dir,
55    /// `ENCODING`: inline encoding of the value (RFC 5545 3.2.7).
56    Encoding,
57    /// `FMTTYPE`: media type of a referenced object (RFC 5545 3.2.8).
58    FmtType,
59    /// `FBTYPE`: free/busy time type (RFC 5545 3.2.9).
60    FbType,
61    /// `LANGUAGE`: language of the value (RFC 5545 3.2.10).
62    Language,
63    /// `MEMBER`: group memberships of an attendee (RFC 5545 3.2.11).
64    Member,
65    /// `PARTSTAT`: participation status (RFC 5545 3.2.12).
66    PartStat,
67    /// `RANGE`: recurrence-instance range (RFC 5545 3.2.13).
68    Range,
69    /// `RELATED`: alarm trigger relationship (RFC 5545 3.2.14).
70    Related,
71    /// `RELTYPE`: relationship type (RFC 5545 3.2.15).
72    RelType,
73    /// `ROLE`: participation role (RFC 5545 3.2.16).
74    Role,
75    /// `RSVP`: RSVP expectation (RFC 5545 3.2.17).
76    Rsvp,
77    /// `SENT-BY`: calendar user acting on behalf of another (RFC 5545 3.2.18).
78    SentBy,
79    /// `TZID`: reference to a time-zone definition (RFC 5545 3.2.19).
80    TzId,
81    /// `VALUE`: value type the value is to be read as (RFC 5545 3.2.20).
82    Value,
83    /// `DISPLAY`: image display type (RFC 7986 6.1).
84    Display,
85    /// `EMAIL`: email address of a calendar user (RFC 7986 6.2).
86    Email,
87    /// `FEATURE`: conference feature set (RFC 7986 6.3).
88    Feature,
89    /// `LABEL`: human-readable label (RFC 7986 6.4).
90    Label,
91    /// `ORDER`: ordering among like properties (RFC 9073 5.1).
92    Order,
93    /// `SCHEMA`: identifies structured-data content (RFC 9073 5.2).
94    Schema,
95    /// `DERIVED`: marks a derived property value (RFC 9073 5.3).
96    Derived,
97    /// `SCHEDULE-AGENT`: who performs scheduling for an attendee (RFC 6638
98    /// 7.1).
99    ScheduleAgent,
100    /// `SCHEDULE-FORCE-SEND`: a request to resend a scheduling message (RFC
101    /// 6638 7.2).
102    ScheduleForceSend,
103    /// `SCHEDULE-STATUS`: the status of a scheduling operation (RFC 6638 7.3).
104    ScheduleStatus,
105    /// `LINKREL`: the relation type of a `LINK` (RFC 9253 6.1).
106    LinkRel,
107    /// `GAP`: the lag or lead between two related components (RFC 9253 6.2).
108    Gap,
109    /// `CHARSET`: character set of the value (vCalendar 1.0).
110    Charset,
111}
112
113impl IcalParamKind {
114    /// Every known parameter kind, for iterating the closed vocabulary, as
115    /// [`IcalPropKind::ALL`](crate::prop::IcalPropKind::ALL) does for
116    /// properties.
117    pub const ALL: [Self; 33] = [
118        Self::AltRep,
119        Self::Cn,
120        Self::CuType,
121        Self::DelegatedFrom,
122        Self::DelegatedTo,
123        Self::Dir,
124        Self::Encoding,
125        Self::FmtType,
126        Self::FbType,
127        Self::Language,
128        Self::Member,
129        Self::PartStat,
130        Self::Range,
131        Self::Related,
132        Self::RelType,
133        Self::Role,
134        Self::Rsvp,
135        Self::SentBy,
136        Self::TzId,
137        Self::Value,
138        Self::Display,
139        Self::Email,
140        Self::Feature,
141        Self::Label,
142        Self::Order,
143        Self::Schema,
144        Self::Derived,
145        Self::ScheduleAgent,
146        Self::ScheduleForceSend,
147        Self::ScheduleStatus,
148        Self::LinkRel,
149        Self::Gap,
150        Self::Charset,
151    ];
152}
153
154impl str::FromStr for IcalParamKind {
155    type Err = ParseIcalParamKindError;
156
157    /// The known parameter for a wire name (case-insensitive).
158    fn from_str(kind: &str) -> Result<Self, Self::Err> {
159        let kind = match kind {
160            kind if kind.eq_ignore_ascii_case("ALTREP") => Self::AltRep,
161            kind if kind.eq_ignore_ascii_case("CN") => Self::Cn,
162            kind if kind.eq_ignore_ascii_case("CUTYPE") => Self::CuType,
163            kind if kind.eq_ignore_ascii_case("DELEGATED-FROM") => Self::DelegatedFrom,
164            kind if kind.eq_ignore_ascii_case("DELEGATED-TO") => Self::DelegatedTo,
165            kind if kind.eq_ignore_ascii_case("DIR") => Self::Dir,
166            kind if kind.eq_ignore_ascii_case("ENCODING") => Self::Encoding,
167            kind if kind.eq_ignore_ascii_case("FMTTYPE") => Self::FmtType,
168            kind if kind.eq_ignore_ascii_case("FBTYPE") => Self::FbType,
169            kind if kind.eq_ignore_ascii_case("LANGUAGE") => Self::Language,
170            kind if kind.eq_ignore_ascii_case("MEMBER") => Self::Member,
171            kind if kind.eq_ignore_ascii_case("PARTSTAT") => Self::PartStat,
172            kind if kind.eq_ignore_ascii_case("RANGE") => Self::Range,
173            kind if kind.eq_ignore_ascii_case("RELATED") => Self::Related,
174            kind if kind.eq_ignore_ascii_case("RELTYPE") => Self::RelType,
175            kind if kind.eq_ignore_ascii_case("ROLE") => Self::Role,
176            kind if kind.eq_ignore_ascii_case("RSVP") => Self::Rsvp,
177            kind if kind.eq_ignore_ascii_case("SENT-BY") => Self::SentBy,
178            kind if kind.eq_ignore_ascii_case("TZID") => Self::TzId,
179            kind if kind.eq_ignore_ascii_case("VALUE") => Self::Value,
180            kind if kind.eq_ignore_ascii_case("DISPLAY") => Self::Display,
181            kind if kind.eq_ignore_ascii_case("EMAIL") => Self::Email,
182            kind if kind.eq_ignore_ascii_case("FEATURE") => Self::Feature,
183            kind if kind.eq_ignore_ascii_case("LABEL") => Self::Label,
184            kind if kind.eq_ignore_ascii_case("ORDER") => Self::Order,
185            kind if kind.eq_ignore_ascii_case("SCHEMA") => Self::Schema,
186            kind if kind.eq_ignore_ascii_case("DERIVED") => Self::Derived,
187            kind if kind.eq_ignore_ascii_case("SCHEDULE-AGENT") => Self::ScheduleAgent,
188            kind if kind.eq_ignore_ascii_case("SCHEDULE-FORCE-SEND") => Self::ScheduleForceSend,
189            kind if kind.eq_ignore_ascii_case("SCHEDULE-STATUS") => Self::ScheduleStatus,
190            kind if kind.eq_ignore_ascii_case("LINKREL") => Self::LinkRel,
191            kind if kind.eq_ignore_ascii_case("GAP") => Self::Gap,
192            kind if kind.eq_ignore_ascii_case("CHARSET") => Self::Charset,
193            _ => return Err(ParseIcalParamKindError(kind.to_string())),
194        };
195
196        Ok(kind)
197    }
198}
199
200impl ops::Deref for IcalParamKind {
201    type Target = str;
202
203    fn deref(&self) -> &Self::Target {
204        match self {
205            Self::AltRep => "ALTREP",
206            Self::Cn => "CN",
207            Self::CuType => "CUTYPE",
208            Self::DelegatedFrom => "DELEGATED-FROM",
209            Self::DelegatedTo => "DELEGATED-TO",
210            Self::Dir => "DIR",
211            Self::Encoding => "ENCODING",
212            Self::FmtType => "FMTTYPE",
213            Self::FbType => "FBTYPE",
214            Self::Language => "LANGUAGE",
215            Self::Member => "MEMBER",
216            Self::PartStat => "PARTSTAT",
217            Self::Range => "RANGE",
218            Self::Related => "RELATED",
219            Self::RelType => "RELTYPE",
220            Self::Role => "ROLE",
221            Self::Rsvp => "RSVP",
222            Self::SentBy => "SENT-BY",
223            Self::TzId => "TZID",
224            Self::Value => "VALUE",
225            Self::Display => "DISPLAY",
226            Self::Email => "EMAIL",
227            Self::Feature => "FEATURE",
228            Self::Label => "LABEL",
229            Self::Order => "ORDER",
230            Self::Schema => "SCHEMA",
231            Self::Derived => "DERIVED",
232            Self::ScheduleAgent => "SCHEDULE-AGENT",
233            Self::ScheduleForceSend => "SCHEDULE-FORCE-SEND",
234            Self::ScheduleStatus => "SCHEDULE-STATUS",
235            Self::LinkRel => "LINKREL",
236            Self::Gap => "GAP",
237            Self::Charset => "CHARSET",
238        }
239    }
240}
241
242/// A decoded parameter: one known kind, or `Unknown` for anything unmodelled.
243/// The list-valued parameters (`DELEGATED-FROM`, `DELEGATED-TO`, `MEMBER`,
244/// `FEATURE`) carry a vector; the rest carry a single value.
245#[derive(Clone, Debug, PartialEq, Eq)]
246pub enum IcalParam<'a> {
247    /// `ALTREP`: an alternate text representation URI.
248    AltRep(Cow<'a, str>),
249    /// `CN`: the common name of a calendar user.
250    Cn(Cow<'a, str>),
251    /// `CUTYPE`: the calendar user type (e.g. `INDIVIDUAL`, `ROOM`).
252    CuType(Cow<'a, str>),
253    /// `DELEGATED-FROM`: the calendar users this attendee is delegated from.
254    DelegatedFrom(Vec<Cow<'a, str>>),
255    /// `DELEGATED-TO`: the calendar users this attendee is delegated to.
256    DelegatedTo(Vec<Cow<'a, str>>),
257    /// `DIR`: a directory-entry reference URI.
258    Dir(Cow<'a, str>),
259    /// `ENCODING`: the inline encoding of the value (`8BIT`, `BASE64`).
260    Encoding(Cow<'a, str>),
261    /// `FMTTYPE`: the media type of a referenced object.
262    FmtType(Cow<'a, str>),
263    /// `FBTYPE`: the free/busy time type (`FREE`, `BUSY`, ...).
264    FbType(Cow<'a, str>),
265    /// `LANGUAGE`: the language of the value (RFC 5646 tag).
266    Language(Cow<'a, str>),
267    /// `MEMBER`: the groups this attendee is a member of.
268    Member(Vec<Cow<'a, str>>),
269    /// `PARTSTAT`: the participation status (`ACCEPTED`, `DECLINED`, ...).
270    PartStat(Cow<'a, str>),
271    /// `RANGE`: the recurrence-instance range (`THISANDFUTURE`).
272    Range(Cow<'a, str>),
273    /// `RELATED`: the alarm trigger relationship (`START`, `END`).
274    Related(Cow<'a, str>),
275    /// `RELTYPE`: the relationship type (`PARENT`, `CHILD`, `SIBLING`).
276    RelType(Cow<'a, str>),
277    /// `ROLE`: the participation role (`CHAIR`, `REQ-PARTICIPANT`, ...).
278    Role(Cow<'a, str>),
279    /// `RSVP`: whether an RSVP is expected (`TRUE`, `FALSE`).
280    Rsvp(Cow<'a, str>),
281    /// `SENT-BY`: the calendar user acting on behalf of another.
282    SentBy(Cow<'a, str>),
283    /// `TZID`: the referenced time-zone identifier.
284    TzId(Cow<'a, str>),
285    /// `VALUE`: the value type the property value is to be read as.
286    Value(Cow<'a, str>),
287    /// `DISPLAY`: the image display type (`BADGE`, `GRAPHIC`, ...).
288    Display(Cow<'a, str>),
289    /// `EMAIL`: the email address of a calendar user.
290    Email(Cow<'a, str>),
291    /// `FEATURE`: the conference feature set (`AUDIO`, `VIDEO`, ...).
292    Feature(Vec<Cow<'a, str>>),
293    /// `LABEL`: a human-readable label.
294    Label(Cow<'a, str>),
295    /// `ORDER`: the ordering among like properties (a positive integer).
296    Order(Cow<'a, str>),
297    /// `SCHEMA`: the URI identifying structured-data content.
298    Schema(Cow<'a, str>),
299    /// `DERIVED`: whether the property value is derived (`TRUE`, `FALSE`).
300    Derived(Cow<'a, str>),
301    /// `SCHEDULE-AGENT`: who performs scheduling for an attendee.
302    ScheduleAgent(Cow<'a, str>),
303    /// `SCHEDULE-FORCE-SEND`: a request to resend a scheduling message.
304    ScheduleForceSend(Cow<'a, str>),
305    /// `SCHEDULE-STATUS`: the status of a scheduling operation.
306    ScheduleStatus(Cow<'a, str>),
307    /// `LINKREL`: the relation type of a `LINK`.
308    LinkRel(Cow<'a, str>),
309    /// `GAP`: the lag or lead between two related components.
310    Gap(Cow<'a, str>),
311    /// `CHARSET`: the character set of the value (vCalendar 1.0).
312    Charset(Cow<'a, str>),
313
314    /// Any parameter the model does not decode: its name and its values.
315    Unknown {
316        /// The verbatim parameter name.
317        name: Cow<'a, str>,
318        /// The parameter values, in source order.
319        values: Vec<Cow<'a, str>>,
320    },
321}
322
323impl IcalParam<'_> {
324    /// The closed [`IcalParamKind`] of this parameter, or `None` for
325    /// [`Unknown`](IcalParam::Unknown) (which is outside the vocabulary).
326    pub fn kind(&self) -> Option<IcalParamKind> {
327        match self {
328            Self::AltRep(_) => Some(IcalParamKind::AltRep),
329            Self::Cn(_) => Some(IcalParamKind::Cn),
330            Self::CuType(_) => Some(IcalParamKind::CuType),
331            Self::DelegatedFrom(_) => Some(IcalParamKind::DelegatedFrom),
332            Self::DelegatedTo(_) => Some(IcalParamKind::DelegatedTo),
333            Self::Dir(_) => Some(IcalParamKind::Dir),
334            Self::Encoding(_) => Some(IcalParamKind::Encoding),
335            Self::FmtType(_) => Some(IcalParamKind::FmtType),
336            Self::FbType(_) => Some(IcalParamKind::FbType),
337            Self::Language(_) => Some(IcalParamKind::Language),
338            Self::Member(_) => Some(IcalParamKind::Member),
339            Self::PartStat(_) => Some(IcalParamKind::PartStat),
340            Self::Range(_) => Some(IcalParamKind::Range),
341            Self::Related(_) => Some(IcalParamKind::Related),
342            Self::RelType(_) => Some(IcalParamKind::RelType),
343            Self::Role(_) => Some(IcalParamKind::Role),
344            Self::Rsvp(_) => Some(IcalParamKind::Rsvp),
345            Self::SentBy(_) => Some(IcalParamKind::SentBy),
346            Self::TzId(_) => Some(IcalParamKind::TzId),
347            Self::Value(_) => Some(IcalParamKind::Value),
348            Self::Display(_) => Some(IcalParamKind::Display),
349            Self::Email(_) => Some(IcalParamKind::Email),
350            Self::Feature(_) => Some(IcalParamKind::Feature),
351            Self::Label(_) => Some(IcalParamKind::Label),
352            Self::Order(_) => Some(IcalParamKind::Order),
353            Self::Schema(_) => Some(IcalParamKind::Schema),
354            Self::Derived(_) => Some(IcalParamKind::Derived),
355            Self::ScheduleAgent(_) => Some(IcalParamKind::ScheduleAgent),
356            Self::ScheduleForceSend(_) => Some(IcalParamKind::ScheduleForceSend),
357            Self::ScheduleStatus(_) => Some(IcalParamKind::ScheduleStatus),
358            Self::LinkRel(_) => Some(IcalParamKind::LinkRel),
359            Self::Gap(_) => Some(IcalParamKind::Gap),
360            Self::Charset(_) => Some(IcalParamKind::Charset),
361            Self::Unknown { .. } => None,
362        }
363    }
364
365    /// The same parameter with every borrow replaced by an allocation, so it
366    /// outlives the bytes it was decoded from. See
367    /// [`IcalValue::into_owned`](crate::value::IcalValue::into_owned).
368    pub fn into_owned(self) -> IcalParam<'static> {
369        use IcalParam::*;
370
371        match self {
372            AltRep(value) => AltRep(owned(value)),
373            Cn(value) => Cn(owned(value)),
374            CuType(value) => CuType(owned(value)),
375            DelegatedFrom(values) => DelegatedFrom(values.into_iter().map(owned).collect()),
376            DelegatedTo(values) => DelegatedTo(values.into_iter().map(owned).collect()),
377            Dir(value) => Dir(owned(value)),
378            Encoding(value) => Encoding(owned(value)),
379            FmtType(value) => FmtType(owned(value)),
380            FbType(value) => FbType(owned(value)),
381            Language(value) => Language(owned(value)),
382            Member(values) => Member(values.into_iter().map(owned).collect()),
383            PartStat(value) => PartStat(owned(value)),
384            Range(value) => Range(owned(value)),
385            Related(value) => Related(owned(value)),
386            RelType(value) => RelType(owned(value)),
387            Role(value) => Role(owned(value)),
388            Rsvp(value) => Rsvp(owned(value)),
389            SentBy(value) => SentBy(owned(value)),
390            TzId(value) => TzId(owned(value)),
391            Value(value) => Value(owned(value)),
392            Display(value) => Display(owned(value)),
393            Email(value) => Email(owned(value)),
394            Feature(values) => Feature(values.into_iter().map(owned).collect()),
395            Label(value) => Label(owned(value)),
396            Order(value) => Order(owned(value)),
397            Schema(value) => Schema(owned(value)),
398            Derived(value) => Derived(owned(value)),
399            ScheduleAgent(value) => ScheduleAgent(owned(value)),
400            ScheduleForceSend(value) => ScheduleForceSend(owned(value)),
401            ScheduleStatus(value) => ScheduleStatus(owned(value)),
402            LinkRel(value) => LinkRel(owned(value)),
403            Gap(value) => Gap(owned(value)),
404            Charset(value) => Charset(owned(value)),
405            Unknown { name, values } => Unknown {
406                name: owned(name),
407                values: values.into_iter().map(owned).collect(),
408            },
409        }
410    }
411}
412
413#[cfg(test)]
414mod tests {
415    use core::str::FromStr;
416
417    use crate::param::IcalParamKind;
418
419    #[test]
420    fn round_trips_every_kind_through_its_wire_name() {
421        for kind in [
422            IcalParamKind::Role,
423            IcalParamKind::DelegatedFrom,
424            IcalParamKind::TzId,
425        ] {
426            assert_eq!(IcalParamKind::from_str(&kind).ok(), Some(kind));
427        }
428        // NOTE: Case-insensitive on the way in; unknown names are not in the
429        // vocabulary.
430        assert_eq!(
431            IcalParamKind::from_str("role").ok(),
432            Some(IcalParamKind::Role),
433        );
434        assert!(IcalParamKind::from_str("X-CUSTOM").is_err());
435    }
436}