aimcal_ical/
typed.rs

1// SPDX-FileCopyrightText: 2025-2026 Zexin Yuan <aim@yzx9.xyz>
2//
3// SPDX-License-Identifier: Apache-2.0
4
5//! Typed representation of iCalendar components and properties.
6//!
7//! This module provides the typed analysis phase of the iCalendar parser,
8//! converting syntax components into strongly-typed representations with
9//! validated parameters and values.
10
11use std::collections::HashSet;
12
13use chumsky::error::Rich;
14use thiserror::Error;
15
16use crate::parameter::{Parameter, ParameterKind, ValueType};
17use crate::property::{Property, PropertyKind};
18use crate::string_storage::{Segments, Span};
19use crate::syntax::{RawComponent, RawParameter, RawProperty};
20use crate::value::{Value, parse_value};
21
22/// Perform typed analysis on raw components, returning typed components or errors.
23///
24/// ## Errors
25/// If there are typing errors, a vector of errors will be returned.
26pub fn typed_analysis(
27    components: Vec<RawComponent<'_>>,
28) -> Result<Vec<TypedComponent<'_>>, Vec<TypedError<'_>>> {
29    let mut typed_components = Vec::with_capacity(components.len());
30    let mut errors = Vec::new();
31    for comp in components {
32        match typed_component(comp) {
33            Ok(typed_comp) => typed_components.push(typed_comp),
34            Err(errs) => errors.extend(errs),
35        }
36    }
37
38    if errors.is_empty() {
39        Ok(typed_components)
40    } else {
41        Err(errors)
42    }
43}
44
45fn typed_component(comp: RawComponent<'_>) -> Result<TypedComponent<'_>, Vec<TypedError<'_>>> {
46    let mut existing_props = HashSet::with_capacity(comp.properties.len());
47    let mut properties = Vec::with_capacity(comp.properties.len());
48    let mut errors = Vec::new();
49    for prop in comp.properties {
50        match parsed_property(&mut existing_props, prop) {
51            // Convert ParsedProperty to Property
52            Ok(prop) => match Property::try_from(prop) {
53                Ok(property) => properties.push(property),
54                Err(errs) => errors.extend(errs),
55            },
56            Err(errs) => errors.extend(errs),
57        }
58    }
59
60    let mut children = Vec::with_capacity(comp.children.len());
61    for comp in comp.children {
62        match typed_component(comp) {
63            Ok(child) => children.push(child),
64            Err(errs) => errors.extend(errs),
65        }
66    }
67
68    if errors.is_empty() {
69        Ok(TypedComponent {
70            name: comp.name,
71            properties,
72            children,
73            span: comp.span,
74        })
75    } else {
76        Err(errors)
77    }
78}
79
80fn parsed_property<'src>(
81    _existing: &mut HashSet<&str>,
82    prop: RawProperty<'src>,
83) -> Result<ParsedProperty<'src>, Vec<TypedError<'src>>> {
84    // Determine property kind from name (infallible - always returns a kind)
85    let kind = PropertyKind::from(prop.name.clone());
86
87    let parameters = parameters(prop.parameters)?;
88    let value_types = value_types(&kind, &parameters)?;
89
90    // PERF: cache parser
91    let value = parse_value(&value_types, &prop.value).map_err(|errs| {
92        errs.into_iter()
93            .map(|err| TypedError::ValueSyntax {
94                value: prop.value.clone(),
95                err,
96            })
97            .collect::<Vec<_>>()
98    })?;
99
100    Ok(ParsedProperty {
101        kind,
102        parameters,
103        value,
104        span: prop.name.span(),
105        name: prop.name,
106    })
107}
108
109/// A typed iCalendar component with validated properties and nested child components.
110#[derive(Debug, Clone)]
111pub struct TypedComponent<'src> {
112    /// Component name (e.g., "VCALENDAR", "VEVENT", "VTIMEZONE", "VALARM")
113    pub name: Segments<'src>,
114    /// Properties in original order
115    pub properties: Vec<Property<Segments<'src>>>,
116    /// Nested child components
117    pub children: Vec<TypedComponent<'src>>,
118    /// Span of the entire component (from BEGIN to END)
119    pub span: Span,
120}
121
122/// A typed iCalendar property with validated parameters and values.
123#[derive(Debug, Clone)]
124pub struct ParsedProperty<'src> {
125    /// Property kind
126    pub kind: PropertyKind<Segments<'src>>,
127    /// Property name (preserved for unknown properties)
128    pub name: Segments<'src>,
129    /// Property parameters
130    pub parameters: Vec<Parameter<Segments<'src>>>,
131    /// Property value
132    pub value: Value<Segments<'src>>,
133    /// The span of the property name (for error reporting)
134    pub span: Span,
135}
136
137/// Errors that can occur during typed analysis of iCalendar components.
138#[non_exhaustive]
139#[derive(Error, Debug, Clone)]
140pub enum TypedError<'src> {
141    /// Parameter occurs multiple times when only one is allowed.
142    #[error("Parameter '{parameter}' occurs multiple times")]
143    ParameterDuplicated {
144        /// The parameter name
145        parameter: ParameterKind<Segments<'src>>,
146        /// The span of the error
147        span: Span,
148    },
149
150    /// Parameter does not allow multiple values.
151    #[error("Parameter '{parameter}' does not allow multiple values")]
152    ParameterMultipleValuesDisallowed {
153        /// The parameter name
154        parameter: ParameterKind<Segments<'src>>,
155        /// The span of the error
156        span: Span,
157    },
158
159    /// Parameter value must be quoted.
160    #[error("Parameter '{parameter}={value}' value must be quoted")]
161    ParameterValueMustBeQuoted {
162        /// The parameter name
163        parameter: ParameterKind<Segments<'src>>,
164        /// The parameter value
165        value: Segments<'src>,
166        /// The span of the error
167        span: Span,
168    },
169
170    /// Parameter value must not be quoted.
171    #[error("Parameter '{parameter}=\"{value}\"' value must not be quoted")]
172    ParameterValueMustNotBeQuoted {
173        /// The parameter name
174        parameter: ParameterKind<Segments<'src>>,
175        /// The parameter value
176        value: Segments<'src>,
177        /// The span of the error
178        span: Span,
179    },
180
181    /// Invalid parameter value.
182    #[error("Invalid value for parameter '{parameter}={value}'")]
183    ParameterValueInvalid {
184        /// The parameter name
185        parameter: ParameterKind<Segments<'src>>,
186        /// The parameter value
187        value: Segments<'src>,
188        /// The span of the error
189        span: Span,
190    },
191
192    /// Value type is not allowed for this property.
193    #[error("Invalid value type '{value_type}' for property '{property}'")]
194    ValueTypeDisallowed {
195        /// The property name
196        property: PropertyKind<Segments<'src>>,
197        /// The value type that was provided
198        value_type: ValueType<Segments<'src>>,
199        /// The expected value types
200        expected_types: &'static [ValueType<String>],
201        /// The span of the error
202        span: Span,
203    },
204
205    /// Syntax error in property value.
206    #[error("Syntax error in value '{value}': {err}")]
207    ValueSyntax {
208        /// The value
209        value: Segments<'src>,
210        /// The syntax error details
211        err: Rich<'src, char>,
212    },
213
214    /// Property kind does not match the expected type.
215    #[error("Expected property kind '{expected}', found '{found}'")]
216    PropertyUnexpectedKind {
217        /// Expected property kind
218        expected: PropertyKind<Segments<'src>>,
219        /// Actual property kind found
220        found: PropertyKind<Segments<'src>>,
221        /// The span of the error
222        span: Span,
223    },
224
225    /// Property has no values when at least one is required.
226    #[error("Property '{property}' has no values")]
227    PropertyMissingValue {
228        /// The property that is missing values
229        property: PropertyKind<Segments<'src>>,
230        /// The span of the error
231        span: Span,
232    },
233
234    /// Property has an invalid value count.
235    #[error("Property '{property}' requires exactly {expected} value(s), but found {found}")]
236    PropertyInvalidValueCount {
237        /// The property kind
238        property: PropertyKind<Segments<'src>>,
239        /// Expected number of values
240        expected: usize,
241        /// Actual number of values found
242        found: usize,
243        /// The span of the error
244        span: Span,
245    },
246
247    /// Property value is invalid or out of allowed range.
248    #[error("Invalid value '{value}' for property '{property}'")]
249    PropertyInvalidValue {
250        /// The property that has the invalid value
251        property: PropertyKind<Segments<'src>>,
252        /// Description of why the value is invalid
253        value: String,
254        /// The span of the error
255        span: Span,
256    },
257
258    /// Property value has unexpected type.
259    #[error("Expected {expected:?} value(s) for property '{property}', found {found}")]
260    PropertyUnexpectedValue {
261        /// The property that has the wrong type
262        property: PropertyKind<Segments<'src>>,
263        /// Expected value types
264        expected: &'static [ValueType<String>],
265        /// Actual value type found
266        found: ValueType<Segments<'src>>,
267        /// The span of the error
268        span: Span,
269    },
270}
271
272impl TypedError<'_> {
273    /// Get the span of this error.
274    #[must_use]
275    pub fn span(&self) -> Span {
276        match self {
277            TypedError::ParameterDuplicated { span, .. }
278            | TypedError::ParameterMultipleValuesDisallowed { span, .. }
279            | TypedError::ParameterValueMustBeQuoted { span, .. }
280            | TypedError::ParameterValueMustNotBeQuoted { span, .. }
281            | TypedError::ParameterValueInvalid { span, .. }
282            | TypedError::ValueTypeDisallowed { span, .. }
283            | TypedError::PropertyUnexpectedKind { span, .. }
284            | TypedError::PropertyInvalidValueCount { span, .. }
285            | TypedError::PropertyInvalidValue { span, .. }
286            | TypedError::PropertyMissingValue { span, .. }
287            | TypedError::PropertyUnexpectedValue { span, .. } => *span,
288
289            TypedError::ValueSyntax { err, .. } => (*err.span()).into(),
290        }
291    }
292}
293
294fn parameters(
295    params: Vec<RawParameter<Segments<'_>>>,
296) -> Result<Vec<Parameter<Segments<'_>>>, Vec<TypedError<'_>>> {
297    let mut parsed = Vec::with_capacity(params.len());
298    let mut errors = Vec::new();
299    for param in params {
300        match Parameter::try_from(param) {
301            Ok(typed) => parsed.push(typed),
302            Err(errs) => errors.extend(errs),
303        }
304    }
305
306    if errors.is_empty() {
307        Ok(parsed)
308    } else {
309        Err(errors)
310    }
311}
312
313fn value_types<'src>(
314    prop_kind: &PropertyKind<Segments<'src>>,
315    params: &Vec<Parameter<Segments<'src>>>,
316) -> Result<Vec<ValueType<String>>, Vec<TypedError<'src>>> {
317    // If VALUE parameter is explicitly specified, use only that type
318    if let Some(Parameter::ValueType { value, span }) = params
319        .iter()
320        .find(|param| matches!(param, Parameter::ValueType { .. }))
321    {
322        match prop_kind.value_types() {
323            Some(value_types) => {
324                if value_types
325                    .iter()
326                    .any(|a| a.try_eq_known(&value.to_owned()).unwrap_or(false))
327                {
328                    // Return only the explicitly specified type
329                    Ok(vec![value.to_owned()])
330                } else {
331                    Err(vec![TypedError::ValueTypeDisallowed {
332                        property: prop_kind.clone(),
333                        value_type: value.clone(),
334                        expected_types: value_types,
335                        span: *span,
336                    }])
337                }
338            }
339
340            // For x-name/unrecognized properties, allow any value type
341            None => Ok(vec![value.to_owned()]),
342        }
343    } else {
344        match prop_kind.value_types() {
345            // No VALUE parameter specified - return all allowed types for type inference,
346            // EXCEPT for BINARY which MUST be explicitly specified with VALUE=BINARY
347            // (per RFC 5545 Section 3.3.1 and 3.8.1.1)
348            Some(value_types) => value_types
349                .iter()
350                .filter(|&t| !matches!(t, ValueType::<String>::Binary))
351                .map(|t| Ok(t.clone()))
352                .collect(),
353
354            // NOTE: For x-name/unrecognized properties, allow any value type.
355            // But we don't return all possible types for type inference, since
356            // we cannot infer the allowed types.
357            None => Ok(vec![ValueType::<String>::Unrecognized(
358                Segments::default().to_string(),
359            )]),
360        }
361    }
362}