Compare commits

...

6 Commits

Author SHA1 Message Date
Alexis Mousset
a468cb3ab8 chore(all): 0.9.3 release 2020-04-19 10:56:46 +02:00
Alexis Mousset
9b591ff932 chore(all): Fix warnings 2020-04-19 10:24:23 +02:00
Alexis Mousset
eff4e1693f chore(builder): Update email to 0.0.21 2020-04-19 09:56:31 +02:00
Alexis Mousset
75ab05229a feat(all): v0.9.2 release 2019-06-11 19:58:32 +02:00
Alexis Mousset
393ef8dcd1 Simplify header formatting and fix nightly build (fixes #340) 2019-06-11 19:48:01 +02:00
Alexis Mousset
ceb57edfdd Remove failure crate usage (fixes #331) 2019-05-06 09:36:10 +02:00
13 changed files with 185 additions and 130 deletions

View File

@@ -1,3 +1,22 @@
<a name="v0.9.3"></a>
### v0.9.3 (2020-04-19)
#### Bug Fixes
* **all:**
* Fix compilation warnings ([9b591ff](https://github.com/lettre/lettre/commit/9b591ff932e35947f793aaaeec0e3f06e8818449))
* **email**
* Update `rust-email` to 0.0.21 ([eff4e169](https://github.com/lettre/lettre/commit/eff4e1693f5e65430b851707fdfd18046bc796e3))
<a name="v0.9.2"></a>
### v0.9.2 (2019-06-11)
#### Bug Fixes
* **email:**
* Fix compilation with Rust 1.36+ ([393ef8d](https://github.com/lettre/lettre/commit/393ef8dcd1b1c6a6119d0666d5f09b12f50f6b4b))
<a name="v0.9.1"></a> <a name="v0.9.1"></a>
### v0.9.1 (2019-05-05) ### v0.9.1 (2019-05-05)

View File

@@ -1,7 +1,7 @@
[package] [package]
name = "lettre" name = "lettre"
version = "0.9.1" # remember to update html_root_url version = "0.9.3" # remember to update html_root_url
description = "Email client" description = "Email client"
readme = "README.md" readme = "README.md"
homepage = "http://lettre.at" homepage = "http://lettre.at"
@@ -28,8 +28,6 @@ hostname = { version = "^0.1", optional = true }
serde = { version = "^1.0", optional = true } serde = { version = "^1.0", optional = true }
serde_json = { version = "^1.0", optional = true } serde_json = { version = "^1.0", optional = true }
serde_derive = { version = "^1.0", optional = true } serde_derive = { version = "^1.0", optional = true }
failure = "^0.1"
failure_derive = "^0.1"
fast_chemail = "^0.9" fast_chemail = "^0.9"
r2d2 = { version = "^0.8", optional = true} r2d2 = { version = "^0.8", optional = true}

View File

@@ -1,18 +1,35 @@
use failure; use self::Error::*;
use std::{
error::Error as StdError,
fmt::{self, Display, Formatter},
};
/// Error type for email content /// Error type for email content
#[derive(Fail, Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum Error { pub enum Error {
/// Missing from in envelope /// Missing from in envelope
#[fail(display = "missing source address, invalid envelope")]
MissingFrom, MissingFrom,
/// Missing to in envelope /// Missing to in envelope
#[fail(display = "missing destination address, invalid envelope")]
MissingTo, MissingTo,
/// Invalid email /// Invalid email
#[fail(display = "invalid email address")]
InvalidEmailAddress, InvalidEmailAddress,
} }
impl Display for Error {
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> {
fmt.write_str(&match *self {
MissingFrom => "missing source address, invalid envelope".to_owned(),
MissingTo => "missing destination address, invalid envelope".to_owned(),
InvalidEmailAddress => "invalid email address".to_owned(),
})
}
}
impl StdError for Error {
fn cause(&self) -> Option<&dyn StdError> {
None
}
}
/// Email result type /// Email result type
pub type EmailResult<T> = Result<T, failure::Error>; pub type EmailResult<T> = Result<T, Error>;

View File

@@ -1,40 +1,61 @@
//! Error and result type for file transport //! Error and result type for file transport
use failure; use self::Error::*;
use serde_json; use serde_json;
use std::io; use std::io;
use std::{
error::Error as StdError,
fmt::{self, Display, Formatter},
};
/// An enum of all error kinds. /// An enum of all error kinds.
#[derive(Fail, Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
/// Internal client error /// Internal client error
#[fail(display = "Internal client error: {}", error)] Client(&'static str),
Client { error: &'static str },
/// IO error /// IO error
#[fail(display = "IO error: {}", error)] Io(io::Error),
Io { error: io::Error },
/// JSON serialization error /// JSON serialization error
#[fail(display = "JSON serialization error: {}", error)] JsonSerialization(serde_json::Error),
JsonSerialization { error: serde_json::Error }, }
impl Display for Error {
fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error> {
match *self {
Client(err) => fmt.write_str(err),
Io(ref err) => err.fmt(fmt),
JsonSerialization(ref err) => err.fmt(fmt),
}
}
}
impl StdError for Error {
fn cause(&self) -> Option<&dyn StdError> {
match *self {
Io(ref err) => Some(&*err),
JsonSerialization(ref err) => Some(&*err),
_ => None,
}
}
} }
impl From<io::Error> for Error { impl From<io::Error> for Error {
fn from(err: io::Error) -> Error { fn from(err: io::Error) -> Error {
Error::Io { error: err } Error::Io(err)
} }
} }
impl From<serde_json::Error> for Error { impl From<serde_json::Error> for Error {
fn from(err: serde_json::Error) -> Error { fn from(err: serde_json::Error) -> Error {
Error::JsonSerialization { error: err } Error::JsonSerialization(err)
} }
} }
impl From<&'static str> for Error { impl From<&'static str> for Error {
fn from(string: &'static str) -> Error { fn from(string: &'static str) -> Error {
Error::Client { error: string } Error::Client(string)
} }
} }
/// SMTP result type /// SMTP result type
pub type FileResult = Result<(), failure::Error>; pub type FileResult = Result<(), Error>;

View File

@@ -3,7 +3,7 @@
//! This mailer contains the available transports for your emails. //! This mailer contains the available transports for your emails.
//! //!
#![doc(html_root_url = "https://docs.rs/lettre/0.9.1")] #![doc(html_root_url = "https://docs.rs/lettre/0.9.3")]
#![deny( #![deny(
missing_copy_implementations, missing_copy_implementations,
trivial_casts, trivial_casts,
@@ -30,14 +30,11 @@ extern crate serde;
#[cfg(feature = "serde-impls")] #[cfg(feature = "serde-impls")]
#[macro_use] #[macro_use]
extern crate serde_derive; extern crate serde_derive;
extern crate failure;
#[cfg(feature = "file-transport")]
extern crate serde_json;
#[macro_use]
extern crate failure_derive;
extern crate fast_chemail; extern crate fast_chemail;
#[cfg(feature = "connection-pool")] #[cfg(feature = "connection-pool")]
extern crate r2d2; extern crate r2d2;
#[cfg(feature = "file-transport")]
extern crate serde_json;
pub mod error; pub mod error;
#[cfg(feature = "file-transport")] #[cfg(feature = "file-transport")]
@@ -49,8 +46,7 @@ pub mod smtp;
pub mod stub; pub mod stub;
use error::EmailResult; use error::EmailResult;
use error::Error as EmailError; use error::Error;
use failure::Error;
use fast_chemail::is_valid_email; use fast_chemail::is_valid_email;
#[cfg(feature = "file-transport")] #[cfg(feature = "file-transport")]
pub use file::FileTransport; pub use file::FileTransport;
@@ -77,7 +73,7 @@ pub struct EmailAddress(String);
impl EmailAddress { impl EmailAddress {
pub fn new(address: String) -> EmailResult<EmailAddress> { pub fn new(address: String) -> EmailResult<EmailAddress> {
if !is_valid_email(&address) && !address.ends_with("localhost") { if !is_valid_email(&address) && !address.ends_with("localhost") {
Err(EmailError::InvalidEmailAddress)?; return Err(Error::InvalidEmailAddress);
} }
Ok(EmailAddress(address)) Ok(EmailAddress(address))
} }
@@ -127,7 +123,7 @@ impl Envelope {
/// Creates a new envelope, which may fail if `to` is empty. /// Creates a new envelope, which may fail if `to` is empty.
pub fn new(from: Option<EmailAddress>, to: Vec<EmailAddress>) -> EmailResult<Envelope> { pub fn new(from: Option<EmailAddress>, to: Vec<EmailAddress>) -> EmailResult<Envelope> {
if to.is_empty() { if to.is_empty() {
Err(EmailError::MissingTo)?; return Err(Error::MissingTo);
} }
Ok(Envelope { Ok(Envelope {
forward_path: to, forward_path: to,
@@ -147,7 +143,7 @@ impl Envelope {
} }
pub enum Message { pub enum Message {
Reader(Box<Read + Send>), Reader(Box<dyn Read + Send>),
Bytes(Cursor<Vec<u8>>), Bytes(Cursor<Vec<u8>>),
} }
@@ -179,7 +175,7 @@ impl SendableEmail {
pub fn new_with_reader( pub fn new_with_reader(
envelope: Envelope, envelope: Envelope,
message_id: String, message_id: String,
message: Box<Read + Send>, message: Box<dyn Read + Send>,
) -> SendableEmail { ) -> SendableEmail {
SendableEmail { SendableEmail {
envelope, envelope,

View File

@@ -1,30 +1,50 @@
//! Error and result type for sendmail transport //! Error and result type for sendmail transport
use failure; use self::Error::*;
use std::io; use std::io;
use std::{
error::Error as StdError,
fmt::{self, Display, Formatter},
};
/// An enum of all error kinds. /// An enum of all error kinds.
#[derive(Fail, Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
/// Internal client error /// Internal client error
#[fail(display = "Internal client error: {}", error)] Client(&'static str),
Client { error: &'static str },
/// IO error /// IO error
#[fail(display = "IO error: {}", error)] Io(io::Error),
Io { error: io::Error }, }
impl Display for Error {
fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error> {
match *self {
Client(ref err) => err.fmt(fmt),
Io(ref err) => err.fmt(fmt),
}
}
}
impl StdError for Error {
fn cause(&self) -> Option<&dyn StdError> {
match *self {
Io(ref err) => Some(&*err),
_ => None,
}
}
} }
impl From<io::Error> for Error { impl From<io::Error> for Error {
fn from(err: io::Error) -> Error { fn from(err: io::Error) -> Error {
Error::Io { error: err } Error::Io(err)
} }
} }
impl From<&'static str> for Error { impl From<&'static str> for Error {
fn from(string: &'static str) -> Error { fn from(string: &'static str) -> Error {
Error::Client { error: string } Error::Client(string)
} }
} }
/// sendmail result type /// sendmail result type
pub type SendmailResult = Result<(), failure::Error>; pub type SendmailResult = Result<(), Error>;

View File

@@ -72,9 +72,7 @@ impl<'a> Transport<'a> for SendmailTransport {
Ok(()) Ok(())
} else { } else {
// TODO display stderr // TODO display stderr
Err(error::Error::Client { Err(error::Error::Client("The message could not be sent"))
error: "The message could not be sent",
})?
} }
} }
} }

View File

@@ -193,7 +193,7 @@ impl<S: Connector + Write + Read + Timeout + Debug> InnerClient<S> {
} }
/// Sends the message content /// Sends the message content
pub fn message(&mut self, message: Box<Read>) -> SmtpResult { pub fn message(&mut self, message: Box<dyn Read>) -> SmtpResult {
let mut out_buf: Vec<u8> = vec![]; let mut out_buf: Vec<u8> = vec![];
let mut codec = ClientCodec::new(); let mut codec = ClientCodec::new();

View File

@@ -245,7 +245,7 @@ impl AuthCommand {
challenge: Option<String>, challenge: Option<String>,
) -> Result<AuthCommand, Error> { ) -> Result<AuthCommand, Error> {
let response = if mechanism.supports_initial_response() || challenge.is_some() { let response = if mechanism.supports_initial_response() || challenge.is_some() {
Some(mechanism.response(&credentials, challenge.as_ref().map(String::as_str))?) Some(mechanism.response(&credentials, challenge.as_deref())?)
} else { } else {
None None
}; };

View File

@@ -42,36 +42,31 @@ pub enum Error {
impl Display for Error { impl Display for Error {
fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error> { fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error> {
fmt.write_str(self.description()) match *self {
// Try to display the first line of the server's response that usually
// contains a short humanly readable error message
Transient(ref err) => fmt.write_str(match err.first_line() {
Some(line) => line,
None => "transient error during SMTP transaction",
}),
Permanent(ref err) => fmt.write_str(match err.first_line() {
Some(line) => line,
None => "permanent error during SMTP transaction",
}),
ResponseParsing(err) => fmt.write_str(err),
ChallengeParsing(ref err) => err.fmt(fmt),
Utf8Parsing(ref err) => err.fmt(fmt),
Resolution => fmt.write_str("could not resolve hostname"),
Client(err) => fmt.write_str(err),
Io(ref err) => err.fmt(fmt),
Tls(ref err) => err.fmt(fmt),
Parsing(ref err) => fmt.write_str(err.description()),
}
} }
} }
impl StdError for Error { impl StdError for Error {
#[cfg_attr(feature = "cargo-clippy", allow(clippy::match_same_arms))] fn cause(&self) -> Option<&dyn StdError> {
fn description(&self) -> &str {
match *self {
// Try to display the first line of the server's response that usually
// contains a short humanly readable error message
Transient(ref err) => match err.first_line() {
Some(line) => line,
None => "undetailed transient error during SMTP transaction",
},
Permanent(ref err) => match err.first_line() {
Some(line) => line,
None => "undetailed permanent error during SMTP transaction",
},
ResponseParsing(err) => err,
ChallengeParsing(ref err) => err.description(),
Utf8Parsing(ref err) => err.description(),
Resolution => "could not resolve hostname",
Client(err) => err,
Io(ref err) => err.description(),
Tls(ref err) => err.description(),
Parsing(ref err) => err.description(),
}
}
fn cause(&self) -> Option<&StdError> {
match *self { match *self {
ChallengeParsing(ref err) => Some(&*err), ChallengeParsing(ref err) => Some(&*err),
Utf8Parsing(ref err) => Some(&*err), Utf8Parsing(ref err) => Some(&*err),

View File

@@ -1,7 +1,7 @@
[package] [package]
name = "lettre_email" name = "lettre_email"
version = "0.9.1" # remember to update html_root_url version = "0.9.3" # remember to update html_root_url
description = "Email builder" description = "Email builder"
readme = "README.md" readme = "README.md"
homepage = "http://lettre.at" homepage = "http://lettre.at"
@@ -23,11 +23,9 @@ lettre = { version = "^0.9", path = "../lettre", features = ["smtp-transport"] }
glob = "0.3" glob = "0.3"
[dependencies] [dependencies]
email = "^0.0.20" email = "^0.0.21"
mime = "^0.3" mime = "^0.3"
time = "^0.1" time = "^0.1"
uuid = { version = "^0.7", features = ["v4"] } uuid = { version = "^0.7", features = ["v4"] }
lettre = { version = "^0.9", path = "../lettre", default-features = false } lettre = { version = "^0.9", path = "../lettre", default-features = false }
base64 = "^0.10" base64 = "^0.10"
failure = "^0.1"
failure_derive = "^0.1"

View File

@@ -2,35 +2,52 @@
use lettre; use lettre;
use std::io; use std::io;
use std::{
error::Error as StdError,
fmt::{self, Display, Formatter},
};
use self::Error::*;
/// An enum of all error kinds. /// An enum of all error kinds.
#[derive(Debug, Fail)] #[derive(Debug)]
pub enum Error { pub enum Error {
/// Envelope error /// Envelope error
#[fail(display = "lettre error: {}", error)] Envelope(lettre::error::Error),
Envelope {
/// inner error
error: lettre::error::Error,
},
/// Unparseable filename for attachment /// Unparseable filename for attachment
#[fail(display = "the attachment filename could not be parsed")]
CannotParseFilename, CannotParseFilename,
/// IO error /// IO error
#[fail(display = "IO error: {}", error)] Io(io::Error),
Io { }
/// inner error
error: io::Error, impl Display for Error {
}, fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> {
fmt.write_str(&match *self {
CannotParseFilename => "Could not parse attachment filename".to_owned(),
Io(ref err) => err.to_string(),
Envelope(ref err) => err.to_string(),
})
}
}
impl StdError for Error {
fn cause(&self) -> Option<&dyn StdError> {
match *self {
Envelope(ref err) => Some(err),
Io(ref err) => Some(err),
_ => None,
}
}
} }
impl From<io::Error> for Error { impl From<io::Error> for Error {
fn from(err: io::Error) -> Error { fn from(err: io::Error) -> Error {
Error::Io { error: err } Error::Io(err)
} }
} }
impl From<lettre::error::Error> for Error { impl From<lettre::error::Error> for Error {
fn from(err: lettre::error::Error) -> Error { fn from(err: lettre::error::Error) -> Error {
Error::Envelope { error: err } Error::Envelope(err)
} }
} }

View File

@@ -1,7 +1,7 @@
//! Lettre is a mailer written in Rust. lettre_email provides a simple email builder. //! Lettre is a mailer written in Rust. lettre_email provides a simple email builder.
//! //!
#![doc(html_root_url = "https://docs.rs/lettre_email/0.9.1")] #![doc(html_root_url = "https://docs.rs/lettre_email/0.9.3")]
#![deny( #![deny(
missing_docs, missing_docs,
missing_debug_implementations, missing_debug_implementations,
@@ -13,22 +13,17 @@
unused_import_braces unused_import_braces
)] )]
extern crate failure;
#[macro_use]
extern crate failure_derive;
extern crate base64; extern crate base64;
extern crate email as email_format; extern crate email as email_format;
extern crate lettre; extern crate lettre;
pub extern crate mime;
extern crate time; extern crate time;
extern crate uuid; extern crate uuid;
pub extern crate mime;
pub mod error; pub mod error;
pub use email_format::{Address, Header, Mailbox, MimeMessage, MimeMultipartType}; pub use email_format::{Address, Header, Mailbox, MimeMessage, MimeMultipartType};
use error::Error as EmailError; use error::Error;
use failure::Error;
use lettre::{error::Error as LettreError, EmailAddress, Envelope, SendableEmail}; use lettre::{error::Error as LettreError, EmailAddress, Envelope, SendableEmail};
use mime::Mime; use mime::Mime;
use std::fs; use std::fs;
@@ -136,7 +131,7 @@ impl PartBuilder {
/// Adds a `ContentType` header with the given MIME type /// Adds a `ContentType` header with the given MIME type
pub fn content_type(self, content_type: &Mime) -> PartBuilder { pub fn content_type(self, content_type: &Mime) -> PartBuilder {
self.header(("Content-Type", format!("{}", content_type).as_ref())) self.header(("Content-Type", content_type.to_string()))
} }
/// Adds a child part /// Adds a child part
@@ -263,7 +258,7 @@ impl EmailBuilder {
filename.unwrap_or( filename.unwrap_or(
path.file_name() path.file_name()
.and_then(|x| x.to_str()) .and_then(|x| x.to_str())
.ok_or(EmailError::CannotParseFilename)?, .ok_or(Error::CannotParseFilename)?,
), ),
content_type, content_type,
) )
@@ -306,10 +301,7 @@ impl EmailBuilder {
pub fn text<S: Into<String>>(self, body: S) -> EmailBuilder { pub fn text<S: Into<String>>(self, body: S) -> EmailBuilder {
let text = PartBuilder::new() let text = PartBuilder::new()
.body(body) .body(body)
.header(( .header(("Content-Type", mime::TEXT_PLAIN_UTF_8.to_string()))
"Content-Type",
format!("{}", mime::TEXT_PLAIN_UTF_8).as_ref(),
))
.build(); .build();
self.child(text) self.child(text)
} }
@@ -318,10 +310,7 @@ impl EmailBuilder {
pub fn html<S: Into<String>>(self, body: S) -> EmailBuilder { pub fn html<S: Into<String>>(self, body: S) -> EmailBuilder {
let html = PartBuilder::new() let html = PartBuilder::new()
.body(body) .body(body)
.header(( .header(("Content-Type", mime::TEXT_HTML_UTF_8.to_string()))
"Content-Type",
format!("{}", mime::TEXT_HTML_UTF_8).as_ref(),
))
.build(); .build();
self.child(html) self.child(html)
} }
@@ -334,18 +323,12 @@ impl EmailBuilder {
) -> EmailBuilder { ) -> EmailBuilder {
let text = PartBuilder::new() let text = PartBuilder::new()
.body(body_text) .body(body_text)
.header(( .header(("Content-Type", mime::TEXT_PLAIN_UTF_8.to_string()))
"Content-Type",
format!("{}", mime::TEXT_PLAIN_UTF_8).as_ref(),
))
.build(); .build();
let html = PartBuilder::new() let html = PartBuilder::new()
.body(body_html) .body(body_html)
.header(( .header(("Content-Type", mime::TEXT_HTML_UTF_8.to_string()))
"Content-Type",
format!("{}", mime::TEXT_HTML_UTF_8).as_ref(),
))
.build(); .build();
let alternate = PartBuilder::new() let alternate = PartBuilder::new()
@@ -384,7 +367,7 @@ impl EmailBuilder {
} }
// Add the sender header, if any. // Add the sender header, if any.
if let Some(ref v) = self.sender { if let Some(ref v) = self.sender {
self.message = self.message.header(("Sender", v.to_string().as_ref())); self.message = self.message.header(("Sender", v.to_string()));
} }
// Calculate the envelope // Calculate the envelope
let envelope = match self.envelope { let envelope = match self.envelope {
@@ -404,7 +387,7 @@ impl EmailBuilder {
} }
} }
let from = Some(EmailAddress::from_str(&match self.sender { let from = Some(EmailAddress::from_str(&match self.sender {
Some(x) => Ok(x.address.clone()), // if we have a sender_header, use it Some(x) => Ok(x.address), // if we have a sender_header, use it
None => { None => {
// use a from header // use a from header
debug_assert!(self.from.len() <= 1); // else we'd have sender_header debug_assert!(self.from.len() <= 1); // else we'd have sender_header
@@ -416,15 +399,11 @@ impl EmailBuilder {
// if it's an author group, use the first author // if it's an author group, use the first author
Some(mailbox) => Ok(mailbox.address.clone()), Some(mailbox) => Ok(mailbox.address.clone()),
// for an empty author group (the rarest of the rare cases) // for an empty author group (the rarest of the rare cases)
None => Err(EmailError::Envelope { None => Err(Error::Envelope(LettreError::MissingFrom)), // empty envelope sender
error: LettreError::MissingFrom,
}), // empty envelope sender
}, },
}, },
// if we don't have a from header // if we don't have a from header
None => Err(EmailError::Envelope { None => Err(Error::Envelope(LettreError::MissingFrom)), // empty envelope sender
error: LettreError::MissingFrom,
}), // empty envelope sender
} }
} }
}?)?); }?)?);
@@ -448,9 +427,7 @@ impl EmailBuilder {
.message .message
.header(Header::new_with_value("From".into(), from).unwrap()); .header(Header::new_with_value("From".into(), from).unwrap());
} else { } else {
Err(EmailError::Envelope { return Err(Error::Envelope(LettreError::MissingFrom));
error: LettreError::MissingFrom,
})?;
} }
if !self.cc.is_empty() { if !self.cc.is_empty() {
self.message = self self.message = self
@@ -476,7 +453,7 @@ impl EmailBuilder {
if !self.date_issued { if !self.date_issued {
self.message = self self.message = self
.message .message
.header(("Date", Tm::rfc822z(&now()).to_string().as_ref())); .header(("Date", Tm::rfc822z(&now()).to_string()));
} }
self.message = self.message.header(("MIME-Version", "1.0")); self.message = self.message.header(("MIME-Version", "1.0"));
@@ -602,5 +579,4 @@ mod test {
.as_slice() .as_slice()
); );
} }
} }