aimcal_ical/
parameter.rs

1// SPDX-FileCopyrightText: 2025-2026 Zexin Yuan <aim@yzx9.xyz>
2//
3// SPDX-License-Identifier: Apache-2.0
4
5//! Parameter parsing module for iCalendar parameters.
6//!
7//! This module handles the parsing and validation of iCalendar parameters
8//! as defined in RFC 5545 Section 3.2.
9
10#[macro_use]
11mod util;
12
13mod definition;
14mod kind;
15
16pub use definition::{
17    AlarmTriggerRelationship, CalendarUserType, Encoding, FreeBusyType, ParticipationRole,
18    ParticipationStatus, RecurrenceIdRange, RelationshipType, ValueType,
19};
20pub use kind::ParameterKind;
21
22use crate::parameter::definition::{
23    parse_alarm_trigger_relationship, parse_cutype, parse_encoding, parse_fbtype, parse_partstat,
24    parse_range, parse_reltype, parse_role, parse_rsvp, parse_tzid, parse_value_type,
25};
26use crate::parameter::util::{parse_multiple_quoted, parse_single, parse_single_quoted};
27use crate::string_storage::{Segments, StringStorage};
28use crate::syntax::RawParameter;
29use crate::typed::TypedError;
30
31/// A typed iCalendar parameter with validated values.
32#[derive(Debug, Clone)]
33#[expect(missing_docs)]
34pub enum Parameter<S: StringStorage> {
35    /// This parameter specifies a URI that points to an alternate
36    /// representation for a textual property value. A property specifying
37    /// this parameter MUST also include a value that reflects the default
38    /// representation of the text value
39    ///
40    /// See also: RFC 5545 Section 3.2.1. Alternate Text Representation
41    AlternateText { value: S, span: S::Span },
42
43    /// This parameter can be specified on properties with a CAL-ADDRESS value
44    /// type. The parameter specifies the common name to be associated with
45    /// the calendar user specified by the property. The parameter value is
46    /// text. The parameter value can be used for display text to be associated
47    /// with the calendar address specified by the property.
48    ///
49    /// See also: RFC 5545 Section 3.2.2. Common Name
50    CommonName { value: S, span: S::Span },
51
52    /// This parameter can be specified on properties with a CAL-ADDRESS value
53    /// type. The parameter identifies the type of calendar user specified by
54    /// the property. Applications MUST treat x-name and iana-token values they
55    /// don't recognize the same way as they would the UNKNOWN value.
56    ///
57    /// See also: RFC 5545 Section 3.2.3. Calendar User Type
58    CalendarUserType {
59        value: CalendarUserType<S>,
60        span: S::Span,
61    },
62
63    /// This parameter can be specified on properties with a CAL-ADDRESS value
64    /// type. This parameter specifies those calendar users that have delegated
65    /// their participation in a group-scheduled event or to-do to the calendar
66    /// user specified by the property.
67    ///
68    /// See also: RFC 5545 Section 3.2.4. Delegators
69    Delegators { values: Vec<S>, span: S::Span },
70
71    /// This parameter can be specified on properties with a CAL-ADDRESS value
72    /// type. This parameter specifies those calendar users whom have been
73    /// delegated participation in a group-scheduled event or to-do by the
74    /// calendar user specified by the property.
75    ///
76    /// See also: RFC 5545 Section 3.2.5. Delegatees
77    Delegatees { values: Vec<S>, span: S::Span },
78
79    /// This parameter can be specified on properties with a CAL-ADDRESS value
80    /// type. The parameter specifies a reference to the directory entry
81    /// associated with the calendar user specified by the property. The
82    /// parameter value is a URI.
83    ///
84    /// See also: RFC 5545 Section 3.2.6. Directory Entry Reference
85    Directory { value: S, span: S::Span },
86
87    /// This property parameter identifies the inline encoding used in a
88    /// property value.
89    ///
90    /// See also: RFC 5545 Section 3.2.7. Inline Encoding
91    Encoding { value: Encoding, span: S::Span },
92
93    /// This parameter can be specified on properties that are used to
94    /// reference an object. The parameter specifies the media type [RFC4288]
95    /// of the referenced object. For example, on the "ATTACH" property, an FTP
96    /// type URI value does not, by itself, necessarily convey the type of
97    /// content associated with the resource. The parameter value MUST be the
98    /// text for either an IANA-registered media type or a non-standard media
99    /// type.
100    ///
101    /// See also: RFC 5545 Section 3.2.8. Format Type
102    FormatType { value: S, span: S::Span },
103
104    /// This parameter specifies the free or busy time type. Applications MUST
105    /// treat x-name and iana-token values they don't recognize the same way as
106    /// they would the BUSY value.
107    ///
108    /// See also: RFC 5545 Section 3.2.9. Free/Busy Time Type
109    FreeBusyType {
110        value: FreeBusyType<S>,
111        span: S::Span,
112    },
113
114    /// This parameter identifies the language of the text in the property
115    /// value and of all property parameter values of the property. The value
116    /// of the "LANGUAGE" property parameter is that defined in [RFC5646].
117    ///
118    /// For transport in a MIME entity, the Content-Language header field can
119    /// be used to set the default language for the entire body part. Otherwise,
120    /// no default language is assumed.
121    ///
122    /// See also: RFC 5545 Section 3.2.10. Language
123    Language { value: S, span: S::Span },
124
125    /// This parameter can be specified on properties with a CAL-ADDRESS value
126    /// type. The parameter identifies the groups or list membership for the
127    /// calendar user specified by the property. The parameter value is either
128    /// a single calendar address in a quoted-string or a COMMA-separated list
129    /// of calendar addresses, each in a quoted-string. The individual calendar
130    /// address parameter values MUST each be specified in a quoted-string.
131    ///
132    /// See also: RFC 5545 Section 3.2.11. Group or List Membership
133    GroupOrListMembership { values: Vec<S>, span: S::Span },
134
135    /// This parameter can be specified on properties with a CAL-ADDRESS value
136    /// type. The parameter identifies the participation status for the
137    /// calendar user specified by the property value. The parameter values
138    /// differ depending on whether they are associated with a group-scheduled
139    /// "VEVENT", "VTODO", or "VJOURNAL". The values MUST match one of the
140    /// values allowed for the given calendar component.  If not specified on a
141    /// property that allows this parameter, the default value is NEEDS-ACTION.
142    /// Applications MUST treat x-name and iana-token values they don't
143    /// recognize the same way as they would the NEEDS-ACTION value.
144    ///
145    /// See also: RFC 5545 Section 3.2.12. Participation Status
146    ParticipationStatus {
147        value: ParticipationStatus<S>,
148        span: S::Span,
149    },
150
151    /// This parameter can be specified on a property that specifies a
152    /// recurrence identifier. The parameter specifies the effective range of
153    /// recurrence instances that is specified by the property. The effective
154    /// range is from the recurrence identifier specified by the property. If
155    /// this parameter is not specified on an allowed property, then the
156    /// default range is the single instance specified by the recurrence
157    /// identifier value of the property.
158    ///
159    /// See also: RFC 5545 Section 3.2.13. Recurrence Identifier Range
160    RecurrenceIdRange {
161        value: RecurrenceIdRange,
162        span: S::Span,
163    },
164
165    /// This parameter can be specified on properties that specify an alarm
166    /// trigger with a "DURATION" value type. The parameter specifies whether
167    /// the alarm will trigger relative to the start or end of the calendar
168    /// component.
169    ///
170    /// See also: RFC 5545 Section 3.2.14. Alarm Trigger Relationship
171    AlarmTriggerRelationship {
172        value: AlarmTriggerRelationship,
173        span: S::Span,
174    },
175
176    /// This parameter can be specified on a property that references another
177    /// related calendar. The parameter specifies the hierarchical relationship
178    /// type of the calendar component referenced by the property. Applications
179    /// MUST treat x-name and iana-token values they don't recognize the same
180    /// way as they would the PARENT value.
181    ///
182    /// See also: RFC 5545 Section 3.2.15. Relationship Type
183    RelationshipType {
184        value: RelationshipType<S>,
185        span: S::Span,
186    },
187
188    /// This parameter can be specified on properties with a CAL-ADDRESS value
189    /// type. The parameter specifies the participation role for the calendar
190    /// user specified by the property in the group schedule calendar component.
191    /// Applications MUST treat x-name and iana-token values they don't
192    /// recognize the same way as they would the REQ-PARTICIPANT value.
193    ///
194    /// See also: RFC 5545 Section 3.2.16. Participation Role
195    ParticipationRole {
196        value: ParticipationRole<S>,
197        span: S::Span,
198    },
199
200    /// This parameter can be specified on properties with a CAL-ADDRESS value
201    /// type. The parameter specifies the calendar user that is acting on behalf
202    /// of the calendar user specified by the property. The parameter value MUST
203    /// be a mailto URI as defined in [RFC2368]. The individual calendar address
204    /// parameter values MUST each be specified in a quoted-string.
205    ///
206    /// See also: RFC 5545 Section 3.2.18. Sent By
207    SendBy { value: S, span: S::Span },
208
209    /// This parameter can be specified on properties with a CAL-ADDRESS value
210    /// type. The parameter identifies the expectation of a reply from the
211    /// calendar user specified by the property value. This parameter is used
212    /// by the "Organizer" to request a participation status reply from an
213    /// "Attendee" of a group-scheduled event or to-do. If not specified on a
214    /// property that allows this parameter, the default value is FALSE.
215    RsvpExpectation { value: bool, span: S::Span },
216
217    /// This parameter MUST be specified on the "DTSTART", "DTEND", "DUE",
218    /// "EXDATE", and "RDATE" properties when either a DATE-TIME or TIME value
219    /// type is specified and when the value is neither a UTC or a "floating"
220    /// time. Refer to the DATE-TIME or TIME value type definition for a
221    /// description of UTC and "floating time" formats.  This property
222    /// parameter specifies a text value that uniquely identifies the
223    /// "VTIMEZONE" calendar component to be used when evaluating the time
224    /// portion of the property.  The value of the "TZID" property parameter
225    /// will be equal to the value of the "TZID" property for the matching time
226    /// zone definition.  An individual "VTIMEZONE" calendar component MUST be
227    /// specified for each unique "TZID" parameter value specified in the
228    /// iCalendar object.
229    ///
230    /// See also: RFC 5545 Section 3.2.19. Time Zone Identifier
231    TimeZoneIdentifier {
232        /// The TZID parameter value
233        value: S,
234        /// The time zone definition associated with this TZID
235        #[cfg(feature = "jiff")]
236        tz: jiff::tz::TimeZone,
237        /// The span of the parameter
238        span: S::Span,
239    },
240
241    /// This parameter specifies the value type and format of the property
242    /// value. The property values MUST be of a single value type. For example,
243    /// a "RDATE" property cannot have a combination of DATE-TIME and TIME
244    /// value types.
245    ///
246    /// If the property's value is the default value type, then this parameter
247    /// need not be specified.  However, if the property's default value type
248    /// is overridden by some other allowable value type, then this parameter
249    /// MUST be specified.
250    ///
251    /// See also: RFC 5545 Section 3.2.20. Value Data Types
252    ValueType { value: ValueType<S>, span: S::Span },
253
254    /// Custom experimental x-name parameter.
255    ///
256    /// Per RFC 5545 Section 3.2: Applications MUST ignore x-param values,
257    /// but preserve the data for round-trip compatibility.
258    ///
259    /// See also: RFC 5545 Section 3.2 (Parameter definition)
260    XName(RawParameter<S>),
261
262    /// Unrecognized iana-token parameter.
263    ///
264    /// Per RFC 5545 Section 3.2: Applications MUST ignore iana-param values
265    /// they don't recognize, but preserve the data for round-trip compatibility.
266    ///
267    /// See also: RFC 5545 Section 3.2 (Parameter definition)
268    Unrecognized(RawParameter<S>),
269}
270
271impl<S: StringStorage> Parameter<S> {
272    /// Get the kind of the parameter
273    #[must_use]
274    pub fn kind(&self) -> ParameterKind<&S> {
275        match self {
276            Parameter::AlternateText { .. } => ParameterKind::AlternateText,
277            Parameter::CommonName { .. } => ParameterKind::CommonName,
278            Parameter::CalendarUserType { .. } => ParameterKind::CalendarUserType,
279            Parameter::Delegators { .. } => ParameterKind::Delegators,
280            Parameter::Delegatees { .. } => ParameterKind::Delegatees,
281            Parameter::Directory { .. } => ParameterKind::Directory,
282            Parameter::Encoding { .. } => ParameterKind::Encoding,
283            Parameter::FormatType { .. } => ParameterKind::FormatType,
284            Parameter::FreeBusyType { .. } => ParameterKind::FreeBusyType,
285            Parameter::Language { .. } => ParameterKind::Language,
286            Parameter::GroupOrListMembership { .. } => ParameterKind::GroupOrListMembership,
287            Parameter::ParticipationStatus { .. } => ParameterKind::ParticipationStatus,
288            Parameter::RecurrenceIdRange { .. } => ParameterKind::RecurrenceIdRange,
289            Parameter::AlarmTriggerRelationship { .. } => ParameterKind::AlarmTriggerRelationship,
290            Parameter::RelationshipType { .. } => ParameterKind::RelationshipType,
291            Parameter::ParticipationRole { .. } => ParameterKind::ParticipationRole,
292            Parameter::SendBy { .. } => ParameterKind::SendBy,
293            Parameter::RsvpExpectation { .. } => ParameterKind::RsvpExpectation,
294            Parameter::TimeZoneIdentifier { .. } => ParameterKind::TimeZoneIdentifier,
295            Parameter::ValueType { .. } => ParameterKind::ValueType,
296            Parameter::XName(raw) => ParameterKind::XName(&raw.name),
297            Parameter::Unrecognized(raw) => ParameterKind::Unrecognized(&raw.name),
298        }
299    }
300
301    /// Span of the parameter
302    #[must_use]
303    pub fn span(&self) -> S::Span {
304        match self {
305            Parameter::AlternateText { span, .. }
306            | Parameter::CommonName { span, .. }
307            | Parameter::CalendarUserType { span, .. }
308            | Parameter::Delegators { span, .. }
309            | Parameter::Delegatees { span, .. }
310            | Parameter::Directory { span, .. }
311            | Parameter::Encoding { span, .. }
312            | Parameter::FormatType { span, .. }
313            | Parameter::FreeBusyType { span, .. }
314            | Parameter::Language { span, .. }
315            | Parameter::GroupOrListMembership { span, .. }
316            | Parameter::ParticipationStatus { span, .. }
317            | Parameter::RecurrenceIdRange { span, .. }
318            | Parameter::AlarmTriggerRelationship { span, .. }
319            | Parameter::RelationshipType { span, .. }
320            | Parameter::ParticipationRole { span, .. }
321            | Parameter::SendBy { span, .. }
322            | Parameter::RsvpExpectation { span, .. }
323            | Parameter::TimeZoneIdentifier { span, .. }
324            | Parameter::ValueType { span, .. } => *span,
325
326            Parameter::XName(raw) | Parameter::Unrecognized(raw) => raw.span,
327        }
328    }
329}
330
331impl Parameter<Segments<'_>> {
332    /// Convert borrowed type to owned type
333    #[must_use]
334    pub fn to_owned(&self) -> Parameter<String> {
335        match self {
336            Parameter::AlternateText { value, .. } => Parameter::AlternateText {
337                value: value.to_owned(),
338                span: (),
339            },
340            Parameter::CommonName { value, .. } => Parameter::CommonName {
341                value: value.to_owned(),
342                span: (),
343            },
344            Parameter::CalendarUserType { value, .. } => Parameter::CalendarUserType {
345                value: value.to_owned(),
346                span: (),
347            },
348            Parameter::Delegators { values, .. } => Parameter::Delegators {
349                values: values.iter().map(Segments::to_owned).collect(),
350                span: (),
351            },
352            Parameter::Delegatees { values, .. } => Parameter::Delegatees {
353                values: values.iter().map(Segments::to_owned).collect(),
354                span: (),
355            },
356            Parameter::Directory { value, .. } => Parameter::Directory {
357                value: value.to_owned(),
358                span: (),
359            },
360            Parameter::Encoding { value, .. } => Parameter::Encoding {
361                value: *value,
362                span: (),
363            },
364            Parameter::FormatType { value, .. } => Parameter::FormatType {
365                value: value.to_owned(),
366                span: (),
367            },
368            Parameter::FreeBusyType { value, .. } => Parameter::FreeBusyType {
369                value: value.to_owned(),
370                span: (),
371            },
372            Parameter::Language { value, .. } => Parameter::Language {
373                value: value.to_owned(),
374                span: (),
375            },
376            Parameter::GroupOrListMembership { values, .. } => Parameter::GroupOrListMembership {
377                values: values.iter().map(Segments::to_owned).collect(),
378                span: (),
379            },
380            Parameter::ParticipationStatus { value, .. } => Parameter::ParticipationStatus {
381                value: value.to_owned(),
382                span: (),
383            },
384            Parameter::RecurrenceIdRange { value, .. } => Parameter::RecurrenceIdRange {
385                value: *value,
386                span: (),
387            },
388            Parameter::AlarmTriggerRelationship { value, .. } => {
389                Parameter::AlarmTriggerRelationship {
390                    value: *value,
391                    span: (),
392                }
393            }
394            Parameter::RelationshipType { value, .. } => Parameter::RelationshipType {
395                value: value.to_owned(),
396                span: (),
397            },
398            Parameter::ParticipationRole { value, .. } => Parameter::ParticipationRole {
399                value: value.to_owned(),
400                span: (),
401            },
402            Parameter::SendBy { value, .. } => Parameter::SendBy {
403                value: value.to_owned(),
404                span: (),
405            },
406            Parameter::RsvpExpectation { value, .. } => Parameter::RsvpExpectation {
407                value: *value,
408                span: (),
409            },
410            Parameter::TimeZoneIdentifier {
411                value,
412                #[cfg(feature = "jiff")]
413                tz,
414                ..
415            } => Parameter::TimeZoneIdentifier {
416                value: value.to_owned(),
417                #[cfg(feature = "jiff")]
418                tz: tz.clone(),
419                span: (),
420            },
421            Parameter::ValueType { value, .. } => Parameter::ValueType {
422                value: value.to_owned(),
423                span: (),
424            },
425            Parameter::XName(raw) => Parameter::XName(raw.to_owned()),
426            Parameter::Unrecognized(raw) => Parameter::Unrecognized(raw.to_owned()),
427        }
428    }
429}
430
431impl<'src> TryFrom<RawParameter<Segments<'src>>> for Parameter<Segments<'src>> {
432    type Error = Vec<TypedError<'src>>;
433
434    fn try_from(mut param: RawParameter<Segments<'src>>) -> Result<Self, Self::Error> {
435        // Parse the parameter kind
436        let kind = ParameterKind::from(param.name.clone());
437
438        // Handle parsing based on the kind
439        match kind {
440            ParameterKind::AlternateText => {
441                parse_single_quoted(&mut param, kind).map(|value| Parameter::AlternateText {
442                    value,
443                    span: param.span,
444                })
445            }
446            ParameterKind::CommonName => {
447                parse_single(&mut param, kind).map(|v| Parameter::CommonName {
448                    value: v.value,
449                    span: param.span,
450                })
451            }
452            ParameterKind::CalendarUserType => parse_cutype(param),
453            ParameterKind::Delegators => {
454                let span = param.span;
455                parse_multiple_quoted(param, &kind)
456                    .map(|values| Parameter::Delegators { values, span })
457            }
458            ParameterKind::Delegatees => {
459                let span = param.span;
460                parse_multiple_quoted(param, &kind)
461                    .map(|values| Parameter::Delegatees { values, span })
462            }
463            ParameterKind::Directory => {
464                parse_single_quoted(&mut param, kind).map(|value| Parameter::Directory {
465                    value,
466                    span: param.span,
467                })
468            }
469            ParameterKind::Encoding => parse_encoding(param),
470            ParameterKind::FormatType => {
471                parse_single(&mut param, kind).map(|v| Parameter::FormatType {
472                    value: v.value,
473                    span: param.span,
474                })
475            }
476            ParameterKind::FreeBusyType => parse_fbtype(param),
477            ParameterKind::Language => {
478                parse_single(&mut param, kind).map(|v| Parameter::Language {
479                    value: v.value,
480                    span: param.span,
481                })
482            }
483            ParameterKind::GroupOrListMembership => {
484                let span = param.span;
485                parse_multiple_quoted(param, &kind)
486                    .map(|values| Parameter::GroupOrListMembership { values, span })
487            }
488            ParameterKind::ParticipationStatus => parse_partstat(param),
489            ParameterKind::RecurrenceIdRange => parse_range(param),
490            ParameterKind::AlarmTriggerRelationship => parse_alarm_trigger_relationship(param),
491            ParameterKind::RelationshipType => parse_reltype(param),
492            ParameterKind::ParticipationRole => parse_role(param),
493            ParameterKind::SendBy => {
494                parse_single_quoted(&mut param, kind).map(|value| Parameter::SendBy {
495                    value,
496                    span: param.span,
497                })
498            }
499            ParameterKind::RsvpExpectation => parse_rsvp(param),
500            ParameterKind::TimeZoneIdentifier => parse_tzid(param),
501            ParameterKind::ValueType => parse_value_type(param),
502            // Preserve unknown parameter per RFC 5545 Section 3.2
503            // TODO: emit warning for x-name / unrecognized iana-token parameter
504            ParameterKind::XName(_) => Ok(Parameter::XName(param)),
505            ParameterKind::Unrecognized(_) => Ok(Parameter::Unrecognized(param)),
506        }
507    }
508}