use crate::{MailError, Result};
use serde::{Deserialize, Serialize};
use std::path::Path;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum ContentDisposition {
#[default]
Attachment,
Inline,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Attachment {
pub filename: String,
pub content_type: String,
pub data: Vec<u8>,
pub disposition: ContentDisposition,
pub content_id: Option<String>,
}
impl Attachment {
pub fn new(
filename: impl Into<String>,
content_type: impl Into<String>,
data: impl Into<Vec<u8>>,
) -> Self {
Self {
filename: filename.into(),
content_type: content_type.into(),
data: data.into(),
disposition: ContentDisposition::Attachment,
content_id: None,
}
}
pub fn from_file(path: impl AsRef<Path>) -> Result<Self> {
let path = path.as_ref();
let filename = path
.file_name()
.and_then(|n| n.to_str())
.ok_or_else(|| MailError::Attachment("Invalid file name".to_string()))?
.to_string();
let content_type = mime_guess::from_path(path)
.first()
.map(|m| m.to_string())
.unwrap_or_else(|| "application/octet-stream".to_string());
let data = std::fs::read(path)?;
Ok(Self::new(filename, content_type, data))
}
pub fn from_bytes(filename: impl Into<String>, data: impl Into<Vec<u8>>) -> Self {
let filename = filename.into();
let content_type = mime_guess::from_path(&filename)
.first()
.map(|m| m.to_string())
.unwrap_or_else(|| "application/octet-stream".to_string());
Self::new(filename, content_type, data)
}
pub fn disposition(mut self, disposition: ContentDisposition) -> Self {
self.disposition = disposition;
self
}
pub fn inline(mut self) -> Self {
self.disposition = ContentDisposition::Inline;
self
}
pub fn content_id(mut self, id: impl Into<String>) -> Self {
self.content_id = Some(id.into());
self.disposition = ContentDisposition::Inline;
self
}
pub fn with_generated_content_id(mut self) -> Self {
self.content_id = Some(format!("{}@armature", uuid::Uuid::new_v4()));
self.disposition = ContentDisposition::Inline;
self
}
pub fn size(&self) -> usize {
self.data.len()
}
}
impl Attachment {
pub fn pdf(filename: impl Into<String>, data: impl Into<Vec<u8>>) -> Self {
Self::new(filename, "application/pdf", data)
}
pub fn png(filename: impl Into<String>, data: impl Into<Vec<u8>>) -> Self {
Self::new(filename, "image/png", data)
}
pub fn jpeg(filename: impl Into<String>, data: impl Into<Vec<u8>>) -> Self {
Self::new(filename, "image/jpeg", data)
}
pub fn gif(filename: impl Into<String>, data: impl Into<Vec<u8>>) -> Self {
Self::new(filename, "image/gif", data)
}
pub fn text(filename: impl Into<String>, content: impl Into<String>) -> Self {
Self::new(
filename,
"text/plain; charset=utf-8",
content.into().into_bytes(),
)
}
pub fn csv(filename: impl Into<String>, content: impl Into<String>) -> Self {
Self::new(
filename,
"text/csv; charset=utf-8",
content.into().into_bytes(),
)
}
pub fn json(filename: impl Into<String>, content: impl Into<String>) -> Self {
Self::new(filename, "application/json", content.into().into_bytes())
}
pub fn xlsx(filename: impl Into<String>, data: impl Into<Vec<u8>>) -> Self {
Self::new(
filename,
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
data,
)
}
pub fn docx(filename: impl Into<String>, data: impl Into<Vec<u8>>) -> Self {
Self::new(
filename,
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
data,
)
}
pub fn zip(filename: impl Into<String>, data: impl Into<Vec<u8>>) -> Self {
Self::new(filename, "application/zip", data)
}
}