use core::fmt;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(try_from = "Parts"))]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Attachment {
content: Vec<u8>,
mime_code: String,
filename: String,
}
#[cfg(feature = "serde")]
#[derive(serde::Deserialize)]
struct Parts {
content: Vec<u8>,
mime_code: String,
filename: String,
}
#[cfg(feature = "serde")]
impl TryFrom<Parts> for Attachment {
type Error = AttachmentError;
fn try_from(p: Parts) -> Result<Self, Self::Error> {
Self::new(p.content, p.mime_code, p.filename)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
pub enum AttachmentError {
#[error("attachment mime code is required (EN 16931-1 §6.5.11)")]
MissingMimeCode,
#[error("attachment filename is required (EN 16931-1 §6.5.11)")]
MissingFilename,
}
impl Attachment {
pub const RECEIVER_MUST_ACCEPT: &'static [&'static str] = &[
"application/pdf",
"image/png",
"image/jpeg",
"text/csv",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"application/vnd.oasis.opendocument.spreadsheet",
];
pub fn new(
content: Vec<u8>,
mime_code: impl Into<String>,
filename: impl Into<String>,
) -> Result<Self, AttachmentError> {
let mime_code = mime_code.into();
let filename = filename.into();
if mime_code.trim().is_empty() {
return Err(AttachmentError::MissingMimeCode);
}
if filename.trim().is_empty() {
return Err(AttachmentError::MissingFilename);
}
Ok(Self {
content,
mime_code,
filename,
})
}
#[must_use]
pub fn content(&self) -> &[u8] {
&self.content
}
#[must_use]
pub fn mime_code(&self) -> &str {
&self.mime_code
}
#[must_use]
pub fn filename(&self) -> &str {
&self.filename
}
#[must_use]
pub fn is_universally_accepted(&self) -> bool {
Self::RECEIVER_MUST_ACCEPT.contains(&self.mime_code.as_str())
}
}
impl fmt::Display for Attachment {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} ({}, {} bytes)",
self.filename,
self.mime_code,
self.content.len()
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn all_three_components_are_present() {
let a = Attachment::new(vec![1, 2, 3], "application/pdf", "terms.pdf")
.expect("valid attachment");
assert_eq!(a.content(), &[1, 2, 3]);
assert_eq!(a.mime_code(), "application/pdf");
assert_eq!(a.filename(), "terms.pdf");
assert_eq!(a.to_string(), "terms.pdf (application/pdf, 3 bytes)");
}
#[test]
fn the_receiver_obligation_list_matches_the_standard() {
assert_eq!(
Attachment::RECEIVER_MUST_ACCEPT,
[
"application/pdf",
"image/png",
"image/jpeg",
"text/csv",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"application/vnd.oasis.opendocument.spreadsheet",
]
);
}
#[test]
fn an_exotic_type_is_lawful_but_not_guaranteed() {
let csv = Attachment::new(vec![], "text/csv", "a.csv").expect("valid");
assert!(csv.is_universally_accepted());
let tiff = Attachment::new(vec![], "image/tiff", "a.tif").expect("valid");
assert!(!tiff.is_universally_accepted());
}
}