calcard 0.3.3

iCalendar/JSCalendar and vCard/JSContact parsing, building and conversion library for Rust
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
/*
 * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
 *
 * SPDX-License-Identifier: Apache-2.0 OR MIT
 */

use crate::{
    common::{Data, IanaString, IanaType},
    icalendar::{
        ICalendarDisplayType, ICalendarEntry, ICalendarFeatureType, ICalendarParameter,
        ICalendarParameterName, ICalendarParameterValue, ICalendarParticipationRole,
        ICalendarParticipationStatus, ICalendarProperty, ICalendarRelated, ICalendarUserTypes,
        ICalendarValue, ICalendarValueType, Uri,
    },
    jscalendar::{
        JSCalendarId, JSCalendarLinkDisplay, JSCalendarParticipantKind, JSCalendarParticipantRole,
        JSCalendarParticipationStatus, JSCalendarProperty, JSCalendarRelativeTo, JSCalendarValue,
        JSCalendarVirtualLocationFeature, import::ICalendarParams,
    },
};
use ahash::AHashMap;
use jmap_tools::{Key, Map, Value};

pub(super) trait ExtractParams {
    fn extract_params(
        &mut self,
        entry: &mut ICalendarEntry,
        extract: &[ICalendarParameterName],
    ) -> Option<String>;
}

impl<I: JSCalendarId, B: JSCalendarId> ExtractParams
    for AHashMap<
        Key<'static, JSCalendarProperty<I>>,
        Value<'static, JSCalendarProperty<I>, JSCalendarValue<I, B>>,
    >
{
    fn extract_params(
        &mut self,
        entry: &mut ICalendarEntry,
        extract: &[ICalendarParameterName],
    ) -> Option<String> {
        let mut jsid = None;

        for param in std::mem::take(&mut entry.params) {
            if !extract.contains(&param.name) {
                entry.params.push(param);
                continue;
            }

            match param.name {
                ICalendarParameterName::Cn => {
                    if let Some(text) = param.value.into_text() {
                        self.insert(Key::Property(JSCalendarProperty::Name), Value::Str(text));
                    }
                }
                ICalendarParameterName::Cutype => {
                    self.insert(
                        Key::Property(JSCalendarProperty::Kind),
                        match param.value {
                            ICalendarParameterValue::Text(value) => Value::Str(value.into()),
                            ICalendarParameterValue::Cutype(value) => match value {
                                ICalendarUserTypes::Individual => {
                                    Value::Element(JSCalendarValue::ParticipantKind(
                                        JSCalendarParticipantKind::Individual,
                                    ))
                                }
                                ICalendarUserTypes::Group => {
                                    Value::Element(JSCalendarValue::ParticipantKind(
                                        JSCalendarParticipantKind::Group,
                                    ))
                                }
                                ICalendarUserTypes::Resource => {
                                    Value::Element(JSCalendarValue::ParticipantKind(
                                        JSCalendarParticipantKind::Resource,
                                    ))
                                }
                                ICalendarUserTypes::Room => {
                                    Value::Element(JSCalendarValue::ParticipantKind(
                                        JSCalendarParticipantKind::Location,
                                    ))
                                }
                                ICalendarUserTypes::Unknown => {
                                    Value::Str(value.as_str().to_lowercase().into())
                                }
                            },
                            _ => continue,
                        },
                    );
                }
                ICalendarParameterName::DelegatedFrom => {
                    if let Some(text) = param.value.into_text() {
                        self.insert(
                            Key::Property(JSCalendarProperty::DelegatedFrom),
                            Value::Str(text),
                        );
                    }
                }
                ICalendarParameterName::DelegatedTo => {
                    if let Some(text) = param.value.into_text() {
                        self.insert(
                            Key::Property(JSCalendarProperty::DelegatedTo),
                            Value::Str(text),
                        );
                    }
                }
                ICalendarParameterName::Email => {
                    if let Some(text) = param.value.into_text() {
                        self.insert(Key::Property(JSCalendarProperty::Email), Value::Str(text));
                    }
                }
                ICalendarParameterName::Rsvp => {
                    if let Some(boolean) = param.value.as_bool() {
                        self.insert(
                            Key::Property(JSCalendarProperty::ExpectReply),
                            Value::Bool(boolean),
                        );
                    }
                }
                ICalendarParameterName::Member => {
                    if let Some(text) = param.value.into_text() {
                        self.entry(Key::Property(JSCalendarProperty::MemberOf))
                            .or_insert_with(Value::new_object)
                            .as_object_mut()
                            .unwrap()
                            .insert(Key::from(text), Value::Bool(true));
                    }
                }
                ICalendarParameterName::Partstat => {
                    self.insert(
                        Key::Property(JSCalendarProperty::ParticipationStatus),
                        match param.value {
                            ICalendarParameterValue::Partstat(value) => {
                                Value::Element(JSCalendarValue::ParticipationStatus(match value {
                                    ICalendarParticipationStatus::NeedsAction => {
                                        JSCalendarParticipationStatus::NeedsAction
                                    }
                                    ICalendarParticipationStatus::Declined => {
                                        JSCalendarParticipationStatus::Declined
                                    }
                                    ICalendarParticipationStatus::Tentative => {
                                        JSCalendarParticipationStatus::Tentative
                                    }
                                    ICalendarParticipationStatus::Delegated => {
                                        JSCalendarParticipationStatus::Delegated
                                    }
                                    ICalendarParticipationStatus::Completed
                                    | ICalendarParticipationStatus::InProcess
                                    | ICalendarParticipationStatus::Failed
                                    | ICalendarParticipationStatus::Accepted => {
                                        JSCalendarParticipationStatus::Accepted
                                    }
                                }))
                            }
                            ICalendarParameterValue::Text(value) => Value::Str(value.into()),
                            _ => continue,
                        },
                    );
                }
                ICalendarParameterName::Role => {
                    let role = match param.value {
                        ICalendarParameterValue::Role(value) => {
                            Key::Property(JSCalendarProperty::ParticipantRole(match value {
                                ICalendarParticipationRole::Chair => {
                                    JSCalendarParticipantRole::Chair
                                }
                                ICalendarParticipationRole::ReqParticipant => {
                                    JSCalendarParticipantRole::Required
                                }
                                ICalendarParticipationRole::OptParticipant => {
                                    JSCalendarParticipantRole::Optional
                                }
                                ICalendarParticipationRole::NonParticipant => {
                                    JSCalendarParticipantRole::Informational
                                }
                                ICalendarParticipationRole::Owner => {
                                    JSCalendarParticipantRole::Owner
                                }
                            }))
                        }
                        ICalendarParameterValue::Text(value) => Key::Owned(value),
                        _ => continue,
                    };
                    self.entry(Key::Property(JSCalendarProperty::Roles))
                        .or_insert_with(Value::new_object)
                        .as_object_mut()
                        .unwrap()
                        .insert(role, Value::Bool(true));
                }
                ICalendarParameterName::SentBy => {
                    if let Some(text) = param.value.into_text() {
                        self.insert(Key::Property(JSCalendarProperty::SentBy), Value::Str(text));
                    }
                }
                ICalendarParameterName::Fmttype => {
                    if let Some(text) = param.value.into_text() {
                        self.insert(
                            Key::Property(
                                if matches!(entry.name, ICalendarProperty::StyledDescription) {
                                    JSCalendarProperty::DescriptionContentType
                                } else {
                                    JSCalendarProperty::ContentType
                                },
                            ),
                            Value::Str(text),
                        );
                    }
                }
                ICalendarParameterName::Label => {
                    if let Some(text) = param.value.into_text() {
                        self.insert(
                            Key::Property(if matches!(entry.name, ICalendarProperty::Conference) {
                                JSCalendarProperty::Name
                            } else {
                                JSCalendarProperty::Title
                            }),
                            Value::Str(text),
                        );
                    }
                }
                ICalendarParameterName::Size => {
                    if let Some(number) = param.value.as_integer() {
                        self.insert(
                            Key::Property(JSCalendarProperty::Size),
                            Value::Number(number.into()),
                        );
                    } else {
                        entry.params.push(ICalendarParameter::size(param.value));
                    }
                }
                ICalendarParameterName::Linkrel => match param.value {
                    ICalendarParameterValue::Linkrel(linkrel) => {
                        self.insert(
                            Key::Property(JSCalendarProperty::Rel),
                            Value::Element(JSCalendarValue::LinkRelation(linkrel)),
                        );
                    }
                    ICalendarParameterValue::Text(value) => {
                        self.insert(
                            Key::Property(JSCalendarProperty::Rel),
                            Value::Str(value.into()),
                        );
                    }
                    value => {
                        entry.params.push(ICalendarParameter::linkrel(value));
                    }
                },
                ICalendarParameterName::Related => {
                    if let ICalendarParameterValue::Related(related) = param.value {
                        self.insert(
                            Key::Property(JSCalendarProperty::RelativeTo),
                            Value::Element(JSCalendarValue::RelativeTo(match related {
                                ICalendarRelated::Start => JSCalendarRelativeTo::Start,
                                ICalendarRelated::End => JSCalendarRelativeTo::End,
                            })),
                        );
                    } else {
                        entry.params.push(ICalendarParameter::related(param.value));
                    }
                }
                ICalendarParameterName::Feature => {
                    let feature = match param.value {
                        ICalendarParameterValue::Feature(value) => {
                            Key::Property(JSCalendarProperty::VirtualLocationFeature(match value {
                                ICalendarFeatureType::Audio => {
                                    JSCalendarVirtualLocationFeature::Audio
                                }
                                ICalendarFeatureType::Chat => {
                                    JSCalendarVirtualLocationFeature::Chat
                                }
                                ICalendarFeatureType::Feed => {
                                    JSCalendarVirtualLocationFeature::Feed
                                }
                                ICalendarFeatureType::Moderator => {
                                    JSCalendarVirtualLocationFeature::Moderator
                                }
                                ICalendarFeatureType::Phone => {
                                    JSCalendarVirtualLocationFeature::Phone
                                }
                                ICalendarFeatureType::Screen => {
                                    JSCalendarVirtualLocationFeature::Screen
                                }
                                ICalendarFeatureType::Video => {
                                    JSCalendarVirtualLocationFeature::Video
                                }
                            }))
                        }
                        ICalendarParameterValue::Text(value) => Key::Owned(value),
                        _ => continue,
                    };
                    self.entry(Key::Property(JSCalendarProperty::Features))
                        .or_insert_with(Value::new_object)
                        .as_object_mut()
                        .unwrap()
                        .insert(feature, Value::Bool(true));
                }
                ICalendarParameterName::Language => {
                    if let Some(text) = param.value.into_text() {
                        self.insert(Key::Property(JSCalendarProperty::Locale), Value::Str(text));
                    }
                }
                ICalendarParameterName::Display => {
                    let display = match param.value {
                        ICalendarParameterValue::Display(value) => {
                            Key::Property(JSCalendarProperty::LinkDisplay(match value {
                                ICalendarDisplayType::Badge => JSCalendarLinkDisplay::Badge,
                                ICalendarDisplayType::Graphic => JSCalendarLinkDisplay::Graphic,
                                ICalendarDisplayType::Fullsize => JSCalendarLinkDisplay::Fullsize,
                                ICalendarDisplayType::Thumbnail => JSCalendarLinkDisplay::Thumbnail,
                            }))
                        }
                        ICalendarParameterValue::Text(value) => Key::Owned(value),
                        value => {
                            entry.params.push(ICalendarParameter::display(value));
                            continue;
                        }
                    };
                    self.entry(Key::Property(JSCalendarProperty::Display))
                        .or_insert_with(Value::new_object)
                        .as_object_mut()
                        .unwrap()
                        .insert(display, Value::Bool(true));
                }
                ICalendarParameterName::Jsid => {
                    jsid = param.value.into_text().map(|v| v.into_owned());
                }
                ICalendarParameterName::Range
                | ICalendarParameterName::Reltype
                | ICalendarParameterName::Dir
                | ICalendarParameterName::Fbtype
                | ICalendarParameterName::ScheduleAgent
                | ICalendarParameterName::ScheduleForceSend
                | ICalendarParameterName::ScheduleStatus
                | ICalendarParameterName::Tzid
                | ICalendarParameterName::Value
                | ICalendarParameterName::Filename
                | ICalendarParameterName::ManagedId
                | ICalendarParameterName::Order
                | ICalendarParameterName::Schema
                | ICalendarParameterName::Derived
                | ICalendarParameterName::Gap
                | ICalendarParameterName::Jsptr
                | ICalendarParameterName::Other(_)
                | ICalendarParameterName::Altrep => {}
            }
        }

        jsid
    }
}

impl<I: JSCalendarId, B: JSCalendarId> ICalendarParams<I, B> {
    pub(super) fn into_jscalendar_value(
        self,
    ) -> Option<Map<'static, JSCalendarProperty<I>, JSCalendarValue<I, B>>> {
        if !self.0.is_empty() {
            let mut obj = Map::from(Vec::with_capacity(self.0.len()));

            for (param, value) in self.0 {
                let value = if value.len() > 1 {
                    Value::Array(value)
                } else {
                    value.into_iter().next().unwrap()
                };
                obj.insert_unchecked(Key::Owned(param.into_string().to_ascii_lowercase()), value);
            }
            Some(obj)
        } else {
            None
        }
    }
}

impl ICalendarValue {
    pub(super) fn into_jscalendar_value<I: JSCalendarId, B: JSCalendarId>(
        self,
        value_type: Option<&IanaType<ICalendarValueType, String>>,
    ) -> Value<'static, JSCalendarProperty<I>, JSCalendarValue<I, B>> {
        match self {
            ICalendarValue::Text(v) => Value::Str(v.into()),
            ICalendarValue::Integer(v) => Value::Number(v.into()),
            ICalendarValue::Float(v) => Value::Number(v.into()),
            ICalendarValue::Boolean(v) => Value::Bool(v),
            ICalendarValue::PartialDateTime(v) => {
                let mut out = String::new();
                let _ = v.format_as_ical(
                    &mut out,
                    value_type.and_then(|v| v.iana()).unwrap_or(
                        match (v.has_date(), v.has_time(), v.has_zone()) {
                            (true, true, _) => &ICalendarValueType::DateTime,
                            (true, _, _) => &ICalendarValueType::Date,
                            (_, true, _) => &ICalendarValueType::Time,
                            (_, _, true) => &ICalendarValueType::UtcOffset,
                            _ => &ICalendarValueType::Text,
                        },
                    ),
                );
                Value::Str(out.into())
            }
            ICalendarValue::Binary(v) => Value::Str(
                Uri::Data(Data {
                    content_type: None,
                    data: v,
                })
                .into_unwrapped_string()
                .into(),
            ),
            ICalendarValue::Uri(v) => Value::Str(v.into_unwrapped_string().into()),
            ICalendarValue::Duration(v) => Value::Str(v.to_string().into()),
            ICalendarValue::RecurrenceRule(v) => Value::Str(v.to_string().into()),
            ICalendarValue::Period(v) => Value::Str(v.to_string().into()),
            ICalendarValue::CalendarScale(v) => Value::Str(v.as_str().into()),
            ICalendarValue::Method(v) => Value::Str(v.as_str().into()),
            ICalendarValue::Classification(v) => Value::Str(v.as_str().into()),
            ICalendarValue::Status(v) => Value::Str(v.as_str().into()),
            ICalendarValue::Transparency(v) => Value::Str(v.as_str().into()),
            ICalendarValue::Action(v) => Value::Str(v.as_str().into()),
            ICalendarValue::BusyType(v) => Value::Str(v.as_str().into()),
            ICalendarValue::ParticipantType(v) => Value::Str(v.as_str().into()),
            ICalendarValue::ResourceType(v) => Value::Str(v.as_str().into()),
            ICalendarValue::Proximity(v) => Value::Str(v.as_str().into()),
        }
    }
}