feat(transport): Add the ability to save envelope as JSON in file transport

This will make it possible to read the message and send it later
properly.
This commit is contained in:
Alexis Mousset
2020-11-25 19:48:20 +01:00
committed by Alexis Mousset
parent 3683d122ba
commit 3f03b6296b
5 changed files with 140 additions and 38 deletions

View File

@@ -33,6 +33,7 @@ regex = "1"
# file transport
serde = { version = "1", optional = true, features = ["derive"] }
serde_json = { version = "1", optional = true }
# smtp
nom = { version = "6", default-features = false, features = ["alloc"], optional = true }
@@ -77,12 +78,12 @@ harness = false
name = "transport_smtp"
[features]
default = ["file-transport", "smtp-transport", "native-tls", "hostname", "r2d2", "sendmail-transport", "builder"]
default = ["file-transport", "file-transport-envelope", "smtp-transport", "native-tls", "hostname", "r2d2", "sendmail-transport", "builder"]
builder = ["mime", "base64", "hyperx", "rand", "quoted_printable"]
# transports
file-transport = []
file-transport-envelope = ["serde", "serde_json"]
sendmail-transport = []
smtp-transport = ["base64", "nom"]

View File

@@ -11,6 +11,7 @@
//!
//! * **builder**: Message builder
//! * **file-transport**: Transport that write messages into a file
//! * **file-transport-envelope**: Allow writing the envelope into a JSON file
//! * **smtp-transport**: Transport over SMTP
//! * **sendmail-transport**: Transport over SMTP
//! * **rustls-tls**: TLS support with the `rustls` crate

View File

@@ -14,6 +14,9 @@ pub enum Error {
Client(&'static str),
/// IO error
Io(io::Error),
/// JSON error
#[cfg(feature = "file-transport-envelope")]
Json(serde_json::Error),
}
impl Display for Error {
@@ -21,6 +24,8 @@ impl Display for Error {
match *self {
Client(err) => fmt.write_str(err),
Io(ref err) => err.fmt(fmt),
#[cfg(feature = "file-transport-envelope")]
Json(ref err) => err.fmt(fmt),
}
}
}
@@ -29,6 +34,8 @@ impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match *self {
Io(ref err) => Some(&*err),
#[cfg(feature = "file-transport-envelope")]
Json(ref err) => Some(&*err),
_ => None,
}
}
@@ -40,6 +47,13 @@ impl From<io::Error> for Error {
}
}
#[cfg(feature = "file-transport-envelope")]
impl From<serde_json::Error> for Error {
fn from(err: serde_json::Error) -> Error {
Error::Json(err)
}
}
impl From<&'static str> for Error {
fn from(string: &'static str) -> Error {
Error::Client(string)

View File

@@ -121,36 +121,62 @@ type Id = String;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct FileTransport {
path: PathBuf,
#[cfg(feature = "file-transport-envelope")]
save_envelope: bool,
}
impl FileTransport {
/// Creates a new transport to the given directory
///
/// Writes the email content in eml format.
pub fn new<P: AsRef<Path>>(path: P) -> FileTransport {
FileTransport {
path: PathBuf::from(path.as_ref()),
#[cfg(feature = "file-transport-envelope")]
save_envelope: false,
}
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
struct SerializableEmail<'a> {
envelope: Envelope,
raw_message: Option<&'a [u8]>,
message: Option<&'a str>,
/// Creates a new transport to the given directory
///
/// Writes the email content in eml format and the envelope
/// in json format.
#[cfg(feature = "file-transport-envelope")]
pub fn with_envelope<P: AsRef<Path>>(path: P) -> FileTransport {
FileTransport {
path: PathBuf::from(path.as_ref()),
#[cfg(feature = "file-transport-envelope")]
save_envelope: true,
}
}
fn path(&self, email_id: &Uuid, extension: &str) -> PathBuf {
self.path.join(format!("{}.{}", email_id, extension))
}
}
impl Transport for FileTransport {
type Ok = Id;
type Error = Error;
fn send_raw(&self, _envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error> {
fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error> {
use std::fs;
let email_id = Uuid::new_v4();
let file = self.path.join(format!("{}.eml", email_id));
let file = self.path(&email_id, "eml");
fs::write(file, email)?;
#[cfg(feature = "file-transport-envelope")]
{
if self.save_envelope {
let file = self.path(&email_id, "json");
fs::write(file, serde_json::to_string(&envelope)?)?;
}
}
// use envelope anyway
let _ = envelope;
Ok(email_id.to_string())
}
}
@@ -161,13 +187,24 @@ impl AsyncStd1Transport for FileTransport {
type Ok = Id;
type Error = Error;
async fn send_raw(&self, _envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error> {
async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error> {
use async_std::fs;
let email_id = Uuid::new_v4();
let file = self.path.join(format!("{}.eml", email_id));
let file = self.path(&email_id, "eml");
fs::write(file, email).await?;
#[cfg(feature = "file-transport-envelope")]
{
if self.save_envelope {
let file = self.path(&email_id, "json");
fs::write(file, serde_json::to_string(&envelope)?).await?;
}
}
// use envelope anyway
let _ = envelope;
Ok(email_id.to_string())
}
}
@@ -178,13 +215,23 @@ impl Tokio02Transport for FileTransport {
type Ok = Id;
type Error = Error;
async fn send_raw(&self, _envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error> {
async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error> {
use tokio02_crate::fs;
let email_id = Uuid::new_v4();
let file = self.path.join(format!("{}.eml", email_id));
let file = self.path(&email_id, "eml");
fs::write(file, email).await?;
#[cfg(feature = "file-transport-envelope")]
{
if self.save_envelope {
let file = self.path(&email_id, "json");
fs::write(file, serde_json::to_string(&envelope)?).await?;
}
}
// use envelope anyway
let _ = envelope;
Ok(email_id.to_string())
}
}
@@ -195,13 +242,24 @@ impl Tokio03Transport for FileTransport {
type Ok = Id;
type Error = Error;
async fn send_raw(&self, _envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error> {
async fn send_raw(&self, envelope: &Envelope, email: &[u8]) -> Result<Self::Ok, Self::Error> {
use tokio03_crate::fs;
let email_id = Uuid::new_v4();
let file = self.path.join(format!("{}.eml", email_id));
let file = self.path(&email_id, "eml");
fs::write(file, email).await?;
#[cfg(feature = "file-transport-envelope")]
{
if self.save_envelope {
let file = self.path(&email_id, "json");
fs::write(file, serde_json::to_string(&envelope)?).await?;
}
}
// use envelope anyway
let _ = envelope;
Ok(email_id.to_string())
}
}

View File

@@ -4,8 +4,7 @@ mod test {
use lettre::{transport::file::FileTransport, Message};
use std::{
env::temp_dir,
fs::{remove_file, File},
io::Read,
fs::{read_to_string, remove_file},
};
#[cfg(feature = "tokio02")]
@@ -27,15 +26,48 @@ mod test {
let result = sender.send(&email);
let id = result.unwrap();
let file = temp_dir().join(format!("{}.eml", id));
let mut f = File::open(file.clone()).unwrap();
let mut buffer = String::new();
let _ = f.read_to_string(&mut buffer);
let eml_file = temp_dir().join(format!("{}.eml", id));
let eml = read_to_string(&eml_file).unwrap();
assert_eq!(
buffer,
eml,
"From: NoBody <nobody@domain.tld>\r\nReply-To: Yuin <yuin@domain.tld>\r\nTo: Hei <hei@domain.tld>\r\nSubject: Happy new year\r\nDate: Tue, 15 Nov 1994 08:12:31 GMT\r\n\r\nBe happy!");
remove_file(file).unwrap();
remove_file(eml_file).unwrap();
}
#[test]
#[cfg(feature = "file-transport-envelope")]
fn file_transport_with_envelope() {
use lettre::Transport;
let sender = FileTransport::with_envelope(temp_dir());
let email = Message::builder()
.from("NoBody <nobody@domain.tld>".parse().unwrap())
.reply_to("Yuin <yuin@domain.tld>".parse().unwrap())
.to("Hei <hei@domain.tld>".parse().unwrap())
.subject("Happy new year")
.date("Tue, 15 Nov 1994 08:12:31 GMT".parse().unwrap())
.body("Be happy!")
.unwrap();
let result = sender.send(&email);
let id = result.unwrap();
let eml_file = temp_dir().join(format!("{}.eml", id));
let eml = read_to_string(&eml_file).unwrap();
let json_file = temp_dir().join(format!("{}.json", id));
let json = read_to_string(&json_file).unwrap();
assert_eq!(
eml,
"From: NoBody <nobody@domain.tld>\r\nReply-To: Yuin <yuin@domain.tld>\r\nTo: Hei <hei@domain.tld>\r\nSubject: Happy new year\r\nDate: Tue, 15 Nov 1994 08:12:31 GMT\r\n\r\nBe happy!");
remove_file(eml_file).unwrap();
assert_eq!(
json,
"{\"forward_path\":[\"hei@domain.tld\"],\"reverse_path\":\"nobody@domain.tld\"}"
);
remove_file(json_file).unwrap();
}
#[cfg(feature = "async-std1")]
@@ -56,15 +88,13 @@ mod test {
let result = sender.send(email).await;
let id = result.unwrap();
let file = temp_dir().join(format!("{}.eml", id));
let mut f = File::open(file.clone()).unwrap();
let mut buffer = String::new();
let _ = f.read_to_string(&mut buffer);
let eml_file = temp_dir().join(format!("{}.eml", id));
let eml = read_to_string(&eml_file).unwrap();
assert_eq!(
buffer,
eml,
"From: NoBody <nobody@domain.tld>\r\nReply-To: Yuin <yuin@domain.tld>\r\nTo: Hei <hei@domain.tld>\r\nSubject: Happy new year\r\nDate: Tue, 15 Nov 1994 08:12:31 GMT\r\n\r\nBe happy!");
remove_file(file).unwrap();
remove_file(eml_file).unwrap();
}
#[cfg(feature = "tokio02")]
@@ -85,14 +115,12 @@ mod test {
let result = sender.send(email).await;
let id = result.unwrap();
let file = temp_dir().join(format!("{}.eml", id));
let mut f = File::open(file.clone()).unwrap();
let mut buffer = String::new();
let _ = f.read_to_string(&mut buffer);
let eml_file = temp_dir().join(format!("{}.eml", id));
let eml = read_to_string(&eml_file).unwrap();
assert_eq!(
buffer,
eml,
"From: NoBody <nobody@domain.tld>\r\nReply-To: Yuin <yuin@domain.tld>\r\nTo: Hei <hei@domain.tld>\r\nSubject: Happy new year\r\nDate: Tue, 15 Nov 1994 08:12:31 GMT\r\n\r\nBe happy!");
remove_file(file).unwrap();
remove_file(eml_file).unwrap();
}
}