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:
committed by
Alexis Mousset
parent
3683d122ba
commit
3f03b6296b
@@ -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"]
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user