use std::path::PathBuf;
#[derive(Debug, Clone)]
pub struct Attachment {
pub(crate) filename: String,
pub(crate) content: Vec<u8>,
pub(crate) content_type: Option<String>,
pub(crate) inline: bool,
pub(crate) content_id: Option<String>,
}
impl Attachment {
pub fn new(filename: impl Into<String>) -> Self {
Self {
filename: filename.into(),
content: Vec::new(),
content_type: None,
inline: false,
content_id: None,
}
}
pub fn from_file(path: impl Into<PathBuf>) -> std::io::Result<Self> {
let path = path.into();
let filename = path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("attachment")
.to_string();
let content = std::fs::read(&path)?;
let content_type = mime_guess::from_path(&path).first().map(|m| m.to_string());
Ok(Self {
filename,
content,
content_type,
inline: false,
content_id: None,
})
}
pub fn content(mut self, content: Vec<u8>) -> Self {
self.content = content;
self
}
pub fn content_type(mut self, content_type: impl Into<String>) -> Self {
self.content_type = Some(content_type.into());
self
}
pub fn inline(mut self) -> Self {
self.inline = true;
self
}
pub fn inline_with_cid(mut self, content_id: impl Into<String>) -> Self {
self.inline = true;
self.content_id = Some(content_id.into());
self
}
pub fn filename(&self) -> &str {
&self.filename
}
pub fn content_type_ref(&self) -> Option<&str> {
self.content_type.as_deref()
}
pub fn content_id(&self) -> Option<&str> {
self.content_id.as_deref()
}
pub fn is_inline(&self) -> bool {
self.inline
}
}
#[cfg(test)]
mod tests {
use super::Attachment;
#[test]
fn inline_with_cid_sets_expected_fields() {
let attachment = Attachment::new("logo.png").inline_with_cid("logo-1");
assert!(attachment.is_inline());
assert_eq!(attachment.content_id(), Some("logo-1"));
assert_eq!(attachment.filename(), "logo.png");
}
}