1
mod component;
2
mod object;
3
mod param;
4
mod property;
5
mod value;
6

            
7
use std::io::Write;
8

            
9
pub trait WriteModel {
10
    fn write_model<W: Write>(&self, writer: &mut W) -> anyhow::Result<()>;
11
}
12

            
13
#[cfg(test)]
14
mod tests {
15
    use crate::convert::ToModel;
16
    use crate::parser::Error;
17
    use crate::serialize::WriteModel;
18
    use crate::test_utils::check_rem;
19

            
20
    #[test]
21
2
    fn rtt_single_event() {
22
2
        let content = "BEGIN:VCALENDAR\r\nBEGIN:VEVENT\r\nDTSTAMP:20211010T000000Z\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n";
23
2
        round_trip_ical_object(content);
24
2
    }
25

            
26
    // Check with data taken from RFC 5545, section 3.6.1
27
    #[test]
28
2
    fn rtt_event() {
29
2
        let example_1 = "BEGIN:VCALENDAR\r\n\
30
2
BEGIN:VEVENT\r\n\
31
2
UID:19970901T130000Z-123401@example.com\r\n\
32
2
DTSTAMP:19970901T130000Z\r\n\
33
2
DTSTART:19970903T163000Z\r\n\
34
2
DTEND:19970903T190000Z\r\n\
35
2
SUMMARY:Annual Employee Review\r\n\
36
2
CLASS:PRIVATE\r\n\
37
2
CATEGORIES:BUSINESS,HUMAN RESOURCES\r\n\
38
2
END:VEVENT\r\n\
39
2
END:VCALENDAR\r\n";
40
2

            
41
2
        round_trip_ical_object(example_1);
42
2

            
43
2
        let example_2 = "BEGIN:VCALENDAR\r\n\
44
2
BEGIN:VEVENT\r\n\
45
2
UID:19970901T130000Z-123402@example.com\r\n\
46
2
DTSTAMP:19970901T130000Z\r\n\
47
2
DTSTART:19970401T163000Z\r\n\
48
2
DTEND:19970402T010000Z\r\n\
49
2
SUMMARY:Laurel is in sensitivity awareness class.\r\n\
50
2
CLASS:PUBLIC\r\n\
51
2
CATEGORIES:BUSINESS,HUMAN RESOURCES\r\n\
52
2
TRANSP:TRANSPARENT\r\n\
53
2
END:VEVENT\r\n\
54
2
END:VCALENDAR\r\n";
55
2

            
56
2
        round_trip_ical_object(example_2);
57
2

            
58
2
        let example_3 = "BEGIN:VCALENDAR\r\n\
59
2
BEGIN:VEVENT\r\n\
60
2
UID:19970901T130000Z-123403@example.com\r\n\
61
2
DTSTAMP:19970901T130000Z\r\n\
62
2
DTSTART;VALUE=DATE:19971102\r\n\
63
2
SUMMARY:Our Blissful Anniversary\r\n\
64
2
TRANSP:TRANSPARENT\r\n\
65
2
CLASS:CONFIDENTIAL\r\n\
66
2
CATEGORIES:ANNIVERSARY,PERSONAL,SPECIAL OCCASION\r\n\
67
2
RRULE:FREQ=YEARLY\r\n\
68
2
END:VEVENT\r\n\
69
2
END:VCALENDAR\r\n";
70
2

            
71
2
        round_trip_ical_object(example_3);
72
2

            
73
2
        let example_4 = "BEGIN:VCALENDAR\r\n\
74
2
BEGIN:VEVENT\r\n\
75
2
UID:20070423T123432Z-541111@example.com\r\n\
76
2
DTSTAMP:20070423T123432Z\r\n\
77
2
DTSTART;VALUE=DATE:20070628\r\n\
78
2
DTEND;VALUE=DATE:20070709\r\n\
79
2
SUMMARY:Festival International de Jazz de Montreal\r\n\
80
2
TRANSP:TRANSPARENT\r\n\
81
2
END:VEVENT\r\n\
82
2
END:VCALENDAR\r\n";
83
2

            
84
2
        round_trip_ical_object(example_4);
85
2
    }
86

            
87
    // Check with data taken from RFC 5545, section 3.6.2
88
    #[test]
89
2
    fn rtt_to_do() {
90
2
        let example_1 = "BEGIN:VCALENDAR\r\n\
91
2
BEGIN:VTODO\r\n\
92
2
UID:20070313T123432Z-456553@example.com\r\n\
93
2
DTSTAMP:20070313T123432Z\r\n\
94
2
DUE;VALUE=DATE:20070501\r\n\
95
2
SUMMARY:Submit Quebec Income Tax Return for 2006\r\n\
96
2
CLASS:CONFIDENTIAL\r\n\
97
2
CATEGORIES:FAMILY,FINANCE\r\n\
98
2
STATUS:NEEDS-ACTION\r\n\
99
2
END:VTODO\r\n\
100
2
END:VCALENDAR\r\n";
101
2

            
102
2
        round_trip_ical_object(example_1);
103
2

            
104
2
        let example_2 = "BEGIN:VCALENDAR\r\n\
105
2
BEGIN:VTODO\r\n\
106
2
UID:20070514T103211Z-123404@example.com\r\n\
107
2
DTSTAMP:20070514T103211Z\r\n\
108
2
DTSTART:20070514T110000Z\r\n\
109
2
DUE:20070709T130000Z\r\n\
110
2
COMPLETED:20070707T100000Z\r\n\
111
2
SUMMARY:Submit Revised Internet-Draft\r\n\
112
2
PRIORITY:1\r\n\
113
2
STATUS:NEEDS-ACTION\r\n\
114
2
END:VTODO\r\n\
115
2
END:VCALENDAR\r\n";
116
2

            
117
2
        round_trip_ical_object(example_2);
118
2
    }
119

            
120
    #[test]
121
2
    fn rtt_journal() {
122
2
        let example_1 = "BEGIN:VCALENDAR\r\n\
123
2
BEGIN:VJOURNAL\r\n\
124
2
UID:19970901T130000Z-123405@example.com\r\n\
125
2
DTSTAMP:19970901T130000Z\r\n\
126
2
DTSTART;VALUE=DATE:19970317\r\n\
127
2
SUMMARY:Staff meeting minutes\r\n\
128
2
DESCRIPTION:1. Staff meeting: Participants include Joe\\, Lisa\\, and Bob. Aurora project plans were reviewed. There is currently no budget reserves for this project. Lisa will escalate to management. Next meeting on Tuesday.\\n 2. Telephone Conference: ABC Corp. sales representative called to discuss new printer. Promised to get us a demo by Friday.\\n3. Henry Miller (Handsoff Insurance): Car was totaled by tree. Is looking into a loaner car. 555-2323 (tel).\r\n\
129
2
END:VJOURNAL\r\n\
130
2
END:VCALENDAR\r\n";
131
2

            
132
2
        round_trip_ical_object(example_1);
133
2
    }
134

            
135
    #[test]
136
2
    fn rtt_free_busy() {
137
2
        let example_1 = "BEGIN:VCALENDAR\r\n\
138
2
BEGIN:VFREEBUSY\r\n\
139
2
UID:19970901T082949Z-FA43EF@example.com\r\n\
140
2
ORGANIZER:mailto:jane_doe@example.com\r\n\
141
2
ATTENDEE:mailto:john_public@example.com\r\n\
142
2
DTSTART:19971015T050000Z\r\n\
143
2
DTEND:19971016T050000Z\r\n\
144
2
DTSTAMP:19970901T083000Z\r\n\
145
2
END:VFREEBUSY\r\n\
146
2
END:VCALENDAR\r\n";
147
2

            
148
2
        round_trip_ical_object(example_1);
149
2

            
150
2
        let example_2 = "BEGIN:VCALENDAR\r\n\
151
2
BEGIN:VFREEBUSY\r\n\
152
2
UID:19970901T095957Z-76A912@example.com\r\n\
153
2
ORGANIZER:mailto:jane_doe@example.com\r\n\
154
2
ATTENDEE:mailto:john_public@example.com\r\n\
155
2
DTSTAMP:19970901T100000Z\r\n\
156
2
FREEBUSY:19971015T050000Z/PT8H30M,19971015T160000Z/PT5H30M,19971015T223000Z/PT6H30M\r\n\
157
2
URL:http://example.com/pub/busy/jpublic-01.ifb\r\n\
158
2
COMMENT:This iCalendar file contains busy time information for the next three months.\r\n\
159
2
END:VFREEBUSY\r\n\
160
2
END:VCALENDAR\r\n";
161
2

            
162
2
        round_trip_ical_object(example_2);
163
2

            
164
2
        let example_3 = "BEGIN:VCALENDAR\r\n\
165
2
BEGIN:VFREEBUSY\r\n\
166
2
UID:19970901T115957Z-76A912@example.com\r\n\
167
2
DTSTAMP:19970901T120000Z\r\n\
168
2
ORGANIZER:mailto:jsmith@example.com\r\n\
169
2
DTSTART:19980313T141711Z\r\n\
170
2
DTEND:19980410T141711Z\r\n\
171
2
FREEBUSY:19980314T233000Z/19980315T003000Z\r\n\
172
2
FREEBUSY:19980316T153000Z/19980316T163000Z\r\n\
173
2
FREEBUSY:19980318T030000Z/19980318T040000Z\r\n\
174
2
URL:http://www.example.com/calendar/busytime/jsmith.ifb\r\n\
175
2
END:VFREEBUSY\r\n\
176
2
END:VCALENDAR\r\n";
177
2

            
178
2
        round_trip_ical_object(example_3);
179
2
    }
180

            
181
    #[test]
182
2
    fn rtt_time_zone() {
183
2
        let example_1 = "BEGIN:VCALENDAR\r\n\
184
2
BEGIN:VTIMEZONE\r\n\
185
2
TZID:America/New_York\r\n\
186
2
LAST-MODIFIED:20050809T050000Z\r\n\
187
2
BEGIN:DAYLIGHT\r\n\
188
2
DTSTART:19670430T020000\r\n\
189
2
RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=-1SU;UNTIL=19730429T070000Z\r\n\
190
2
TZOFFSETFROM:-0500\r\n\
191
2
TZOFFSETTO:-0400\r\n\
192
2
TZNAME:EDT\r\n\
193
2
END:DAYLIGHT\r\n\
194
2
BEGIN:STANDARD\r\n\
195
2
DTSTART:19671029T020000\r\n\
196
2
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU;UNTIL=20061029T060000Z\r\n\
197
2
TZOFFSETFROM:-0400\r\n\
198
2
TZOFFSETTO:-0500\r\n\
199
2
TZNAME:EST\r\n\
200
2
END:STANDARD\r\n\
201
2
BEGIN:DAYLIGHT\r\n\
202
2
DTSTART:19740106T020000\r\n\
203
2
RDATE:19750223T020000\r\n\
204
2
TZOFFSETFROM:-0500\r\n\
205
2
TZOFFSETTO:-0400\r\n\
206
2
TZNAME:EDT\r\n\
207
2
END:DAYLIGHT\r\n\
208
2
BEGIN:DAYLIGHT\r\n\
209
2
DTSTART:19760425T020000\r\n\
210
2
RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=-1SU;UNTIL=19860427T070000Z\r\n\
211
2
TZOFFSETFROM:-0500\r\n\
212
2
TZOFFSETTO:-0400\r\n\
213
2
TZNAME:EDT\r\n\
214
2
END:DAYLIGHT\r\n\
215
2
BEGIN:DAYLIGHT\r\n\
216
2
DTSTART:19870405T020000\r\n\
217
2
RRULE:FREQ=YEARLY;BYMONTH=4;BYDAY=1SU;UNTIL=20060402T070000Z\r\n\
218
2
TZOFFSETFROM:-0500\r\n\
219
2
TZOFFSETTO:-0400\r\n\
220
2
TZNAME:EDT\r\n\
221
2
END:DAYLIGHT\r\n\
222
2
BEGIN:DAYLIGHT\r\n\
223
2
DTSTART:20070311T020000\r\n\
224
2
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU\r\n\
225
2
TZOFFSETFROM:-0500\r\n\
226
2
TZOFFSETTO:-0400\r\n\
227
2
TZNAME:EDT\r\n\
228
2
END:DAYLIGHT\r\n\
229
2
BEGIN:STANDARD\r\n\
230
2
DTSTART:20071104T020000\r\n\
231
2
RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU\r\n\
232
2
TZOFFSETFROM:-0400\r\n\
233
2
TZOFFSETTO:-0500\r\n\
234
2
TZNAME:EST\r\n\
235
2
END:STANDARD\r\n\
236
2
END:VTIMEZONE\r\n\
237
2
END:VCALENDAR\r\n";
238
2

            
239
2
        round_trip_ical_object(example_1);
240
2

            
241
2
        let example_2 = "BEGIN:VCALENDAR\r\n\
242
2
BEGIN:VTIMEZONE\r\n\
243
2
TZID:America/New_York\r\n\
244
2
LAST-MODIFIED:20050809T050000Z\r\n\
245
2
BEGIN:STANDARD\r\n\
246
2
DTSTART:20071104T020000\r\n\
247
2
TZOFFSETFROM:-0400\r\n\
248
2
TZOFFSETTO:-0500\r\n\
249
2
TZNAME:EST\r\n\
250
2
END:STANDARD\r\n\
251
2
BEGIN:DAYLIGHT\r\n\
252
2
DTSTART:20070311T020000\r\n\
253
2
TZOFFSETFROM:-0500\r\n\
254
2
TZOFFSETTO:-0400\r\n\
255
2
TZNAME:EDT\r\n\
256
2
END:DAYLIGHT\r\n\
257
2
END:VTIMEZONE\r\n\
258
2
END:VCALENDAR\r\n";
259
2

            
260
2
        round_trip_ical_object(example_2);
261
2

            
262
2
        let example_3 = "BEGIN:VCALENDAR\r\n\
263
2
BEGIN:VTIMEZONE\r\n\
264
2
TZID:America/New_York\r\n\
265
2
LAST-MODIFIED:20050809T050000Z\r\n\
266
2
TZURL:http://zones.example.com/tz/America-New_York.ics\r\n\
267
2
BEGIN:STANDARD\r\n\
268
2
DTSTART:20071104T020000\r\n\
269
2
RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU\r\n\
270
2
TZOFFSETFROM:-0400\r\n\
271
2
TZOFFSETTO:-0500\r\n\
272
2
TZNAME:EST\r\n\
273
2
END:STANDARD\r\n\
274
2
BEGIN:DAYLIGHT\r\n\
275
2
DTSTART:20070311T020000\r\n\
276
2
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU\r\n\
277
2
TZOFFSETFROM:-0500\r\n\
278
2
TZOFFSETTO:-0400\r\n\
279
2
TZNAME:EDT\r\n\
280
2
END:DAYLIGHT\r\n\
281
2
END:VTIMEZONE\r\n\
282
2
END:VCALENDAR\r\n";
283
2

            
284
2
        round_trip_ical_object(example_3);
285
2

            
286
2
        let example_4 = "BEGIN:VCALENDAR\r\n\
287
2
BEGIN:VTIMEZONE\r\n\
288
2
TZID:Fictitious\r\n\
289
2
LAST-MODIFIED:19870101T000000Z\r\n\
290
2
BEGIN:STANDARD\r\n\
291
2
DTSTART:19671029T020000\r\n\
292
2
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10\r\n\
293
2
TZOFFSETFROM:-0400\r\n\
294
2
TZOFFSETTO:-0500\r\n\
295
2
TZNAME:EST\r\n\
296
2
END:STANDARD\r\n\
297
2
BEGIN:DAYLIGHT\r\n\
298
2
DTSTART:19870405T020000\r\n\
299
2
RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4;UNTIL=19980404T070000Z\r\n\
300
2
TZOFFSETFROM:-0500\r\n\
301
2
TZOFFSETTO:-0400\r\n\
302
2
TZNAME:EDT\r\n\
303
2
END:DAYLIGHT\r\n\
304
2
END:VTIMEZONE\r\n\
305
2
END:VCALENDAR\r\n";
306
2

            
307
2
        round_trip_ical_object(example_4);
308
2

            
309
2
        let example_5 = "BEGIN:VCALENDAR\r\n\
310
2
BEGIN:VTIMEZONE\r\n\
311
2
TZID:Fictitious\r\n\
312
2
LAST-MODIFIED:19870101T000000Z\r\n\
313
2
BEGIN:STANDARD\r\n\
314
2
DTSTART:19671029T020000\r\n\
315
2
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10\r\n\
316
2
TZOFFSETFROM:-0400\r\n\
317
2
TZOFFSETTO:-0500\r\n\
318
2
TZNAME:EST\r\n\
319
2
END:STANDARD\r\n\
320
2
BEGIN:DAYLIGHT\r\n\
321
2
DTSTART:19870405T020000\r\n\
322
2
RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4;UNTIL=19980404T070000Z\r\n\
323
2
TZOFFSETFROM:-0500\r\n\
324
2
TZOFFSETTO:-0400\r\n\
325
2
TZNAME:EDT\r\n\
326
2
END:DAYLIGHT\r\n\
327
2
BEGIN:DAYLIGHT\r\n\
328
2
DTSTART:19990424T020000\r\n\
329
2
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=4\r\n\
330
2
TZOFFSETFROM:-0500\r\n\
331
2
TZOFFSETTO:-0400\r\n\
332
2
TZNAME:EDT\r\n\
333
2
END:DAYLIGHT\r\n\
334
2
END:VTIMEZONE\r\n\
335
2
END:VCALENDAR\r\n";
336
2

            
337
2
        round_trip_ical_object(example_5);
338
2
    }
339

            
340
    #[test]
341
2
    fn rtt_alarm() {
342
2
        let example_1 = "BEGIN:VCALENDAR\r\n\
343
2
BEGIN:VEVENT\r\n\
344
2
BEGIN:VALARM\r\n\
345
2
TRIGGER;VALUE=DATE-TIME:19970317T133000Z\r\n\
346
2
REPEAT:4\r\n\
347
2
DURATION:PT15M\r\n\
348
2
ACTION:AUDIO\r\n\
349
2
ATTACH;FMTTYPE=audio/basic:ftp://example.com/pub/sounds/bell-01.aud\r\n\
350
2
END:VALARM\r\n\
351
2
END:VEVENT\r\n\
352
2
END:VCALENDAR\r\n";
353
2

            
354
2
        round_trip_ical_object(example_1);
355
2

            
356
2
        let example_2 = "BEGIN:VCALENDAR\r\n\
357
2
BEGIN:VEVENT\r\n\
358
2
BEGIN:VALARM\r\n\
359
2
TRIGGER:-PT30M\r\n\
360
2
REPEAT:2\r\n\
361
2
DURATION:PT15M\r\n\
362
2
ACTION:DISPLAY\r\n\
363
2
DESCRIPTION:Breakfast meeting with executive\\n team at 8:30 AM EST.\r\n\
364
2
END:VALARM\r\n\
365
2
END:VEVENT\r\n\
366
2
END:VCALENDAR\r\n";
367
2

            
368
2
        round_trip_ical_object(example_2);
369
2

            
370
2
        let example_3 = "BEGIN:VCALENDAR\r\n\
371
2
BEGIN:VEVENT\r\n\
372
2
BEGIN:VALARM\r\n\
373
2
TRIGGER;RELATED=END:-P2D\r\n\
374
2
ACTION:EMAIL\r\n\
375
2
ATTENDEE:mailto:john_doe@example.com\r\n\
376
2
SUMMARY:*** REMINDER: SEND AGENDA FOR WEEKLY STAFF MEETING ***\r\n\
377
2
DESCRIPTION:A draft agenda needs to be sent out to the attendees to the weekly managers meeting (MGR-LIST). Attached is a pointer the document template for the agenda file.\r\n\
378
2
ATTACH;FMTTYPE=application/msword:http://example.com/templates/agenda.doc\r\n\
379
2
END:VALARM\r\n\
380
2
END:VEVENT\r\n\
381
2
END:VCALENDAR\r\n";
382
2

            
383
2
        round_trip_ical_object(example_3);
384
2
    }
385

            
386
38
    fn round_trip_ical_object(content: &str) {
387
38
        let (rem, object) = crate::parser::ical_object::<Error>(content.as_bytes()).unwrap();
388
38
        check_rem(rem, 0);
389
38
        let model = object.to_model().unwrap();
390
38

            
391
38
        let mut buffer = Vec::new();
392
38
        model.write_model(&mut buffer).unwrap();
393
38
        let out_content = String::from_utf8_lossy(&buffer);
394
38

            
395
38
        similar_asserts::assert_eq!(content, out_content);
396
38
    }
397
}