contack 0.9.2

A simple and easy contact library.
Documentation
//! # Sql Support.
//!
//! This is the main module for sql support. `SqlContact` can store
//! all features of a normal Contact, par `contact_information`, which
//! can be found in
//! [`SqlContactInformation`](crate::sql::contact_information::
//! SqlContactInformation.)
//!
//! Diesel migrations are stored in an `embeded_diesel_migrations` module.

#[cfg(feature = "diesel_support")]
use crate::schema::*;
use crate::*;
#[cfg(feature = "diesel_support")]
use diesel_migrations::embed_migrations;
#[cfg(feature = "sqlx")]
use sqlx::migrate::Migrator;
pub mod contact_information;
pub mod error;
pub use error::*;

/// This is a struct, mirroring `Contact`, but with all its parts 'exploded',
/// aside from `ContactInformation` which has to come separately.
#[cfg_attr(
    feature = "diesel",
    derive(Insertable, Queryable, Identifiable, AsChangeset),
    primary_key(uid),
    table_name = "contacts"
)]
#[derive(Default, Clone, PartialEq, Debug)]
pub struct SqlContact {
    /// The contacts unique identifier
    pub uid: String,
    /// The given name of the contact. Also called
    /// first name.
    ///
    /// Escaped during conversion through
    /// [`Name::escape_with_spaces`]
    pub name_given: Option<String>,
    /// The additional name of the contact. Also called
    /// middle names.
    ///
    /// Escaped during conversion through
    /// [`Name::escape_with_spaces`]
    pub name_additional: Option<String>,
    /// The family name of the contact. Also called
    /// last names.
    ///
    /// Escaped during conversion through
    /// [`Name::escape_with_spaces`]
    pub name_family: Option<String>,
    /// The prefix names of the contacts, for example
    /// "Mr", "Mx" or "Ms"
    ///
    /// Escaped during conversion through
    /// [`Name::escape_with_spaces`]
    pub name_prefixes: Option<String>,
    /// The suffix names of the contact, for example
    /// esquire.
    ///
    /// Escaped during conversion through
    /// [`Name::escape_with_spaces`]
    pub name_suffixes: Option<String>,
    /// The contact's nickname.
    pub nickname: Option<String>,
    /// The contact's anniversary.
    pub anniversary: Option<chrono::NaiveDateTime>,
    /// The contact's birthday
    pub bday: Option<chrono::NaiveDateTime>,
    /// The contact's gender
    pub gender_gender: Option<String>,
    /// The contact's sex
    pub gender_sex: Option<String>,
    /// The contact's photo, stored as a uri.
    ///
    /// Due to how rust enums work this is linked with
    /// `photo_bin` and `photo_mime` aswell.
    pub photo_uri: Option<String>,
    /// The contact's photo, stored as binary data.
    ///
    /// Due to how rust enums work this is linked with
    /// `photo_uri` and `photo_mime` aswell.
    pub photo_bin: Option<Vec<u8>>,
    /// The contact's photo, the mime associated
    /// with the binary data.
    ///
    /// Due to how rust enums work this is linked with
    /// `photo_bin` and `photo_uri` aswell.
    pub photo_mime: Option<String>,
    /// The contact's job title.
    pub title: Option<String>,
    /// The contact's job role
    pub role: Option<String>,
    /// The contacts organisation
    pub org_org: Option<String>,
    /// The contacts organisational unit
    pub org_unit: Option<String>,
    /// The contacts organisational office
    pub org_office: Option<String>,
    /// The contact's logo, stored as a uri.
    ///
    /// Due to how rust enums work this is linked with
    /// `logo_bin` and `logo_mime` aswell.
    pub logo_uri: Option<String>,
    /// The contact's logo, stored as binary data.
    ///
    /// Due to how rust enums work this is linked with
    /// `logo_uri` and `logo_mime` aswell.
    pub logo_bin: Option<Vec<u8>>,
    /// The contact's logo, the mime associated
    /// with the binary data.
    ///
    /// Due to how rust enums work this is linked with
    /// `logo_bin` and `logo_uri` aswell.
    pub logo_mime: Option<String>,
    /// The street of the contact's home's address.
    pub home_address_street: Option<String>,
    /// The locality of the contact's home's address.
    pub home_address_locality: Option<String>,
    /// The region of the contact's home's address.
    pub home_address_region: Option<String>,
    /// The code of the contact's home's address.
    pub home_address_code: Option<String>,
    /// The country of the contact's home's address.
    pub home_address_country: Option<String>,
    /// The geo's lontitude of the contact's home's address.
    pub home_address_geo_longitude: Option<f64>,
    /// The geo's latitude of the contact's home's address.
    pub home_address_geo_latitude: Option<f64>,
    /// The street of the contact's work's address.
    pub work_address_street: Option<String>,
    /// The locality of the contact's work's address.
    pub work_address_locality: Option<String>,
    /// The region of the contact's work's address.
    pub work_address_region: Option<String>,
    /// The code of the contact's work's address.
    pub work_address_code: Option<String>,
    /// The country of the contact's work's address.
    pub work_address_country: Option<String>,
    /// The geo's lontitude of the contact's work's address.
    pub work_address_geo_longitude: Option<f64>,
    /// The geo's latitude of the contact's work's address.
    pub work_address_geo_latitude: Option<f64>,
}

impl TryFrom<SqlContact> for Contact {
    type Error = SqlConversionError;

    fn try_from(them: SqlContact) -> Result<Self, Self::Error> {
        Ok(Self {
            uid: them.uid,

            // Name
            name: Name {
                given: them.name_given.map_or(Vec::new(), |x| Name::unescape_with_spaces(&x)),
                additional: them.name_additional.map_or(Vec::new(), |x| Name::unescape_with_spaces(&x)),
                family: them.name_family.map_or(Vec::new(), |x| Name::unescape_with_spaces(&x)),
                prefixes: them.name_prefixes.map_or(Vec::new(), |x| Name::unescape_with_spaces(&x)),
                suffixes: them.name_suffixes.map_or(Vec::new(), |x| Name::unescape_with_spaces(&x)),
            },

            // Nickname
            nickname: them.nickname,

            // Anniversary
            anniversary: match them.anniversary {
                None => None,
                Some(x) => Some(DateTime::try_from(x)?),
            },

            // Birthday
            bday: match them.bday {
                None => None,
                Some(x) => Some(DateTime::try_from(x)?),
            },
        
            // Photo
            photo: Uri::try_from_sql_raw((
                them.photo_uri,
                them.photo_bin,
                them.photo_mime,
                String::from("photo"),
            ))?,

            // Title
            title: them.title,

            // Role
            role: them.role,

            // Gender
            gender: if them.gender_gender.is_some() || them.gender_sex.is_some() {
                Some(Gender {
                    sex: match them.gender_sex.as_deref() {
                        None | Some("N") => None,
                        Some("M") => Some(Sex::Male),
                        Some("F") => Some(Sex::Female),
                        Some("O") => Some(Sex::Other),
                        Some("U") => Some(Sex::Unknown),
                        _ => return Err(Self::Error::InvalidGender),
                    },
                    identity: them.gender_gender
                })
            } else {
                None
            },

            // Org
            org: {
                if them.org_org.is_some()
                    && them.org_unit.is_some()
                    && them.org_office.is_some()
                {
                    Some(Org::new(
                        them.org_org.unwrap(),
                        them.org_unit.unwrap(),
                        them.org_office.unwrap(),
                    ))
                } else if them.org_org.is_none()
                    && them.org_unit.is_none()
                    && them.org_office.is_none()
                {
                    None
                } else {
                    return Err(Self::Error::IncompleteOrg);
                }
            },

            // Logo
            logo: Uri::try_from_sql_raw((
                them.logo_uri,
                them.logo_bin,
                them.logo_mime,
                String::from("logo"),
            ))?,

            home_address: address::AddressRaw::new(
                them.home_address_street,
                them.home_address_locality,
                them.home_address_region,
                them.home_address_code,
                them.home_address_country,
                them.home_address_geo_longitude,
                them.home_address_geo_latitude,
            )
            .try_from_sql_raw()?,
            work_address: address::AddressRaw::new(
                them.work_address_street,
                them.work_address_locality,
                them.work_address_region,
                them.work_address_code,
                them.work_address_country,
                them.work_address_geo_longitude,
                them.work_address_geo_latitude,
            )
            .try_from_sql_raw()?,
            contact_information: vec![],
        })
    }
}

impl TryFrom<Contact> for SqlContact {
    type Error = Box<dyn std::error::Error>;

    fn try_from(them: Contact) -> Result<Self, Self::Error> {
        // Get the photo properties.
        let (photo_uri, photo_bin, photo_mime) = Uri::to_sql_raw(them.photo);

        // Get the logo properties.
        let (logo_uri, logo_bin, logo_mime) = Uri::to_sql_raw(them.logo);

        // Get the org properties.
        let (org_org, org_unit, org_office) = match them.org {
            Some(org) => (Some(org.org), Some(org.unit), Some(org.office)),
            None => (None, None, None),
        };

        // Get the address properties.
        let (
            home_address_street,
            home_address_locality,
            home_address_region,
            home_address_code,
            home_address_country,
            (home_address_geo_longitude, home_address_geo_latitude),
        ) = Address::to_sql_raw(them.home_address);

        let (
            work_address_street,
            work_address_locality,
            work_address_region,
            work_address_code,
            work_address_country,
            (work_address_geo_longitude, work_address_geo_latitude),
        ) = Address::to_sql_raw(them.work_address);

        Ok(Self {
            // Get the Uid
            uid: them.uid,

            // Get the names
            name_given: Name::escape_with_spaces(them.name.given.iter().map(String::as_str)),
            name_additional: Name::escape_with_spaces(them.name.additional.iter().map(String::as_str)),
            name_family: Name::escape_with_spaces(them.name.family.iter().map(String::as_str)),
            name_prefixes: Name::escape_with_spaces(them.name.prefixes.iter().map(String::as_str)),
            name_suffixes: Name::escape_with_spaces(them.name.suffixes.iter().map(String::as_str)),
            nickname: them.nickname,

            // Get the time based properties
            anniversary: match them.anniversary {
                Some(x) => x.try_into()?,
                None => None,
            },
            bday: match them.bday {
                Some(x) => x.try_into()?,
                None => None,
            },

            // Gender
            gender_sex: them.gender.as_ref().map(|x| x.sex.map_or('N', char::from).to_string()),
            gender_gender: them.gender.and_then(|x| x.identity),

            // Gets the photo properties.
            photo_uri,
            photo_bin,
            photo_mime,

            // Title
            title: them.title,

            // Role
            role: them.role,

            // Org
            org_org,
            org_unit,
            org_office,

            // Logo Properties
            logo_uri,
            logo_bin,
            logo_mime,

            // Home Address
            home_address_street,
            home_address_locality,
            home_address_region,
            home_address_code,
            home_address_country,
            home_address_geo_longitude,
            home_address_geo_latitude,

            // Work Address
            work_address_street,
            work_address_locality,
            work_address_region,
            work_address_code,
            work_address_country,
            work_address_geo_longitude,
            work_address_geo_latitude,
        })
    }
}

#[cfg(feature = "diesel_support")]
embed_migrations!("diesel-migrations/");
#[cfg(feature = "diesel_support")]
pub mod embeded_diesel_migrations {
    pub use super::embedded_migrations::*;
}

#[cfg(feature = "sqlx_support")]
pub static SQLX_MIGRATIONS: Migrator = {
    let mut migrator = sqlx::migrate!("./sqlx-migrations");
    migrator.ignore_missing = true;
    migrator
};