From 75a85831ab6bb378044c7fc97b80c3bc40ab953b Mon Sep 17 00:00:00 2001 From: Alexis Mousset Date: Sun, 12 Jun 2016 20:38:43 +0200 Subject: [PATCH] feat(email): Improve Email management Add a new layer called SimpleEmail, useful for some transport (like Web APIs). --- Cargo.toml | 2 +- src/email/error.rs | 31 ++++++ src/email/mod.rs | 183 ++++++++++++++++++++++++++++++- src/transport/smtp/client/mod.rs | 2 +- 4 files changed, 210 insertions(+), 8 deletions(-) create mode 100644 src/email/error.rs diff --git a/Cargo.toml b/Cargo.toml index 2c08e07..d6e716f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ openssl = "0.7" rustc-serialize = "0.3" rust-crypto = "0.2" time = "0.1" -uuid = { version = "0.2", features = ["v4"] } +uuid = { version = "0.3", features = ["v4"] } [dev-dependencies] env_logger = "0.3" diff --git a/src/email/error.rs b/src/email/error.rs new file mode 100644 index 0000000..574b219 --- /dev/null +++ b/src/email/error.rs @@ -0,0 +1,31 @@ +//! Error and result type for emails + +use std::error::Error as StdError; +use std::fmt; +use std::fmt::{Display, Formatter}; + +use self::Error::*; + +/// An enum of all error kinds. +#[derive(Debug)] +pub enum Error { + /// Missinf sender + MissingFrom, + /// Missing recipient + MissingTo, +} + +impl Display for Error { + fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error> { + fmt.write_str(self.description()) + } +} + +impl StdError for Error { + fn description(&self) -> &str { + match *self { + MissingFrom => "the sender is missing", + MissingTo => "the recipient is missing", + } + } +} diff --git a/src/email/mod.rs b/src/email/mod.rs index efd8983..2fc6533 100644 --- a/src/email/mod.rs +++ b/src/email/mod.rs @@ -1,4 +1,5 @@ -//! Simple email (very incomplete) +//! Simple email representation +pub mod error; use std::fmt; use std::fmt::{Display, Formatter}; @@ -7,6 +8,7 @@ use email_format::{Header, Mailbox, MimeMessage, MimeMultipartType}; use mime::Mime; use time::{Tm, now}; use uuid::Uuid; +use email::error::Error; /// Converts an address or an address with an alias to a `Header` pub trait ToHeader { @@ -52,6 +54,177 @@ impl<'a> ToMailbox for (&'a str, &'a str) { } } +/// Can be transformed to a sendable email +pub trait IntoEmail { + /// Builds an email + fn into_email(&self) -> Result; +} + +impl IntoEmail for SimpleEmail { + fn into_email(&self) -> Result { + let mut builder = EmailBuilder::new(); + + if self.from.is_some() { + builder.add_from(self.from.as_ref().unwrap().as_str().to_mailbox()); + } + + for to_address in self.to.as_slice() { + builder.add_to(to_address.as_str().to_mailbox()); + } + + for cc_address in self.cc.as_slice() { + builder.add_cc(cc_address.as_str().to_mailbox()); + } + + // No bcc for now + + if self.reply_to.is_some() { + builder.add_reply_to(self.reply_to.as_ref().unwrap().as_str().to_mailbox()); + } + + if self.subject.is_some() { + builder.set_subject(self.subject.as_ref().unwrap().as_str()); + } + + // No date for now + + match (self.text.as_ref(), self.html.as_ref()) { + (Some(text), Some(html)) => builder.set_alternative(html.as_str(), text.as_str()), + (Some(text), None) => builder.set_text(text.as_str()), + (None, Some(html)) => builder.set_html(html.as_str()), + (None, None) => (), + } + + for header in self.headers.as_slice() { + builder.add_header(header.to_header()); + } + + Ok(builder.build().unwrap()) + } +} + + +/// Simple representation of an email, useful for some transports +#[derive(PartialEq,Eq,Clone,Debug,Default)] +pub struct SimpleEmail { + from: Option, + to: Vec, + cc: Vec, + // bcc: Vec, + reply_to: Option, + subject: Option, + date: Option, + html: Option, + text: Option, + // attachments: Vec, + headers: Vec
, +} + +impl SimpleEmail { + /// Adds a generic header + pub fn header(mut self, header: A) -> SimpleEmail { + self.add_header(header); + self + } + + /// Adds a generic header + pub fn add_header(&mut self, header: A) { + self.headers.push(header.to_header()); + } + + /// Adds a `From` header and stores the sender address + pub fn from(mut self, address: A) -> SimpleEmail { + self.add_from(address); + self + } + + /// Adds a `From` header and stores the sender address + pub fn add_from(&mut self, address: A) { + let mailbox = address.to_mailbox(); + self.from = Some(mailbox.address); + } + + /// Adds a `To` header and stores the recipient address + pub fn to(mut self, address: A) -> SimpleEmail { + self.add_to(address); + self + } + + /// Adds a `To` header and stores the recipient address + pub fn add_to(&mut self, address: A) { + let mailbox = address.to_mailbox(); + self.to.push(mailbox.address); + } + + /// Adds a `Cc` header and stores the recipient address + pub fn cc(mut self, address: A) -> SimpleEmail { + self.add_cc(address); + self + } + + /// Adds a `Cc` header and stores the recipient address + pub fn add_cc(&mut self, address: A) { + let mailbox = address.to_mailbox(); + self.cc.push(mailbox.address); + } + + /// Adds a `Reply-To` header + pub fn reply_to(mut self, address: A) -> SimpleEmail { + self.add_reply_to(address); + self + } + + /// Adds a `Reply-To` header + pub fn add_reply_to(&mut self, address: A) { + let mailbox = address.to_mailbox(); + self.reply_to = Some(mailbox.address); + } + + /// Adds a `Subject` header + pub fn subject(mut self, subject: &str) -> SimpleEmail { + self.set_subject(subject); + self + } + + /// Adds a `Subject` header + pub fn set_subject(&mut self, subject: &str) { + self.subject = Some(subject.to_string()); + } + + /// Adds a `Date` header with the given date + pub fn date(mut self, date: &Tm) -> SimpleEmail { + self.set_date(date); + self + } + + /// Adds a `Date` header with the given date + pub fn set_date(&mut self, date: &Tm) { + self.date = Some(date.clone()); + } + + /// Sets the email body to plain text content + pub fn text(mut self, body: &str) -> SimpleEmail { + self.set_text(body); + self + } + + /// Sets the email body to plain text content + pub fn set_text(&mut self, body: &str) { + self.text = Some(body.to_string()); + } + + /// Sets the email body to HTML content + pub fn html(mut self, body: &str) -> SimpleEmail { + self.set_html(body); + self + } + + /// Sets the email body to HTML content + pub fn set_html(&mut self, body: &str) { + self.html = Some(body.to_string()); + } +} + /// Builds a `MimeMessage` structure #[derive(PartialEq,Eq,Clone,Debug)] pub struct PartBuilder { @@ -167,8 +340,6 @@ impl PartBuilder { } } - - impl EmailBuilder { /// Creates a new empty email pub fn new() -> EmailBuilder { @@ -366,12 +537,12 @@ impl EmailBuilder { } /// Builds the Email - pub fn build(mut self) -> Result { + pub fn build(mut self) -> Result { if self.from.is_none() { - return Err("No from address"); + return Err(Error::MissingFrom); } if self.to.is_empty() { - return Err("No to address"); + return Err(Error::MissingTo); } if !self.date_issued { diff --git a/src/transport/smtp/client/mod.rs b/src/transport/smtp/client/mod.rs index 95924b5..cc61e9c 100644 --- a/src/transport/smtp/client/mod.rs +++ b/src/transport/smtp/client/mod.rs @@ -110,7 +110,7 @@ impl Client { Some(addr) => addr, None => return_err!("Could not resolve hostname", self), }; - + debug!("connecting to {}", server_addr); // Try to connect