vcard 0.4.13

A pure Rust implementation of vCard based on RFC 6350.
Documentation
use std::{
    fmt::Display,
    fs::File,
    io::{self, Read},
    path::Path,
};

use base64_stream::ToBase64Reader;
use mime::Mime;
use validators::{base64::Base64, Validated, ValidatedWrapper};

use super::{super::values::uri::URI, *};

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[allow(clippy::upper_case_acronyms)]
enum AudioValueInner {
    Base64(Mime, Base64),
    URI(URI),
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct AudioValue {
    inner: AudioValueInner,
}

#[derive(Debug)]
pub enum AudioValueError {
    FileMediaTypeCannotBeDefined,
    MediaTypeNotAudio,
    IOError(io::Error),
}

impl From<io::Error> for AudioValueError {
    #[inline]
    fn from(err: io::Error) -> Self {
        AudioValueError::IOError(err)
    }
}

impl AudioValue {
    pub fn from_base64(mime_type: Mime, base64: Base64) -> Result<AudioValue, AudioValueError> {
        if mime_type.type_() != mime::AUDIO {
            return Err(AudioValueError::MediaTypeNotAudio);
        }

        Ok(AudioValue {
            inner: AudioValueInner::Base64(mime_type, base64)
        })
    }

    pub fn from_uri(uri: URI) -> AudioValue {
        AudioValue {
            inner: AudioValueInner::URI(uri)
        }
    }

    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<AudioValue, AudioValueError> {
        Self::from_file_inner(path, None)
    }

    pub fn from_file_with_mime<P: AsRef<Path>>(
        path: P,
        mime_type: Mime,
    ) -> Result<AudioValue, AudioValueError> {
        Self::from_file_inner(path, Some(mime_type))
    }

    fn from_file_inner<P: AsRef<Path>>(
        path: P,
        mime_type: Option<Mime>,
    ) -> Result<AudioValue, AudioValueError> {
        let path = path.as_ref();

        let mime_type = match mime_type {
            Some(audio_type) => audio_type,
            None => match path.extension() {
                Some(ext) => match ext.to_str() {
                    Some(ext) => {
                        let mime_type = mime_guess::from_ext(ext).first_or_octet_stream();

                        if mime_type.type_() != mime::AUDIO {
                            return Err(AudioValueError::MediaTypeNotAudio);
                        }

                        mime_type
                    },
                    None => {
                        return Err(AudioValueError::FileMediaTypeCannotBeDefined);
                    },
                },
                None => {
                    return Err(AudioValueError::FileMediaTypeCannotBeDefined);
                },
            },
        };

        let mut reader = ToBase64Reader::new(File::open(path)?);

        let mut base64_raw = Vec::new();

        reader.read_to_end(&mut base64_raw)?;

        let base64 = unsafe { String::from_utf8_unchecked(base64_raw) };

        let base64 = unsafe { Base64::from_string_unchecked(base64) };

        Ok(AudioValue {
            inner: AudioValueInner::Base64(mime_type, base64)
        })
    }
}

impl Value for AudioValue {
    fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
        match &self.inner {
            AudioValueInner::Base64(typ, base64) => {
                f.write_str("data:")?;
                f.write_str(typ.as_ref())?;
                f.write_str(";base64,")?;
                f.write_str(base64.get_base64())?;
            },
            AudioValueInner::URI(uri) => {
                f.write_str(uri.get_full_uri())?;
            },
        }

        Ok(())
    }
}

impl Display for AudioValue {
    fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
        Value::fmt(self, f)
    }
}

impl Validated for AudioValue {}

impl ValidatedWrapper for AudioValue {
    type Error = &'static str;

    fn from_string(_from_string_input: String) -> Result<Self, Self::Error> {
        unimplemented!();
    }

    fn from_str(_from_str_input: &str) -> Result<Self, Self::Error> {
        unimplemented!();
    }
}