Improve error handling

This commit is contained in:
Alexis Mousset
2014-11-07 15:01:13 +01:00
parent 94d79862ff
commit 8f29cb7974
5 changed files with 179 additions and 52 deletions

View File

@@ -22,12 +22,14 @@ fn sendmail(source_address: String, recipient_addresses: Vec<String>, message: S
Client::new(
server,
port,
my_hostname);
my_hostname
);
let result = email_client.send_mail::<TcpStream>(
source_address,
recipient_addresses,
message
);
panic!(result)
}
fn print_usage(description: String, _opts: &[OptGroup]) {

View File

@@ -9,9 +9,9 @@
//! SMTP client
use std::fmt::Show;
use std::string::String;
use std::io::net::ip::Port;
use std::error::FromError;
use common::{get_first_word, unquote_email_address};
use common::{CRLF, SMTP_PORT};
@@ -22,7 +22,7 @@ use command;
use command::Command;
use transaction;
use transaction::TransactionState;
use error::{SmtpResult, SmtpError, ErrorKind};
use error::{SmtpResult, ErrorKind};
use client::connecter::Connecter;
use client::server_info::ServerInfo;
use client::stream::ClientStream;
@@ -49,6 +49,18 @@ pub struct Client<S> {
state: TransactionState
}
macro_rules! try_smtp (
($expr:expr $sp: ident) => ({
match $expr {
Ok(val) => val,
Err(err) => {
$sp.smtp_fail::<S>();
return Err(::std::error::FromError::from_error(err))
}
}
})
)
impl<S> Client<S> {
/// Creates a new SMTP client
pub fn new(host: String, port: Option<Port>, my_hostname: Option<String>) -> Client<S> {
@@ -66,33 +78,30 @@ impl<S> Client<S> {
impl<S: Connecter + ClientStream + Clone> Client<S> {
/// TODO
fn smtp_fail_if_err<S>(&mut self, response: SmtpResult<Response>) {
match response {
Err(response) => {
self.smtp_fail::<S, SmtpError>(response)
},
Ok(_) => {}
}
}
// fn try_smtp<S, T>(&mut self, result: SmtpResult<T>) -> T {
// match result {
// Ok(val) => val,
// Err(err) => {
// self.smtp_fail::<S>();
// return Err(FromError::from_error(err));
// }
// }
// }
/// Closes the connection and fail with a given messgage
fn smtp_fail<S, T: Show>(&mut self, reason: T) {
let is_connected = self.is_connected::<S>();
if is_connected {
match self.quit::<S>() {
Ok(..) => {},
Err(response) => panic!("Failed: {}", response)
}
}
self.close();
panic!("Failed: {}", reason);
/// Closes the SMTP transaction if possible, and then closes the TCP session
fn smtp_fail<S>(&mut self) {
if self.is_connected::<S>() {
let _ = self.quit::<S>();
self.close();
} else {
self.close();
};
}
/// Sends an email
pub fn send_mail<S>(&mut self, from_address: String,
to_addresses: Vec<String>, message: String) -> SmtpResult<Response> {
let my_hostname = self.my_hostname.clone();
let mut smtp_result: SmtpResult<Response>;
// Connect to the server
try!(self.connect());
@@ -101,11 +110,12 @@ impl<S: Connecter + ClientStream + Clone> Client<S> {
match self.ehlo::<S>(my_hostname.clone().to_string()) {
Err(error) => match error.kind {
ErrorKind::PermanentError(Response{code: 550, message: _}) => {
smtp_result = self.helo::<S>(my_hostname.clone());
self.smtp_fail_if_err::<S>(smtp_result);
try_smtp!(self.helo::<S>(my_hostname.clone()) self);
//self.smtp_fail_if_err::<S>(smtp_result);
},
_ => {
self.smtp_fail::<S, SmtpError>(error)
self.smtp_fail::<S>();
return Err(FromError::from_error(error))
}
},
_ => {}
@@ -114,8 +124,8 @@ impl<S: Connecter + ClientStream + Clone> Client<S> {
debug!("Server {}", self.server_info.clone().unwrap());
// Mail
smtp_result = self.mail::<S>(from_address.clone(), None);
self.smtp_fail_if_err::<S>(smtp_result);
try_smtp!(self.mail::<S>(from_address.clone(), None) self);
//self.smtp_fail_if_err::<S>(smtp_result);
// Log the mail command
info!("from=<{}>, size={}, nrcpt={}", from_address, message.len(), to_addresses.len());
@@ -124,27 +134,23 @@ impl<S: Connecter + ClientStream + Clone> Client<S> {
// TODO Return rejected addresses
// TODO Manage the number of recipients
for to_address in to_addresses.iter() {
smtp_result = self.rcpt::<S>(to_address.clone(), None);
self.smtp_fail_if_err::<S>(smtp_result);
try_smtp!(self.rcpt::<S>(to_address.clone(), None) self);
//self.smtp_fail_if_err::<S>(smtp_result);
}
// Data
smtp_result = self.data::<S>();
self.smtp_fail_if_err::<S>(smtp_result);
try_smtp!(self.data::<S>() self);
//self.smtp_fail_if_err::<S>(smtp_result);
// Message content
let sent = self.message::<S>(message.as_slice());
if sent.clone().is_err() {
self.smtp_fail::<S, SmtpError>(sent.clone().err().unwrap())
}
let sent = try_smtp!(self.message::<S>(message.as_slice()) self);
info!("to=<{}>, status=sent ({})",
to_addresses.clone().connect(">, to=<"), sent.clone().ok().unwrap());
to_addresses.clone().connect(">, to=<"), sent.clone());
// Quit
smtp_result = self.quit::<S>();
self.smtp_fail_if_err::<S>(smtp_result);
try_smtp!(self.quit::<S>() self);
//self.smtp_fail_if_err::<S>(smtp_result);
return Ok(Response{code:100, message:None});
}
@@ -293,8 +299,10 @@ impl<S: Connecter + ClientStream + Clone> Client<S> {
let server_info = self.server_info.clone().expect("Bad command sequence");
// Get maximum message size if defined and compare to the message size
match server_info.supports_feature(extension::Size(0)) {
Some(extension::Size(max)) if message_content.len() > max =>
self.smtp_fail::<S, String>(format!("Message is too big. The limit is {}", max)),
Some(extension::Size(max)) if message_content.len() > max => {
self.smtp_fail::<S>();
return Err(FromError::from_error("Message is too big"))
}
_ => ()
};
@@ -302,7 +310,8 @@ impl<S: Connecter + ClientStream + Clone> Client<S> {
// TODO : Add an encoding check.
if ! server_info.supports_feature(extension::EightBitMime).is_some() {
if ! message_content.clone().is_ascii() {
self.smtp_fail::<S, &str>("Server does not accepts UTF-8 strings");
self.smtp_fail::<S>();
return Err(FromError::from_error("Server does not accepts UTF-8 strings"))
}
}
@@ -321,14 +330,7 @@ impl<S: Connecter + ClientStream + Clone> Client<S> {
/// Sends a QUIT command
pub fn quit<S>(&mut self) -> SmtpResult<Response> {
let res = try!(self.send_command(command::Quit));
match res.with_code(vec!(221)) {
Ok(response) => {
Ok(response)
},
Err(response) => {
Err(response)
}
}
res.with_code(vec!(221))
}
/// Sends a RSET command

View File

@@ -71,6 +71,7 @@ impl ClientStream for TcpStream {
/// Gets the SMTP response
fn get_reply(&mut self) -> SmtpResult<Response> {
let response = try!(self.read_into_string());
match from_str::<Response>(response.as_slice()) {
Some(response) => Ok(response),
None => Err(FromError::from_error("Could not parse response"))

122
src/error.rs Normal file
View File

@@ -0,0 +1,122 @@
// Copyright 2014 Alexis Mousset. See the COPYRIGHT
// file at the top-level directory of this distribution.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! TODO
use std::error::Error;
use std::io::IoError;
use std::error::FromError;
use response::Response;
/// An enum of all error kinds.
#[deriving(PartialEq, Eq, Clone, Show)]
pub enum ErrorKind {
/// TODO
TransientError(Response),
/// TODO
PermanentError(Response),
/// TODO
UnknownError(String),
/// TODO
InternalIoError(IoError),
}
/// TODO
#[deriving(PartialEq, Eq, Clone, Show)]
pub struct SmtpError {
/// TODO
pub kind: ErrorKind,
/// TODO
pub desc: &'static str,
/// TODO
pub detail: Option<String>,
}
impl FromError<IoError> for SmtpError {
fn from_error(err: IoError) -> SmtpError {
SmtpError {
kind: InternalIoError(err),
desc: "An internal IO error ocurred.",
detail: None
}
}
}
impl FromError<(ErrorKind, &'static str)> for SmtpError {
fn from_error((kind, desc): (ErrorKind, &'static str)) -> SmtpError {
SmtpError {
kind: kind,
desc: desc,
detail: None,
}
}
}
impl FromError<Response> for SmtpError {
fn from_error(response: Response) -> SmtpError {
let kind = match response.code/100 {
4 => TransientError(response.clone()),
5 => PermanentError(response.clone()),
_ => UnknownError(response.clone().to_string()),
};
let desc = match kind {
TransientError(_) => "a permanent error occured during the SMTP transaction",
PermanentError(_) => "a permanent error occured during the SMTP transaction",
UnknownError(_) => "an unknown error occured during the SMTP transaction",
InternalIoError(_) => "an I/O error occurred",
};
SmtpError {
kind: kind,
desc: desc,
detail: None
}
}
}
impl FromError<&'static str> for SmtpError {
fn from_error(string: &'static str) -> SmtpError {
SmtpError {
kind: UnknownError(string.to_string()),
desc: "an unknown error occured during the SMTP transaction",
detail: None
}
}
}
impl Error for SmtpError {
fn description(&self) -> &str {
match self.kind {
TransientError(_) => "a permanent error occured during the SMTP transaction",
PermanentError(_) => "a permanent error occured during the SMTP transaction",
UnknownError(_) => "an unknown error occured during the SMTP transaction",
InternalIoError(_) => "an I/O error occurred",
}
}
fn detail(&self) -> Option<String> {
match self.kind {
TransientError(ref response) => Some(response.to_string()),
PermanentError(ref response) => Some(response.to_string()),
UnknownError(ref string) => Some(string.to_string()),
_ => None,
}
}
fn cause(&self) -> Option<&Error> {
match self.kind {
InternalIoError(ref err) => Some(&*err as &Error),
_ => None,
}
}
}
/// Library generic result type
pub type SmtpResult<T> = Result<T, SmtpError>;

View File

@@ -48,7 +48,7 @@
#![doc(html_root_url = "http://amousset.github.io/rust-smtp/smtp/")]
#![experimental]
#![feature(phase)]
#![feature(phase, macro_rules)]
#![deny(missing_docs, warnings)]
#![feature(phase)] #[phase(plugin, link)] extern crate log;