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}