1
use crate::common::{PropertyKind, Status, Value};
2
use crate::model::param::Param;
3
use crate::model::property::{
4
    Action, ComponentProperty, DateTimeCompletedProperty, DateTimeDueProperty, DateTimeEndProperty,
5
    DateTimeStampProperty, DateTimeStartProperty, DurationProperty, FreeBusyTimeProperty,
6
    LastModifiedProperty, PeriodEnd, StatusProperty,
7
};
8
use crate::validate::recur::validate_recurrence_rule;
9
use crate::validate::value::check_declared_value;
10
use crate::validate::{
11
    check_occurrence, component_property_name, get_declared_value_type, validate_params,
12
    CalendarInfo, ComponentPropertyError, ComponentPropertyLocation, ICalendarErrorSeverity,
13
    OccurrenceExpectation, PropertyInfo, PropertyLocation, ValueType, WithinPropertyLocation,
14
};
15
use std::cmp::Ordering;
16
use std::collections::HashMap;
17

            
18
macro_rules! check_component_property_occurrence {
19
    ($errors:ident, $seen:ident, $property:ident, $index:ident, $occur:expr) => {
20
        let name = $crate::validate::component_property_name($property);
21
        $crate::validate::add_to_seen(&mut $seen, name);
22
        if let Some(message) = $crate::validate::check_occurrence(&$seen, name, $occur.clone()) {
23
            $errors.push(ComponentPropertyError {
24
                message,
25
                severity: ICalendarErrorSeverity::Error,
26
                location: Some($crate::validate::ComponentPropertyLocation {
27
                    index: $index,
28
                    name: name.to_string(),
29
                    property_location: None,
30
                }),
31
            });
32
        }
33

            
34
        // If the property shouldn't appear then don't validate it further.
35
        if $occur == OccurrenceExpectation::Never {
36
            continue;
37
        }
38
    };
39
}
40

            
41
348
pub(super) fn validate_component_properties(
42
348
    calendar_info: &CalendarInfo,
43
348
    property_location: PropertyLocation,
44
348
    properties: &[ComponentProperty],
45
348
) -> anyhow::Result<Vec<ComponentPropertyError>> {
46
348
    let mut errors = Vec::new();
47
348

            
48
348
    if properties.is_empty() {
49
2
        errors.push(ComponentPropertyError {
50
2
            message: "No properties found in component, required at least one".to_string(),
51
2
            severity: ICalendarErrorSeverity::Error,
52
2
            location: None,
53
2
        });
54
2
        // If there are no properties we are going to get other errors, but is that really useful?
55
2
        return Ok(errors);
56
346
    }
57

            
58
346
    let dt_stamp_occurrence_expectation = match property_location {
59
        PropertyLocation::Event
60
        | PropertyLocation::ToDo
61
        | PropertyLocation::Journal
62
166
        | PropertyLocation::FreeBusy => OccurrenceExpectation::Once,
63
        PropertyLocation::TimeZone
64
        | PropertyLocation::TimeZoneComponent
65
128
        | PropertyLocation::Alarm => OccurrenceExpectation::Never,
66
52
        PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
67
        _ => OccurrenceExpectation::Never,
68
    };
69

            
70
346
    let uid_occurrence_expectation = match property_location {
71
        PropertyLocation::Event
72
        | PropertyLocation::ToDo
73
        | PropertyLocation::Journal
74
166
        | PropertyLocation::FreeBusy => OccurrenceExpectation::Once,
75
        PropertyLocation::TimeZone
76
        | PropertyLocation::TimeZoneComponent
77
128
        | PropertyLocation::Alarm => OccurrenceExpectation::Never,
78
52
        PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
79
        _ => OccurrenceExpectation::Never,
80
    };
81

            
82
346
    let is_recurring = properties
83
346
        .iter()
84
1841
        .any(|p| matches!(p, ComponentProperty::RecurrenceRule(_)));
85

            
86
346
    let dt_start_expectation = match property_location {
87
        PropertyLocation::Event => {
88
124
            if calendar_info.method.is_none() || is_recurring {
89
42
                OccurrenceExpectation::Once
90
            } else {
91
82
                OccurrenceExpectation::OptionalOnce
92
            }
93
        }
94
44
        PropertyLocation::TimeZoneComponent => OccurrenceExpectation::Once,
95
        PropertyLocation::ToDo | PropertyLocation::Journal => {
96
32
            if is_recurring {
97
12
                OccurrenceExpectation::Once
98
            } else {
99
20
                OccurrenceExpectation::OptionalOnce
100
            }
101
        }
102
10
        PropertyLocation::FreeBusy => OccurrenceExpectation::OptionalOnce,
103
84
        PropertyLocation::TimeZone | PropertyLocation::Alarm => OccurrenceExpectation::Never,
104
52
        PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
105
        _ => OccurrenceExpectation::Never,
106
    };
107

            
108
346
    let tz_id_occurrence_expectation = match property_location {
109
        PropertyLocation::Event
110
        | PropertyLocation::ToDo
111
        | PropertyLocation::Journal
112
        | PropertyLocation::FreeBusy
113
        | PropertyLocation::TimeZoneComponent
114
266
        | PropertyLocation::Alarm => OccurrenceExpectation::Never,
115
28
        PropertyLocation::TimeZone => OccurrenceExpectation::Once,
116
52
        PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
117
        _ => OccurrenceExpectation::Never,
118
    };
119

            
120
346
    let tz_offset_to_occurrence_expectation = match property_location {
121
44
        PropertyLocation::TimeZoneComponent => OccurrenceExpectation::Once,
122
        PropertyLocation::Event
123
        | PropertyLocation::ToDo
124
        | PropertyLocation::Journal
125
        | PropertyLocation::FreeBusy
126
        | PropertyLocation::TimeZone
127
250
        | PropertyLocation::Alarm => OccurrenceExpectation::Never,
128
52
        PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
129
        _ => OccurrenceExpectation::Never,
130
    };
131

            
132
346
    let tz_offset_from_occurrence_expectation = match property_location {
133
44
        PropertyLocation::TimeZoneComponent => OccurrenceExpectation::Once,
134
        PropertyLocation::Event
135
        | PropertyLocation::ToDo
136
        | PropertyLocation::Journal
137
        | PropertyLocation::FreeBusy
138
        | PropertyLocation::TimeZone
139
250
        | PropertyLocation::Alarm => OccurrenceExpectation::Never,
140
52
        PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
141
        _ => OccurrenceExpectation::Never,
142
    };
143

            
144
346
    let mut alarm_action = None;
145
346
    let action_occurrence_expectation = match property_location {
146
        PropertyLocation::Alarm => {
147
56
            let actions = properties
148
56
                .iter()
149
324
                .filter_map(|p| {
150
304
                    if let ComponentProperty::Action(action) = p {
151
52
                        Some(action.value.clone())
152
                    } else {
153
252
                        None
154
                    }
155
324
                })
156
56
                .collect::<Vec<_>>();
157
56
            if actions.len() == 1 {
158
52
                alarm_action = actions.first().cloned();
159
52
                OccurrenceExpectation::Once
160
            } else {
161
4
                errors.push(ComponentPropertyError {
162
4
                    message: format!(
163
4
                        "Required exactly one ACTION property but found {}",
164
4
                        actions.len()
165
4
                    ),
166
4
                    severity: ICalendarErrorSeverity::Error,
167
4
                    location: None,
168
4
                });
169
4
                return Ok(errors);
170
            }
171
        }
172
52
        PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
173
238
        _ => OccurrenceExpectation::Never,
174
    };
175

            
176
342
    let trigger_occurrence_expectation = match property_location {
177
52
        PropertyLocation::Alarm => OccurrenceExpectation::Once,
178
        PropertyLocation::Event
179
        | PropertyLocation::ToDo
180
        | PropertyLocation::Journal
181
        | PropertyLocation::FreeBusy
182
        | PropertyLocation::TimeZone
183
238
        | PropertyLocation::TimeZoneComponent => OccurrenceExpectation::Never,
184
52
        PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
185
        _ => OccurrenceExpectation::Never,
186
    };
187

            
188
342
    let description_occurrence_expectation = match property_location {
189
144
        PropertyLocation::Event | PropertyLocation::ToDo => OccurrenceExpectation::OptionalOnce,
190
        PropertyLocation::FreeBusy
191
        | PropertyLocation::TimeZone
192
82
        | PropertyLocation::TimeZoneComponent => OccurrenceExpectation::Never,
193
52
        PropertyLocation::Alarm => match alarm_action.clone().expect("Always present for an alarm")
194
        {
195
36
            Action::Display | Action::Email => OccurrenceExpectation::Once,
196
16
            Action::Audio => OccurrenceExpectation::Never,
197
            _ => OccurrenceExpectation::OptionalMany,
198
        },
199
64
        PropertyLocation::Journal | PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
200
        _ => OccurrenceExpectation::Never,
201
    };
202

            
203
342
    let summary_occurrence_expectation = match property_location {
204
        PropertyLocation::Event | PropertyLocation::ToDo | PropertyLocation::Journal => {
205
156
            OccurrenceExpectation::OptionalOnce
206
        }
207
        PropertyLocation::FreeBusy
208
        | PropertyLocation::TimeZone
209
82
        | PropertyLocation::TimeZoneComponent => OccurrenceExpectation::Never,
210
52
        PropertyLocation::Alarm => match alarm_action.clone().expect("Always present for an alarm")
211
        {
212
14
            Action::Email => OccurrenceExpectation::Once,
213
38
            Action::Audio | Action::Display => OccurrenceExpectation::Never,
214
            _ => OccurrenceExpectation::OptionalMany,
215
        },
216
52
        PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
217
        _ => OccurrenceExpectation::Never,
218
    };
219

            
220
342
    let attendee_occurrence_expectation = match property_location {
221
52
        PropertyLocation::Alarm => match alarm_action.clone().expect("Always present for an alarm")
222
        {
223
14
            Action::Email => OccurrenceExpectation::OnceOrMany,
224
38
            Action::Audio | Action::Display => OccurrenceExpectation::Never,
225
            _ => OccurrenceExpectation::OptionalMany,
226
        },
227
        PropertyLocation::Event
228
        | PropertyLocation::ToDo
229
        | PropertyLocation::Journal
230
166
        | PropertyLocation::FreeBusy => OccurrenceExpectation::OptionalMany,
231
        PropertyLocation::TimeZone | PropertyLocation::TimeZoneComponent => {
232
72
            OccurrenceExpectation::Never
233
        }
234
52
        PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
235
        _ => OccurrenceExpectation::Never,
236
    };
237

            
238
342
    let mut has_dt_start = false;
239
342
    let mut has_dt_end = false;
240
342
    let mut has_duration = false;
241
342
    let mut has_due = false;
242
342
    let mut has_repeat = false;
243
342

            
244
1281
    let maybe_dt_start = properties.iter().find_map(|p| match p {
245
116
        ComponentProperty::DateTimeStart(dt_start) => Some(dt_start),
246
1028
        _ => None,
247
1281
    });
248
342

            
249
342
    let mut seen = HashMap::<String, u32>::new();
250
2122
    for (index, property) in properties.iter().enumerate() {
251
2122
        check_declared_value(&mut errors, maybe_dt_start, property, index)?;
252

            
253
2122
        let do_validate_params = |errors: &mut Vec<ComponentPropertyError>,
254
                                  property_info: PropertyInfo,
255
1532
                                  params: &[Param]| {
256
1532
            errors.extend_from_slice(
257
1532
                ComponentPropertyError::many_from_param_errors(
258
1532
                    validate_params(params, property_info),
259
1532
                    index,
260
1532
                    component_property_name(property).to_string(),
261
1532
                )
262
1532
                .as_slice(),
263
1532
            );
264
1532
        };
265

            
266
2122
        match property {
267
158
            ComponentProperty::DateTimeStamp(date_time_stamp) => {
268
158
                check_component_property_occurrence!(
269
                    errors,
270
                    seen,
271
                    property,
272
                    index,
273
158
                    dt_stamp_occurrence_expectation.clone()
274
                );
275

            
276
158
                validate_date_time_stamp(&mut errors, date_time_stamp, index);
277
158

            
278
158
                let property_info = PropertyInfo::new(
279
158
                    calendar_info,
280
158
                    property_location.clone(),
281
158
                    PropertyKind::DateTimeStamp,
282
158
                    ValueType::DateTime,
283
158
                );
284
158
                do_validate_params(&mut errors, property_info, &date_time_stamp.params);
285
            }
286
            ComponentProperty::UniqueIdentifier(_) => {
287
158
                check_component_property_occurrence!(
288
                    errors,
289
                    seen,
290
                    property,
291
                    index,
292
158
                    uid_occurrence_expectation.clone()
293
                );
294
            }
295
124
            ComponentProperty::DateTimeStart(date_time_start) => {
296
124
                has_dt_start = true;
297
124

            
298
124
                check_component_property_occurrence!(
299
                    errors,
300
                    seen,
301
                    property,
302
                    index,
303
124
                    dt_start_expectation.clone()
304
                );
305

            
306
124
                validate_date_time_start(
307
124
                    &mut errors,
308
124
                    date_time_start,
309
124
                    index,
310
124
                    property_location.clone(),
311
124
                );
312

            
313
124
                let property_info = PropertyInfo::new(
314
124
                    calendar_info,
315
124
                    property_location.clone(),
316
124
                    PropertyKind::DateTimeStart,
317
124
                    if date_time_start.value.is_date_time() {
318
116
                        ValueType::DateTime
319
                    } else {
320
8
                        ValueType::Date
321
                    },
322
                )
323
124
                .utc(date_time_start.value.is_utc());
324
124
                do_validate_params(&mut errors, property_info, &date_time_start.params);
325
            }
326
            ComponentProperty::Classification(_) => {
327
36
                let occurrence_expectation = match property_location {
328
                    PropertyLocation::Event
329
                    | PropertyLocation::ToDo
330
36
                    | PropertyLocation::Journal => OccurrenceExpectation::OptionalOnce,
331
                    PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
332
                    _ => OccurrenceExpectation::Never,
333
                };
334
36
                check_component_property_occurrence!(
335
                    errors,
336
                    seen,
337
                    property,
338
                    index,
339
36
                    occurrence_expectation
340
                );
341
            }
342
36
            ComponentProperty::DateTimeCreated(date_time_created) => {
343
36
                let occurrence_expectation = match property_location {
344
                    PropertyLocation::Event
345
                    | PropertyLocation::ToDo
346
36
                    | PropertyLocation::Journal => OccurrenceExpectation::OptionalOnce,
347
                    PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
348
                    _ => OccurrenceExpectation::Never,
349
                };
350
36
                check_component_property_occurrence!(
351
                    errors,
352
                    seen,
353
                    property,
354
                    index,
355
36
                    occurrence_expectation
356
                );
357

            
358
36
                let property_info = PropertyInfo::new(
359
36
                    calendar_info,
360
36
                    property_location.clone(),
361
36
                    PropertyKind::DateTimeCreated,
362
36
                    ValueType::DateTime,
363
36
                );
364
36
                do_validate_params(&mut errors, property_info, &date_time_created.params);
365
            }
366
96
            ComponentProperty::Description(description) => {
367
96
                check_component_property_occurrence!(
368
                    errors,
369
                    seen,
370
                    property,
371
                    index,
372
96
                    description_occurrence_expectation
373
                );
374

            
375
96
                let property_info = PropertyInfo::new(
376
96
                    calendar_info,
377
96
                    property_location.clone(),
378
96
                    PropertyKind::Description,
379
96
                    ValueType::Text,
380
96
                );
381
96
                do_validate_params(&mut errors, property_info, &description.params);
382
            }
383
20
            ComponentProperty::GeographicPosition(geographic_position) => {
384
20
                let occurrence_expectation = match property_location {
385
                    PropertyLocation::Event | PropertyLocation::ToDo => {
386
20
                        OccurrenceExpectation::OptionalOnce
387
                    }
388
                    PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
389
                    _ => OccurrenceExpectation::Never,
390
                };
391
20
                check_component_property_occurrence!(
392
                    errors,
393
                    seen,
394
                    property,
395
                    index,
396
20
                    occurrence_expectation
397
                );
398

            
399
20
                let property_info = PropertyInfo::new(
400
20
                    calendar_info,
401
20
                    property_location.clone(),
402
20
                    PropertyKind::GeographicPosition,
403
20
                    ValueType::Float,
404
20
                );
405
20
                do_validate_params(&mut errors, property_info, &geographic_position.params);
406
            }
407
42
            ComponentProperty::LastModified(last_modified) => {
408
42
                let occurrence_expectation = match property_location {
409
                    PropertyLocation::Event
410
                    | PropertyLocation::ToDo
411
                    | PropertyLocation::Journal
412
42
                    | PropertyLocation::TimeZone => OccurrenceExpectation::OptionalOnce,
413
                    PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
414
                    _ => OccurrenceExpectation::Never,
415
                };
416
42
                check_component_property_occurrence!(
417
                    errors,
418
                    seen,
419
                    property,
420
                    index,
421
42
                    occurrence_expectation
422
                );
423

            
424
42
                validate_last_modified(&mut errors, last_modified, index);
425
42

            
426
42
                let property_info = PropertyInfo::new(
427
42
                    calendar_info,
428
42
                    property_location.clone(),
429
42
                    PropertyKind::LastModified,
430
42
                    ValueType::DateTime,
431
42
                );
432
42
                do_validate_params(&mut errors, property_info, &last_modified.params);
433
            }
434
26
            ComponentProperty::Location(location) => {
435
26
                let occurrence_expectation = match property_location {
436
                    PropertyLocation::Event | PropertyLocation::ToDo => {
437
26
                        OccurrenceExpectation::OptionalOnce
438
                    }
439
                    PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
440
                    _ => OccurrenceExpectation::Never,
441
                };
442
26
                check_component_property_occurrence!(
443
                    errors,
444
                    seen,
445
                    property,
446
                    index,
447
26
                    occurrence_expectation
448
                );
449

            
450
26
                let property_info = PropertyInfo::new(
451
26
                    calendar_info,
452
26
                    property_location.clone(),
453
26
                    PropertyKind::Location,
454
26
                    ValueType::Text,
455
26
                );
456
26
                do_validate_params(&mut errors, property_info, &location.params);
457
            }
458
48
            ComponentProperty::Organizer(organizer) => {
459
48
                let occurrence_expectation = match property_location {
460
                    PropertyLocation::Event
461
                    | PropertyLocation::ToDo
462
                    | PropertyLocation::Journal
463
48
                    | PropertyLocation::FreeBusy => OccurrenceExpectation::OptionalOnce,
464
                    PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
465
                    _ => OccurrenceExpectation::Never,
466
                };
467
48
                check_component_property_occurrence!(
468
                    errors,
469
                    seen,
470
                    property,
471
                    index,
472
48
                    occurrence_expectation
473
                );
474

            
475
48
                let property_info = PropertyInfo::new(
476
48
                    calendar_info,
477
48
                    property_location.clone(),
478
48
                    PropertyKind::Organizer,
479
48
                    ValueType::CalendarAddress,
480
48
                );
481
48
                errors.extend_from_slice(
482
48
                    ComponentPropertyError::many_from_param_errors(
483
48
                        validate_params(&organizer.params, property_info),
484
48
                        index,
485
48
                        component_property_name(property).to_string(),
486
48
                    )
487
48
                    .as_slice(),
488
48
                );
489
            }
490
20
            ComponentProperty::Priority(priority) => {
491
20
                let occurrence_expectation = match property_location {
492
                    PropertyLocation::Event | PropertyLocation::ToDo => {
493
20
                        OccurrenceExpectation::OptionalOnce
494
                    }
495
                    PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
496
                    _ => OccurrenceExpectation::Never,
497
                };
498
20
                check_component_property_occurrence!(
499
                    errors,
500
                    seen,
501
                    property,
502
                    index,
503
20
                    occurrence_expectation
504
                );
505

            
506
20
                let property_info = PropertyInfo::new(
507
20
                    calendar_info,
508
20
                    property_location.clone(),
509
20
                    PropertyKind::Priority,
510
20
                    ValueType::Integer,
511
20
                );
512
20
                do_validate_params(&mut errors, property_info, &priority.params);
513
            }
514
30
            ComponentProperty::Sequence(sequence) => {
515
30
                let occurrence_expectation = match property_location {
516
                    PropertyLocation::Event
517
                    | PropertyLocation::ToDo
518
30
                    | PropertyLocation::Journal => OccurrenceExpectation::OptionalOnce,
519
                    PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
520
                    _ => OccurrenceExpectation::Never,
521
                };
522
30
                check_component_property_occurrence!(
523
                    errors,
524
                    seen,
525
                    property,
526
                    index,
527
30
                    occurrence_expectation
528
                );
529

            
530
30
                let property_info = PropertyInfo::new(
531
30
                    calendar_info,
532
30
                    property_location.clone(),
533
30
                    PropertyKind::Sequence,
534
30
                    ValueType::Integer,
535
30
                );
536
30
                do_validate_params(&mut errors, property_info, &sequence.params);
537
            }
538
30
            ComponentProperty::Status(status) => {
539
30
                let occurrence_expectation = match property_location {
540
                    PropertyLocation::Event
541
                    | PropertyLocation::ToDo
542
30
                    | PropertyLocation::Journal => OccurrenceExpectation::OptionalOnce,
543
                    PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
544
                    _ => OccurrenceExpectation::Never,
545
                };
546
30
                check_component_property_occurrence!(
547
                    errors,
548
                    seen,
549
                    property,
550
                    index,
551
30
                    occurrence_expectation
552
                );
553

            
554
30
                validate_status(&mut errors, status, index, property_location.clone());
555
30

            
556
30
                let property_info = PropertyInfo::new(
557
30
                    calendar_info,
558
30
                    property_location.clone(),
559
30
                    PropertyKind::Status,
560
30
                    ValueType::Text,
561
30
                );
562
30
                do_validate_params(&mut errors, property_info, &status.params);
563
            }
564
48
            ComponentProperty::Summary(summary) => {
565
48
                check_component_property_occurrence!(
566
                    errors,
567
                    seen,
568
                    property,
569
                    index,
570
48
                    summary_occurrence_expectation
571
                );
572

            
573
48
                let property_info = PropertyInfo::new(
574
48
                    calendar_info,
575
48
                    property_location.clone(),
576
48
                    PropertyKind::Summary,
577
48
                    ValueType::Text,
578
48
                );
579
48
                do_validate_params(&mut errors, property_info, &summary.params);
580
            }
581
10
            ComponentProperty::TimeTransparency(time_transparency) => {
582
10
                let occurrence_expectation = match property_location {
583
10
                    PropertyLocation::Event => OccurrenceExpectation::OptionalOnce,
584
                    PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
585
                    _ => OccurrenceExpectation::Never,
586
                };
587
10
                check_component_property_occurrence!(
588
                    errors,
589
                    seen,
590
                    property,
591
                    index,
592
10
                    occurrence_expectation
593
                );
594

            
595
10
                let property_info = PropertyInfo::new(
596
10
                    calendar_info,
597
10
                    property_location.clone(),
598
10
                    PropertyKind::TimeTransparency,
599
10
                    ValueType::Text,
600
10
                );
601
10
                do_validate_params(&mut errors, property_info, &time_transparency.params);
602
            }
603
            ComponentProperty::Url(_) => {
604
40
                let occurrence_expectation = match property_location {
605
                    PropertyLocation::Event
606
                    | PropertyLocation::ToDo
607
                    | PropertyLocation::Journal
608
40
                    | PropertyLocation::FreeBusy => OccurrenceExpectation::OptionalOnce,
609
                    PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
610
                    _ => OccurrenceExpectation::Never,
611
                };
612
40
                check_component_property_occurrence!(
613
                    errors,
614
                    seen,
615
                    property,
616
                    index,
617
40
                    occurrence_expectation
618
                );
619
            }
620
30
            ComponentProperty::RecurrenceId(recurrence_id) => {
621
30
                let occurrence_expectation = match property_location {
622
                    PropertyLocation::Event
623
                    | PropertyLocation::ToDo
624
30
                    | PropertyLocation::Journal => OccurrenceExpectation::OptionalOnce,
625
                    PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
626
                    _ => OccurrenceExpectation::Never,
627
                };
628
30
                check_component_property_occurrence!(
629
                    errors,
630
                    seen,
631
                    property,
632
                    index,
633
30
                    occurrence_expectation
634
                );
635

            
636
30
                let dt_start_type = maybe_dt_start
637
39
                    .and_then(|dt_start| {
638
30
                        get_declared_value_type(&ComponentProperty::DateTimeStart(dt_start.clone()))
639
39
                    })
640
30
                    .map(|v| v.0)
641
30
                    .unwrap_or(Value::DateTime);
642

            
643
30
                let property_info = PropertyInfo::new(
644
30
                    calendar_info,
645
30
                    property_location.clone(),
646
30
                    PropertyKind::RecurrenceId,
647
30
                    if dt_start_type == Value::Date {
648
                        ValueType::Date
649
                    } else {
650
30
                        ValueType::DateTime
651
                    },
652
                );
653
30
                do_validate_params(&mut errors, property_info, &recurrence_id.params);
654
            }
655
176
            p @ ComponentProperty::RecurrenceRule(recurrence_rule) => {
656
                // An RRULE can appear more than once, it just SHOULD NOT.
657
176
                let occurrence_expectation = match property_location {
658
                    PropertyLocation::Event
659
                    | PropertyLocation::ToDo
660
                    | PropertyLocation::Journal
661
176
                    | PropertyLocation::TimeZoneComponent => OccurrenceExpectation::OptionalMany,
662
                    PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
663
                    _ => OccurrenceExpectation::Never,
664
                };
665
176
                check_component_property_occurrence!(
666
                    errors,
667
                    seen,
668
                    property,
669
                    index,
670
176
                    occurrence_expectation
671
                );
672

            
673
176
                validate_recurrence_rule(
674
176
                    &mut errors,
675
176
                    p,
676
176
                    &recurrence_rule.value,
677
176
                    maybe_dt_start,
678
176
                    property_location.clone(),
679
176
                    index,
680
176
                )?;
681

            
682
176
                let property_info = PropertyInfo::new(
683
176
                    calendar_info,
684
176
                    property_location.clone(),
685
176
                    PropertyKind::RecurrenceRule,
686
176
                    ValueType::Recurrence,
687
176
                );
688
176
                do_validate_params(&mut errors, property_info, &recurrence_rule.params);
689
            }
690
30
            ComponentProperty::DateTimeEnd(date_time_end) => {
691
30
                has_dt_end = true;
692

            
693
30
                let occurrence_expectation = match property_location {
694
                    PropertyLocation::Event | PropertyLocation::FreeBusy => {
695
30
                        OccurrenceExpectation::OptionalOnce
696
                    }
697
                    PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
698
                    _ => OccurrenceExpectation::Never,
699
                };
700
30
                check_component_property_occurrence!(
701
                    errors,
702
                    seen,
703
                    property,
704
                    index,
705
30
                    occurrence_expectation
706
                );
707

            
708
30
                validate_date_time_end(
709
30
                    &mut errors,
710
30
                    date_time_end,
711
30
                    maybe_dt_start,
712
30
                    index,
713
30
                    property_location.clone(),
714
30
                );
715

            
716
30
                let property_info = PropertyInfo::new(
717
30
                    calendar_info,
718
30
                    property_location.clone(),
719
30
                    PropertyKind::DateTimeEnd,
720
30
                    if date_time_end.value.is_date_time() {
721
30
                        ValueType::DateTime
722
                    } else {
723
                        ValueType::Date
724
                    },
725
                );
726
30
                do_validate_params(&mut errors, property_info, &date_time_end.params);
727
            }
728
62
            ComponentProperty::Duration(duration) => {
729
62
                has_duration = true;
730

            
731
62
                let occurrence_expectation = match property_location {
732
                    PropertyLocation::Event | PropertyLocation::ToDo | PropertyLocation::Alarm => {
733
62
                        OccurrenceExpectation::OptionalOnce
734
                    }
735
                    PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
736
                    _ => OccurrenceExpectation::Never,
737
                };
738
62
                check_component_property_occurrence!(
739
                    errors,
740
                    seen,
741
                    property,
742
                    index,
743
62
                    occurrence_expectation
744
                );
745

            
746
62
                validate_duration_property(
747
62
                    &mut errors,
748
62
                    duration,
749
62
                    maybe_dt_start,
750
62
                    index,
751
62
                    property_location.clone(),
752
62
                );
753
62

            
754
62
                let property_info = PropertyInfo::new(
755
62
                    calendar_info,
756
62
                    property_location.clone(),
757
62
                    PropertyKind::Duration,
758
62
                    ValueType::Duration,
759
62
                );
760
62
                do_validate_params(&mut errors, property_info, &duration.params);
761
            }
762
44
            ComponentProperty::Attach(attach) => {
763
44
                let occurrence_expectation = match property_location {
764
                    PropertyLocation::Event
765
                    | PropertyLocation::ToDo
766
24
                    | PropertyLocation::Journal => OccurrenceExpectation::OptionalMany,
767
                    PropertyLocation::Alarm => {
768
20
                        match alarm_action.clone().expect("Always present for an alarm") {
769
14
                            Action::Audio => OccurrenceExpectation::OptionalOnce,
770
6
                            Action::Email => OccurrenceExpectation::OptionalMany,
771
                            Action::Display => OccurrenceExpectation::Never,
772
                            _ => OccurrenceExpectation::OptionalMany,
773
                        }
774
                    }
775
                    PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
776
                    _ => OccurrenceExpectation::Never,
777
                };
778
44
                check_component_property_occurrence!(
779
                    errors,
780
                    seen,
781
                    property,
782
                    index,
783
44
                    occurrence_expectation
784
                );
785

            
786
44
                do_validate_params(
787
44
                    &mut errors,
788
44
                    PropertyInfo::new(
789
44
                        calendar_info,
790
44
                        property_location.clone(),
791
44
                        PropertyKind::Attach,
792
44
                        ValueType::Binary,
793
44
                    ),
794
44
                    &attach.params,
795
44
                );
796
            }
797
40
            ComponentProperty::Attendee(attendee) => {
798
40
                check_component_property_occurrence!(
799
                    errors,
800
                    seen,
801
                    property,
802
                    index,
803
40
                    attendee_occurrence_expectation
804
                );
805

            
806
40
                let property_info = PropertyInfo::new(
807
40
                    calendar_info,
808
40
                    property_location.clone(),
809
40
                    PropertyKind::Attendee,
810
40
                    ValueType::CalendarAddress,
811
40
                );
812
40
                do_validate_params(&mut errors, property_info, &attendee.params);
813
            }
814
24
            ComponentProperty::Categories(categories) => {
815
24
                let occurrence_expectation = match property_location {
816
                    PropertyLocation::Event
817
                    | PropertyLocation::ToDo
818
24
                    | PropertyLocation::Journal => OccurrenceExpectation::OptionalMany,
819
                    PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
820
                    _ => OccurrenceExpectation::Never,
821
                };
822
24
                check_component_property_occurrence!(
823
                    errors,
824
                    seen,
825
                    property,
826
                    index,
827
24
                    occurrence_expectation
828
                );
829

            
830
24
                let property_info = PropertyInfo::new(
831
24
                    calendar_info,
832
24
                    property_location.clone(),
833
24
                    PropertyKind::Categories,
834
24
                    ValueType::Text,
835
24
                );
836
24
                do_validate_params(&mut errors, property_info, &categories.params);
837
            }
838
36
            ComponentProperty::Comment(comment) => {
839
36
                let occurrence_expectation = match property_location {
840
                    PropertyLocation::Event
841
                    | PropertyLocation::ToDo
842
                    | PropertyLocation::Journal
843
                    | PropertyLocation::FreeBusy
844
36
                    | PropertyLocation::TimeZoneComponent => OccurrenceExpectation::OptionalMany,
845
                    PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
846
                    _ => OccurrenceExpectation::Never,
847
                };
848
36
                check_component_property_occurrence!(
849
                    errors,
850
                    seen,
851
                    property,
852
                    index,
853
36
                    occurrence_expectation
854
                );
855

            
856
36
                let property_info = PropertyInfo::new(
857
36
                    calendar_info,
858
36
                    property_location.clone(),
859
36
                    PropertyKind::Comment,
860
36
                    ValueType::Text,
861
36
                );
862
36
                do_validate_params(&mut errors, property_info, &comment.params);
863
            }
864
28
            ComponentProperty::Contact(contact) => {
865
28
                let occurrence_expectation = match property_location {
866
10
                    PropertyLocation::FreeBusy => OccurrenceExpectation::OptionalOnce,
867
                    PropertyLocation::Event
868
                    | PropertyLocation::ToDo
869
18
                    | PropertyLocation::Journal => OccurrenceExpectation::OptionalMany,
870
                    PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
871
                    _ => OccurrenceExpectation::Never,
872
                };
873
28
                check_component_property_occurrence!(
874
                    errors,
875
                    seen,
876
                    property,
877
                    index,
878
28
                    occurrence_expectation
879
                );
880

            
881
28
                let property_info = PropertyInfo::new(
882
28
                    calendar_info,
883
28
                    property_location.clone(),
884
28
                    PropertyKind::Contact,
885
28
                    ValueType::Text,
886
28
                );
887
28
                do_validate_params(&mut errors, property_info, &contact.params);
888
            }
889
20
            ComponentProperty::ExceptionDateTimes(exception_date_times) => {
890
20
                let occurrence_expectation = match property_location {
891
                    PropertyLocation::Event
892
                    | PropertyLocation::ToDo
893
20
                    | PropertyLocation::Journal => OccurrenceExpectation::OptionalMany,
894
                    PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
895
                    _ => OccurrenceExpectation::Never,
896
                };
897
20
                check_component_property_occurrence!(
898
                    errors,
899
                    seen,
900
                    property,
901
                    index,
902
20
                    occurrence_expectation
903
                );
904

            
905
20
                let property_info = PropertyInfo::new(
906
20
                    calendar_info,
907
20
                    property_location.clone(),
908
20
                    PropertyKind::ExceptionDateTimes,
909
20
                    ValueType::DateTime,
910
20
                );
911
20
                do_validate_params(&mut errors, property_info, &exception_date_times.params);
912
            }
913
24
            ComponentProperty::RequestStatus(request_status) => {
914
24
                let occurrence_expectation = match property_location {
915
                    PropertyLocation::Event
916
                    | PropertyLocation::ToDo
917
                    | PropertyLocation::Journal
918
24
                    | PropertyLocation::FreeBusy => OccurrenceExpectation::OptionalMany,
919
                    PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
920
                    _ => OccurrenceExpectation::Never,
921
                };
922
24
                check_component_property_occurrence!(
923
                    errors,
924
                    seen,
925
                    property,
926
                    index,
927
24
                    occurrence_expectation
928
                );
929

            
930
24
                let property_info = PropertyInfo::new(
931
24
                    calendar_info,
932
24
                    property_location.clone(),
933
24
                    PropertyKind::RequestStatus,
934
24
                    ValueType::Text,
935
24
                );
936
24
                do_validate_params(&mut errors, property_info, &request_status.params);
937
            }
938
18
            ComponentProperty::RelatedTo(related_to) => {
939
18
                let occurrence_expectation = match property_location {
940
                    PropertyLocation::Event
941
                    | PropertyLocation::ToDo
942
18
                    | PropertyLocation::Journal => OccurrenceExpectation::OptionalMany,
943
                    PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
944
                    _ => OccurrenceExpectation::Never,
945
                };
946
18
                check_component_property_occurrence!(
947
                    errors,
948
                    seen,
949
                    property,
950
                    index,
951
18
                    occurrence_expectation
952
                );
953

            
954
18
                let property_info = PropertyInfo::new(
955
18
                    calendar_info,
956
18
                    property_location.clone(),
957
18
                    PropertyKind::Related,
958
18
                    ValueType::Text,
959
18
                );
960
18
                do_validate_params(&mut errors, property_info, &related_to.params);
961
            }
962
12
            ComponentProperty::Resources(resources) => {
963
12
                let occurrence_expectation = match property_location {
964
                    PropertyLocation::Event | PropertyLocation::ToDo => {
965
12
                        OccurrenceExpectation::OptionalMany
966
                    }
967
                    PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
968
                    _ => OccurrenceExpectation::Never,
969
                };
970
12
                check_component_property_occurrence!(
971
                    errors,
972
                    seen,
973
                    property,
974
                    index,
975
12
                    occurrence_expectation
976
                );
977

            
978
12
                let property_info = PropertyInfo::new(
979
12
                    calendar_info,
980
12
                    property_location.clone(),
981
12
                    PropertyKind::Resources,
982
12
                    ValueType::Text,
983
12
                );
984
12
                do_validate_params(&mut errors, property_info, &resources.params);
985
            }
986
32
            p @ ComponentProperty::RecurrenceDateTimes(recurrence_date_times) => {
987
32
                let occurrence_expectation = match property_location {
988
                    PropertyLocation::Event
989
                    | PropertyLocation::ToDo
990
                    | PropertyLocation::Journal
991
32
                    | PropertyLocation::TimeZoneComponent => OccurrenceExpectation::OptionalMany,
992
                    PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
993
                    _ => OccurrenceExpectation::Never,
994
                };
995
32
                check_component_property_occurrence!(
996
                    errors,
997
                    seen,
998
                    property,
999
                    index,
32
                    occurrence_expectation
                );
32
                let value_type = get_declared_value_type(p)
33
                    .map(|v| v.0)
32
                    .unwrap_or(Value::DateTime);
32
                let property_info = PropertyInfo::new(
32
                    calendar_info,
32
                    property_location.clone(),
32
                    PropertyKind::RecurrenceDateTimes,
32
                    match value_type {
                        Value::Date => ValueType::Date,
                        Value::Period => ValueType::Period,
                        // Either a DATE-TIME or something invalid, which will get checked separately.
32
                        _ => ValueType::DateTime,
                    },
                );
32
                do_validate_params(&mut errors, property_info, &recurrence_date_times.params);
            }
6
            ComponentProperty::DateTimeCompleted(date_time_completed) => {
6
                let occurrence_expectation = match property_location {
6
                    PropertyLocation::ToDo => OccurrenceExpectation::OptionalOnce,
                    PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
                    _ => OccurrenceExpectation::Never,
                };
6
                check_component_property_occurrence!(
                    errors,
                    seen,
                    property,
                    index,
6
                    occurrence_expectation
                );
6
                validate_date_time_completed(&mut errors, date_time_completed, index);
6

            
6
                let property_info = PropertyInfo::new(
6
                    calendar_info,
6
                    property_location.clone(),
6
                    PropertyKind::DateTimeCompleted,
6
                    ValueType::DateTime,
6
                );
6
                do_validate_params(&mut errors, property_info, &date_time_completed.params);
            }
10
            ComponentProperty::PercentComplete(percent_complete) => {
10
                let occurrence_expectation = match property_location {
10
                    PropertyLocation::ToDo => OccurrenceExpectation::OptionalOnce,
                    PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
                    _ => OccurrenceExpectation::Never,
                };
10
                check_component_property_occurrence!(
                    errors,
                    seen,
                    property,
                    index,
10
                    occurrence_expectation
                );
10
                let property_info = PropertyInfo::new(
10
                    calendar_info,
10
                    property_location.clone(),
10
                    PropertyKind::PercentComplete,
10
                    ValueType::Integer,
10
                );
10
                do_validate_params(&mut errors, property_info, &percent_complete.params);
            }
8
            ComponentProperty::DateTimeDue(date_time_due) => {
8
                has_due = true;
8
                let occurrence_expectation = match property_location {
8
                    PropertyLocation::ToDo => OccurrenceExpectation::OptionalOnce,
                    PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
                    _ => OccurrenceExpectation::Never,
                };
8
                check_component_property_occurrence!(
                    errors,
                    seen,
                    property,
                    index,
8
                    occurrence_expectation
                );
8
                validate_date_time_due(
8
                    &mut errors,
8
                    date_time_due,
8
                    maybe_dt_start,
8
                    index,
8
                    property_location.clone(),
8
                );
8
                let property_info = PropertyInfo::new(
8
                    calendar_info,
8
                    property_location.clone(),
8
                    PropertyKind::DateTimeDue,
8
                    if date_time_due.value.is_date_time() {
8
                        ValueType::DateTime
                    } else {
                        ValueType::Date
                    },
                );
8
                do_validate_params(&mut errors, property_info, &date_time_due.params);
            }
6
            ComponentProperty::FreeBusyTime(free_busy_time) => {
6
                let occurrence_expectation = match property_location {
6
                    PropertyLocation::FreeBusy => OccurrenceExpectation::OptionalMany,
                    PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
                    _ => OccurrenceExpectation::Never,
                };
6
                check_component_property_occurrence!(
                    errors,
                    seen,
                    property,
                    index,
6
                    occurrence_expectation
                );
6
                validate_free_busy_time(&mut errors, free_busy_time, index)?;
6
                let property_info = PropertyInfo::new(
6
                    calendar_info,
6
                    property_location.clone(),
6
                    PropertyKind::FreeBusyTime,
6
                    ValueType::Period,
6
                );
6
                do_validate_params(&mut errors, property_info, &free_busy_time.params);
            }
26
            ComponentProperty::TimeZoneId(time_zone_id) => {
26
                check_component_property_occurrence!(
                    errors,
                    seen,
                    property,
                    index,
26
                    tz_id_occurrence_expectation
                );
26
                let property_info = PropertyInfo::new(
26
                    calendar_info,
26
                    property_location.clone(),
26
                    PropertyKind::TimeZoneId,
26
                    ValueType::Text,
26
                );
26
                do_validate_params(&mut errors, property_info, &time_zone_id.params);
            }
10
            ComponentProperty::TimeZoneUrl(time_zone_url) => {
10
                let occurrence_expectation = match property_location {
10
                    PropertyLocation::TimeZone => OccurrenceExpectation::OptionalOnce,
                    PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
                    _ => OccurrenceExpectation::Never,
                };
10
                check_component_property_occurrence!(
                    errors,
                    seen,
                    property,
                    index,
10
                    occurrence_expectation
                );
10
                let property_info = PropertyInfo::new(
10
                    calendar_info,
10
                    property_location.clone(),
10
                    PropertyKind::TimeZoneUrl,
10
                    ValueType::Uri,
10
                );
10
                do_validate_params(&mut errors, property_info, &time_zone_url.params);
            }
40
            ComponentProperty::TimeZoneOffsetTo(time_zone_offset_to) => {
40
                check_component_property_occurrence!(
                    errors,
                    seen,
                    property,
                    index,
40
                    tz_offset_to_occurrence_expectation
                );
40
                let property_info = PropertyInfo::new(
40
                    calendar_info,
40
                    property_location.clone(),
40
                    PropertyKind::TimeZoneOffsetTo,
40
                    ValueType::UtcOffset,
40
                );
40
                do_validate_params(&mut errors, property_info, &time_zone_offset_to.params);
            }
40
            ComponentProperty::TimeZoneOffsetFrom(time_zone_offset_from) => {
40
                check_component_property_occurrence!(
                    errors,
                    seen,
                    property,
                    index,
40
                    tz_offset_from_occurrence_expectation
                );
40
                let property_info = PropertyInfo::new(
40
                    calendar_info,
40
                    property_location.clone(),
40
                    PropertyKind::TimeZoneOffsetFrom,
40
                    ValueType::UtcOffset,
40
                );
40
                do_validate_params(&mut errors, property_info, &time_zone_offset_from.params);
            }
30
            ComponentProperty::TimeZoneName(time_zone_name) => {
30
                let occurrence_expectation = match property_location {
30
                    PropertyLocation::TimeZoneComponent => OccurrenceExpectation::OptionalMany,
                    PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
                    _ => OccurrenceExpectation::Never,
                };
30
                check_component_property_occurrence!(
                    errors,
                    seen,
                    property,
                    index,
30
                    occurrence_expectation
                );
30
                let property_info = PropertyInfo::new(
30
                    calendar_info,
30
                    property_location.clone(),
30
                    PropertyKind::TimeZoneName,
30
                    ValueType::Text,
30
                );
30
                do_validate_params(&mut errors, property_info, &time_zone_name.params);
            }
52
            ComponentProperty::Action(action) => {
52
                check_component_property_occurrence!(
                    errors,
                    seen,
                    property,
                    index,
52
                    action_occurrence_expectation
                );
52
                let property_info = PropertyInfo::new(
52
                    calendar_info,
52
                    property_location.clone(),
52
                    PropertyKind::Action,
52
                    ValueType::Text,
52
                );
52
                do_validate_params(&mut errors, property_info, &action.params);
            }
46
            ComponentProperty::Trigger(trigger) => {
46
                check_component_property_occurrence!(
                    errors,
                    seen,
                    property,
                    index,
46
                    trigger_occurrence_expectation
                );
46
                let value_type = get_declared_value_type(property)
49
                    .map(|v| v.0)
46
                    .unwrap_or(Value::Duration);
46
                let property_info = PropertyInfo::new(
46
                    calendar_info,
46
                    property_location.clone(),
46
                    PropertyKind::Trigger,
46
                    match value_type {
12
                        Value::DateTime => ValueType::DateTime,
                        // Either a duration, or invalid which will be caught separately.
34
                        _ => ValueType::Duration,
                    },
                );
46
                do_validate_params(&mut errors, property_info, &trigger.params);
            }
42
            ComponentProperty::Repeat(repeat) => {
42
                has_repeat = true;
42
                let occurrence_expectation = match property_location {
42
                    PropertyLocation::Alarm => OccurrenceExpectation::OptionalOnce,
                    PropertyLocation::Other => OccurrenceExpectation::OptionalMany,
                    _ => OccurrenceExpectation::Never,
                };
42
                check_component_property_occurrence!(
                    errors,
                    seen,
                    property,
                    index,
42
                    occurrence_expectation
                );
42
                let property_info = PropertyInfo::new(
42
                    calendar_info,
42
                    property_location.clone(),
42
                    PropertyKind::Repeat,
42
                    ValueType::Integer,
42
                );
42
                do_validate_params(&mut errors, property_info, &repeat.params);
            }
164
            ComponentProperty::IanaProperty(_) => {
164
                // Nothing to validate
164
            }
144
            ComponentProperty::XProperty(_) => {
144
                // Nothing to validate
144
            }
        }
    }
342
    if dt_stamp_occurrence_expectation == OccurrenceExpectation::Once {
166
        if let Some(message) = check_occurrence(&seen, "DTSTAMP", dt_stamp_occurrence_expectation) {
8
            errors.push(ComponentPropertyError {
8
                message,
8
                severity: ICalendarErrorSeverity::Error,
8
                location: None,
8
            });
158
        }
176
    }
342
    if uid_occurrence_expectation == OccurrenceExpectation::Once {
166
        if let Some(message) = check_occurrence(&seen, "UID", uid_occurrence_expectation) {
8
            errors.push(ComponentPropertyError {
8
                message,
8
                severity: ICalendarErrorSeverity::Error,
8
                location: None,
8
            });
158
        }
176
    }
342
    if dt_start_expectation == OccurrenceExpectation::Once {
98
        if let Some(message) = check_occurrence(&seen, "DTSTART", dt_start_expectation) {
8
            errors.push(ComponentPropertyError {
8
                message,
8
                severity: ICalendarErrorSeverity::Error,
8
                location: None,
8
            });
90
        }
244
    }
342
    if tz_id_occurrence_expectation == OccurrenceExpectation::Once {
28
        if let Some(message) = check_occurrence(&seen, "TZID", tz_id_occurrence_expectation) {
2
            errors.push(ComponentPropertyError {
2
                message,
2
                severity: ICalendarErrorSeverity::Error,
2
                location: None,
2
            });
26
        }
314
    }
342
    if tz_offset_to_occurrence_expectation == OccurrenceExpectation::Once {
4
        if let Some(message) =
44
            check_occurrence(&seen, "TZOFFSETTO", tz_offset_to_occurrence_expectation)
4
        {
4
            errors.push(ComponentPropertyError {
4
                message,
4
                severity: ICalendarErrorSeverity::Error,
4
                location: None,
4
            });
40
        }
298
    }
342
    if tz_offset_from_occurrence_expectation == OccurrenceExpectation::Once {
4
        if let Some(message) =
44
            check_occurrence(&seen, "TZOFFSETFROM", tz_offset_from_occurrence_expectation)
4
        {
4
            errors.push(ComponentPropertyError {
4
                message,
4
                severity: ICalendarErrorSeverity::Error,
4
                location: None,
4
            });
40
        }
298
    }
342
    if action_occurrence_expectation == OccurrenceExpectation::Once {
52
        if let Some(message) = check_occurrence(&seen, "ACTION", action_occurrence_expectation) {
            errors.push(ComponentPropertyError {
                message,
                severity: ICalendarErrorSeverity::Error,
                location: None,
            });
52
        }
290
    }
342
    if trigger_occurrence_expectation == OccurrenceExpectation::Once {
52
        if let Some(message) = check_occurrence(&seen, "TRIGGER", trigger_occurrence_expectation) {
6
            errors.push(ComponentPropertyError {
6
                message,
6
                severity: ICalendarErrorSeverity::Error,
6
                location: None,
6
            });
46
        }
290
    }
342
    if description_occurrence_expectation == OccurrenceExpectation::Once {
4
        if let Some(message) =
36
            check_occurrence(&seen, "DESCRIPTION", description_occurrence_expectation)
4
        {
4
            errors.push(ComponentPropertyError {
4
                message,
4
                severity: ICalendarErrorSeverity::Error,
4
                location: None,
4
            });
32
        }
306
    }
342
    if summary_occurrence_expectation == OccurrenceExpectation::Once {
14
        if let Some(message) = check_occurrence(&seen, "SUMMARY", summary_occurrence_expectation) {
2
            errors.push(ComponentPropertyError {
2
                message,
2
                severity: ICalendarErrorSeverity::Error,
2
                location: None,
2
            });
12
        }
328
    }
342
    if attendee_occurrence_expectation == OccurrenceExpectation::Once {
        if let Some(message) = check_occurrence(&seen, "ATTENDEE", attendee_occurrence_expectation)
        {
            errors.push(ComponentPropertyError {
                message,
                severity: ICalendarErrorSeverity::Error,
                location: None,
            });
        }
342
    }
342
    match property_location {
        PropertyLocation::Event => {
124
            if has_dt_end && has_duration {
2
                errors.push(ComponentPropertyError {
2
                    message: "Both DTEND and DURATION properties are present, only one is allowed"
2
                        .to_string(),
2
                    severity: ICalendarErrorSeverity::Error,
2
                    location: None,
2
                });
122
            }
        }
        PropertyLocation::ToDo => {
20
            if has_due && has_duration {
2
                errors.push(ComponentPropertyError {
2
                    message: "Both DUE and DURATION properties are present, only one is allowed"
2
                        .to_string(),
2
                    severity: ICalendarErrorSeverity::Error,
2
                    location: None,
2
                });
18
            }
20
            if has_duration && !has_dt_start {
2
                errors.push(ComponentPropertyError {
2
                    message: "DURATION property is present but no DTSTART property is present"
2
                        .to_string(),
2
                    severity: ICalendarErrorSeverity::Error,
2
                    location: None,
2
                });
18
            }
        }
        PropertyLocation::Alarm => {
52
            if (has_duration && !has_repeat) || (!has_duration && has_repeat) {
12
                errors.push(ComponentPropertyError {
12
                    message: "DURATION and REPEAT properties must be present together".to_string(),
12
                    severity: ICalendarErrorSeverity::Error,
12
                    location: None,
12
                });
40
            }
        }
146
        _ => {}
    }
342
    Ok(errors)
348
}
62
fn validate_duration_property(
62
    errors: &mut Vec<ComponentPropertyError>,
62
    duration_property: &DurationProperty,
62
    maybe_dt_start: Option<&DateTimeStartProperty>,
62
    index: usize,
62
    property_location: PropertyLocation,
62
) {
62
    match property_location {
        PropertyLocation::Event | PropertyLocation::ToDo => {
20
            if let Some(dt_start) = maybe_dt_start {
12
                if dt_start.value.is_date()
                    && duration_property.value.weeks.is_none()
                    && duration_property.value.days.is_none()
                {
                    errors.push(ComponentPropertyError {
                            message: "DURATION must have at least one of weeks or days when DTSTART is a date".to_string(),
                        severity: ICalendarErrorSeverity::Error,
                            location: Some(ComponentPropertyLocation {
                                index,
                                name: "DURATION".to_string(),
                                property_location: Some(WithinPropertyLocation::Value),
                            }),
                        });
12
                }
8
            }
        }
42
        _ => {
42
            // Check is not relevant here
42
        }
    }
62
}
/// RFC 5545, 3.8.2.1
6
fn validate_date_time_completed(
6
    errors: &mut Vec<ComponentPropertyError>,
6
    date_time_completed_property: &DateTimeCompletedProperty,
6
    index: usize,
6
) {
6
    if !date_time_completed_property.value.is_utc() {
        errors.push(ComponentPropertyError {
            message: "COMPLETED must be a UTC date-time".to_string(),
            severity: ICalendarErrorSeverity::Error,
            location: Some(ComponentPropertyLocation {
                index,
                name: "COMPLETED".to_string(),
                property_location: Some(WithinPropertyLocation::Value),
            }),
        });
6
    }
6
}
/// RFC 5545, 3.8.2.2
30
fn validate_date_time_end(
30
    errors: &mut Vec<ComponentPropertyError>,
30
    date_time_end_property: &DateTimeEndProperty,
30
    maybe_dt_start: Option<&DateTimeStartProperty>,
30
    index: usize,
30
    property_location: PropertyLocation,
30
) {
30
    // For a VEVENT, the date/date-time types must match at the start and end
30
    if property_location == PropertyLocation::Event {
20
        if let Some(dt_start) = maybe_dt_start {
14
            let dt_start_type =
14
                get_declared_value_type(&ComponentProperty::DateTimeStart(dt_start.clone()));
14
            let dt_end_type = get_declared_value_type(&ComponentProperty::DateTimeEnd(
14
                date_time_end_property.clone(),
14
            ));
14

            
14
            check_date_time_value_type_match(errors, index, dt_start_type, dt_end_type, "DTEND");
18
        }
10
    }
30
    if let Some(dt_start) = maybe_dt_start {
24
        match property_location {
            PropertyLocation::Event => {
14
                if dt_start.value.is_utc() != date_time_end_property.value.is_utc() {
                    errors.push(ComponentPropertyError {
                        message: "DTEND must have the same time type as DTSTART, both UTC or both not UTC".to_string(),
                        severity: ICalendarErrorSeverity::Error,
                        location: Some(ComponentPropertyLocation {
                            index,
                            name: "DTEND".to_string(),
                            property_location: Some(WithinPropertyLocation::Value),
                        }),
                    });
14
                }
            }
            PropertyLocation::FreeBusy => {
10
                if !dt_start.value.is_utc() || !date_time_end_property.value.is_utc() {
                    errors.push(ComponentPropertyError {
                        message: "DTSTART and DTEND for FREEBUSY must be UTC date-times"
                            .to_string(),
                        severity: ICalendarErrorSeverity::Error,
                        location: Some(ComponentPropertyLocation {
                            index,
                            name: "DTEND".to_string(),
                            property_location: Some(WithinPropertyLocation::Value),
                        }),
                    });
10
                }
            }
            _ => {
                // Not expected to be specified elsewhere, and not restricted on Other
            }
        }
24
        match date_time_end_property
24
            .value
24
            .date()
24
            .cmp(dt_start.value.date())
        {
            Ordering::Less => {
                errors.push(ComponentPropertyError {
                    message: "DTEND is before DTSTART".to_string(),
                    severity: ICalendarErrorSeverity::Error,
                    location: Some(ComponentPropertyLocation {
                        index,
                        name: "DTEND".to_string(),
                        property_location: Some(WithinPropertyLocation::Value),
                    }),
                });
            }
            Ordering::Equal => {
                // Because the types must match above, not need to check for other combinations here
24
                if let (Some(dt_end_time), Some(dt_start_time)) = (
24
                    date_time_end_property.value.time_opt(),
24
                    dt_start.value.time_opt(),
24
                ) {
24
                    if dt_end_time < dt_start_time {
                        errors.push(ComponentPropertyError {
                            message: "DTEND is before DTSTART".to_string(),
                            severity: ICalendarErrorSeverity::Error,
                            location: Some(ComponentPropertyLocation {
                                index,
                                name: "DTEND".to_string(),
                                property_location: Some(WithinPropertyLocation::Value),
                            }),
                        });
24
                    }
                }
            }
            Ordering::Greater => {
                // Valid
            }
        }
6
    }
30
}
16
fn check_date_time_value_type_match(
16
    errors: &mut Vec<ComponentPropertyError>,
16
    index: usize,
16
    dt_start_type: Option<(Value, usize)>,
16
    other_type: Option<(Value, usize)>,
16
    other_type_name: &str,
16
) {
16
    match (dt_start_type, other_type) {
14
        (None, None) => {
14
            // Fine, both default
14
        }
        (Some((Value::Date, _)), Some((Value::Date, _))) => {
            // Fine, both date
        }
2
        (Some((Value::DateTime, _)), Some((Value::DateTime, _))) => {
2
            // Fine, both date-time
2
        }
        (Some((Value::DateTime, _)), None) | (None, Some((Value::DateTime, _))) => {
            // Fine, one specified and other at default
        }
        (Some((Value::DateTime, _)) | None, Some((Value::Date, _))) => {
            errors.push(ComponentPropertyError {
                message: format!("DTSTART is date-time but {other_type_name} is date"),
                severity: ICalendarErrorSeverity::Error,
                location: Some(ComponentPropertyLocation {
                    index,
                    name: other_type_name.to_string(),
                    property_location: Some(WithinPropertyLocation::Value),
                }),
            });
        }
        (Some((Value::Date, _)), Some((Value::DateTime, _)) | None) => {
            errors.push(ComponentPropertyError {
                message: format!("DTSTART is date but {other_type_name} is date-time"),
                severity: ICalendarErrorSeverity::Error,
                location: Some(ComponentPropertyLocation {
                    index,
                    name: other_type_name.to_string(),
                    property_location: Some(WithinPropertyLocation::Value),
                }),
            });
        }
        _ => {
            // This is reachable, but any such combination should be rejected later by value type checking
        }
    }
16
}
/// RFC 5545, 3.8.2.3
8
fn validate_date_time_due(
8
    errors: &mut Vec<ComponentPropertyError>,
8
    date_time_due_property: &DateTimeDueProperty,
8
    maybe_dt_start: Option<&DateTimeStartProperty>,
8
    index: usize,
8
    property_location: PropertyLocation,
8
) {
8
    // For a to-do, the date/date-time types must match at the start and end
8
    if property_location == PropertyLocation::ToDo {
8
        if let Some(dt_start) = maybe_dt_start {
2
            let dt_start_type =
2
                get_declared_value_type(&ComponentProperty::DateTimeStart(dt_start.clone()));
2
            let dt_due_type = get_declared_value_type(&ComponentProperty::DateTimeDue(
2
                date_time_due_property.clone(),
2
            ));
2

            
2
            check_date_time_value_type_match(errors, index, dt_start_type, dt_due_type, "DUE");
6
        }
    }
8
    if let Some(dt_start) = maybe_dt_start {
2
        match property_location {
            PropertyLocation::Event => {
                if dt_start.value.is_utc() != date_time_due_property.value.is_utc() {
                    errors.push(ComponentPropertyError {
                        message:
                            "DUE must have the same time type as DTSTART, both UTC or both not UTC"
                                .to_string(),
                        severity: ICalendarErrorSeverity::Error,
                        location: Some(ComponentPropertyLocation {
                            index,
                            name: "DUE".to_string(),
                            property_location: Some(WithinPropertyLocation::Value),
                        }),
                    });
                }
            }
2
            _ => {
2
                // Not expected to be specified elsewhere, and not restricted on Other
2
            }
        }
2
        match date_time_due_property
2
            .value
2
            .date()
2
            .cmp(dt_start.value.date())
        {
            Ordering::Less => {
                errors.push(ComponentPropertyError {
                    message: "DUE is before DTSTART".to_string(),
                    severity: ICalendarErrorSeverity::Error,
                    location: Some(ComponentPropertyLocation {
                        index,
                        name: "DUE".to_string(),
                        property_location: Some(WithinPropertyLocation::Value),
                    }),
                });
            }
            Ordering::Equal => {
                // Because the types must match above, not need to check for other combinations here
2
                if let (Some(dt_end_time), Some(dt_start_time)) = (
2
                    date_time_due_property.value.time_opt(),
2
                    dt_start.value.time_opt(),
2
                ) {
2
                    if dt_end_time < dt_start_time {
                        errors.push(ComponentPropertyError {
                            message: "DUE is before DTSTART".to_string(),
                            severity: ICalendarErrorSeverity::Error,
                            location: Some(ComponentPropertyLocation {
                                index,
                                name: "DUE".to_string(),
                                property_location: Some(WithinPropertyLocation::Value),
                            }),
                        });
2
                    }
                }
            }
            Ordering::Greater => {
                // Valid
            }
        }
6
    }
8
}
/// RFC 5545, 3.8.2.4
124
fn validate_date_time_start(
124
    errors: &mut Vec<ComponentPropertyError>,
124
    date_time_start_property: &DateTimeStartProperty,
124
    index: usize,
124
    property_location: PropertyLocation,
124
) {
124
    if !date_time_start_property
124
        .params
124
        .iter()
185
        .any(|p| matches!(p, Param::ValueType { .. }))
114
        && date_time_start_property.value.is_date()
    {
        errors.push(ComponentPropertyError {
            message: "DTSTART defaults to date-time but has a date value".to_string(),
            severity: ICalendarErrorSeverity::Error,
            location: Some(ComponentPropertyLocation {
                index,
                name: "DTSTART".to_string(),
                property_location: Some(WithinPropertyLocation::Value),
            }),
        });
124
    }
124
    match property_location {
50
        PropertyLocation::Event => {
50
            // Nothing further to check, valid
50
        }
        PropertyLocation::FreeBusy => {
10
            if date_time_start_property.value.is_date() || !date_time_start_property.value.is_utc()
            {
                errors.push(ComponentPropertyError {
                    message: "DTSTART for FREEBUSY must be a UTC date-time".to_string(),
                    severity: ICalendarErrorSeverity::Error,
                    location: Some(ComponentPropertyLocation {
                        index,
                        name: "DTSTART".to_string(),
                        property_location: Some(WithinPropertyLocation::Value),
                    }),
                });
10
            }
        }
        PropertyLocation::TimeZoneComponent => {
40
            if date_time_start_property.value.is_date() || date_time_start_property.value.is_utc() {
                errors.push(ComponentPropertyError {
                    message: "DTSTART must be a local time".to_string(),
                    severity: ICalendarErrorSeverity::Error,
                    location: Some(ComponentPropertyLocation {
                        index,
                        name: "DTSTART".to_string(),
                        property_location: Some(WithinPropertyLocation::Value),
                    }),
                });
40
            }
        }
24
        _ => {}
    }
124
}
30
fn validate_status(
30
    errors: &mut Vec<ComponentPropertyError>,
30
    status: &StatusProperty,
30
    index: usize,
30
    property_location: PropertyLocation,
30
) {
30
    match property_location {
        PropertyLocation::Event => {
10
            match status.value {
10
                Status::Tentative | Status::Confirmed | Status::Cancelled => {
10
                    // Valid
10
                }
                _ => {
                    errors.push(ComponentPropertyError {
                        message: format!("Invalid STATUS value for event: {:?}", status.value),
                        severity: ICalendarErrorSeverity::Error,
                        location: Some(ComponentPropertyLocation {
                            index,
                            name: "STATUS".to_string(),
                            property_location: Some(WithinPropertyLocation::Value),
                        }),
                    });
                }
            }
        }
        PropertyLocation::ToDo => {
10
            match status.value {
10
                Status::NeedsAction | Status::Completed | Status::InProcess | Status::Cancelled => {
10
                    // Valid
10
                }
                _ => {
                    errors.push(ComponentPropertyError {
                        message: format!("Invalid STATUS value for to-do: {:?}", status.value),
                        severity: ICalendarErrorSeverity::Error,
                        location: Some(ComponentPropertyLocation {
                            index,
                            name: "STATUS".to_string(),
                            property_location: Some(WithinPropertyLocation::Value),
                        }),
                    });
                }
            }
        }
        PropertyLocation::Journal => {
10
            match status.value {
10
                Status::Draft | Status::Final | Status::Cancelled => {
10
                    // Valid
10
                }
                _ => {
                    errors.push(ComponentPropertyError {
                        message: format!("Invalid STATUS value for journal: {:?}", status.value),
                        severity: ICalendarErrorSeverity::Error,
                        location: Some(ComponentPropertyLocation {
                            index,
                            name: "STATUS".to_string(),
                            property_location: Some(WithinPropertyLocation::Value),
                        }),
                    });
                }
            }
        }
        PropertyLocation::Other => {
            // Permit any
        }
        _ => {
            // Property occurrence checks should have prevented this being reached
            unreachable!()
        }
    }
30
}
/// RFC 5545, 3.8.2.6
6
fn validate_free_busy_time(
6
    errors: &mut Vec<ComponentPropertyError>,
6
    free_busy_time_property: &FreeBusyTimeProperty,
6
    index: usize,
6
) -> anyhow::Result<()> {
13
    if !free_busy_time_property.value.iter().all(|p| {
12
        p.start.2
12
            && match p.end {
6
                PeriodEnd::DateTime((_, _, is_utc)) => is_utc,
6
                PeriodEnd::Duration(_) => true,
            }
13
    }) {
        errors.push(ComponentPropertyError {
            message: "FREEBUSY periods must be UTC".to_string(),
            severity: ICalendarErrorSeverity::Error,
            location: Some(ComponentPropertyLocation {
                index,
                name: "FREEBUSY".to_string(),
                property_location: Some(WithinPropertyLocation::Value),
            }),
        });
6
    }
6
    let date_times = free_busy_time_property
6
        .value
6
        .iter()
13
        .map(|p| p.expand().map(|v| v.unwrap()))
6
        .collect::<anyhow::Result<Vec<_>>>()?;
7
    let all_ordered = date_times.windows(2).all(|w| {
6
        let (s1, e1) = &w[0];
6
        let (s2, e2) = &w[1];
6

            
6
        match s1.cmp(s2) {
6
            Ordering::Less => true,
            Ordering::Equal => {
                matches!(e1.cmp(e2), Ordering::Less)
            }
            _ => false,
        }
7
    });
6

            
6
    if !all_ordered {
        errors.push(ComponentPropertyError {
            message: "FREEBUSY periods should be ordered".to_string(),
            severity: ICalendarErrorSeverity::Error,
            location: Some(ComponentPropertyLocation {
                index,
                name: "FREEBUSY".to_string(),
                property_location: Some(WithinPropertyLocation::Value),
            }),
        });
6
    }
6
    Ok(())
6
}
// RFC 5545, 3.8.7.2
158
fn validate_date_time_stamp(
158
    errors: &mut Vec<ComponentPropertyError>,
158
    date_time_stamp_property: &DateTimeStampProperty,
158
    index: usize,
158
) {
158
    if !date_time_stamp_property.value.is_utc() {
        errors.push(ComponentPropertyError {
            message: "DTSTAMP must be a UTC date-time".to_string(),
            severity: ICalendarErrorSeverity::Error,
            location: Some(ComponentPropertyLocation {
                index,
                name: "DTSTAMP".to_string(),
                property_location: Some(WithinPropertyLocation::Value),
            }),
        });
158
    }
158
}
// RFC 5545, 3.8.7.3
42
fn validate_last_modified(
42
    errors: &mut Vec<ComponentPropertyError>,
42
    last_modified_property: &LastModifiedProperty,
42
    index: usize,
42
) {
42
    if !last_modified_property.value.is_utc() {
        errors.push(ComponentPropertyError {
            message: "LAST-MODIFIED must be a UTC date-time".to_string(),
            severity: ICalendarErrorSeverity::Error,
            location: Some(ComponentPropertyLocation {
                index,
                name: "LAST-MODIFIED".to_string(),
                property_location: Some(WithinPropertyLocation::Value),
            }),
        });
42
    }
42
}