use std::borrow::Cow;
#[cfg(feature="serde")]
use std::fmt;
use failure::Fail;
use soft_ascii_string::SoftAsciiStr;
use media_type::push_params_to_buffer;
use media_type::spec::{MimeSpec, Ascii, Modern, Internationalized};
#[cfg(feature="serde")]
use serde::{
Serialize, Serializer,
Deserialize, Deserializer,
};
use internals::error::{EncodingError, EncodingErrorKind};
use internals::encoder::{EncodableInHeader, EncodingWriter};
use ::HeaderTryFrom;
use ::error::ComponentCreationError;
use super::FileMeta;
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
#[cfg_attr(feature="serde", derive(Serialize, Deserialize))]
pub struct Disposition {
kind: DispositionKind,
file_meta: DispositionParameters
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
#[cfg_attr(feature="serde", derive(Serialize, Deserialize))]
struct DispositionParameters(FileMeta);
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum DispositionKind {
Inline,
Attachment
}
impl Disposition {
pub fn inline() -> Self {
Disposition::new( DispositionKind::Inline, FileMeta::default() )
}
pub fn attachment() -> Self {
Disposition::new( DispositionKind::Attachment, FileMeta::default() )
}
pub fn new( kind: DispositionKind, file_meta: FileMeta ) -> Self {
Disposition { kind, file_meta: DispositionParameters( file_meta ) }
}
pub fn kind( &self ) -> DispositionKind {
self.kind
}
pub fn file_meta( &self ) -> &FileMeta {
&self.file_meta
}
pub fn file_meta_mut( &mut self ) -> &mut FileMeta {
&mut self.file_meta
}
}
#[cfg(feature="serde")]
impl Serialize for DispositionKind {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer
{
match self {
&DispositionKind::Inline =>
serializer.serialize_str("inline"),
&DispositionKind::Attachment =>
serializer.serialize_str("attachment")
}
}
}
#[cfg(feature="serde")]
impl<'de> Deserialize<'de> for DispositionKind {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where D: Deserializer<'de>
{
struct Visitor;
impl<'de> ::serde::de::Visitor<'de> for Visitor {
type Value = DispositionKind;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("\"inline\" or \"attachment\"")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where E: ::serde::de::Error,
{
if value.eq_ignore_ascii_case("inline") {
Ok(DispositionKind::Inline)
} else if value.eq_ignore_ascii_case("attachment") {
Ok(DispositionKind::Attachment)
} else {
Err(E::custom(format!(
"unknown disposition: {:?}", value
)))
}
}
}
deserializer.deserialize_str(Visitor)
}
}
impl<'a> HeaderTryFrom<&'a str> for Disposition {
fn try_from(text: &'a str) -> Result<Self, ComponentCreationError> {
if text.eq_ignore_ascii_case("Inline") {
Ok(Disposition::inline())
} else if text.eq_ignore_ascii_case("Attachment") {
Ok(Disposition::attachment())
} else {
let mut err = ComponentCreationError::new("Disposition");
err.set_str_context(text);
return Err(err);
}
}
}
impl EncodableInHeader for DispositionParameters {
fn encode(&self, handle: &mut EncodingWriter) -> Result<(), EncodingError> {
let mut params = Vec::<(&str, Cow<str>)>::new();
if let Some(filename) = self.file_name.as_ref() {
params.push(("filename", Cow::Borrowed(filename)));
}
if let Some(creation_date) = self.creation_date.as_ref() {
params.push(("creation-date", Cow::Owned(creation_date.to_rfc2822())));
}
if let Some(date) = self.modification_date.as_ref() {
params.push(("modification-date", Cow::Owned(date.to_rfc2822())));
}
if let Some(date) = self.read_date.as_ref() {
params.push(("read-date", Cow::Owned(date.to_rfc2822())));
}
if let Some(size) = self.size.as_ref() {
params.push(("size", Cow::Owned(size.to_string())));
}
let mut buff = String::new();
let res =
if handle.mail_type().is_internationalized() {
push_params_to_buffer::<MimeSpec<Internationalized, Modern>, _, _, _>(
&mut buff, params
)
} else {
push_params_to_buffer::<MimeSpec<Ascii, Modern>, _, _, _>(
&mut buff, params
)
};
match res {
Err(err) => {
Err(err.context(EncodingErrorKind::Malformed).into())
},
Ok(_) => {
handle.write_str_unchecked(&*buff)?;
Ok(())
}
}
}
fn boxed_clone(&self) -> Box<EncodableInHeader> {
Box::new(self.clone())
}
}
impl EncodableInHeader for Disposition {
fn encode(&self, handle: &mut EncodingWriter) -> Result<(), EncodingError> {
use self::DispositionKind::*;
match self.kind {
Inline => {
handle.write_str(SoftAsciiStr::from_unchecked("inline"))?;
},
Attachment => {
handle.write_str(SoftAsciiStr::from_unchecked("attachment"))?;
}
}
self.file_meta.encode( handle )?;
Ok( () )
}
fn boxed_clone(&self) -> Box<EncodableInHeader> {
Box::new(self.clone())
}
}
deref0!{+mut DispositionParameters => FileMeta }
#[cfg(test)]
mod test {
use chrono;
use std::default::Default;
use super::*;
pub fn test_time( modif: u32 ) -> chrono::DateTime<chrono::Utc> {
use chrono::prelude::*;
Utc.ymd( 2013, 8, 6 ).and_hms( 7, 11, modif )
}
ec_test!{ no_params_inline, {
Disposition::inline()
} => ascii => [
Text "inline"
]}
ec_test!{ no_params_attachment, {
Disposition::attachment()
} => ascii => [
Text "attachment"
]}
ec_test!{ attachment_encode_file_name, {
Disposition::new( DispositionKind::Attachment, FileMeta {
file_name: Some("this is nice".to_owned()),
..Default::default()
})
} => ascii => [
Text "attachment; filename=\"this is nice\""
]}
ec_test!{ attachment_all_params, {
Disposition::new( DispositionKind::Attachment, FileMeta {
file_name: Some( "random.png".to_owned() ),
creation_date: Some( test_time( 1 ) ),
modification_date: Some( test_time( 2 ) ),
read_date: Some( test_time( 3 ) ),
size: Some( 4096 )
})
} => ascii => [
Text concat!( "attachment",
"; filename=random.png",
"; creation-date=\"Tue, 6 Aug 2013 07:11:01 +0000\"",
"; modification-date=\"Tue, 6 Aug 2013 07:11:02 +0000\"",
"; read-date=\"Tue, 6 Aug 2013 07:11:03 +0000\"",
"; size=4096" ),
]}
ec_test!{ inline_file_name_param, {
Disposition::new(DispositionKind::Inline, FileMeta {
file_name: Some("logo.png".to_owned()),
..Default::default()
})
} => ascii => [
Text "inline; filename=logo.png"
]}
#[test]
fn test_from_str() {
assert_ok!( Disposition::try_from( "Inline" ) );
assert_ok!( Disposition::try_from( "InLine" ) );
assert_ok!( Disposition::try_from( "Attachment" ) );
assert_err!( Disposition::try_from( "In line") );
}
#[cfg(feature="serde")]
fn assert_serialize<S: ::serde::Serialize>() {}
#[cfg(feature="serde")]
fn assert_deserialize<S: ::serde::Serialize>() {}
#[cfg(feature="serde")]
#[test]
fn disposition_serialization() {
assert_serialize::<Disposition>();
assert_serialize::<DispositionKind>();
assert_serialize::<DispositionParameters>();
assert_deserialize::<Disposition>();
assert_deserialize::<DispositionKind>();
assert_deserialize::<DispositionParameters>();
}
}