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