1use crate::{
4 Date, DateTime, Uri, Vcard,
5 property::{DeliveryAddress, Gender, Kind, TextListProperty},
6};
7
8#[cfg(feature = "language-tags")]
9use language_tags::LanguageTag;
10
11pub struct VcardBuilder {
24 card: Vcard,
25}
26
27impl VcardBuilder {
28 pub fn new(formatted_name: String) -> Self {
30 Self {
31 card: Vcard::new(formatted_name),
32 }
33 }
34
35 pub fn kind(mut self, value: Kind) -> Self {
39 self.card.kind = Some(value.into());
40 self
41 }
42
43 pub fn source(mut self, value: Uri) -> Self {
45 self.card.source.push(value.into());
46 self
47 }
48
49 pub fn xml(mut self, value: String) -> Self {
51 self.card.xml.push(value.into());
52 self
53 }
54
55 pub fn formatted_name(mut self, value: String) -> Self {
59 self.card.formatted_name.push(value.into());
60 self
61 }
62
63 pub fn name(mut self, value: [String; 5]) -> Self {
68 self.card.name =
69 Some(TextListProperty::new_semi_colon(value.to_vec()));
70 self
71 }
72
73 pub fn nickname(mut self, value: String) -> Self {
75 self.card.nickname.push(value.into());
76 self
77 }
78
79 pub fn photo(mut self, value: Uri) -> Self {
81 self.card.photo.push(value.into());
82 self
83 }
84
85 pub fn birthday(mut self, value: Date) -> Self {
90 self.card.bday = Some(value.into());
91 self
92 }
93
94 pub fn anniversary(mut self, value: Date) -> Self {
96 self.card.anniversary = Some(value.into());
97 self
98 }
99
100 pub fn gender(mut self, value: &str) -> Self {
105 if let Ok(gender) = value.parse::<Gender>() {
106 self.card.gender = Some(gender.into());
107 }
108 self
109 }
110
111 pub fn address(mut self, value: DeliveryAddress) -> Self {
113 self.card.address.push(value.into());
114 self
115 }
116
117 pub fn telephone(mut self, value: String) -> Self {
121 self.card.tel.push(value.into());
122 self
123 }
124
125 pub fn email(mut self, value: String) -> Self {
127 self.card.email.push(value.into());
128 self
129 }
130
131 pub fn impp(mut self, value: Uri) -> Self {
133 self.card.impp.push(value.into());
134 self
135 }
136
137 #[cfg(feature = "language-tags")]
138 pub fn lang(mut self, value: LanguageTag) -> Self {
140 self.card.lang.push(value.into());
141 self
142 }
143
144 #[cfg(not(feature = "language-tags"))]
145 pub fn lang(mut self, value: String) -> Self {
147 self.card.lang.push(value.into());
148 self
149 }
150
151 pub fn timezone(mut self, value: String) -> Self {
155 self.card.timezone.push(value.into());
156 self
157 }
158
159 pub fn geo(mut self, value: Uri) -> Self {
161 self.card.geo.push(value.into());
162 self
163 }
164
165 pub fn title(mut self, value: String) -> Self {
169 self.card.title.push(value.into());
170 self
171 }
172
173 pub fn role(mut self, value: String) -> Self {
175 self.card.role.push(value.into());
176 self
177 }
178
179 pub fn logo(mut self, value: Uri) -> Self {
181 self.card.logo.push(value.into());
182 self
183 }
184
185 pub fn org(mut self, value: Vec<String>) -> Self {
187 self.card.org.push(TextListProperty::new_semi_colon(value));
188 self
189 }
190
191 pub fn member(mut self, value: Uri) -> Self {
195 self.card.member.push(value.into());
196 self
197 }
198
199 pub fn related(mut self, value: Uri) -> Self {
201 self.card.related.push(value.into());
202 self
203 }
204
205 pub fn categories(mut self, value: Vec<String>) -> Self {
209 self.card
210 .categories
211 .push(TextListProperty::new_comma(value));
212 self
213 }
214
215 pub fn note(mut self, value: String) -> Self {
217 self.card.note.push(value.into());
218 self
219 }
220
221 pub fn prod_id(mut self, value: String) -> Self {
223 self.card.prod_id = Some(value.into());
224 self
225 }
226
227 pub fn rev(mut self, value: DateTime) -> Self {
229 self.card.rev = Some(value.into());
230 self
231 }
232
233 pub fn sound(mut self, value: Uri) -> Self {
235 self.card.sound.push(value.into());
236 self
237 }
238
239 pub fn uid(mut self, value: Uri) -> Self {
241 self.card.uid = Some(value.into());
242 self
243 }
244
245 pub fn url(mut self, value: Uri) -> Self {
247 self.card.url.push(value.into());
248 self
249 }
250
251 pub fn key(mut self, value: Uri) -> Self {
255 self.card.key.push(value.into());
256 self
257 }
258
259 pub fn fburl(mut self, value: Uri) -> Self {
263 self.card.fburl.push(value.into());
264 self
265 }
266
267 pub fn cal_adr_uri(mut self, value: Uri) -> Self {
269 self.card.cal_adr_uri.push(value.into());
270 self
271 }
272
273 pub fn cal_uri(mut self, value: Uri) -> Self {
275 self.card.cal_uri.push(value.into());
276 self
277 }
278
279 pub fn finish(self) -> Vcard {
281 self.card
282 }
283}
284
285#[cfg(test)]
286mod tests {
287 use super::VcardBuilder;
288 use crate::property::{DeliveryAddress, Kind, LanguageProperty};
289 use time::{Date, Month, OffsetDateTime, Time};
290
291 #[test]
292 fn builder_vcard() {
293 let mut rev = OffsetDateTime::now_utc();
294 rev = rev.replace_date(
295 Date::from_calendar_date(2000, Month::January, 3).unwrap(),
296 );
297 rev = rev.replace_time(Time::MIDNIGHT);
298
299 let card = VcardBuilder::new("Jane Doe".to_owned())
300 .source(
302 "http://directory.example.com/addressbooks/jdoe.vcf"
303 .parse()
304 .unwrap(),
305 )
306 .name([
308 "Doe".to_owned(),
309 "Jane".to_owned(),
310 "Claire".to_owned(),
311 "Dr.".to_owned(),
312 "MS".to_owned(),
313 ])
314 .nickname("JC".to_owned())
315 .photo("file:///images/jdoe.jpeg".parse().unwrap())
316 .birthday(
317 Date::from_calendar_date(1986, Month::February, 7)
318 .unwrap()
319 .into(),
320 )
321 .anniversary(
322 Date::from_calendar_date(2002, Month::March, 18)
323 .unwrap()
324 .into(),
325 )
326 .gender("F")
327 .address(DeliveryAddress {
328 po_box: None,
329 extended_address: None,
330 street_address: Some("123 Main Street".to_owned()),
331 locality: Some("Mock City".to_owned()),
332 region: Some("Mock State".to_owned()),
333 country_name: Some("Mock Country".to_owned()),
334 postal_code: Some("123".to_owned()),
335 })
336 .telephone("+10987654321".to_owned())
338 .email("janedoe@example.com".to_owned())
339 .impp("im://example.com/messenger".parse().unwrap())
340 .timezone("Raleigh/North America".to_owned())
342 .geo("geo:37.386013,-122.082932".parse().unwrap())
343 .org(vec!["Mock Hospital".to_owned(), "Surgery".to_owned()])
345 .title("Dr".to_owned())
346 .role("Master Surgeon".to_owned())
347 .logo("https://example.com/mock.jpeg".parse().unwrap())
348 .related("https://example.com/johndoe".parse().unwrap())
349 .categories(vec!["Medical".to_owned(), "Health".to_owned()])
351 .note("Saved my life!".to_owned())
352 .prod_id("Contact App v1".to_owned())
353 .rev(rev.into())
354 .sound("https://example.com/janedoe.wav".parse().unwrap())
355 .uid(
356 "urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6"
357 .parse()
358 .unwrap(),
359 )
360 .url("https://example.com/janedoe".parse().unwrap())
361 .key("urn:eth:0x00".parse().unwrap())
363 .fburl("https://www.example.com/busy/janedoe".parse().unwrap())
365 .cal_adr_uri(
366 "https://www.example.com/calendar/janedoe".parse().unwrap(),
367 )
368 .cal_uri("https://calendar.example.com".parse().unwrap())
369 .finish();
370
371 let expected = "BEGIN:VCARD\r\nVERSION:4.0\r\nSOURCE:http://directory.example.com/addressbooks/jdoe.vcf\r\nFN:Jane Doe\r\nN:Doe;Jane;Claire;Dr.;MS\r\nNICKNAME:JC\r\nPHOTO:file:///images/jdoe.jpeg\r\nBDAY:19860207\r\nANNIVERSARY:20020318\r\nGENDER:F\r\nURL:https://example.com/janedoe\r\nADR:;;123 Main Street;Mock City;Mock State;123;Mock Country\r\nTITLE:Dr\r\nROLE:Master Surgeon\r\nLOGO:https://example.com/mock.jpeg\r\nORG:Mock Hospital;Surgery\r\nRELATED:https://example.com/johndoe\r\nTEL:+10987654321\r\nEMAIL:janedoe@example.com\r\nIMPP:im://example.com/messenger\r\nTZ:Raleigh/North America\r\nGEO:geo:37.386013,-122.082932\r\nCATEGORIES:Medical,Health\r\nNOTE:Saved my life!\r\nPRODID:Contact App v1\r\nREV:20000103T000000Z\r\nSOUND:https://example.com/janedoe.wav\r\nUID:urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6\r\nKEY:urn:eth:0x00\r\nFBURL:https://www.example.com/busy/janedoe\r\nCALADRURI:https://www.example.com/calendar/janedoe\r\nCALURI:https://calendar.example.com/\r\nEND:VCARD\r\n";
372
373 let vcard = format!("{}", card);
374 assert_eq!(expected, &vcard);
375 }
376
377 #[test]
378 fn builder_member_group() {
379 let card = VcardBuilder::new("Mock Company".to_owned())
380 .kind(Kind::Group)
381 .member("https://example.com/foo".parse().unwrap())
382 .member("https://example.com/bar".parse().unwrap())
383 .finish();
384 assert_eq!(2, card.member.len());
385 assert!(card.validate().is_ok());
386 }
387
388 #[test]
389 fn builder_member_invalid() {
390 let card = VcardBuilder::new("Mock Company".to_owned())
391 .member("https://example.com/bar".parse().unwrap())
392 .finish();
393 assert_eq!(1, card.member.len());
394 assert!(card.validate().is_err());
395 }
396
397 #[cfg(not(feature = "language-tags"))]
398 #[test]
399 fn builder_language() {
400 let card = VcardBuilder::new("Jane Doe".to_owned())
401 .lang("en".to_owned())
402 .lang("fr".to_owned())
403 .finish();
404 assert_eq!(
405 card.lang.get(0).unwrap(),
406 &LanguageProperty {
407 value: "en".to_owned(),
408 group: None,
409 parameters: None
410 }
411 );
412 assert_eq!(
413 card.lang.get(1).unwrap(),
414 &LanguageProperty {
415 value: "fr".to_owned(),
416 group: None,
417 parameters: None
418 }
419 );
420 }
421
422 #[cfg(feature = "language-tags")]
423 #[test]
424 fn builder_language_tags() {
425 use language_tags::LanguageTag;
426 let card = VcardBuilder::new("Jane Doe".to_owned())
427 .lang("en".parse::<LanguageTag>().unwrap())
428 .lang("fr".parse::<LanguageTag>().unwrap())
429 .finish();
430 assert_eq!(
431 card.lang.get(0).unwrap(),
432 &LanguageProperty {
433 value: "en".parse::<LanguageTag>().unwrap(),
434 group: None,
435 parameters: None
436 }
437 );
438 assert_eq!(
439 card.lang.get(1).unwrap(),
440 &LanguageProperty {
441 value: "fr".parse::<LanguageTag>().unwrap(),
442 group: None,
443 parameters: None
444 }
445 );
446 }
447}