• 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

67.27
/src/parser/ical/component/todo.rs
1
use rrule::RRule;
2

3
use crate::{
4
    PropertyParser,
5
    component::IcalAlarmBuilder,
6
    parser::{
7
        Component, ComponentMut, GetProperty, IcalDTSTAMPProperty, IcalDTSTARTProperty,
8
        IcalDUEProperty, IcalDURATIONProperty, IcalEXDATEProperty, IcalEXRULEProperty,
9
        IcalRDATEProperty, IcalRECURIDProperty, IcalRRULEProperty, IcalUIDProperty, ParserError,
10
        ical::component::IcalAlarm,
11
    },
12
    property::ContentLine,
13
};
14
use std::{
15
    collections::{HashMap, HashSet},
16
    io::BufRead,
17
};
18

19
#[derive(Debug, Clone)]
20
pub struct IcalTodo {
21
    uid: String,
22
    pub dtstart: Option<IcalDTSTARTProperty>,
23
    pub due: Option<IcalDUEProperty>,
24
    pub dtstamp: IcalDTSTAMPProperty,
25
    pub properties: Vec<ContentLine>,
26
    pub alarms: Vec<IcalAlarm>,
27
    rdates: Vec<IcalRDATEProperty>,
28
    rrules: Vec<RRule>,
29
    exdates: Vec<IcalEXDATEProperty>,
30
    exrules: Vec<RRule>,
31
    pub(crate) recurid: Option<IcalRECURIDProperty>,
32
}
33

34
#[derive(Debug, Clone, Default)]
35
pub struct IcalTodoBuilder {
36
    pub properties: Vec<ContentLine>,
37
    pub alarms: Vec<IcalAlarmBuilder>,
38
}
39

40
impl IcalTodo {
UNCOV
41
    pub fn get_uid(&self) -> &str {
×
NEW
42
        &self.uid
×
43
    }
44

45
    pub fn has_rruleset(&self) -> bool {
1✔
46
        !self.rrules.is_empty()
1✔
47
            || !self.rdates.is_empty()
1✔
48
            || !self.exrules.is_empty()
1✔
49
            || !self.exdates.is_empty()
1✔
50
    }
51
}
52

53
impl Component for IcalTodo {
54
    const NAMES: &[&str] = &["VTODO"];
55
    type Unverified = IcalTodoBuilder;
56

57
    fn get_properties(&self) -> &Vec<ContentLine> {
1✔
58
        &self.properties
1✔
59
    }
60

NEW
61
    fn mutable(self) -> Self::Unverified {
×
62
        IcalTodoBuilder {
NEW
63
            properties: self.properties,
×
NEW
64
            alarms: self
×
65
                .alarms
66
                .into_iter()
67
                .map(|alarm| alarm.mutable())
68
                .collect(),
69
        }
70
    }
71
}
72

73
impl Component for IcalTodoBuilder {
74
    const NAMES: &[&str] = &["VTODO"];
75
    type Unverified = IcalTodoBuilder;
76

77
    fn get_properties(&self) -> &Vec<ContentLine> {
1✔
78
        &self.properties
79
    }
80

81
    fn mutable(self) -> Self::Unverified {
×
NEW
82
        self
×
83
    }
84
}
85

86
impl ComponentMut for IcalTodoBuilder {
87
    type Verified = IcalTodo;
88

89
    fn get_properties_mut(&mut self) -> &mut Vec<ContentLine> {
1✔
90
        &mut self.properties
91
    }
92

93
    #[inline]
UNCOV
94
    fn add_sub_component<B: BufRead>(
×
95
        &mut self,
96
        value: &str,
97
        line_parser: &mut PropertyParser<B>,
98
    ) -> Result<(), ParserError> {
99
        match value {
×
100
            "VALARM" => {
×
NEW
101
                let mut alarm = IcalAlarmBuilder::new();
×
102
                alarm.parse(line_parser)?;
×
NEW
103
                self.alarms.push(alarm);
×
104
            }
NEW
105
            _ => return Err(ParserError::InvalidComponent(value.to_owned())),
×
106
        };
107

108
        Ok(())
×
109
    }
110

111
    fn build(
1✔
112
        self,
113
        timezones: Option<&HashMap<String, Option<chrono_tz::Tz>>>,
114
    ) -> Result<IcalTodo, ParserError> {
115
        // REQUIRED, but ONLY ONCE
116
        let IcalUIDProperty(uid, _) = self.safe_get_required(timezones)?;
2✔
117
        let dtstamp = self.safe_get_required(timezones)?;
1✔
118

119
        // OPTIONAL, but ONLY ONCE: class / completed / created / description / dtstart / geo / last-mod / location / organizer / percent / priority / recurid / seq / status / summary / url / rrule
120
        let dtstart = self.safe_get_optional::<IcalDTSTARTProperty>(timezones)?;
2✔
121
        let recurid = self.safe_get_optional::<IcalRECURIDProperty>(timezones)?;
2✔
122
        if let Some(IcalDTSTARTProperty(dtstart, _)) = &dtstart
1✔
123
            && let Some(recurid) = &recurid
1✔
124
        {
NEW
125
            recurid.validate_dtstart(dtstart)?;
×
126
        }
127
        // OPTIONAL, but MUTUALLY EXCLUSIVE
128
        if self.has_prop::<IcalDURATIONProperty>() && self.has_prop::<IcalDUEProperty>() {
2✔
NEW
129
            return Err(ParserError::PropertyConflict(
×
130
                "both DUE and DURATION are defined",
131
            ));
132
        }
133
        let _duration = self.safe_get_optional::<IcalDURATIONProperty>(timezones)?;
2✔
134
        let due = self.safe_get_optional::<IcalDUEProperty>(timezones)?;
2✔
135

136
        // OPTIONAL, MULTIPLE ALLOWED: attach / attendee / categories / comment / contact / exdate / rstatus / related / resources / rdate / x-prop / iana-prop
137
        let rdates = self.safe_get_all::<IcalRDATEProperty>(timezones)?;
2✔
138
        let exdates = self.safe_get_all::<IcalEXDATEProperty>(timezones)?;
2✔
139
        let (rrules, exrules) = if let Some(dtstart) = dtstart.as_ref() {
3✔
140
            let rrule_dtstart = dtstart.0.utc().with_timezone(&rrule::Tz::UTC);
2✔
141
            let rrules = self
3✔
142
                .safe_get_all::<IcalRRULEProperty>(timezones)?
1✔
143
                .into_iter()
144
                .map(|rrule| rrule.0.validate(rrule_dtstart))
1✔
145
                .collect::<Result<Vec<_>, _>>()?;
146
            let exrules = self
3✔
147
                .safe_get_all::<IcalEXRULEProperty>(timezones)?
1✔
148
                .into_iter()
149
                .map(|rrule| rrule.0.validate(rrule_dtstart))
1✔
150
                .collect::<Result<Vec<_>, _>>()?;
151
            (rrules, exrules)
1✔
152
        } else {
NEW
153
            (vec![], vec![])
×
154
        };
155

156
        let verified = IcalTodo {
157
            uid,
158
            dtstamp,
159
            dtstart,
160
            due,
161
            rdates,
162
            rrules,
163
            exdates,
164
            exrules,
165
            recurid,
166
            properties: self.properties,
1✔
167
            alarms: self
2✔
168
                .alarms
169
                .into_iter()
170
                .map(|alarm| alarm.build(timezones))
171
                .collect::<Result<Vec<_>, _>>()?,
172
        };
173

174
        Ok(verified)
1✔
175
    }
176
}
177

178
impl IcalTodo {
179
    pub fn get_tzids(&self) -> HashSet<&str> {
1✔
180
        self.properties
1✔
181
            .iter()
182
            .filter_map(|prop| prop.params.get_tzid())
3✔
183
            .chain(self.alarms.iter().flat_map(IcalAlarm::get_tzids))
1✔
184
            .collect()
185
    }
186
}
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