mod from_vcard;
pub(crate) mod jscontact;
mod to_vcard;
use std::{collections::BTreeMap, fmt::Display};
use buildstructor::Builder;
use crate::prelude::to_opt_vec;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Contact {
pub(crate) langs: Option<Vec<Lang>>,
pub(crate) kind: Option<String>,
pub(crate) emails: Option<Vec<Email>>,
pub(crate) phones: Option<Vec<Phone>>,
pub(crate) contact_uris: Option<Vec<String>>,
pub(crate) urls: Option<Vec<String>>,
pub(crate) unlocalized: Localizable,
pub(crate) localizations: BTreeMap<String, Localizable>,
}
#[buildstructor::buildstructor]
impl Contact {
#[builder(visibility = "pub")]
fn new(
langs: Vec<Lang>,
kind: Option<String>,
full_name: Option<String>,
name_parts: Option<NameParts>,
nick_names: Vec<String>,
titles: Vec<String>,
roles: Vec<String>,
organization_names: Vec<String>,
postal_addresses: Vec<PostalAddress>,
emails: Vec<Email>,
phones: Vec<Phone>,
contact_uris: Vec<String>,
urls: Vec<String>,
) -> Self {
Self {
langs: to_opt_vec(langs),
kind,
unlocalized: Localizable {
full_name,
name_parts,
nick_names: to_opt_vec(nick_names),
titles: to_opt_vec(titles),
roles: to_opt_vec(roles),
organization_names: to_opt_vec(organization_names),
postal_addresses: to_opt_vec(postal_addresses),
},
emails: to_opt_vec(emails),
phones: to_opt_vec(phones),
contact_uris: to_opt_vec(contact_uris),
urls: to_opt_vec(urls),
localizations: BTreeMap::new(),
}
}
pub fn is_non_empty(&self) -> bool {
self.langs.is_some()
|| self.kind.is_some()
|| self.unlocalized.full_name.is_some()
|| self.unlocalized.name_parts.is_some()
|| self.unlocalized.nick_names.is_some()
|| self.unlocalized.titles.is_some()
|| self.unlocalized.roles.is_some()
|| self.unlocalized.organization_names.is_some()
|| self.unlocalized.postal_addresses.is_some()
|| self.emails.is_some()
|| self.phones.is_some()
|| self.contact_uris.is_some()
|| self.urls.is_some()
}
pub fn with_localization(mut self, lang: String, localization: Localizable) -> Self {
self.localizations.insert(lang, localization);
self
}
pub fn with_email_addresses(mut self, emails: &[impl ToString]) -> Self {
let emails: Vec<Email> = emails
.iter()
.map(|e| Email::builder().email(e.to_string()).build())
.collect();
self.emails = (!emails.is_empty()).then_some(emails);
self
}
pub fn with_emails(mut self, emails: Vec<Email>) -> Self {
self.emails = Some(emails);
self
}
pub fn with_voice_phone_numbers(mut self, phones: &[impl ToString]) -> Self {
let mut phones: Vec<Phone> = phones
.iter()
.map(|p| {
Phone::builder()
.contexts(vec!["voice".to_string()])
.phone(p.to_string())
.build()
})
.collect();
if let Some(mut self_phones) = self.phones.clone() {
phones.append(&mut self_phones);
} else {
self.phones = (!phones.is_empty()).then_some(phones);
}
self
}
pub fn with_fax_phone_numbers(mut self, phones: &[impl ToString]) -> Self {
let mut phones: Vec<Phone> = phones
.iter()
.map(|p| {
Phone::builder()
.contexts(vec!["fax".to_string()])
.phone(p.to_string())
.build()
})
.collect();
if let Some(mut self_phones) = self.phones.clone() {
phones.append(&mut self_phones);
} else {
self.phones = (!phones.is_empty()).then_some(phones);
}
self
}
pub fn with_phones(mut self, phones: Vec<Phone>) -> Self {
self.phones = Some(phones);
self
}
pub fn with_postal_address(mut self, postal_address: PostalAddress) -> Self {
self.unlocalized.postal_addresses = Some(vec![postal_address]);
self
}
pub fn with_postal_addresses(mut self, postal_addresses: Vec<PostalAddress>) -> Self {
self.unlocalized.postal_addresses = Some(postal_addresses);
self
}
pub fn with_full_name(mut self, full_name: String) -> Self {
self.unlocalized.full_name = Some(full_name);
self
}
pub fn with_name_parts(mut self, name_parts: Option<NameParts>) -> Self {
self.unlocalized.name_parts = name_parts;
self
}
pub fn with_nick_names(mut self, nick_names: Vec<String>) -> Self {
self.unlocalized.nick_names = (!nick_names.is_empty()).then_some(nick_names);
self
}
pub fn with_titles(mut self, titles: Vec<String>) -> Self {
self.unlocalized.titles = (!titles.is_empty()).then_some(titles);
self
}
pub fn with_roles(mut self, roles: Vec<String>) -> Self {
self.unlocalized.roles = (!roles.is_empty()).then_some(roles);
self
}
pub fn with_organization_names(mut self, organization_names: Vec<String>) -> Self {
self.unlocalized.organization_names = Some(organization_names);
self
}
pub fn with_kind(mut self, kind: String) -> Self {
self.kind = Some(kind);
self
}
pub fn with_langs(mut self, langs: Vec<Lang>) -> Self {
self.langs = (!langs.is_empty()).then_some(langs);
self
}
pub fn langs(&self) -> &[Lang] {
self.langs.as_deref().unwrap_or_default()
}
pub fn lang(&self) -> Option<&Lang> {
self.langs().first()
}
pub fn kind(&self) -> Option<&str> {
self.kind.as_deref()
}
pub fn full_name(&self) -> Option<&str> {
self.unlocalized.full_name.as_deref()
}
pub fn name_parts(&self) -> Option<&NameParts> {
self.unlocalized.name_parts.as_ref()
}
pub fn nick_names(&self) -> &[String] {
self.unlocalized.nick_names.as_deref().unwrap_or_default()
}
pub fn titles(&self) -> &[String] {
self.unlocalized.titles.as_deref().unwrap_or_default()
}
pub fn roles(&self) -> &[String] {
self.unlocalized.roles.as_deref().unwrap_or_default()
}
pub fn organization_names(&self) -> &[String] {
self.unlocalized
.organization_names
.as_deref()
.unwrap_or_default()
}
pub fn organization_name(&self) -> Option<&str> {
self.organization_names().first().map(|x| x.as_str())
}
pub fn postal_addresses(&self) -> &[PostalAddress] {
self.unlocalized
.postal_addresses
.as_deref()
.unwrap_or_default()
}
pub fn postal_address(&self) -> Option<&PostalAddress> {
self.postal_addresses().first()
}
pub fn emails(&self) -> &[Email] {
self.emails.as_deref().unwrap_or_default()
}
pub fn email(&self) -> Option<&Email> {
self.emails().first()
}
pub fn phones(&self) -> &[Phone] {
self.phones.as_deref().unwrap_or_default()
}
pub fn phone(&self) -> Option<&Phone> {
self.phones().first()
}
pub fn voice_phone(&self) -> Option<&Phone> {
self.phones()
.iter()
.find(|phone| phone.features().contains(&"voice".to_string()))
}
pub fn fax_phone(&self) -> Option<&Phone> {
self.phones()
.iter()
.find(|phone| phone.features().contains(&"fax".to_string()))
}
pub fn prefer_voice_phone(&self) -> Option<&Phone> {
self.voice_phone().or_else(|| self.phone())
}
pub fn contact_uris(&self) -> &[String] {
self.contact_uris.as_deref().unwrap_or_default()
}
pub fn contact_uri(&self) -> Option<&str> {
self.contact_uris().first().map(|x| x.as_str())
}
pub fn urls(&self) -> &[String] {
self.urls.as_deref().unwrap_or_default()
}
pub fn url(&self) -> Option<&str> {
self.urls().first().map(|x| x.as_str())
}
pub fn localization(&self, lang: &str) -> Option<&Localizable> {
self.localizations.get(lang)
}
pub fn localizations_iter(&self) -> impl Iterator<Item = (&String, &Localizable)> {
self.localizations.iter()
}
pub fn localizations_iter_mut(&mut self) -> impl Iterator<Item = (&String, &mut Localizable)> {
self.localizations.iter_mut()
}
pub fn localizations_is_empty(&self) -> bool {
self.localizations.is_empty()
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Localizable {
pub(crate) full_name: Option<String>,
pub(crate) name_parts: Option<NameParts>,
pub(crate) nick_names: Option<Vec<String>>,
pub(crate) titles: Option<Vec<String>>,
pub(crate) roles: Option<Vec<String>>,
pub(crate) organization_names: Option<Vec<String>>,
pub(crate) postal_addresses: Option<Vec<PostalAddress>>,
}
#[buildstructor::buildstructor]
impl Localizable {
#[builder(visibility = "pub")]
fn new(
full_name: Option<String>,
name_parts: Option<NameParts>,
nick_names: Vec<String>,
titles: Vec<String>,
roles: Vec<String>,
organization_names: Vec<String>,
postal_addresses: Vec<PostalAddress>,
) -> Self {
Self {
full_name,
name_parts,
nick_names: to_opt_vec(nick_names),
titles: to_opt_vec(titles),
roles: to_opt_vec(roles),
organization_names: to_opt_vec(organization_names),
postal_addresses: to_opt_vec(postal_addresses),
}
}
pub fn full_name(&self) -> Option<&str> {
self.full_name.as_deref()
}
pub fn name_parts(&self) -> Option<&NameParts> {
self.name_parts.as_ref()
}
pub fn nick_names(&self) -> &[String] {
self.nick_names.as_deref().unwrap_or_default()
}
pub fn titles(&self) -> &[String] {
self.titles.as_deref().unwrap_or_default()
}
pub fn roles(&self) -> &[String] {
self.roles.as_deref().unwrap_or_default()
}
pub fn organization_names(&self) -> &[String] {
self.organization_names.as_deref().unwrap_or_default()
}
pub fn organization_name(&self) -> Option<&str> {
self.organization_names().first().map(|x| x.as_str())
}
pub fn postal_addresses(&self) -> &[PostalAddress] {
self.postal_addresses.as_deref().unwrap_or_default()
}
pub fn postal_address(&self) -> Option<&PostalAddress> {
self.postal_addresses().first()
}
pub fn with_postal_address(mut self, postal_address: PostalAddress) -> Self {
self.postal_addresses = Some(vec![postal_address]);
self
}
pub fn with_postal_addresses(mut self, postal_addresses: Vec<PostalAddress>) -> Self {
self.postal_addresses = Some(postal_addresses);
self
}
pub fn with_full_name(mut self, full_name: String) -> Self {
self.full_name = Some(full_name);
self
}
pub fn with_organization_names(mut self, organization_names: Vec<String>) -> Self {
self.organization_names = Some(organization_names);
self
}
pub fn with_name_parts(mut self, name_parts: Option<NameParts>) -> Self {
self.name_parts = name_parts;
self
}
pub fn with_nick_names(mut self, nick_names: Vec<String>) -> Self {
self.nick_names = (!nick_names.is_empty()).then_some(nick_names);
self
}
pub fn with_titles(mut self, titles: Vec<String>) -> Self {
self.titles = (!titles.is_empty()).then_some(titles);
self
}
pub fn with_roles(mut self, roles: Vec<String>) -> Self {
self.roles = (!roles.is_empty()).then_some(roles);
self
}
}
#[derive(Debug, Builder, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Lang {
pub preference: Option<u64>,
pub tag: String,
}
impl Lang {
pub fn preference(&self) -> Option<u64> {
self.preference
}
pub fn tag(&self) -> &str {
self.tag.as_str()
}
}
impl Display for Lang {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(pref) = self.preference {
write!(f, "{} (pref: {})", self.tag, pref)
} else {
f.write_str(&self.tag)
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct NameParts {
pub prefixes: Option<Vec<String>>,
pub surnames: Option<Vec<String>>,
pub middle_names: Option<Vec<String>>,
pub given_names: Option<Vec<String>>,
pub suffixes: Option<Vec<String>>,
pub generations: Option<Vec<String>>,
}
#[buildstructor::buildstructor]
impl NameParts {
#[builder(visibility = "pub")]
fn new(
prefixes: Vec<String>,
surnames: Vec<String>,
middle_names: Vec<String>,
given_names: Vec<String>,
suffixes: Vec<String>,
generations: Vec<String>,
) -> Self {
Self {
prefixes: to_opt_vec(prefixes),
surnames: to_opt_vec(surnames),
middle_names: to_opt_vec(middle_names),
given_names: to_opt_vec(given_names),
suffixes: to_opt_vec(suffixes),
generations: to_opt_vec(generations),
}
}
pub fn prefixes(&self) -> &[String] {
self.prefixes.as_deref().unwrap_or_default()
}
pub fn prefix(&self) -> Option<&str> {
self.prefixes().first().map(|x| x.as_str())
}
pub fn surnames(&self) -> &[String] {
self.surnames.as_deref().unwrap_or_default()
}
pub fn surname(&self) -> Option<&str> {
self.surnames().first().map(|x| x.as_str())
}
pub fn middle_names(&self) -> &[String] {
self.middle_names.as_deref().unwrap_or_default()
}
pub fn middle_name(&self) -> Option<&str> {
self.middle_names().first().map(|x| x.as_str())
}
pub fn given_names(&self) -> &[String] {
self.given_names.as_deref().unwrap_or_default()
}
pub fn given_name(&self) -> Option<&str> {
self.given_names().first().map(|x| x.as_str())
}
pub fn suffixes(&self) -> &[String] {
self.suffixes.as_deref().unwrap_or_default()
}
pub fn suffix(&self) -> Option<&str> {
self.suffixes().first().map(|x| x.as_str())
}
pub fn generations(&self) -> &[String] {
self.generations.as_deref().unwrap_or_default()
}
pub fn generation(&self) -> Option<&str> {
self.generations().first().map(|x| x.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct PostalAddress {
pub preference: Option<u64>,
pub contexts: Option<Vec<String>>,
pub full_address: Option<String>,
pub street_parts: Option<Vec<String>>,
pub locality: Option<String>,
pub region_name: Option<String>,
pub region_code: Option<String>,
pub country_name: Option<String>,
pub country_code: Option<String>,
pub postal_code: Option<String>,
}
#[buildstructor::buildstructor]
impl PostalAddress {
#[builder(visibility = "pub")]
fn new(
preference: Option<u64>,
contexts: Vec<String>,
full_address: Option<String>,
street_parts: Vec<String>,
locality: Option<String>,
region_name: Option<String>,
region_code: Option<String>,
country_name: Option<String>,
country_code: Option<String>,
postal_code: Option<String>,
) -> Self {
Self {
preference,
contexts: to_opt_vec(contexts),
full_address,
street_parts: to_opt_vec(street_parts),
locality,
region_name,
region_code,
country_name,
country_code,
postal_code,
}
}
pub fn preference(&self) -> Option<u64> {
self.preference
}
pub fn contexts(&self) -> &[String] {
self.contexts.as_deref().unwrap_or_default()
}
pub fn full_address(&self) -> Option<&str> {
self.full_address.as_deref()
}
pub fn street_parts(&self) -> &[String] {
self.street_parts.as_deref().unwrap_or_default()
}
pub fn locality(&self) -> Option<&str> {
self.locality.as_deref()
}
pub fn region_name(&self) -> Option<&str> {
self.region_name.as_deref()
}
pub fn region_code(&self) -> Option<&str> {
self.region_code.as_deref()
}
pub fn country_name(&self) -> Option<&str> {
self.country_name.as_deref()
}
pub fn country_code(&self) -> Option<&str> {
self.country_code.as_deref()
}
pub fn postal_code(&self) -> Option<&str> {
self.postal_code.as_deref()
}
pub fn with_postal_code(mut self, postal_code: String) -> Self {
self.postal_code = Some(postal_code);
self
}
pub fn with_locality(mut self, locality: String) -> Self {
self.locality = Some(locality);
self
}
pub fn with_street_parts(mut self, street_parts: Vec<String>) -> Self {
self.street_parts = Some(street_parts);
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Email {
pub preference: Option<u64>,
pub contexts: Option<Vec<String>>,
pub email: String,
}
#[buildstructor::buildstructor]
impl Email {
#[builder(visibility = "pub")]
fn new(preference: Option<u64>, contexts: Vec<String>, email: String) -> Self {
Self {
preference,
contexts: to_opt_vec(contexts),
email,
}
}
pub fn preference(&self) -> Option<u64> {
self.preference
}
pub fn contexts(&self) -> &[String] {
self.contexts.as_deref().unwrap_or_default()
}
pub fn email(&self) -> &str {
self.email.as_str()
}
}
impl Display for Email {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut qualifiers = vec![];
if let Some(pref) = self.preference {
qualifiers.push(format!("(pref: {pref})"));
}
if let Some(contexts) = &self.contexts {
qualifiers.push(format!("({})", contexts.join(",")));
}
let qualifiers = qualifiers.join(" ");
if qualifiers.is_empty() {
f.write_str(&self.email)
} else {
write!(f, "{} {}", &self.email, qualifiers)
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Phone {
pub preference: Option<u64>,
pub contexts: Option<Vec<String>>,
pub phone: String,
pub features: Option<Vec<String>>,
}
#[buildstructor::buildstructor]
impl Phone {
#[builder(visibility = "pub")]
fn new(
preference: Option<u64>,
contexts: Vec<String>,
phone: String,
features: Vec<String>,
) -> Self {
Self {
preference,
contexts: to_opt_vec(contexts),
phone,
features: to_opt_vec(features),
}
}
pub fn preference(&self) -> Option<u64> {
self.preference
}
pub fn contexts(&self) -> &[String] {
self.contexts.as_deref().unwrap_or_default()
}
pub fn phone(&self) -> &str {
self.phone.as_str()
}
pub fn features(&self) -> &[String] {
self.features.as_deref().unwrap_or_default()
}
}
impl Display for Phone {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut qualifiers = vec![];
if let Some(pref) = self.preference {
qualifiers.push(format!("(pref: {pref})"));
}
if let Some(contexts) = &self.contexts {
qualifiers.push(format!("({})", contexts.join(",")));
}
if let Some(features) = &self.features {
qualifiers.push(format!("({})", features.join(",")));
}
let qualifiers = qualifiers.join(" ");
if qualifiers.is_empty() {
f.write_str(&self.phone)
} else {
write!(f, "{} {}", &self.phone, qualifiers)
}
}
}