diff options
author | Philipp Korber <p.korber@1aim.com> | 2018-09-06 18:15:26 +0200 |
---|---|---|
committer | Philipp Korber <p.korber@1aim.com> | 2018-09-06 18:15:26 +0200 |
commit | 708a5454bb3579ba94a922b94e1e21d177db4ff2 (patch) | |
tree | 2239d68765f71a1eb740902232770022c7afbe10 | |
parent | 8df7b8b1604bb340f25c58005e1b424255012495 (diff) |
chore(deps) use new-tokio-smtp::send_mails functionality
- reduces complexity of the interface _and_ the implementation
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | src/error.rs | 64 | ||||
-rw-r--r-- | src/lib.rs | 22 | ||||
-rw-r--r-- | src/send_mail.rs | 232 |
4 files changed, 125 insertions, 195 deletions
@@ -1,7 +1,7 @@ [package] authors = ["Philipp Korber <p.korber@1aim.com>"] name = "mail-smtp" -version = "0.1.0" +version = "0.2.0-wip" categories = [] description = "[internal/mail-api] combines mail-types with new-tokio-smtp" documentation = "https://docs.rs/mail-smtp" diff --git a/src/error.rs b/src/error.rs index 850e0ee..8cc1c8e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,10 +1,9 @@ //! Module containing all custom errors. use std::{io as std_io}; -use new_tokio_smtp::error::{ConnectingFailed, LogicError}; +use new_tokio_smtp::error::{ConnectingFailed, LogicError, GeneralError}; use mail::error::MailError; - /// Error used when sending a mail fails. /// /// Failing to encode a mail before sending @@ -12,7 +11,8 @@ use mail::error::MailError; /// it's done "on the fly" when sending a mail. #[derive(Debug, Fail)] pub enum MailSendError { - /// Creating the mail failed. + + /// Something is wrong with the mail instance (e.g. it can't be encoded). /// /// This can happen for a number of reasons including: /// @@ -32,33 +32,8 @@ pub enum MailSendError { /// 3. Server rejects sending the mail for other reasons (it's /// closing, overloaded etc.). #[fail(display = "{}", _0)] - Smtp(LogicError) -} - -impl From<MailError> for MailSendError { - fn from(err: MailError) -> Self { - MailSendError::Mail(err) - } -} + Smtp(LogicError), -impl From<LogicError> for MailSendError { - fn from(err: LogicError) -> Self { - MailSendError::Smtp(err) - } -} - -/// Error returned when something on the transport layer failed. -/// -/// Thinks causing this error include: -/// -/// - TLS required but not supported by server -/// - authentication not valid -/// - connection "broke" (e.g. because you -/// internet connection is gone or the server -/// crashed) -/// -#[derive(Debug, Fail)] -pub enum TransportError { /// Setting up the connection failed. /// /// Failures can include but are not limited to: @@ -79,14 +54,37 @@ pub enum TransportError { Io(std_io::Error) } -impl From<std_io::Error> for TransportError { +impl From<MailError> for MailSendError { + fn from(err: MailError) -> Self { + MailSendError::Mail(err) + } +} + +impl From<LogicError> for MailSendError { + fn from(err: LogicError) -> Self { + MailSendError::Smtp(err) + } +} + +impl From<std_io::Error> for MailSendError { fn from(err: std_io::Error) -> Self { - TransportError::Io(err) + MailSendError::Io(err) } } -impl From<ConnectingFailed> for TransportError { +impl From<ConnectingFailed> for MailSendError { fn from(err: ConnectingFailed) -> Self { - TransportError::Connecting(err) + MailSendError::Connecting(err) + } +} + +impl From<GeneralError> for MailSendError { + fn from(err: GeneralError) -> Self { + use self::GeneralError::*; + match err { + Connecting(err) => Self::from(err), + Cmd(err) => Self::from(err), + Io(err) => Self::from(err) + } } } @@ -30,7 +30,7 @@ //! use mail_headers::*; //! use mail_headers::components::Domain; //! use mail_types::{Mail, default_impl::simple_context}; -//! use mail_smtp::{send_mails, ConnectionConfig}; +//! use mail_smtp::{self as smtp, ConnectionConfig}; //! //! # fn main() { //! // this is normally done _once per application instance_ @@ -50,7 +50,7 @@ //! // simplified examples //! let con_config = ConnectionConfig::build_local_unencrypted().build(); //! -//! let fut = send_mails(con_config, vec![mail.into()], ctx); +//! let fut = smtp::send(mail.into(), con_config, ctx); //! let results = fut.wait(); //! # } //! ``` @@ -74,13 +74,9 @@ pub use self::request::MailRequest; #[cfg(feature="extended-api")] pub use self::request::derive_envelop_data_from_mail; -pub use self::send_mail::{ - send_mails, - SendMailResult -}; +pub use self::send_mail::{send, send_batch}; #[cfg(feature="extended-api")] -pub use self::send_mail::{encode_mails, send_encoded_mails}; - +pub use self::send_mail::encode; pub use new_tokio_smtp::{ConnectionConfig, ConnectionBuilder}; @@ -100,7 +96,11 @@ pub mod auth { pub mod misc { //! A small collection of usefull types re-exported from `new-tokio-smtp`. - pub use new_tokio_smtp::ClientId; - pub use new_tokio_smtp::Domain; - pub use new_tokio_smtp::AddressLiteral; + pub use new_tokio_smtp::{ + ClientId, + Domain, + AddressLiteral, + SetupTls, + DefaultTlsSetup + }; }
\ No newline at end of file diff --git a/src/send_mail.rs b/src/send_mail.rs index 8730647..861159e 100644 --- a/src/send_mail.rs +++ b/src/send_mail.rs @@ -1,166 +1,98 @@ -use std::iter::FromIterator; -use std::vec; +//! Module implementing mail sending using `new-tokio-smtp::send_mail`. -use futures::future::{self, Either, Loop, Future}; -use new_tokio_smtp::{Cmd, Connection, ConnectionConfig, SetupTls}; -use new_tokio_smtp::send_mail::{self as smtp}; +use std::iter::{once as one}; -use mail_common::MailType; -use mail_common::encoder::EncodingBuffer; -use mail::Context; -use mail::error::MailError; - -use ::resolve_all::ResolveAll; -use ::request::MailRequest; -use ::error::{MailSendError, TransportError}; - -/// Result of encoding a mail. -pub type EncodeMailResult = Result<smtp::MailEnvelop, MailError>; - -/// Creates a `Future` which encodes all mails. -/// -/// To encode the mails this function turns every `MailRequest` into mails with -/// envelope data, then creates a `Future` resolving when the mail is ready to -/// be encoded and chain this result by offloading the actual encoding of each -/// mail to a thread pool. Lastly all of these `Future`s are polled in parallel -/// by the returned `Future`. -/// -/// # Error -/// -/// The `Future` will never error, but it will resolve to a vector of results -/// representing the encoding result for each mail in the input separately. -pub fn encode_mails( - requests: impl IntoIterator<Item=MailRequest>, - ctx: impl Context - //TODO[futures/v>=0.2 | rust/! type]: use Never or ! -) -> impl Future<Item=Vec<EncodeMailResult>, Error=()> + Send -{ - let pending = requests - .into_iter() - .map(|request| { - let (mail, envelop_data) = - match request.into_mail_with_envelop() { - Ok(pair) => pair, - Err(e) => return Either::A(future::err(e.into())) - }; +use futures::{ + stream::{self, Stream}, + future::{self, Future, Either} +}; - let _ctx = ctx.clone(); - let fut = mail - .into_encodeable_mail(ctx.clone()) - .and_then(move |enc_mail| _ctx.offload_fn(move || { - let (mail_type, requirement) = - if envelop_data.needs_smtputf8() { - (MailType::Internationalized, smtp::EncodingRequirement::Smtputf8) - } else { - (MailType::Ascii, smtp::EncodingRequirement::None) - }; +use mail_common::{ + MailType, + encoder::EncodingBuffer +}; +use mail::Context; - let mut buffer = EncodingBuffer::new(mail_type); - enc_mail.encode(&mut buffer)?; +use new_tokio_smtp::{ + ConnectionConfig, + Cmd, + SetupTls, + send_mail::MailEnvelop, + Connection, + send_mail as smtp +}; + +use ::{ + error::MailSendError, + request::MailRequest +}; + +pub fn send<A, S>(mail: MailRequest, conconf: ConnectionConfig<A, S>, ctx: impl Context) + -> impl Future<Item=(), Error=MailSendError> + where A: Cmd, S: SetupTls +{ + let fut = encode(mail, ctx) + .then(move |envelop_res| Connection + ::connect_send_quit(conconf, one(envelop_res)) + .collect()) + .map(|mut results| results.pop().expect("[BUG] sending one mail expects one result")); - let vec_buffer: Vec<_> = buffer.into(); - let smtp_mail = smtp::Mail::new(requirement, vec_buffer); + fut +} - Ok(smtp::MailEnvelop::from((smtp_mail, envelop_data))) - })); +pub fn send_batch<A, S, C>( + mails: Vec<MailRequest>, + conconf: ConnectionConfig<A, S>, + ctx: C +) -> impl Stream<Item=(), Error=MailSendError> + where A: Cmd, S: SetupTls, C: Context +{ + let iter = mails.into_iter().map(move |mail| encode(mail, ctx.clone())); - Either::B(fut) - }); + let fut = collect_res(stream::futures_ordered(iter)) + .map(move |vec_of_res| Connection::connect_send_quit(conconf, vec_of_res)) + .flatten_stream(); - ResolveAll::from_iter(pending) + fut } -/// Results of sending an encoded mail. -pub type SendMailResult = Result<(), MailSendError>; - -/// Sends all encoded mails through the given `Connection`. -/// -/// This method accepts an iterator of `EncodedMailResult`'s as it's -/// meant to be chained with `encode_mails`. -/// -/// # Error -/// -/// The returned `Future` resolves to a vector of results, one for each mail -/// sent. -/// -/// If a transport error happens (e.g. an I/O error) a tuple consisting of -/// the `Error`, the already sent mails and and iterator of the remaining mails is -/// returned. -pub fn send_encoded_mails<I>(con: Connection, mails: I) - -> impl Future< - Item=(Connection, Vec<SendMailResult>), - Error=(TransportError, Vec<SendMailResult>, I::IntoIter)> - where I: IntoIterator<Item=EncodeMailResult>, I::IntoIter: 'static +//FIXME[futures/v>=0.2] use Error=Never +fn collect_res<S, E>(stream: S) -> impl Future<Item=Vec<Result<S::Item, S::Error>>, Error=E> + where S: Stream { - let iter = mails.into_iter(); - let results = Vec::new(); - let fut = future::loop_fn((con, iter, results), |(con, mut iter, mut results)| match iter.next() { - None => Either::A(future::ok(Loop::Break((con, results)))), - Some(Err(err)) => { - results.push(Err(MailSendError::from(err))); - Either::A(future::ok(Loop::Continue((con, iter, results)))) - }, - Some(Ok(envelop)) => Either::B(con - .send_mail(envelop) - .then(move |res| match res { - Ok((con, logic_result)) => { - results.push(logic_result.map_err(|(_idx, err)| MailSendError::from(err))); - Ok(Loop::Continue((con, iter, results))) - }, - Err(err) => { - Err((TransportError::Io(err), results, iter)) - } - })) - }); - - fut + stream.then(|res| Ok(res)).collect() } -/// Send mails to _one_ specific mail server. -/// -/// This encodes the mails, opens a connection, sends the mails over and -/// closes the connection again. -/// -/// While this uses the `To` field of a mail to determine the SMTP receiver it -/// does not resolve the server based on the mail address domain. This means -/// it's best suited for sending to a Mail Submission Agent (MSA), but less for -/// sending to a Mail Exchanger (MX). -/// -/// Automatically handling `Bcc`/`Cc` is _not yet_ implemented. -pub fn send_mails( - config: ConnectionConfig<impl Cmd, impl SetupTls>, - requests: impl IntoIterator<Item=MailRequest>, - ctx: impl Context) - -> impl Future< - Item=Vec<SendMailResult>, - Error=(TransportError, Vec<SendMailResult>, vec::IntoIter<EncodeMailResult>)> + +pub fn encode<C>(request: MailRequest, ctx: C) + -> impl Future<Item=MailEnvelop, Error=MailSendError> + where C: Context { + let (mail, envelop_data) = + match request.into_mail_with_envelop() { + Ok(pair) => pair, + Err(e) => return Either::A(future::err(e.into())) + }; + + let fut = mail + .into_encodeable_mail(ctx.clone()) + .and_then(move |enc_mail| ctx.offload_fn(move || { + let (mail_type, requirement) = + if envelop_data.needs_smtputf8() { + (MailType::Internationalized, smtp::EncodingRequirement::Smtputf8) + } else { + (MailType::Ascii, smtp::EncodingRequirement::None) + }; - let fut = encode_mails(requests, ctx) - .map_err(|_| unreachable!()) - .and_then(|mails| { - if mails.iter().all(|r| r.is_err()) { - let send_skipped = mails - .into_iter() - .map(|result| match result { - Ok(_) => unreachable!(), - Err(err) => Err(MailSendError::Mail(err)) - }) - .collect(); - - Either::A(future::ok(send_skipped)) - } else { - let fut = Connection - ::connect(config) - .then(|result| match result { - Err(err) => Either::A(future::err((TransportError::Connecting(err), Vec::new(), mails.into_iter()))), - Ok(con) => Either::B(send_encoded_mails(con, mails)) - }) - .and_then(|(con, results)| con.quit().then(|_| Ok(results))); - - Either::B(fut) - } - }); + let mut buffer = EncodingBuffer::new(mail_type); + enc_mail.encode(&mut buffer)?; - fut -} + let vec_buffer: Vec<_> = buffer.into(); + let smtp_mail = smtp::Mail::new(requirement, vec_buffer); + + Ok(smtp::MailEnvelop::from((smtp_mail, envelop_data))) + })) + .map_err(MailSendError::from); + + Either::B(fut) +}
\ No newline at end of file |