• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lennart-k / ical-rs / #79

13 Jan 2026 01:52PM UTC coverage: 77.757% (-0.05%) from 77.802%
#79

Pull #2

lennart-k
Remove forgotten dbg!()
Pull Request #2: Major overhaul

955 of 1241 new or added lines in 31 files covered. (76.95%)

28 existing lines in 10 files now uncovered.

1241 of 1596 relevant lines covered (77.76%)

2.7 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

77.97
/src/parser/mod.rs
1
//! Wrapper around `PropertyParser`
2
//!
3
//! #### Warning
4
//!   The parsers (`VcardParser` / `IcalParser`) only parse the content and set to uppercase
5
//!   the case-insensitive fields.  No checks are made on the fields validity.
6
//!
7
//!
8
pub mod ical;
9
pub mod vcard;
10
use crate::line::BytesLines;
11
use crate::types::{CalDateTimeError, InvalidDuration};
12
use crate::{
13
    LineReader,
14
    property::{ContentLine, PropertyError, PropertyParser},
15
};
16
use std::borrow::Cow;
17
use std::collections::HashMap;
18
use std::marker::PhantomData;
19

20
mod property;
21
pub use property::*;
22

23
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
24
pub enum ParserError {
25
    #[error("empty input")]
26
    EmptyInput,
27
    #[error("too many components in input, expected one")]
28
    TooManyComponents,
29
    #[error("invalid component: {0}")]
30
    InvalidComponent(String),
31
    #[error("incomplete object")]
32
    NotComplete,
33
    #[error("missing header")]
34
    MissingHeader,
35
    #[error("property error: {0}")]
36
    PropertyError(#[from] PropertyError),
37
    #[error("missing property: {0}")]
38
    MissingProperty(&'static str),
39
    #[error("missing property: UID")]
40
    MissingUID,
41
    #[error("property conflict: {0}")]
42
    PropertyConflict(&'static str),
43
    #[error(transparent)]
44
    InvalidDuration(#[from] InvalidDuration),
45
    #[error("invalid property value: {0}")]
46
    InvalidPropertyValue(String),
47
    #[error("invalid property value type for: {0}")]
48
    InvalidPropertyType(String),
49
    #[error(transparent)]
50
    RRule(#[from] rrule::RRuleError),
51
    #[error(transparent)]
52
    DateTime(#[from] CalDateTimeError),
53
    #[error("Invalid CALSCALE: Only GREGORIAN supported")]
54
    InvalidCalscale,
55
    #[error("Invalid VERSION: MUST be 1.0 or 2.0")]
56
    InvalidVersion,
57
    #[error("Multiple main events are not allowed in a calendar object")]
58
    MultipleMainObjects,
59
    #[error("Differing UIDs inside a calendar object")]
60
    DifferingUIDs,
61
    #[error("Override without RECURRENCE-ID")]
62
    MissingRecurId,
63
    #[error("DTSTART and RECURRENCE-ID must have the same value type and timezone")]
64
    DtstartNotMatchingRecurId,
65
}
66

67
/// An immutable interface for an Ical/Vcard component.
68
/// This is also implemented by verified components
69
pub trait Component: Clone {
70
    const NAMES: &[&str];
71

NEW
72
    fn get_comp_name(&self) -> &'static str {
×
NEW
73
        assert_eq!(
×
NEW
74
            Self::NAMES.len(),
×
NEW
75
            1,
×
NEW
76
            "Default implementation only applicable for fixed component name"
×
77
        );
NEW
78
        Self::NAMES[0]
×
79
    }
80

81
    type Unverified: ComponentMut;
82

83
    fn get_properties(&self) -> &Vec<ContentLine>;
84
    fn mutable(self) -> Self::Unverified;
85

86
    fn get_property<'c>(&'c self, name: &str) -> Option<&'c ContentLine> {
5✔
87
        self.get_properties().iter().find(|p| p.name == name)
16✔
88
    }
89

90
    fn get_named_properties<'c>(&'c self, name: &'c str) -> impl Iterator<Item = &'c ContentLine> {
7✔
91
        self.get_properties().iter().filter(move |p| p.name == name)
21✔
92
    }
93
}
94

95
/// A mutable interface for an Ical/Vcard component.
96
///
97
/// It takes a `PropertyParser` and fills the component with. It's also able to create
98
/// sub-component used by event and alarms.
99
pub trait ComponentMut: Component + Default {
100
    type Verified: Component<Unverified = Self>;
101

102
    /// Add the givent sub component.
103
    fn add_sub_component<'a, T: Iterator<Item = Cow<'a, [u8]>>>(
104
        &mut self,
105
        value: &str,
106
        line_parser: &mut PropertyParser<'a, T>,
107
    ) -> Result<(), ParserError>;
108

109
    fn get_properties_mut(&mut self) -> &mut Vec<ContentLine>;
110

UNCOV
111
    fn remove_property(&mut self, name: &str) {
×
112
        self.get_properties_mut().retain(|prop| prop.name != name);
×
113
    }
114

115
    /// Add the given property.
116
    #[inline]
117
    fn add_content_line(&mut self, property: ContentLine) {
14✔
118
        self.get_properties_mut().push(property);
29✔
119
    }
120

121
    fn build(
122
        self,
123
        timezones: Option<&HashMap<String, Option<chrono_tz::Tz>>>,
124
    ) -> Result<Self::Verified, ParserError>;
125

126
    /// Parse the content from `line_parser` and fill the component with.
127
    fn parse<'a, T: Iterator<Item = Cow<'a, [u8]>>>(
12✔
128
        &mut self,
129
        line_parser: &mut PropertyParser<'a, T>,
130
    ) -> Result<(), ParserError> {
131
        loop {
26✔
132
            let line = line_parser.next().ok_or(ParserError::NotComplete)??;
14✔
133

134
            match line.name.as_ref() {
27✔
135
                "END" => break,
13✔
136
                "BEGIN" => match line.value {
34✔
137
                    Some(v) => self.add_sub_component(v.as_str(), line_parser)?,
10✔
UNCOV
138
                    None => return Err(ParserError::NotComplete),
×
139
                },
140

141
                _ => self.add_content_line(line),
27✔
142
            };
143
        }
144
        Ok(())
10✔
145
    }
146

147
    fn from_parser<'a, T: Iterator<Item = Cow<'a, [u8]>>>(
4✔
148
        line_parser: &mut PropertyParser<'a, T>,
149
    ) -> Result<Self, ParserError> {
150
        let mut out = Self::default();
4✔
151
        out.parse(line_parser)?;
8✔
152
        Ok(out)
4✔
153
    }
154
}
155

156
pub struct ComponentParser<'a, C: Component, I: Iterator<Item = Cow<'a, [u8]>>> {
157
    line_parser: PropertyParser<'a, I>,
158
    _t: PhantomData<C>,
159
}
160

161
impl<'a, C: Component> ComponentParser<'a, C, BytesLines<'a>> {
162
    /// Return a new `IcalParser` from a `Reader`.
163
    pub fn from_slice(slice: &'a [u8]) -> Self {
6✔
164
        let line_reader = LineReader::from_slice(slice);
6✔
165
        let line_parser = PropertyParser::new(line_reader);
6✔
166

167
        ComponentParser {
168
            line_parser,
169
            _t: Default::default(),
6✔
170
        }
171
    }
172
}
173

174
impl<'a, C: Component, I: Iterator<Item = Cow<'a, [u8]>>> ComponentParser<'a, C, I> {
175
    /// Read the next line and check if it's a valid VCALENDAR start.
176
    #[inline]
177
    fn check_header(&mut self) -> Result<Option<()>, ParserError> {
6✔
178
        let line = match self.line_parser.next() {
6✔
179
            Some(val) => val.map_err(ParserError::PropertyError)?,
6✔
180
            None => return Ok(None),
4✔
181
        };
182

183
        if line.name != "BEGIN"
12✔
184
            || line.value.is_none()
12✔
185
            || !C::NAMES.contains(&line.value.as_ref().unwrap().to_uppercase().as_str())
6✔
186
            || !line.params.is_empty()
6✔
187
        {
188
            return Err(ParserError::MissingHeader);
2✔
189
        }
190

191
        Ok(Some(()))
6✔
192
    }
193

194
    pub fn expect_one(mut self) -> Result<<C::Unverified as ComponentMut>::Verified, ParserError> {
2✔
195
        let item = self.next().ok_or(ParserError::EmptyInput)??;
4✔
196
        if self.next().is_some() {
4✔
NEW
197
            return Err(ParserError::TooManyComponents);
×
198
        }
199
        Ok(item)
2✔
200
    }
201
}
202

203
impl<'a, C: Component, I: Iterator<Item = Cow<'a, [u8]>>> Iterator for ComponentParser<'a, C, I> {
204
    type Item = Result<<C::Unverified as ComponentMut>::Verified, ParserError>;
205

206
    fn next(&mut self) -> Option<Self::Item> {
6✔
207
        match self.check_header() {
6✔
208
            Ok(res) => res?,
6✔
209
            Err(err) => return Some(Err(err)),
2✔
210
        };
211

212
        let mut comp = C::Unverified::default();
6✔
213
        let result = match comp.parse(&mut self.line_parser) {
10✔
214
            Ok(_) => comp.build(None),
4✔
215
            Err(err) => Err(err),
2✔
216
        };
217

218
        #[cfg(all(feature = "test", not(feature = "bench")))]
219
        {
220
            // Run this for more test coverage
UNCOV
221
            if let Ok(comp) = result.as_ref() {
×
UNCOV
222
                let mutable = comp.clone().mutable();
×
UNCOV
223
                mutable.get_properties();
×
224
            }
225
        }
226

227
        Some(result)
5✔
228
    }
229
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc