From 8f29cb79744cb9332e64ffd71e6407111a177b3c Mon Sep 17 00:00:00 2001 From: Alexis Mousset Date: Fri, 7 Nov 2014 15:01:13 +0100 Subject: [PATCH] Improve error handling --- examples/client.rs | 4 +- src/client/mod.rs | 102 ++++++++++++++++++------------------ src/client/stream.rs | 1 + src/error.rs | 122 +++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 +- 5 files changed, 179 insertions(+), 52 deletions(-) create mode 100644 src/error.rs diff --git a/examples/client.rs b/examples/client.rs index cc4547c..9fef801 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -22,12 +22,14 @@ fn sendmail(source_address: String, recipient_addresses: Vec, message: S Client::new( server, port, - my_hostname); + my_hostname + ); let result = email_client.send_mail::( source_address, recipient_addresses, message ); + panic!(result) } fn print_usage(description: String, _opts: &[OptGroup]) { diff --git a/src/client/mod.rs b/src/client/mod.rs index beca02c..1eb0baf 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -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 { state: TransactionState } +macro_rules! try_smtp ( + ($expr:expr $sp: ident) => ({ + match $expr { + Ok(val) => val, + Err(err) => { + $sp.smtp_fail::(); + return Err(::std::error::FromError::from_error(err)) + } + } + }) +) + impl Client { /// Creates a new SMTP client pub fn new(host: String, port: Option, my_hostname: Option) -> Client { @@ -66,33 +78,30 @@ impl Client { impl Client { /// TODO - fn smtp_fail_if_err(&mut self, response: SmtpResult) { - match response { - Err(response) => { - self.smtp_fail::(response) - }, - Ok(_) => {} - } - } + // fn try_smtp(&mut self, result: SmtpResult) -> T { + // match result { + // Ok(val) => val, + // Err(err) => { + // self.smtp_fail::(); + // return Err(FromError::from_error(err)); + // } + // } + // } - /// Closes the connection and fail with a given messgage - fn smtp_fail(&mut self, reason: T) { - let is_connected = self.is_connected::(); - if is_connected { - match self.quit::() { - 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(&mut self) { + if self.is_connected::() { + let _ = self.quit::(); + self.close(); + } else { + self.close(); + }; } /// Sends an email pub fn send_mail(&mut self, from_address: String, to_addresses: Vec, message: String) -> SmtpResult { let my_hostname = self.my_hostname.clone(); - let mut smtp_result: SmtpResult; // Connect to the server try!(self.connect()); @@ -101,11 +110,12 @@ impl Client { match self.ehlo::(my_hostname.clone().to_string()) { Err(error) => match error.kind { ErrorKind::PermanentError(Response{code: 550, message: _}) => { - smtp_result = self.helo::(my_hostname.clone()); - self.smtp_fail_if_err::(smtp_result); + try_smtp!(self.helo::(my_hostname.clone()) self); + //self.smtp_fail_if_err::(smtp_result); }, _ => { - self.smtp_fail::(error) + self.smtp_fail::(); + return Err(FromError::from_error(error)) } }, _ => {} @@ -114,8 +124,8 @@ impl Client { debug!("Server {}", self.server_info.clone().unwrap()); // Mail - smtp_result = self.mail::(from_address.clone(), None); - self.smtp_fail_if_err::(smtp_result); + try_smtp!(self.mail::(from_address.clone(), None) self); + //self.smtp_fail_if_err::(smtp_result); // Log the mail command info!("from=<{}>, size={}, nrcpt={}", from_address, message.len(), to_addresses.len()); @@ -124,27 +134,23 @@ impl Client { // TODO Return rejected addresses // TODO Manage the number of recipients for to_address in to_addresses.iter() { - smtp_result = self.rcpt::(to_address.clone(), None); - self.smtp_fail_if_err::(smtp_result); + try_smtp!(self.rcpt::(to_address.clone(), None) self); + //self.smtp_fail_if_err::(smtp_result); } // Data - smtp_result = self.data::(); - self.smtp_fail_if_err::(smtp_result); + try_smtp!(self.data::() self); + //self.smtp_fail_if_err::(smtp_result); // Message content - let sent = self.message::(message.as_slice()); - - if sent.clone().is_err() { - self.smtp_fail::(sent.clone().err().unwrap()) - } + let sent = try_smtp!(self.message::(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::(); - self.smtp_fail_if_err::(smtp_result); + try_smtp!(self.quit::() self); + //self.smtp_fail_if_err::(smtp_result); return Ok(Response{code:100, message:None}); } @@ -293,8 +299,10 @@ impl Client { 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::(format!("Message is too big. The limit is {}", max)), + Some(extension::Size(max)) if message_content.len() > max => { + self.smtp_fail::(); + return Err(FromError::from_error("Message is too big")) + } _ => () }; @@ -302,7 +310,8 @@ impl Client { // TODO : Add an encoding check. if ! server_info.supports_feature(extension::EightBitMime).is_some() { if ! message_content.clone().is_ascii() { - self.smtp_fail::("Server does not accepts UTF-8 strings"); + self.smtp_fail::(); + return Err(FromError::from_error("Server does not accepts UTF-8 strings")) } } @@ -321,14 +330,7 @@ impl Client { /// Sends a QUIT command pub fn quit(&mut self) -> SmtpResult { 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 diff --git a/src/client/stream.rs b/src/client/stream.rs index c6e41e5..5ee26b6 100644 --- a/src/client/stream.rs +++ b/src/client/stream.rs @@ -71,6 +71,7 @@ impl ClientStream for TcpStream { /// Gets the SMTP response fn get_reply(&mut self) -> SmtpResult { let response = try!(self.read_into_string()); + match from_str::(response.as_slice()) { Some(response) => Ok(response), None => Err(FromError::from_error("Could not parse response")) diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..41dc9f6 --- /dev/null +++ b/src/error.rs @@ -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 or the MIT license +// , 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, +} + + +impl FromError 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 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 { + 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 = Result; diff --git a/src/lib.rs b/src/lib.rs index 2d73e40..2e17f7c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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;