contack 0.9.2

A simple and easy contact library.
Documentation
use crate::*;

/// A structure to store contact information.
///
/// All fields correspond (at least somewhat) to
/// the [VCard 4 specification](https://datatracker.ietf.org/doc/html/rfc6350)
///
#[derive(Debug, Clone, PartialEq, PartialOrd, Default)]
pub struct Contact {
    /*
     * Explanatory Properties
     */
    /// The Contact's Uid
    pub uid: String,

    /*
     * Identification Property
     */
    /// The Contacts Name
    pub name: Name,

    /// The Contact's Nickname
    pub nickname: Option<String>,

    /// The Contact's Anniversary
    pub anniversary: Option<DateTime>,

    /// The Contact's Birthday
    pub bday: Option<DateTime>,

    /// The Contact's Photo
    pub photo: Option<Uri>,

    /*
     * Organisational Properties
     */
    /// The Contact's Job Title
    pub title: Option<String>,

    /// The Contact's Job Role
    pub role: Option<String>,

    /// The Contact's Organization
    pub org: Option<Org>,

    /// The Contact's Organization's Logo
    pub logo: Option<Uri>,

    /*
     * Communication Properties
     */
    /// The Contact's contact information, emails,
    /// phones etc
    pub contact_information: Vec<ContactInformation>,

    /// The contact's sex and gender
    pub gender: Option<Gender>,

    /*
     * Deliver Addressing Properties
     */
    /// The Contact's Work Address
    pub work_address: Option<address::Address>,

    /// The Contact's Home Address
    pub home_address: Option<address::Address>,
}

impl Contact {
    /// Generates a uuid for the contact's uid.
    ///
    /// This should be called after cloning a contact,
    /// as not to have a clashing uid which breaks
    /// the specification.
    ///
    /// The `uuid` crate is used for this generation.
    ///
    /// # Example
    ///
    /// Clone a contact and give it a new uid.
    /// 
    /// ```rust
    /// use contack::{Name, Contact};
    /// # let mut contact_1 = Contact::new(Name::default());
    /// let mut contact = contact_1.clone();
    /// contact.gen_uid();
    /// assert_ne!(contact.uid, "");
    /// ```
    pub fn gen_uid(&mut self) {
        // Generates a UID from the UUID Value
        self.uid = uuid::Uuid::new_v4().to_string();
    }

    /// Creates a contact from a [`Name`]
    ///
    /// It is required to use a `name` from the VCard
    /// specification, alongside a `uid`, which is
    /// generated automatically from the `uuid` crate.
    ///
    /// # Example
    ///
    /// Create a new contact with the name "John Doe"
    /// 
    /// ```rust
    /// use contack::{Name, Contact};
    /// let contact = Contact::new(Name {
    ///    given: vec!["John".to_string()],
    ///    family: vec!["Doe".to_string()],
    ///    ..Default::default()
    /// });
    /// ```
    #[must_use]
    pub fn new(name: name::Name) -> Self {
        Self {
            name,
            uid: uuid::Uuid::new_v4().to_string(),
            ..Self::default()
        }
    }
}

#[cfg(feature = "read_write")]
#[cfg_attr(feature = "dox", doc(cfg(feature = "read_write")))]
impl From<Contact> for read_write::vcard::VCard {
    fn from(c: Contact) -> Self {
        // Vec to store propertiess
        let mut vec = vec![
            // Insert the UID
            Component {
                name: "UID".to_string(),
                values: vec![vec![c.uid]],
                ..Component::default()
            },
            // And the name (FN followed by N)
            Component {
                name: "FN".to_string(),
                values: vec![vec![c.name.to_string()]],
                ..Component::default()
            },
            c.name.into(),
        ];

        // Nickname next
        if let Some(nickname) = c.nickname {
            vec.push(Component {
                name: "NICKNAME".to_string(),
                values: vec![vec![nickname]],
                ..Component::default()
            });
        }

        // Anniversary
        if let Some(anniversary) = c.anniversary {
            let mut anniversary: Component = anniversary.into();
            anniversary.name = "ANNIVERSARY".to_string();
            vec.push(anniversary);
        }

        // Bithday
        if let Some(bday) = c.bday {
            let mut bday: Component = bday.into();
            bday.name = "BDAY".to_string();
            vec.push(bday);
        }

        // Photo
        if let Some(photo) = c.photo {
            let mut photo: Component = photo.into();
            photo.name = "PHOTO".to_string();
            vec.push(photo);
        }

        // Title
        if let Some(title) = c.title {
            vec.push(Component {
                name: "TITLE".to_string(),
                values: vec![vec![title]],
                ..Component::default()
            });
        }

        // Role
        if let Some(role) = c.role {
            vec.push(Component {
                name: "ROLE".to_string(),
                values: vec![vec![role]],
                ..Component::default()
            });
        }

        // Org
        if let Some(org) = c.org {
            vec.push(org.into());
        }

        // Logo
        if let Some(logo) = c.logo {
            let mut logo: Component = logo.into();
            logo.name = "LOGO".to_string();
            vec.push(logo);
        }

        // Contact information
        for ci in c.contact_information {
            vec.push(ci.into());
        }

        // Gender
        if let Some(gender) = c.gender {
            vec.push(gender.into());
        }

        // Work Address
        if let Some(adr) = c.work_address {
            let mut adr: Component = adr.into();
            adr.parameters
                .insert("TYPE".to_string(), "work".to_string());
            vec.push(adr);
        }

        // Home Address
        if let Some(adr) = c.home_address {
            let mut adr: Component = adr.into();
            adr.parameters
                .insert("TYPE".to_string(), "home".to_string());
            vec.push(adr);
        }

        Self::new(vec)
    }
}

#[cfg(feature = "read_write")]
#[cfg_attr(feature = "dox", doc(cfg(feature = "read_write")))]
impl TryFrom<read_write::vcard::VCard> for Contact {
    type Error = FromComponentError;

    fn try_from(vcard: read_write::vcard::VCard) -> Result<Self, Self::Error> {
        // Create a contact - with no name (for now)
        let mut contact = Self::new(Name::default());

        // Loop through all the components
        for mut comp in vcard.0 {
            match comp.name.as_str() {
                "UID" => {
                    if let Some(uid) =
                        comp.values.pop().and_then(|mut x| x.pop())
                    {
                        contact.uid = uid;
                    }
                }

                "FN" => {
                    if contact.name == Name::default() {
                        if let Some(name) =
                            comp.values.pop().and_then(|mut x| x.pop())
                        {
                            contact.name.given = vec![name];
                        }
                    }
                }

                "N" => contact.name = comp.into(),

                "NICKNAME" => {
                    contact.nickname = Some(
                        comp.values
                            .pop()
                            .and_then(|mut x| x.pop())
                            .ok_or(FromComponentError::NotEnoughValues)?,
                    );
                }

                "ANNIVERSARY" => contact.anniversary = Some(comp.try_into()?),

                "BDAY" => contact.bday = Some(comp.try_into()?),

                "PHOTO" => contact.photo = Some(comp.try_into()?),

                "TITLE" => {
                    contact.title = Some(
                        comp.values
                            .pop()
                            .and_then(|mut x| x.pop())
                            .ok_or(FromComponentError::NotEnoughValues)?,
                    );
                }

                "ROLE" => {
                    contact.role = Some(
                        comp.values
                            .pop()
                            .and_then(|mut x| x.pop())
                            .ok_or(FromComponentError::NotEnoughValues)?,
                    );
                }

                "ORG" => contact.org = Some(Org::from(comp)),

                "LOGO" => contact.logo = Some(comp.try_into()?),

                "EMAIL" | "TEL" | "X-DISCORD" | "X-MATRIX" | "X-SKYPE"
                | "X-AIM" | "X-JABBER" | "X-ICQ" | "X-GROUPWISE"
                | "X-GADUGADU" | "IMPP" => {
                    contact.contact_information.push(comp.try_into()?);
                }

                "GENDER" => contact.gender = Some(comp.try_into()?),

                // Addresses.
                //
                // Note that if ambigouous it is assumed that an address
                // is home.
                "ADR" => match comp
                    .parameters
                    .get("TYPE")
                    .map_or("home", String::as_str)
                {
                    "work" => contact.work_address = Some(comp.try_into()?),
                    _ => contact.home_address = Some(comp.try_into()?),
                },
                // Values which we don't know shall be scrapped.
                _ => (),
            }
        }

        Ok(contact)
    }
}