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