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

lennart-k / ical-rs / #68

12 Jan 2026 10:17AM UTC coverage: 80.485% (+2.7%) from 77.802%
#68

Pull #2

lennart-k
fixes
Pull Request #2: Major overhaul

935 of 1181 new or added lines in 30 files covered. (79.17%)

21 existing lines in 9 files now uncovered.

1262 of 1568 relevant lines covered (80.48%)

2.85 hits per line

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

91.94
/src/parser/property/mod.rs
1
use crate::{
2
    parser::{Component, ParserError},
3
    property::ContentLine,
4
    types::{CalDateOrDateTime, DateOrDateTimeOrPeriod, PartialDateAndOrTime, parse_duration},
5
};
6
use std::collections::HashMap;
7
use std::str::FromStr;
8

9
mod duration;
10
pub use duration::*;
11
mod exdate;
12
pub use exdate::*;
13
mod rdate;
14
pub use rdate::*;
15
mod dtstart;
16
pub use dtstart::*;
17
mod recurid;
18
pub use recurid::*;
19
mod due;
20
pub use due::*;
21
mod dtstamp;
22
pub use dtstamp::*;
23
mod dtend;
24
pub use dtend::*;
25
mod calscale;
26
pub use calscale::*;
27
mod version;
28
pub use version::*;
29

30
pub trait ICalProperty: Sized {
31
    const NAME: &'static str;
32
    const DEFAULT_TYPE: &'static str;
33

34
    fn parse_prop(
35
        prop: &ContentLine,
36
        timezones: Option<&HashMap<String, Option<chrono_tz::Tz>>>,
37
    ) -> Result<Self, ParserError>;
38

39
    fn utc_or_local(self) -> Self;
40
}
41

42
pub trait GetProperty: Component {
43
    fn safe_get_all<T: ICalProperty>(
15✔
44
        &self,
45
        timezones: Option<&HashMap<String, Option<chrono_tz::Tz>>>,
46
    ) -> Result<Vec<T>, ParserError> {
47
        self.get_named_properties(T::NAME)
20✔
48
            .into_iter()
49
            .map(|prop| ICalProperty::parse_prop(prop, timezones))
22✔
50
            .collect::<Result<Vec<_>, _>>()
51
    }
52

53
    fn safe_get_optional<T: ICalProperty>(
43✔
54
        &self,
55
        timezones: Option<&HashMap<String, Option<chrono_tz::Tz>>>,
56
    ) -> Result<Option<T>, ParserError> {
57
        let mut props = self.get_named_properties(T::NAME).into_iter();
50✔
58
        let Some(prop) = props.next() else {
95✔
59
            return Ok(None);
18✔
60
        };
61
        if props.next().is_some() {
76✔
NEW
62
            return Err(ParserError::PropertyConflict(
×
NEW
63
                "Multiple instances of property",
×
64
            ));
65
        }
66
        ICalProperty::parse_prop(prop, timezones).map(Some)
76✔
67
    }
68

69
    fn safe_get_required<T: ICalProperty>(
18✔
70
        &self,
71
        timezones: Option<&HashMap<String, Option<chrono_tz::Tz>>>,
72
    ) -> Result<T, ParserError> {
73
        self.safe_get_optional(timezones)?
18✔
74
            .ok_or(ParserError::MissingProperty(T::NAME))
22✔
75
    }
76

77
    fn has_prop<T: ICalProperty>(&self) -> bool {
3✔
78
        self.get_property(T::NAME).is_some()
6✔
79
    }
80
}
81

82
impl<C: Component> GetProperty for C {}
83

84
pub trait ParseProp: Sized {
85
    fn parse_prop(
86
        prop: &ContentLine,
87
        timezones: Option<&HashMap<String, Option<chrono_tz::Tz>>>,
88
        default_type: &str,
89
    ) -> Result<Self, ParserError>;
90
}
91

92
impl ParseProp for String {
93
    fn parse_prop(
1✔
94
        prop: &ContentLine,
95
        _timezones: Option<&HashMap<String, Option<chrono_tz::Tz>>>,
96
        _default_type: &str,
97
    ) -> Result<Self, ParserError> {
98
        Ok(prop.value.to_owned().unwrap_or_default())
1✔
99
    }
100
}
101

102
impl ParseProp for DateOrDateTimeOrPeriod {
103
    fn parse_prop(
1✔
104
        prop: &ContentLine,
105
        timezones: Option<&HashMap<String, Option<chrono_tz::Tz>>>,
106
        default_type: &str,
107
    ) -> Result<Self, ParserError> {
108
        Self::parse_prop(prop, timezones, default_type)
1✔
109
    }
110
}
111

112
impl ParseProp for CalDateOrDateTime {
113
    fn parse_prop(
3✔
114
        prop: &ContentLine,
115
        timezones: Option<&HashMap<String, Option<chrono_tz::Tz>>>,
116
        default_type: &str,
117
    ) -> Result<Self, ParserError> {
118
        Self::parse_prop(prop, timezones, default_type)
3✔
119
    }
120
}
121

122
impl ParseProp for chrono::Duration {
123
    fn parse_prop(
2✔
124
        prop: &ContentLine,
125
        _timezones: Option<&HashMap<String, Option<chrono_tz::Tz>>>,
126
        _default_type: &str,
127
    ) -> Result<Self, ParserError> {
128
        Ok(parse_duration(prop.value.as_deref().unwrap_or_default())?)
2✔
129
    }
130
}
131

132
impl ParseProp for rrule::RRule<rrule::Unvalidated> {
133
    fn parse_prop(
1✔
134
        prop: &ContentLine,
135
        _timezones: Option<&HashMap<String, Option<chrono_tz::Tz>>>,
136
        _default_type: &str,
137
    ) -> Result<Self, ParserError> {
138
        Ok(rrule::RRule::from_str(
1✔
139
            prop.value.as_deref().unwrap_or_default(),
1✔
140
        )?)
141
    }
142
}
143

144
impl<T: ParseProp> ParseProp for Vec<T> {
145
    fn parse_prop(
2✔
146
        prop: &ContentLine,
147
        timezones: Option<&HashMap<String, Option<chrono_tz::Tz>>>,
148
        default_type: &str,
149
    ) -> Result<Self, ParserError> {
150
        let mut out = vec![];
2✔
151
        for value in prop
5✔
NEW
152
            .value
×
153
            .as_deref()
2✔
154
            .unwrap_or_default()
2✔
155
            .trim_end_matches(',')
2✔
156
            .split(',')
3✔
157
        {
158
            let content_line = ContentLine {
159
                name: prop.name.to_owned(),
3✔
160
                params: prop.params.to_owned(),
3✔
161
                value: Some(value.to_owned()),
6✔
162
            };
163
            out.push(T::parse_prop(&content_line, timezones, default_type)?);
6✔
164
        }
165
        Ok(out)
3✔
166
    }
167
}
168

169
macro_rules! property {
170
    ($name:literal, $default_type:literal, $prop:ty) => {
171
        impl crate::parser::property::ICalProperty for $prop {
172
            const NAME: &'static str = $name;
173
            const DEFAULT_TYPE: &'static str = $default_type;
174

175
            fn parse_prop(
33✔
176
                prop: &crate::property::ContentLine,
177
                timezones: Option<&std::collections::HashMap<String, Option<chrono_tz::Tz>>>,
178
            ) -> Result<Self, crate::parser::ParserError> {
179
                Ok(Self(
34✔
180
                    crate::parser::ParseProp::parse_prop(prop, timezones, $default_type)?,
33✔
181
                    prop.params.clone(),
33✔
182
                ))
183
            }
184

185
            fn utc_or_local(self) -> Self {
3✔
186
                let Self(dt, mut params) = self;
3✔
187
                params.remove("TZID");
3✔
188
                Self(crate::types::Value::utc_or_local(dt), params)
3✔
189
            }
190
        }
191
    };
192

193
    ($name:literal, $default_type:literal, $prop:ident, $inner:ty) => {
194
        #[derive(Debug, Clone, PartialEq, Eq, derive_more::From)]
53✔
195
        pub struct $prop(pub $inner, pub crate::property::ContentLineParams);
27✔
196
        crate::parser::property!($name, $default_type, $prop);
18✔
197

198
        impl From<$prop> for crate::property::ContentLine {
199
            fn from(prop: $prop) -> Self {
16✔
200
                let $prop(inner, mut params) = prop;
15✔
201
                let value_type = crate::types::Value::value_type(&inner).unwrap_or($default_type);
33✔
202
                if value_type != $default_type {
3✔
203
                    params.replace_param("VALUE".to_owned(), value_type.to_owned());
3✔
204
                }
205
                crate::property::ContentLine {
17✔
206
                    name: $name.to_owned(),
16✔
207
                    params,
18✔
208
                    value: Some(crate::types::Value::value(&inner)),
35✔
209
                }
210
            }
211
        }
212
    };
213
}
214
pub(crate) use property;
215

216
property!("UID", "TEXT", IcalUIDProperty, String);
217

218
impl From<String> for IcalUIDProperty {
NEW
219
    fn from(value: String) -> Self {
×
NEW
220
        Self(value, Default::default())
×
221
    }
222
}
223

224
property!("SUMMARY", "TEXT", IcalSUMMARYProperty, String);
225

226
property!(
227
    "RRULE",
228
    "RECUR",
229
    IcalRRULEProperty,
230
    rrule::RRule<rrule::Unvalidated>
231
);
232
property!(
233
    "EXRULE",
234
    "RECUR",
235
    IcalEXRULEProperty,
236
    rrule::RRule<rrule::Unvalidated>
237
);
238
property!("PRODID", "TEXT", IcalPRODIDProperty, String);
239

240
property!("METHOD", "TEXT", IcalMETHODProperty, String);
241

242
property!("FN", "TEXT", VcardFNProperty, String);
243
property!("N", "TEXT", VcardNProperty, String);
244
property!("NICKNAME", "TEXT", VcardNICKNAMEProperty, String);
245
property!(
246
    "BDAY",
247
    "DATE-AND-OR-TIME",
248
    VcardBDAYProperty,
249
    PartialDateAndOrTime
250
);
251
property!(
252
    "ANNIVERSARY",
253
    "DATE-AND-OR-TIME",
254
    VcardANNIVERSARYProperty,
255
    PartialDateAndOrTime
256
);
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