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

lennart-k / ical-rs / #73

12 Jan 2026 01:50PM UTC coverage: 77.771% (-0.03%) from 77.802%
#73

Pull #2

lennart-k
IcalCalendarObjectBuilder: Make attributes public
Pull Request #2: Major overhaul

908 of 1184 new or added lines in 30 files covered. (76.69%)

32 existing lines in 10 files now uncovered.

1221 of 1570 relevant lines covered (77.77%)

2.77 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>(
18✔
44
        &self,
45
        timezones: Option<&HashMap<String, Option<chrono_tz::Tz>>>,
46
    ) -> Result<Vec<T>, ParserError> {
47
        self.get_named_properties(T::NAME)
18✔
48
            .map(|prop| ICalProperty::parse_prop(prop, timezones))
24✔
49
            .collect::<Result<Vec<_>, _>>()
50
    }
51

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

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

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

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

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

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

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

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

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

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

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

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

174
            #[inline]
175
            fn parse_prop(
25✔
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(
29✔
180
                    crate::parser::ParseProp::parse_prop(prop, timezones, $default_type)?,
30✔
181
                    prop.params.clone(),
30✔
182
                ))
183
            }
184

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

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

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

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

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

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

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

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

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