1
0
Fork 0
icann-rdap/icann-rdap-common/src/contact/to_vcard.rs
Daniel Baumann b06d3acde8
Adding upstream version 0.0.22.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-05-08 18:41:54 +02:00

303 lines
11 KiB
Rust

//! Convert a Contact to jCard/vCard.
use std::str::FromStr;
use serde_json::{json, Map, Value};
use super::Contact;
impl Contact {
/// Output the Contact data as vCard in JSON values ([`Vec<Value>`]).
///
/// ```rust
/// use icann_rdap_common::contact::Contact;
/// use serde::Serialize;
/// use serde_json::Value;
///
/// let contact = Contact::builder()
/// .kind("individual")
/// .full_name("Bob Smurd")
/// .build();
///
/// let v = contact.to_vcard();
/// let json = serde_json::to_string(&v);
/// ```
pub fn to_vcard(&self) -> Vec<Value> {
// start the vcard with the version.
let mut vcard: Vec<Value> = vec![json!(["version", {}, "text", "4.0"])];
if let Some(full_name) = &self.full_name {
vcard.push(json!(["fn", {}, "text", full_name]));
}
if let Some(name_parts) = &self.name_parts {
let surnames = vec_string_to_value(&name_parts.surnames);
let given_names = vec_string_to_value(&name_parts.given_names);
let middle_names = vec_string_to_value(&name_parts.middle_names);
let prefixes = vec_string_to_value(&name_parts.prefixes);
let suffixes = vec_string_to_value(&name_parts.suffixes);
vcard.push(json!([
"n",
{},
"text",
[surnames, given_names, middle_names, prefixes, suffixes]
]));
}
if let Some(kind) = &self.kind {
vcard.push(json!(["kind", {}, "text", kind]));
}
if let Some(langs) = &self.langs {
for lang in langs {
let mut params: Map<String, Value> = Map::new();
if let Some(pref) = lang.preference {
params.insert("pref".to_string(), Value::String(pref.to_string()));
}
vcard.push(json!([
"lang",
Value::from(params),
"language-tag",
lang.tag
]))
}
}
if let Some(org_names) = &self.organization_names {
for org_name in org_names {
vcard.push(json!(["org", {}, "text", org_name]));
}
}
if let Some(titles) = &self.titles {
for title in titles {
vcard.push(json!(["title", {}, "text", title]));
}
}
if let Some(roles) = &self.roles {
for role in roles {
vcard.push(json!(["role", {}, "text", role]));
}
}
if let Some(nick_names) = &self.nick_names {
for nick_name in nick_names {
vcard.push(json!(["nickname", {}, "text", nick_name]));
}
}
if let Some(emails) = &self.emails {
for email in emails {
let mut params: Map<String, Value> = Map::new();
if let Some(pref) = email.preference {
params.insert("pref".to_string(), Value::String(pref.to_string()));
}
if let Some(contexts) = email.contexts.as_ref() {
params.insert("type".to_string(), vec_string_to_param(contexts));
}
vcard.push(json!(["email", Value::from(params), "text", email.email]))
}
}
if let Some(phones) = &self.phones {
for phone in phones {
let mut params: Map<String, Value> = Map::new();
if let Some(pref) = phone.preference {
params.insert("pref".to_string(), Value::String(pref.to_string()));
}
let mut types: Vec<String> = vec![];
if let Some(contexts) = &phone.contexts {
types.append(&mut contexts.clone());
}
if let Some(features) = &phone.features {
types.append(&mut features.clone());
}
params.insert("type".to_string(), vec_string_to_param(&types));
vcard.push(json!(["tel", Value::from(params), "text", phone.phone]))
}
}
if let Some(addrs) = &self.postal_addresses {
for addr in addrs {
let mut params: Map<String, Value> = Map::new();
if let Some(pref) = addr.preference {
params.insert("pref".to_string(), Value::String(pref.to_string()));
}
if let Some(contexts) = addr.contexts.as_ref() {
params.insert("type".to_string(), vec_string_to_param(contexts));
}
if let Some(full_address) = &addr.full_address {
params.insert(
"label".to_string(),
Value::from_str(full_address).expect("serializing full address"),
);
}
let mut lines: Vec<String> = vec![];
if let Some(street_parts) = &addr.street_parts {
lines.push(street_parts.first().cloned().unwrap_or("".to_string()));
lines.push(street_parts.get(1).cloned().unwrap_or("".to_string()));
lines.push(street_parts.get(2).cloned().unwrap_or("".to_string()));
} else {
lines.push("".to_string());
lines.push("".to_string());
lines.push("".to_string());
}
if let Some(locality) = &addr.locality {
lines.push(locality.to_owned());
} else {
lines.push("".to_string());
}
if let Some(region_name) = &addr.region_name {
lines.push(region_name.to_owned());
} else if let Some(region_code) = &addr.region_code {
lines.push(region_code.to_owned());
} else {
lines.push("".to_string());
}
if let Some(postal_code) = &addr.postal_code {
lines.push(postal_code.to_owned());
} else {
lines.push("".to_string());
}
if let Some(country_name) = &addr.country_name {
lines.push(country_name.to_owned());
} else if let Some(country_code) = &addr.country_code {
lines.push(country_code.to_owned());
} else {
lines.push("".to_string());
}
vcard.push(json!(["adr", Value::from(params), "text", lines]))
}
}
if let Some(contact_uris) = &self.contact_uris {
for uri in contact_uris {
vcard.push(json!(["contact-uri", {}, "uri", uri]));
}
}
if let Some(urls) = &self.urls {
for url in urls {
vcard.push(json!(["url", {}, "uri", url]));
}
}
// return the vcard array
vec![Value::String("vcard".to_string()), Value::from(vcard)]
}
}
fn vec_string_to_value(strings: &Option<Vec<String>>) -> Value {
let Some(strings) = strings else {
return Value::String("".to_string());
};
if strings.is_empty() {
return Value::String("".to_string());
};
if strings.len() == 1 {
let Some(one) = strings.first() else {
panic!("couldn't get first element on length of 1")
};
return Value::String(one.to_owned());
};
// else
Value::from(strings.clone())
}
fn vec_string_to_param(strings: &[String]) -> Value {
if strings.is_empty() {
return Value::String("".to_string());
};
if strings.len() == 1 {
let Some(one) = strings.first() else {
panic!("couldn't get first element on length of 1")
};
return Value::String(one.to_owned());
};
// else
Value::from(strings)
}
#[cfg(test)]
#[allow(non_snake_case)]
mod tests {
use crate::contact::{Contact, Email, Lang, NameParts, Phone, PostalAddress};
#[test]
fn GIVEN_contact_WHEN_to_vcard_THEN_from_vcard_is_same() {
// GIVEN
let contact = Contact::builder()
.full_name("Joe User")
.name_parts(
NameParts::builder()
.surnames(vec!["User".to_string()])
.given_names(vec!["Joe".to_string()])
.suffixes(vec!["ing. jr".to_string(), "M.Sc.".to_string()])
.build(),
)
.kind("individual")
.langs(vec![
Lang::builder().preference(1).tag("fr").build(),
Lang::builder().preference(2).tag("en").build(),
])
.organization_names(vec!["Example".to_string()])
.titles(vec!["Research Scientist".to_string()])
.roles(vec!["Project Lead".to_string()])
.contact_uris(vec!["https://example.com/contact-form".to_string()])
.postal_addresses(vec![PostalAddress::builder()
.country_name("Canada")
.postal_code("G1V 2M2")
.region_code("QC")
.locality("Quebec")
.street_parts(vec![
"Suite 1234".to_string(),
"4321 Rue Somewhere".to_string(),
])
.build()])
.phones(vec![
Phone::builder()
.preference(1)
.contexts(vec!["work".to_string()])
.features(vec!["voice".to_string()])
.phone("tel:+1-555-555-1234;ext=102")
.build(),
Phone::builder()
.contexts(vec!["work".to_string(), "cell".to_string()])
.features(vec![
"voice".to_string(),
"video".to_string(),
"text".to_string(),
])
.phone("tel:+1-555-555-4321")
.build(),
])
.emails(vec![Email::builder()
.contexts(vec!["work".to_string()])
.email("joe.user@example.com")
.build()])
.urls(vec!["https://example.com/some-url".to_string()])
.build();
// WHEN
let actual = Contact::from_vcard(&contact.to_vcard()).expect("from vcard");
// THEN
assert_eq!(contact.full_name, actual.full_name);
assert_eq!(contact.name_parts, actual.name_parts);
assert_eq!(contact.kind, actual.kind);
assert_eq!(contact.langs, actual.langs);
assert_eq!(contact.organization_names, actual.organization_names);
assert_eq!(contact.titles, actual.titles);
assert_eq!(contact.roles, actual.roles);
assert_eq!(contact.postal_addresses, actual.postal_addresses);
assert_eq!(contact.phones, actual.phones);
assert_eq!(contact.emails, actual.emails);
assert_eq!(contact.contact_uris, actual.contact_uris);
assert_eq!(contact.urls, actual.urls);
}
}