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

lennart-k / ical-rs / #32

31 Dec 2025 12:29PM UTC coverage: 77.802% (-0.3%) from 78.057%
#32

push

lennart-k
Add accessor for all properties by name

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

1 existing line in 1 file now uncovered.

715 of 919 relevant lines covered (77.8%)

3.5 hits per line

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

75.44
/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 {
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> {
26✔
51
        self.get_properties().iter().find(|p| p.name == name)
76✔
52
    }
53

NEW
54
    fn get_named_properties<'c>(&'c self, name: &str) -> Vec<&'c Property> {
×
NEW
55
        self.get_properties()
×
56
            .iter()
NEW
57
            .filter(|p| p.name == name)
×
58
            .collect()
59
    }
60
}
61

62
/// A mutable interface for an Ical/Vcard component.
63
///
64
/// It take a `PropertyParser` and fill the component with. It's also able to create
65
/// sub-component used by event and alarms.
UNCOV
66
pub trait ComponentMut: Component + Default {
×
67
    type Verified: Component<Unverified = Self>;
68

69
    /// Add the givent sub component.
70
    fn add_sub_component<B: BufRead>(
71
        &mut self,
72
        value: &str,
73
        line_parser: &mut PropertyParser<B>,
74
    ) -> Result<(), ParserError>;
75

76
    fn get_properties_mut(&mut self) -> &mut Vec<Property>;
77

78
    /// Add the givent property.
79
    fn add_property(&mut self, property: Property) {
25✔
80
        self.get_properties_mut().push(property);
50✔
81
    }
82

83
    fn get_property_mut<'c>(&'c mut self, name: &str) -> Option<&'c mut Property> {
×
84
        self.get_properties_mut()
×
85
            .iter_mut()
86
            .find(|p| p.name == name)
×
87
    }
88

89
    fn remove_property(&mut self, name: &str) {
×
90
        self.get_properties_mut().retain(|prop| prop.name != name);
×
91
    }
92
    fn set_property(&mut self, prop: Property) {
×
93
        self.remove_property(&prop.name);
×
94
        self.add_property(prop);
×
95
    }
96

97
    fn verify(self) -> Result<Self::Verified, ParserError>;
98

99
    /// Parse the content from `line_parser` and fill the component with.
100
    fn parse<B: BufRead>(
25✔
101
        &mut self,
102
        line_parser: &mut PropertyParser<B>,
103
    ) -> Result<(), ParserError> {
104
        loop {
50✔
105
            let line = line_parser.next().ok_or(ParserError::NotComplete)??;
25✔
106

107
            match line.name.to_uppercase().as_str() {
75✔
108
                "END" => break,
25✔
109
                "BEGIN" => match line.value {
63✔
110
                    Some(v) => self.add_sub_component(v.as_str(), line_parser)?,
15✔
111
                    None => return Err(ParserError::NotComplete),
×
112
                },
113

114
                _ => self.add_property(line),
50✔
115
            };
116
        }
117
        Ok(())
24✔
118
    }
119

120
    fn from_parser<B: BufRead>(line_parser: &mut PropertyParser<B>) -> Result<Self, ParserError> {
4✔
121
        let mut out = Self::default();
6✔
122
        out.parse(line_parser)?;
12✔
123
        Ok(out)
5✔
124
    }
125
}
126

127
/// Reader returning `IcalCalendar` object from a `BufRead`.
128
pub struct ComponentParser<B: BufRead, T: Component> {
129
    line_parser: PropertyParser<B>,
130
    _t: PhantomData<T>,
131
}
132

133
impl<B: BufRead, T: Component> ComponentParser<B, T> {
134
    /// Return a new `IcalParser` from a `Reader`.
135
    pub fn new(reader: B) -> ComponentParser<B, T> {
7✔
136
        let line_reader = LineReader::new(reader);
7✔
137
        let line_parser = PropertyParser::new(line_reader);
7✔
138

139
        ComponentParser {
140
            line_parser,
141
            _t: Default::default(),
7✔
142
        }
143
    }
144

145
    /// Read the next line and check if it's a valid VCALENDAR start.
146
    fn check_header(&mut self) -> Result<Option<()>, ParserError> {
8✔
147
        let line = match self.line_parser.next() {
8✔
148
            Some(val) => val.map_err(ParserError::PropertyError)?,
8✔
149
            None => return Ok(None),
4✔
150
        };
151

152
        if line.name.to_uppercase() != "BEGIN"
16✔
153
            || line.value.is_none()
8✔
154
            || !T::NAMES.contains(&line.value.as_ref().unwrap().to_uppercase().as_str())
16✔
155
            || !line.params.is_empty()
8✔
156
        {
157
            return Err(ParserError::MissingHeader);
2✔
158
        }
159

160
        Ok(Some(()))
8✔
161
    }
162
}
163

164
impl<B: BufRead, T: Component> Iterator for ComponentParser<B, T> {
165
    type Item = Result<<T::Unverified as ComponentMut>::Verified, ParserError>;
166

167
    fn next(&mut self) -> Option<Self::Item> {
8✔
168
        match self.check_header() {
8✔
169
            Ok(res) => res?,
8✔
170
            Err(err) => return Some(Err(err)),
2✔
171
        };
172

173
        let mut comp = T::Unverified::default();
8✔
174
        let result = match comp.parse(&mut self.line_parser) {
14✔
175
            Ok(_) => comp.verify(),
6✔
176
            Err(err) => Err(err),
2✔
177
        };
178

179
        #[cfg(feature = "test")]
180
        {
181
            // Run this for more test coverage
182
            if let Ok(comp) = result.as_ref() {
21✔
183
                let mutable = comp.clone().mutable();
7✔
184
                mutable.get_properties();
8✔
185
            }
186
        }
187

188
        Some(result)
6✔
189
    }
190
}
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