• 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

92.45
/src/parser/ical/component/event/builder.rs
1
use crate::{
2
    PropertyParser,
3
    component::{IcalAlarmBuilder, IcalEvent},
4
    parser::{
5
        Component, ComponentMut, GetProperty, IcalDTENDProperty, IcalDTSTARTProperty,
6
        IcalDURATIONProperty, IcalEXDATEProperty, IcalEXRULEProperty, IcalMETHODProperty,
7
        IcalRDATEProperty, IcalRECURIDProperty, IcalRRULEProperty, IcalSUMMARYProperty,
8
        IcalUIDProperty, ParserError,
9
    },
10
    property::ContentLine,
11
};
12
use std::{collections::HashMap, io::BufRead};
13

14
#[derive(Debug, Clone, Default)]
15
pub struct IcalEventBuilder {
16
    pub properties: Vec<ContentLine>,
17
    pub alarms: Vec<IcalAlarmBuilder>,
18
}
19

20
impl IcalEventBuilder {
21
    pub fn new() -> Self {
1✔
22
        Self {
23
            properties: Vec::new(),
2✔
24
            alarms: Vec::new(),
2✔
25
        }
26
    }
27
}
28

29
impl Component for IcalEventBuilder {
30
    const NAMES: &[&str] = &["VEVENT"];
31
    type Unverified = Self;
32

33
    fn get_properties(&self) -> &Vec<ContentLine> {
2✔
34
        &self.properties
35
    }
36

NEW
37
    fn mutable(self) -> Self::Unverified {
×
NEW
38
        self
×
39
    }
40
}
41

42
impl ComponentMut for IcalEventBuilder {
43
    type Verified = IcalEvent;
44

45
    fn get_properties_mut(&mut self) -> &mut Vec<ContentLine> {
1✔
46
        &mut self.properties
47
    }
48

49
    fn add_sub_component<B: BufRead>(
2✔
50
        &mut self,
51
        value: &str,
52
        line_parser: &mut PropertyParser<B>,
53
    ) -> Result<(), ParserError> {
54
        match value {
2✔
55
            "VALARM" => {
2✔
56
                let mut alarm = IcalAlarmBuilder::new();
2✔
57
                alarm.parse(line_parser)?;
4✔
58
                self.alarms.push(alarm);
2✔
59
            }
NEW
60
            _ => return Err(ParserError::InvalidComponent(value.to_owned())),
×
61
        };
62

63
        Ok(())
2✔
64
    }
65

66
    fn build(
2✔
67
        self,
68
        timezones: Option<&HashMap<String, Option<chrono_tz::Tz>>>,
69
    ) -> Result<IcalEvent, ParserError> {
70
        // The following are REQUIRED, but MUST NOT occur more than once: dtstamp / uid
71
        let dtstamp = self.safe_get_required(timezones)?;
3✔
72
        let IcalUIDProperty(uid, _) = self.safe_get_required(timezones)?;
7✔
73
        // REQUIRED if METHOD not specified:
74
        // For now just ensure that no METHOD property exists
75
        assert!(
3✔
76
            self.safe_get_optional::<IcalMETHODProperty>(timezones)?
77
                .is_none()
78
        );
79
        let dtstart: IcalDTSTARTProperty = self.safe_get_required(timezones)?;
1✔
80

81
        // OPTIONAL, but NOT MORE THAN ONCE: class / created / description / geo / last-mod / location / organizer / priority / seq / status / summary / transp / url / recurid / rrule
82
        let summary = self.safe_get_optional::<IcalSUMMARYProperty>(timezones)?;
4✔
83
        let recurid = self.safe_get_optional::<IcalRECURIDProperty>(timezones)?;
6✔
84
        if let Some(recurid) = &recurid {
1✔
85
            recurid.validate_dtstart(&dtstart.0)?;
2✔
86
        }
87

88
        // OPTIONAL, but MUTUALLY EXCLUSIVE
89
        if self.has_prop::<IcalDTENDProperty>() && self.has_prop::<IcalDURATIONProperty>() {
6✔
NEW
90
            return Err(ParserError::PropertyConflict(
×
91
                "both DTEND and DURATION are defined",
92
            ));
93
        }
94
        let dtend = self.safe_get_optional::<IcalDTENDProperty>(timezones)?;
2✔
95
        let duration = self.safe_get_optional::<IcalDURATIONProperty>(timezones)?;
4✔
96

97
        // OPTIONAL, allowed multiple times: attach / attendee / categories / comment / contact / exdate / rstatus / related / resources / rdate / x-prop / iana-prop
98
        let rrule_dtstart = dtstart.0.utc().with_timezone(&rrule::Tz::UTC);
2✔
99
        let rdates = self.safe_get_all::<IcalRDATEProperty>(timezones)?;
3✔
100
        let exdates = self.safe_get_all::<IcalEXDATEProperty>(timezones)?;
6✔
101
        let rrules = self
5✔
102
            .safe_get_all::<IcalRRULEProperty>(timezones)?
3✔
103
            .into_iter()
104
            .map(|rrule| rrule.0.validate(rrule_dtstart))
4✔
105
            .collect::<Result<Vec<_>, _>>()?;
106
        let exrules = self
4✔
107
            .safe_get_all::<IcalEXRULEProperty>(timezones)?
2✔
108
            .into_iter()
109
            .map(|rrule| rrule.0.validate(rrule_dtstart))
3✔
110
            .collect::<Result<Vec<_>, _>>()?;
111

112
        Ok(IcalEvent {
1✔
113
            uid,
3✔
114
            dtstamp,
1✔
115
            dtstart,
3✔
116
            dtend,
1✔
117
            duration,
3✔
118
            rdates,
1✔
119
            rrules,
3✔
120
            exdates,
1✔
121
            exrules,
3✔
122
            recurid,
123
            summary,
1✔
124
            properties: self.properties,
3✔
125
            alarms: self
2✔
126
                .alarms
127
                .into_iter()
3✔
128
                .map(|alarm| alarm.build(timezones))
5✔
129
                .collect::<Result<Vec<_>, _>>()?,
3✔
130
        })
131
    }
132
}
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