contack 0.9.2

A simple and easy contact library.
Documentation
#[cfg(feature = "read_write")]
use crate::read_write::component::Component;
#[cfg(feature = "read_write")]
use crate::read_write::error::FromComponentError;
#[cfg(feature = "sql")]
use crate::SqlConversionError;
#[cfg(feature = "read_write")]
use regex::Regex;

/// A struct which can either store byte information or a url
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub enum Uri {
    /// Represents a classic url of any type.
    Url {
        /// A url/uri, such as <http://www.example.com>
        url: String
    },
    /// Used (for ease of use) to represent binary data,
    /// will be encoded as base64, and will attempt
    /// to be decoded as such aswell.
    Bytes {
        /// The bytes that make up the value, ie the
        /// contents of a png file.
        val: Vec<u8>,
        /// The mimetype, ie "image/png"
        mime: String
    },
}

impl Uri {
    /// Creates a new url from a string
    #[must_use]
    pub const fn new_url(string: String) -> Self {
        Self::Url { url: string }
    }

    /// Creates a new bytes from a vec
    #[must_use]
    pub const fn new_bytes(bytes: Vec<u8>, mime: String) -> Self {
        Self::Bytes { val: bytes, mime }
    }
}

#[cfg(feature = "sql")]
impl Uri {
    /// Converts an Uri into something which can be used by a `SqlContact`
    #[must_use]
    pub fn to_sql_raw(
        this: Option<Self>,
    ) -> (Option<String>, Option<Vec<u8>>, Option<String>) {
        match this {
            Some(photo) => match photo {
                Uri::Url { url } => (Some(url), None, None),
                Uri::Bytes { val, mime } => (None, Some(val), Some(mime)),
            },
            None => (None, None, None),
        }
    }

    /// This should only be used internally.
    ///
    /// The first `Option<String>` is the url, the `Option<Vec<u8>>` a binary
    /// value and the final `Option<String>` is the mime.
    ///
    /// # Errors
    ///
    /// Fails with an incomplete binary.
    pub fn try_from_sql_raw(
        tup: (Option<String>, Option<Vec<u8>>, Option<String>, String),
    ) -> Result<Option<Self>, SqlConversionError> {
        match tup.0 {
            // Standard URL
            Some(val) => Ok(Some(Self::new_url(val))),

            // Binary Type
            None => match (tup.1, tup.2) {
                (Some(bin), Some(mime)) => Ok(Some(Self::new_bytes(bin, mime))),
                (None, None) => Ok(None),
                _ => Err(SqlConversionError::IncompleteUri),
            },
        }
    }
}

#[cfg(feature = "read_write")]
impl From<Uri> for Component {
    fn from(uri: Uri) -> Self {
        match uri {
            Uri::Url { url } => Self {
                name: "_URI".to_string(),
                values: vec![vec![url]],
                ..Self::default()
            },
            Uri::Bytes { val, mime } => Self {
                name: "_URI".to_string(),
                values: vec![
                    vec![format!("data:{}", mime)],
                    vec!["base64".to_string(), base64::encode(val)],
                ],
                ..Self::default()
            },
        }
    }
}

#[cfg(feature = "read_write")]
impl TryFrom<Component> for Uri {
    type Error = FromComponentError;

    fn try_from(comp: Component) -> Result<Self, Self::Error> {
        // Generate a regex, which can be used to grab base64 data.
        lazy_static! {
            static ref RE_BIN_1: Regex =
                Regex::new(r#"^data:\w+/[\w\+\.-]+$"#).unwrap();
        }

        // Check the amount of arguments:
        //  * Two: Binary
        //  * One: Url
        match comp.values.len() {
            // Binary
            2 => {
                // Get the mime.
                let mime = comp.values[0]
                    .get(0)
                    .ok_or(FromComponentError::NotEnoughValues)?;
                // Check if it follows the correct pattern
                if RE_BIN_1.is_match(mime) {
                    Ok(Self::Bytes {
                        mime: mime[5..].to_string(),
                        val: base64::decode(
                            comp.values[1]
                                .get(1)
                                .ok_or(FromComponentError::NotEnoughValues)?,
                        )
                        .map_err(FromComponentError::Base64DecodeError)?,
                    })
                } else {
                    Err(FromComponentError::InvalidRegex)
                }
            }
            1 => Ok(Self::Url {
                url: comp.values[0]
                    .get(0)
                    .ok_or(FromComponentError::NotEnoughValues)?
                    .to_string(),
            }),
            // Don't know how to handle
            _ => Err(FromComponentError::NotEnoughValues),
        }
    }
}