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

lennart-k / ical-rs / #30

22 Dec 2025 12:31PM UTC coverage: 78.142% (-3.6%) from 81.714%
#30

push

lennart-k
Add timezone type

0 of 38 new or added lines in 1 file covered. (0.0%)

2 existing lines in 1 file now uncovered.

715 of 915 relevant lines covered (78.14%)

3.21 hits per line

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

79.63
/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

9
pub mod ical;
10
pub mod vcard;
11

12
// Sys mods
13
use crate::types::InvalidDuration;
14
use std::io::BufRead;
15
use std::marker::PhantomData;
16
// Internal mods
17
use crate::{
18
    LineReader,
19
    property::{Property, PropertyError, PropertyParser},
20
};
21

22
#[derive(Debug, thiserror::Error, PartialEq, Eq)]
23
pub enum ParserError {
24
    #[error("invalid component")]
25
    InvalidComponent,
26
    #[error("incomplete object")]
27
    NotComplete,
28
    #[error("missing header")]
29
    MissingHeader,
30
    #[error("property error: {0}")]
31
    PropertyError(#[from] PropertyError),
32
    #[error("missing property: {0}")]
33
    MissingProperty(&'static str),
34
    #[error("property conflict: {0}")]
35
    PropertyConflict(&'static str),
36
    #[error(transparent)]
37
    InvalidDuration(#[from] InvalidDuration),
38
}
39

40
/// An immutable interface for an Ical/Vcard component.
41
/// This is also implemented by verified components
42
pub trait Component: Clone {
UNCOV
43
    const NAMES: &[&str];
×
44

45
    type Unverified: ComponentMut;
46

47
    fn get_properties(&self) -> &Vec<Property>;
48
    fn mutable(self) -> Self::Unverified;
49

50
    fn get_property<'c>(&'c self, name: &str) -> Option<&'c Property> {
21✔
51
        self.get_properties().iter().find(|p| p.name == name)
65✔
52
    }
53
}
54

55
/// A mutable interface for an Ical/Vcard component.
56
///
57
/// It take a `PropertyParser` and fill the component with. It's also able to create
58
/// sub-component used by event and alarms.
59
pub trait ComponentMut: Component + Default {
UNCOV
60
    type Verified: Component<Unverified = Self>;
×
61

62
    /// Add the givent sub component.
63
    fn add_sub_component<B: BufRead>(
64
        &mut self,
65
        value: &str,
66
        line_parser: &mut PropertyParser<B>,
67
    ) -> Result<(), ParserError>;
68

69
    fn get_properties_mut(&mut self) -> &mut Vec<Property>;
70

71
    /// Add the givent property.
72
    fn add_property(&mut self, property: Property) {
19✔
73
        self.get_properties_mut().push(property);
36✔
74
    }
75

76
    fn get_property_mut<'c>(&'c mut self, name: &str) -> Option<&'c mut Property> {
×
77
        self.get_properties_mut()
×
78
            .iter_mut()
79
            .find(|p| p.name == name)
×
80
    }
81

82
    fn remove_property(&mut self, name: &str) {
×
83
        self.get_properties_mut().retain(|prop| prop.name != name);
×
84
    }
85
    fn set_property(&mut self, prop: Property) {
×
86
        self.remove_property(&prop.name);
×
87
        self.add_property(prop);
×
88
    }
89

90
    fn verify(self) -> Result<Self::Verified, ParserError>;
91

92
    /// Parse the content from `line_parser` and fill the component with.
93
    fn parse<B: BufRead>(
22✔
94
        &mut self,
95
        line_parser: &mut PropertyParser<B>,
96
    ) -> Result<(), ParserError> {
97
        loop {
38✔
98
            let line = line_parser.next().ok_or(ParserError::NotComplete)??;
20✔
99

100
            match line.name.to_uppercase().as_str() {
59✔
101
                "END" => break,
20✔
102
                "BEGIN" => match line.value {
47✔
103
                    Some(v) => self.add_sub_component(v.as_str(), line_parser)?,
11✔
104
                    None => return Err(ParserError::NotComplete),
×
105
                },
106

107
                _ => self.add_property(line),
37✔
108
            };
109
        }
110
        Ok(())
22✔
111
    }
112

113
    fn from_parser<B: BufRead>(line_parser: &mut PropertyParser<B>) -> Result<Self, ParserError> {
5✔
114
        let mut out = Self::default();
4✔
115
        out.parse(line_parser)?;
11✔
116
        Ok(out)
7✔
117
    }
118
}
119

120
/// Reader returning `IcalCalendar` object from a `BufRead`.
121
pub struct ComponentParser<B: BufRead, T: Component> {
122
    line_parser: PropertyParser<B>,
123
    _t: PhantomData<T>,
124
}
125

126
impl<B: BufRead, T: Component> ComponentParser<B, T> {
127
    /// Return a new `IcalParser` from a `Reader`.
128
    pub fn new(reader: B) -> ComponentParser<B, T> {
7✔
129
        let line_reader = LineReader::new(reader);
7✔
130
        let line_parser = PropertyParser::new(line_reader);
7✔
131

132
        ComponentParser {
133
            line_parser,
134
            _t: Default::default(),
7✔
135
        }
136
    }
137

138
    /// Read the next line and check if it's a valid VCALENDAR start.
139
    fn check_header(&mut self) -> Result<Option<()>, ParserError> {
7✔
140
        let line = match self.line_parser.next() {
7✔
141
            Some(val) => val.map_err(ParserError::PropertyError)?,
6✔
142
            None => return Ok(None),
4✔
143
        };
144

145
        if line.name.to_uppercase() != "BEGIN"
11✔
146
            || line.value.is_none()
5✔
147
            || !T::NAMES.contains(&line.value.as_ref().unwrap().to_uppercase().as_str())
11✔
148
            || !line.params.is_empty()
6✔
149
        {
150
            return Err(ParserError::MissingHeader);
2✔
151
        }
152

153
        Ok(Some(()))
5✔
154
    }
155
}
156

157
impl<B: BufRead, T: Component> Iterator for ComponentParser<B, T> {
158
    type Item = Result<<T::Unverified as ComponentMut>::Verified, ParserError>;
159

160
    fn next(&mut self) -> Option<Self::Item> {
7✔
161
        match self.check_header() {
7✔
162
            Ok(res) => res?,
5✔
163
            Err(err) => return Some(Err(err)),
2✔
164
        };
165

166
        let mut comp = T::Unverified::default();
6✔
167
        let result = match comp.parse(&mut self.line_parser) {
12✔
168
            Ok(_) => comp.verify(),
8✔
169
            Err(err) => Err(err),
2✔
170
        };
171

172
        #[cfg(feature = "test")]
173
        {
174
            // Run this for more test coverage
175
            if let Ok(comp) = result.as_ref() {
19✔
176
                let mutable = comp.clone().mutable();
7✔
177
                mutable.get_properties();
9✔
178
            }
179
        }
180

181
        Some(result)
7✔
182
    }
183
}
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