Merge branch 'use-failure' into develop

This commit is contained in:
Aaron Weiss 2018-01-28 01:16:38 +01:00
commit e4d441695e
No known key found for this signature in database
GPG key ID: 047D32DF25DC22EF
14 changed files with 513 additions and 269 deletions

View file

@ -25,7 +25,7 @@ bufstream = "0.1"
bytes = "0.4"
chrono = "0.4"
encoding = "0.2"
error-chain = "0.10"
failure = "0.1"
futures = "0.1"
log = "0.3"
native-tls = "0.1"

View file

@ -16,7 +16,7 @@ fn main() {
let args: Vec<_> = env::args().collect();
match parse(&args) {
Ok(Some((ref input, ref output))) => {
let cfg = Config::load(input).unwrap();
let mut cfg = Config::load(input).unwrap();
cfg.save(output).unwrap();
println!("Converted {} to {}.", input, output);
}

View file

@ -1,6 +1,6 @@
//! A module providing IRC connections for use by `IrcServer`s.
use std::fs::File;
use std::{fmt, io};
use std::fmt;
use std::io::Read;
use encoding::EncoderTrap;
@ -43,7 +43,7 @@ impl fmt::Debug for Connection {
}
/// A convenient type alias representing the `TlsStream` future.
type TlsFuture = Box<Future<Error = error::Error, Item = TlsStream<TcpStream>> + Send>;
type TlsFuture = Box<Future<Error = error::IrcError, Item = TlsStream<TcpStream>> + Send>;
/// A future representing an eventual `Connection`.
pub enum ConnectionFuture<'a> {
@ -76,7 +76,7 @@ impl<'a> fmt::Debug for ConnectionFuture<'a> {
impl<'a> Future for ConnectionFuture<'a> {
type Item = Connection;
type Error = error::Error;
type Error = error::IrcError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
match *self {
@ -95,21 +95,18 @@ impl<'a> Future for ConnectionFuture<'a> {
ConnectionFuture::Mock(config) => {
let enc: error::Result<_> = encoding_from_whatwg_label(
config.encoding()
).ok_or_else(|| io::Error::new(
io::ErrorKind::InvalidInput,
&format!("Attempted to use unknown codec {}.", config.encoding())[..],
).into());
).ok_or_else(|| error::IrcError::UnknownCodec {
codec: config.encoding().to_owned(),
});
let encoding = enc?;
let init_str = config.mock_initial_value();
let initial: error::Result<_> = {
encoding.encode(init_str, EncoderTrap::Replace).map_err(
|data| {
io::Error::new(
io::ErrorKind::InvalidInput,
&format!("Failed to encode {} as {}.", data, encoding.name())[..],
).into()
},
)
encoding.encode(init_str, EncoderTrap::Replace).map_err(|data| {
error::IrcError::CodecFailed {
codec: encoding.name(),
data: data.into_owned(),
}
})
};
let framed = MockStream::new(&initial?).framed(IrcCodec::new(config.encoding())?);
@ -139,17 +136,14 @@ impl Connection {
info!("Added {} to trusted certificates.", cert_path);
}
let connector = builder.build()?;
let stream = Box::new(TcpStream::connect(&config.socket_addr()?, handle)
.map_err(|e| {
let res: error::Error = e.into();
res
})
.and_then(move |socket| {
connector.connect_async(&domain, socket).map_err(
|e| e.into(),
)
}
));
let stream = Box::new(TcpStream::connect(&config.socket_addr()?, handle).map_err(|e| {
let res: error::IrcError = e.into();
res
}).and_then(move |socket| {
connector.connect_async(&domain, socket).map_err(
|e| e.into(),
)
}));
Ok(ConnectionFuture::Secured(config, stream))
} else {
info!("Connecting to {}.", config.server()?);
@ -172,7 +166,7 @@ impl Connection {
impl Stream for Connection {
type Item = Message;
type Error = error::Error;
type Error = error::IrcError;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
match *self {
@ -185,7 +179,7 @@ impl Stream for Connection {
impl Sink for Connection {
type SinkItem = Message;
type SinkError = error::Error;
type SinkError = error::IrcError;
fn start_send(&mut self, item: Self::SinkItem) -> StartSend<Self::SinkItem, Self::SinkError> {
match *self {

View file

@ -3,9 +3,8 @@ use std::borrow::ToOwned;
use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;
use std::io::{Error, ErrorKind};
use std::net::{SocketAddr, ToSocketAddrs};
use std::path::Path;
use std::path::{Path, PathBuf};
#[cfg(feature = "json")]
use serde_json;
@ -14,8 +13,10 @@ use serde_yaml;
#[cfg(feature = "toml")]
use toml;
use error;
use error::{Result, ResultExt};
#[cfg(feature = "toml")]
use error::TomlError;
use error::{ConfigError, Result};
use error::IrcError::InvalidConfig;
/// Configuration data.
#[derive(Clone, Deserialize, Serialize, Default, PartialEq, Debug)]
@ -88,9 +89,25 @@ pub struct Config {
pub channel_keys: Option<HashMap<String, String>>,
/// A map of additional options to be stored in config.
pub options: Option<HashMap<String, String>>,
/// The path that this configuration was loaded from.
///
/// This should not be specified in any configuration. It will automatically be handled by the library.
pub path: Option<PathBuf>,
}
impl Config {
fn with_path<P: AsRef<Path>>(mut self, path: P) -> Config {
self.path = Some(path.as_ref().to_owned());
self
}
fn path(&self) -> String {
self.path.as_ref().map(|buf| buf.to_string_lossy().into_owned()).unwrap_or_else(|| {
"<none>".to_owned()
})
}
/// Loads a configuration from the desired path. This will use the file extension to detect
/// which format to parse the file as (json, toml, or yaml). Using each format requires having
/// its respective crate feature enabled. Only json is available by default.
@ -99,155 +116,171 @@ impl Config {
let mut data = String::new();
file.read_to_string(&mut data)?;
match path.as_ref().extension().and_then(|s| s.to_str()) {
Some("json") => Config::load_json(&data),
Some("toml") => Config::load_toml(&data),
Some("yaml") | Some("yml") => Config::load_yaml(&data),
Some(ext) => Err(Error::new(
ErrorKind::InvalidInput,
format!("Failed to decode configuration of unknown format {}", ext),
).into()),
None => Err(Error::new(
ErrorKind::InvalidInput,
"Failed to decode configuration of missing or non-unicode format.",
).into()),
}
let res = match path.as_ref().extension().and_then(|s| s.to_str()) {
Some("json") => Config::load_json(&path, &data),
Some("toml") => Config::load_toml(&path, &data),
Some("yaml") | Some("yml") => Config::load_yaml(&path, &data),
Some(ext) => Err(InvalidConfig {
path: path.as_ref().to_string_lossy().into_owned(),
cause: ConfigError::UnknownConfigFormat {
format: ext.to_owned(),
},
}),
None => Err(InvalidConfig {
path: path.as_ref().to_string_lossy().into_owned(),
cause: ConfigError::MissingExtension,
}),
};
res.map(|config| {
config.with_path(path)
})
}
#[cfg(feature = "json")]
fn load_json(data: &str) -> Result<Config> {
serde_json::from_str(&data[..]).chain_err(|| {
let e: error::Error = Error::new(
ErrorKind::InvalidInput,
"Failed to decode JSON configuration file.",
).into();
e
fn load_json<P: AsRef<Path>>(path: &P, data: &str) -> Result<Config> {
serde_json::from_str(&data[..]).map_err(|e| {
InvalidConfig {
path: path.as_ref().to_string_lossy().into_owned(),
cause: ConfigError::InvalidJson(e),
}
})
}
#[cfg(not(feature = "json"))]
fn load_json(_: &str) -> Result<Config> {
Err(Error::new(
ErrorKind::InvalidInput,
"JSON file decoding is disabled.",
).into())
fn load_json<P: AsRef<Path>>(path: &P, _: &str) -> Result<Config> {
Err(InvalidConfig {
path: path.as_ref().to_string_lossy().into_owned(),
cause: ConfigError::ConfigFormatDisabled {
format: "JSON"
}
})
}
#[cfg(feature = "toml")]
fn load_toml(data: &str) -> Result<Config> {
toml::from_str(&data[..]).chain_err(|| {
let e: error::Error = Error::new(
ErrorKind::InvalidInput,
"Failed to decode TOML configuration file.",
).into();
e
fn load_toml<P: AsRef<Path>>(path: &P, data: &str) -> Result<Config> {
toml::from_str(&data[..]).map_err(|e| {
InvalidConfig {
path: path.as_ref().to_string_lossy().into_owned(),
cause: ConfigError::InvalidToml(TomlError::Read(e)),
}
})
}
#[cfg(not(feature = "toml"))]
fn load_toml(_: &str) -> Result<Config> {
Err(Error::new(
ErrorKind::InvalidInput,
"TOML file decoding is disabled.",
).into())
fn load_toml<P: AsRef<Path>>(path: &P, _: &str) -> Result<Config> {
Err(InvalidConfig {
path: path.as_ref().to_string_lossy().into_owned(),
cause: ConfigError::ConfigFormatDisabled {
format: "TOML"
}
})
}
#[cfg(feature = "yaml")]
fn load_yaml(data: &str) -> Result<Config> {
serde_yaml::from_str(&data[..]).chain_err(|| {
let e: error::Error = Error::new(
ErrorKind::InvalidInput,
"Failed to decode YAML configuration file.",
).into();
e
fn load_yaml<P: AsRef<Path>>(path: &P, data: &str) -> Result<Config> {
serde_yaml::from_str(&data[..]).map_err(|e| {
InvalidConfig {
path: path.as_ref().to_string_lossy().into_owned(),
cause: ConfigError::InvalidYaml(e),
}
})
}
#[cfg(not(feature = "yaml"))]
fn load_yaml(_: &str) -> Result<Config> {
Err(Error::new(
ErrorKind::InvalidInput,
"YAML file decoding is disabled.",
).into())
fn load_yaml<P: AsRef<Path>>(path: &P, _: &str) -> Result<Config> {
Err(InvalidConfig {
path: path.as_ref().to_string_lossy().into_owned(),
cause: ConfigError::ConfigFormatDisabled {
format: "YAML"
}
})
}
/// Saves a configuration to the desired path. This will use the file extension to detect
/// which format to parse the file as (json, toml, or yaml). Using each format requires having
/// its respective crate feature enabled. Only json is available by default.
pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> {
pub fn save<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
let _ = self.path.take();
let mut file = File::create(&path)?;
let data = match path.as_ref().extension().and_then(|s| s.to_str()) {
Some("json") => self.save_json()?,
Some("toml") => self.save_toml()?,
Some("yaml") | Some("yml") => self.save_yaml()?,
Some(ext) => return Err(Error::new(
ErrorKind::InvalidInput,
format!("Failed to encode configuration of unknown format {}", ext),
).into()),
None => return Err(Error::new(
ErrorKind::InvalidInput,
"Failed to encode configuration of missing or non-unicode format.",
).into()),
Some("json") => self.save_json(&path)?,
Some("toml") => self.save_toml(&path)?,
Some("yaml") | Some("yml") => self.save_yaml(&path)?,
Some(ext) => return Err(InvalidConfig {
path: path.as_ref().to_string_lossy().into_owned(),
cause: ConfigError::UnknownConfigFormat {
format: ext.to_owned(),
},
}),
None => return Err(InvalidConfig {
path: path.as_ref().to_string_lossy().into_owned(),
cause: ConfigError::MissingExtension,
}),
};
file.write_all(data.as_bytes())?;
self.path = Some(path.as_ref().to_owned());
Ok(())
}
#[cfg(feature = "json")]
fn save_json(&self) -> Result<String> {
serde_json::to_string(self).chain_err(|| {
let e: error::Error = Error::new(
ErrorKind::InvalidInput,
"Failed to encode JSON configuration file.",
).into();
e
fn save_json<P: AsRef<Path>>(&self, path: &P) -> Result<String> {
serde_json::to_string(self).map_err(|e| {
InvalidConfig {
path: path.as_ref().to_string_lossy().into_owned(),
cause: ConfigError::InvalidJson(e),
}
})
}
#[cfg(not(feature = "json"))]
fn save_json(&self) -> Result<String> {
Err(Error::new(
ErrorKind::InvalidInput,
"JSON file encoding is disabled.",
).into())
fn save_json<P: AsRef<Path>>(&self, path: &P) -> Result<String> {
Err(InvalidConfig {
path: path.as_ref().to_string_lossy().into_owned(),
cause: ConfigError::ConfigFormatDisabled {
format: "JSON"
}
})
}
#[cfg(feature = "toml")]
fn save_toml(&self) -> Result<String> {
toml::to_string(self).chain_err(|| {
let e: error::Error = Error::new(
ErrorKind::InvalidInput,
"Failed to encode TOML configuration file.",
).into();
e
fn save_toml<P: AsRef<Path>>(&self, path: &P) -> Result<String> {
toml::to_string(self).map_err(|e| {
InvalidConfig {
path: path.as_ref().to_string_lossy().into_owned(),
cause: ConfigError::InvalidToml(TomlError::Write(e)),
}
})
}
#[cfg(not(feature = "toml"))]
fn save_toml(&self) -> Result<String> {
Err(Error::new(
ErrorKind::InvalidInput,
"TOML file encoding is disabled.",
).into())
fn save_toml<P: AsRef<Path>>(&self, path: &P) -> Result<String> {
Err(InvalidConfig {
path: path.as_ref().to_string_lossy().into_owned(),
cause: ConfigError::ConfigFormatDisabled {
format: "TOML"
}
})
}
#[cfg(feature = "yaml")]
fn save_yaml(&self) -> Result<String> {
serde_yaml::to_string(self).chain_err(|| {
let e: error::Error = Error::new(
ErrorKind::InvalidInput,
"Failed to encode YAML configuration file.",
).into();
e
fn save_yaml<P: AsRef<Path>>(&self, path: &P) -> Result<String> {
serde_yaml::to_string(self).map_err(|e| {
InvalidConfig {
path: path.as_ref().to_string_lossy().into_owned(),
cause: ConfigError::InvalidYaml(e),
}
})
}
#[cfg(not(feature = "yaml"))]
fn save_yaml(&self) -> Result<String> {
Err(Error::new(
ErrorKind::InvalidInput,
"YAML file encoding is disabled.",
).into())
fn save_yaml<P: AsRef<Path>>(&self, path: &P) -> Result<String> {
Err(InvalidConfig {
path: path.as_ref().to_string_lossy().into_owned(),
cause: ConfigError::ConfigFormatDisabled {
format: "YAML"
}
})
}
/// Determines whether or not the nickname provided is the owner of the bot.
@ -260,7 +293,12 @@ impl Config {
/// Gets the nickname specified in the configuration.
pub fn nickname(&self) -> Result<&str> {
self.nickname.as_ref().map(|s| &s[..]).ok_or(error::ErrorKind::NicknameNotSpecified.into())
self.nickname.as_ref().map(|s| &s[..]).ok_or_else(|| {
InvalidConfig {
path: self.path(),
cause: ConfigError::NicknameNotSpecified,
}
})
}
/// Gets the bot's nickserv password specified in the configuration.
@ -292,8 +330,12 @@ impl Config {
/// Gets the address of the server specified in the configuration.
pub fn server(&self) -> Result<&str> {
self.server.as_ref().map(|s| &s[..]).ok_or(error::ErrorKind::NicknameNotSpecified.into())
self.server.as_ref().map(|s| &s[..]).ok_or_else(|| {
InvalidConfig {
path: self.path(),
cause: ConfigError::ServerNotSpecified,
}
})
}
/// Gets the port of the server specified in the configuration.
@ -487,31 +529,27 @@ mod test {
options: Some(HashMap::new()),
use_mock_connection: None,
mock_initial_value: None,
..Default::default()
}
}
#[test]
#[cfg(feature = "json")]
fn load() {
assert_eq!(Config::load(Path::new("client_config.json")).unwrap(), test_config());
}
#[test]
#[cfg(feature = "json")]
fn load_from_str() {
assert_eq!(Config::load("client_config.json").unwrap(), test_config());
fn load_from_json() {
assert_eq!(Config::load("client_config.json").unwrap(), test_config().with_path("client_config.json"));
}
#[test]
#[cfg(feature = "toml")]
fn load_from_toml() {
assert_eq!(Config::load("client_config.toml").unwrap(), test_config());
assert_eq!(Config::load("client_config.toml").unwrap(), test_config().with_path("client_config.toml"));
}
#[test]
#[cfg(feature = "yaml")]
fn load_from_yaml() {
assert_eq!(Config::load("client_config.yaml").unwrap(), test_config());
assert_eq!(Config::load("client_config.yaml").unwrap(), test_config().with_path("client_config.yaml"));
}
#[test]

View file

@ -41,7 +41,7 @@ use proto::Message;
/// For a full example usage, see [`irc::client::reactor`](./index.html).
pub struct IrcReactor {
inner: Core,
handlers: Vec<Box<Future<Item = (), Error = error::Error>>>,
handlers: Vec<Box<Future<Item = (), Error = error::IrcError>>>,
}
impl IrcReactor {
@ -139,7 +139,7 @@ impl IrcReactor {
pub fn register_server_with_handler<F, U>(
&mut self, server: IrcServer, handler: F
) where F: Fn(&IrcServer, Message) -> U + 'static,
U: IntoFuture<Item = (), Error = error::Error> + 'static {
U: IntoFuture<Item = (), Error = error::IrcError> + 'static {
self.handlers.push(Box::new(server.stream().for_each(move |message| {
handler(&server, message)
})));
@ -151,7 +151,7 @@ impl IrcReactor {
/// be sufficient for most use cases.
pub fn register_future<F>(
&mut self, future: F
) where F: IntoFuture<Item = (), Error = error::Error> + 'static {
) where F: IntoFuture<Item = (), Error = error::IrcError> + 'static {
self.handlers.push(Box::new(future.into_future()))
}

View file

@ -95,7 +95,7 @@ pub mod utils;
/// }).unwrap();
/// # }
/// ```
pub trait EachIncomingExt: Stream<Item=Message, Error=error::Error> {
pub trait EachIncomingExt: Stream<Item=Message, Error=error::IrcError> {
/// Blocks on the stream, running the given function on each incoming message as they arrive.
fn for_each_incoming<F>(self, mut f: F) -> error::Result<()>
where
@ -109,7 +109,7 @@ pub trait EachIncomingExt: Stream<Item=Message, Error=error::Error> {
}
}
impl<T> EachIncomingExt for T where T: Stream<Item=Message, Error=error::Error> {}
impl<T> EachIncomingExt for T where T: Stream<Item=Message, Error=error::IrcError> {}
/// An interface for communicating with an IRC server.
pub trait Server {
@ -209,7 +209,7 @@ pub struct ServerStream {
impl Stream for ServerStream {
type Item = Message;
type Error = error::Error;
type Error = error::IrcError;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
match try_ready!(self.stream.poll()) {
@ -439,7 +439,16 @@ impl ServerState {
if self.config().umodes().is_empty() {
Ok(())
} else {
self.send_mode(self.current_nickname(), &Mode::as_user_modes(self.config().umodes())?)
self.send_mode(
self.current_nickname(), &Mode::as_user_modes(self.config().umodes()).map_err(|e| {
error::IrcError::InvalidMessage {
string: format!(
"MODE {} {}", self.current_nickname(), self.config().umodes()
),
cause: e,
}
})?
)
}
}
@ -718,9 +727,8 @@ impl IrcServer {
tx_view.send(conn.log_view()).unwrap();
let (sink, stream) = conn.split();
let outgoing_future = sink.send_all(rx_outgoing.map_err(|_| {
let res: error::Error = error::ErrorKind::ChannelError.into();
res
let outgoing_future = sink.send_all(rx_outgoing.map_err::<error::IrcError, _>(|_| {
unreachable!("futures::sync::mpsc::Receiver should never return Err");
})).map(|_| ()).map_err(|e| panic!("{}", e));
// Send the stream half back to the original thread.
@ -818,7 +826,7 @@ pub struct IrcServerFuture<'a> {
impl<'a> Future for IrcServerFuture<'a> {
type Item = PackedIrcServer;
type Error = error::Error;
type Error = error::IrcError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let conn = try_ready!(self.conn.poll());
@ -826,10 +834,11 @@ impl<'a> Future for IrcServerFuture<'a> {
let view = conn.log_view();
let (sink, stream) = conn.split();
let outgoing_future = sink.send_all(self.rx_outgoing.take().unwrap().map_err(|()| {
let res: error::Error = error::ErrorKind::ChannelError.into();
res
})).map(|_| ());
let outgoing_future = sink.send_all(
self.rx_outgoing.take().unwrap().map_err::<error::IrcError, _>(|()| {
unreachable!("futures::sync::mpsc::Receiver should never return Err");
})
).map(|_| ());
let server = IrcServer {
state: Arc::new(ServerState::new(
@ -847,7 +856,7 @@ impl<'a> Future for IrcServerFuture<'a> {
/// This type should only be used by advanced users who are familiar with the implementation of this
/// crate. An easy to use abstraction that does not require this knowledge is available via
/// [`IrcReactors`](../reactor/struct.IrcReactor.html).
pub struct PackedIrcServer(pub IrcServer, pub Box<Future<Item = (), Error = error::Error>>);
pub struct PackedIrcServer(pub IrcServer, pub Box<Future<Item = (), Error = error::IrcError>>);
#[cfg(test)]
mod test {

View file

@ -88,12 +88,12 @@ where
T: AsyncRead + AsyncWrite,
{
type Item = Message;
type Error = error::Error;
type Error = error::IrcError;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
if self.ping_timed_out() {
self.close()?;
return Err(error::ErrorKind::PingTimeout.into())
return Err(error::IrcError::PingTimeout)
}
let timer_poll = self.ping_timer.poll()?;
@ -144,12 +144,12 @@ where
T: AsyncRead + AsyncWrite,
{
type SinkItem = Message;
type SinkError = error::Error;
type SinkError = error::IrcError;
fn start_send(&mut self, item: Self::SinkItem) -> StartSend<Self::SinkItem, Self::SinkError> {
if self.ping_timed_out() {
self.close()?;
Err(error::ErrorKind::PingTimeout.into())
Err(error::IrcError::PingTimeout)
} else {
// Check if the oldest message in the rolling window is discounted.
if let Async::Ready(()) = self.rolling_burst_window_front()? {
@ -180,7 +180,7 @@ where
fn poll_complete(&mut self) -> Poll<(), Self::SinkError> {
if self.ping_timed_out() {
self.close()?;
Err(error::ErrorKind::PingTimeout.into())
Err(error::IrcError::PingTimeout)
} else {
Ok(self.inner.poll_complete()?)
}
@ -201,16 +201,12 @@ pub struct LogView {
impl LogView {
/// Gets a read guard for all the messages sent on the transport.
pub fn sent(&self) -> error::Result<RwLockReadGuard<Vec<Message>>> {
self.sent.read().map_err(
|_| error::ErrorKind::PoisonedLog.into(),
)
self.sent.read().map_err(|_| error::IrcError::PoisonedLog)
}
/// Gets a read guard for all the messages received on the transport.
pub fn received(&self) -> error::Result<RwLockReadGuard<Vec<Message>>> {
self.received.read().map_err(
|_| error::ErrorKind::PoisonedLog.into(),
)
self.received.read().map_err(|_| error::IrcError::PoisonedLog)
}
}
@ -250,13 +246,13 @@ where
T: AsyncRead + AsyncWrite,
{
type Item = Message;
type Error = error::Error;
type Error = error::IrcError;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
match try_ready!(self.inner.poll()) {
Some(msg) => {
let recv: error::Result<_> = self.view.received.write().map_err(|_| {
error::ErrorKind::PoisonedLog.into()
error::IrcError::PoisonedLog
});
recv?.push(msg.clone());
Ok(Async::Ready(Some(msg)))
@ -271,12 +267,12 @@ where
T: AsyncRead + AsyncWrite,
{
type SinkItem = Message;
type SinkError = error::Error;
type SinkError = error::IrcError;
fn start_send(&mut self, item: Self::SinkItem) -> StartSend<Self::SinkItem, Self::SinkError> {
let res = self.inner.start_send(item.clone())?;
let sent: error::Result<_> = self.view.sent.write().map_err(|_| {
error::ErrorKind::PoisonedLog.into()
error::IrcError::PoisonedLog
});
sent?.push(item);
Ok(res)

View file

@ -1,67 +1,233 @@
//! Errors for `irc` crate using `error_chain`.
#![allow(missing_docs)]
//! Errors for `irc` crate using `failure`.
error_chain! {
foreign_links {
Io(::std::io::Error);
Tls(::native_tls::Error);
Recv(::std::sync::mpsc::RecvError);
SendMessage(::futures::sync::mpsc::SendError<::proto::Message>);
OneShotCancelled(::futures::sync::oneshot::Canceled);
Timer(::tokio_timer::TimerError);
}
use std::io::Error as IoError;
use std::sync::mpsc::RecvError;
errors {
/// A parsing error for empty strings as messages.
ParseEmpty {
description("Cannot parse an empty string as a message.")
display("Cannot parse an empty string as a message.")
}
use futures::sync::mpsc::SendError;
use futures::sync::oneshot::Canceled;
use native_tls::Error as TlsError;
#[cfg(feature = "json")]
use serde_json::Error as JsonError;
#[cfg(feature = "yaml")]
use serde_yaml::Error as YamlError;
use tokio_timer::TimerError;
#[cfg(feature = "toml")]
use toml::de::Error as TomlReadError;
#[cfg(feature = "toml")]
use toml::ser::Error as TomlWriteError;
/// A parsing error for invalid or missing commands in messages.
InvalidCommand {
description("Message contained a missing or invalid Command.")
display("Message contained a missing or invalid Command.")
}
use proto::Message;
/// A parsing error for failures in subcommand parsing (e.g. CAP and metadata).
SubCommandParsingFailed {
description("Failed to parse an IRC subcommand.")
display("Failed to parse an IRC subcommand.")
}
/// A specialized `Result` type for the `irc` crate.
pub type Result<T> = ::std::result::Result<T, IrcError>;
/// Failed to parse a mode correctly.
ModeParsingFailed {
description("Failed to parse a mode correctly.")
display("Failed to parse a mode correctly.")
}
/// The main crate-wide error type.
#[derive(Debug, Fail)]
pub enum IrcError {
/// An internal I/O error.
#[fail(display = "an io error occurred")]
Io(#[cause] IoError),
/// An error occurred on one of the internal channels of the `IrcServer`.
ChannelError {
description("An error occured on one of the IrcServer's internal channels.")
display("An error occured on one of the IrcServer's internal channels.")
}
/// An internal TLS error.
#[fail(display = "a TLS error occurred")]
Tls(#[cause] TlsError),
/// An error occured causing a mutex for a logged transport to be poisoned.
PoisonedLog {
description("An error occured causing a mutex for a logged transport to be poisoned.")
display("An error occured causing a mutex for a logged transport to be poisoned.")
}
/// An internal synchronous channel closed.
#[fail(display = "a sync channel closed")]
SyncChannelClosed(#[cause] RecvError),
/// Connection timed out due to no ping response.
PingTimeout {
description("The connection timed out due to no ping response.")
display("The connection timed out due to no ping response.")
}
/// An internal asynchronous channel closed.
#[fail(display = "an async channel closed")]
AsyncChannelClosed(#[cause] SendError<Message>),
NicknameNotSpecified {
description("No nickname was specified for use with this IrcServer.")
display("No nickname was specified for use with this IrcServer.")
}
/// An internal oneshot channel closed.
#[fail(display = "a oneshot channel closed")]
OneShotCanceled(#[cause] Canceled),
ServerNotSpecified {
description("No server was specified to connect to.")
display("No server was specified to connect to.")
}
/// An internal timer error.
#[fail(display = "timer failed")]
Timer(#[cause] TimerError),
/// Error for invalid configurations.
#[fail(display = "invalid config: {}", path)]
InvalidConfig {
/// The path to the configuration, or "<none>" if none specified.
path: String,
/// The detailed configuration error.
#[cause]
cause: ConfigError,
},
/// Error for invalid messages.
#[fail(display = "invalid message: {}", string)]
InvalidMessage {
/// The string that failed to parse.
string: String,
/// The detailed message parsing error.
#[cause]
cause: MessageParseError,
},
/// Mutex for a logged transport was poisoned making the log inaccessible.
#[fail(display = "mutex for a logged transport was poisoned")]
PoisonedLog,
/// Ping timed out due to no response.
#[fail(display = "connection reset: no ping response")]
PingTimeout,
/// Failed to lookup an unknown codec.
#[fail(display = "unknown codec: {}", codec)]
UnknownCodec {
/// The attempted codec.
codec: String,
},
/// Failed to encode or decode something with the given codec.
#[fail(display = "codec {} failed: {}", codec, data)]
CodecFailed {
/// The canonical codec name.
codec: &'static str,
/// The data that failed to encode or decode.
data: String,
},
}
/// Errors that occur when parsing messages.
#[derive(Debug, Fail)]
pub enum MessageParseError {
/// The message was empty.
#[fail(display = "empty message")]
EmptyMessage,
/// The command was invalid (i.e. missing).
#[fail(display = "invalid command")]
InvalidCommand,
/// The mode string was malformed.
#[fail(display = "invalid mode string: {}", string)]
InvalidModeString {
/// The invalid mode string.
string: String,
/// The detailed mode parsing error.
#[cause]
cause: ModeParseError,
},
/// The subcommand used was invalid.
#[fail(display = "invalid {} subcommand: {}", cmd, sub)]
InvalidSubcommand {
/// The command whose invalid subcommand was referenced.
cmd: &'static str,
/// The invalid subcommand.
sub: String,
}
}
/// Errors that occur while parsing mode strings.
#[derive(Debug, Fail)]
pub enum ModeParseError {
/// Invalid modifier used in a mode string (only + and - are valid).
#[fail(display = "invalid mode modifier: {}", modifier)]
InvalidModeModifier {
/// The invalid mode modifier.
modifier: char,
},
/// Missing modifier used in a mode string.
#[fail(display = "missing mode modifier")]
MissingModeModifier,
}
/// Errors that occur with configurations.
#[derive(Debug, Fail)]
pub enum ConfigError {
/// Failed to parse as TOML.
#[cfg(feature = "toml")]
#[fail(display = "invalid toml")]
InvalidToml(#[cause] TomlError),
/// Failed to parse as JSON.
#[cfg(feature = "json")]
#[fail(display = "invalid json")]
InvalidJson(#[cause] JsonError),
/// Failed to parse as YAML.
#[cfg(feature = "yaml")]
#[fail(display = "invalid yaml")]
InvalidYaml(#[cause] YamlError),
/// Failed to parse the given format because it was disabled at compile-time.
#[fail(display = "config format disabled: {}", format)]
ConfigFormatDisabled {
/// The disabled file format.
format: &'static str,
},
/// Could not identify the given file format.
#[fail(display = "config format unknown: {}", format)]
UnknownConfigFormat {
/// The unknown file extension.
format: String,
},
/// File was missing an extension to identify file format.
#[fail(display = "missing format extension")]
MissingExtension,
/// Configuration does not specify a nickname.
#[fail(display = "nickname not specified")]
NicknameNotSpecified,
/// Configuration does not specify a server.
#[fail(display = "server not specified")]
ServerNotSpecified,
}
/// A wrapper that combines toml's serialization and deserialization errors.
#[cfg(feature = "toml")]
#[derive(Debug, Fail)]
pub enum TomlError {
/// A TOML deserialization error.
#[fail(display = "deserialization failed")]
Read(#[cause] TomlReadError),
/// A TOML serialization error.
#[fail(display = "serialization failed")]
Write(#[cause] TomlWriteError),
}
impl From<IoError> for IrcError {
fn from(e: IoError) -> IrcError {
IrcError::Io(e)
}
}
impl From<TlsError> for IrcError {
fn from(e: TlsError) -> IrcError {
IrcError::Tls(e)
}
}
impl From<RecvError> for IrcError {
fn from(e: RecvError) -> IrcError {
IrcError::SyncChannelClosed(e)
}
}
impl From<SendError<Message>> for IrcError {
fn from(e: SendError<Message>) -> IrcError {
IrcError::AsyncChannelClosed(e)
}
}
impl From<Canceled> for IrcError {
fn from(e: Canceled) -> IrcError {
IrcError::OneShotCanceled(e)
}
}
impl From<TimerError> for IrcError {
fn from(e: TimerError) -> IrcError {
IrcError::Timer(e)
}
}

View file

@ -40,13 +40,12 @@
//! ```
#![warn(missing_docs)]
#![recursion_limit="128"]
extern crate bufstream;
extern crate bytes;
extern crate chrono;
#[macro_use]
extern crate error_chain;
extern crate failure;
extern crate encoding;
#[macro_use]
extern crate futures;

View file

@ -1,7 +1,7 @@
//! Enumeration of all available client commands.
use std::str::FromStr;
use error;
use error::MessageParseError;
use proto::{ChannelExt, ChannelMode, Mode, Response, UserMode};
/// List of all client commands as defined in [RFC 2812](http://tools.ietf.org/html/rfc2812). This
@ -445,7 +445,7 @@ impl<'a> From<&'a Command> for String {
impl Command {
/// Constructs a new Command.
pub fn new(cmd: &str, args: Vec<&str>, suffix: Option<&str>) -> error::Result<Command> {
pub fn new(cmd: &str, args: Vec<&str>, suffix: Option<&str>) -> Result<Command, MessageParseError> {
Ok(if cmd.eq_ignore_ascii_case("PASS") {
match suffix {
Some(suffix) => {
@ -1652,8 +1652,9 @@ impl CapSubCommand {
}
impl FromStr for CapSubCommand {
type Err = error::Error;
fn from_str(s: &str) -> error::Result<CapSubCommand> {
type Err = MessageParseError;
fn from_str(s: &str) -> Result<CapSubCommand, Self::Err> {
if s.eq_ignore_ascii_case("LS") {
Ok(CapSubCommand::LS)
} else if s.eq_ignore_ascii_case("LIST") {
@ -1671,7 +1672,10 @@ impl FromStr for CapSubCommand {
} else if s.eq_ignore_ascii_case("DEL") {
Ok(CapSubCommand::DEL)
} else {
Err(error::ErrorKind::SubCommandParsingFailed.into())
Err(MessageParseError::InvalidSubcommand {
cmd: "CAP",
sub: s.to_owned(),
})
}
}
}
@ -1703,8 +1707,9 @@ impl MetadataSubCommand {
}
impl FromStr for MetadataSubCommand {
type Err = error::Error;
fn from_str(s: &str) -> error::Result<MetadataSubCommand> {
type Err = MessageParseError;
fn from_str(s: &str) -> Result<MetadataSubCommand, Self::Err> {
if s.eq_ignore_ascii_case("GET") {
Ok(MetadataSubCommand::GET)
} else if s.eq_ignore_ascii_case("LIST") {
@ -1714,7 +1719,10 @@ impl FromStr for MetadataSubCommand {
} else if s.eq_ignore_ascii_case("CLEAR") {
Ok(MetadataSubCommand::CLEAR)
} else {
Err(error::ErrorKind::SubCommandParsingFailed.into())
Err(MessageParseError::InvalidSubcommand {
cmd: "METADATA",
sub: s.to_owned(),
})
}
}
}
@ -1742,8 +1750,9 @@ impl BatchSubCommand {
}
impl FromStr for BatchSubCommand {
type Err = error::Error;
fn from_str(s: &str) -> error::Result<BatchSubCommand> {
type Err = MessageParseError;
fn from_str(s: &str) -> Result<BatchSubCommand, Self::Err> {
if s.eq_ignore_ascii_case("NETSPLIT") {
Ok(BatchSubCommand::NETSPLIT)
} else if s.eq_ignore_ascii_case("NETJOIN") {

View file

@ -20,7 +20,7 @@ impl IrcCodec {
impl Decoder for IrcCodec {
type Item = Message;
type Error = error::Error;
type Error = error::IrcError;
fn decode(&mut self, src: &mut BytesMut) -> error::Result<Option<Message>> {
self.inner.decode(src).and_then(|res| {
@ -31,7 +31,7 @@ impl Decoder for IrcCodec {
impl Encoder for IrcCodec {
type Item = Message;
type Error = error::Error;
type Error = error::IrcError;
fn encode(&mut self, msg: Message, dst: &mut BytesMut) -> error::Result<()> {

View file

@ -28,7 +28,7 @@ impl LineCodec {
impl Decoder for LineCodec {
type Item = String;
type Error = error::Error;
type Error = error::IrcError;
fn decode(&mut self, src: &mut BytesMut) -> error::Result<Option<String>> {
if let Some(n) = src.as_ref().iter().position(|b| *b == b'\n') {
@ -53,7 +53,7 @@ impl Decoder for LineCodec {
impl Encoder for LineCodec {
type Item = String;
type Error = error::Error;
type Error = error::IrcError;
fn encode(&mut self, msg: String, dst: &mut BytesMut) -> error::Result<()> {
// Encode the message using the codec's encoding.

View file

@ -4,7 +4,7 @@ use std::fmt::{Display, Formatter, Result as FmtResult};
use std::str::FromStr;
use error;
use error::{Error, ErrorKind};
use error::{IrcError, MessageParseError};
use proto::{Command, ChannelExt};
/// A data structure representing an IRC message according to the protocol specification. It
@ -43,7 +43,7 @@ impl Message {
command: &str,
args: Vec<&str>,
suffix: Option<&str>,
) -> error::Result<Message> {
) -> Result<Message, MessageParseError> {
Message::with_tags(None, prefix, command, args, suffix)
}
@ -56,7 +56,7 @@ impl Message {
command: &str,
args: Vec<&str>,
suffix: Option<&str>,
) -> error::Result<Message> {
) -> Result<Message, error::MessageParseError> {
Ok(Message {
tags: tags,
prefix: prefix.map(|s| s.to_owned()),
@ -170,13 +170,18 @@ impl From<Command> for Message {
}
impl FromStr for Message {
type Err = Error;
type Err = IrcError;
fn from_str(s: &str) -> Result<Message, Self::Err> {
let mut state = s;
if s.is_empty() {
return Err(ErrorKind::ParseEmpty.into());
return Err(IrcError::InvalidMessage {
string: s.to_owned(),
cause: MessageParseError::EmptyMessage,
})
}
let mut state = s;
let tags = if state.starts_with('@') {
let tags = state.find(' ').map(|i| &state[1..i]);
state = state.find(' ').map_or("", |i| &state[i + 1..]);
@ -193,6 +198,7 @@ impl FromStr for Message {
} else {
None
};
let prefix = if state.starts_with(':') {
let prefix = state.find(' ').map(|i| &state[1..i]);
state = state.find(' ').map_or("", |i| &state[i + 1..]);
@ -200,6 +206,7 @@ impl FromStr for Message {
} else {
None
};
let line_ending_len = if state.ends_with("\r\n") {
"\r\n"
} else if state.ends_with('\r') {
@ -209,6 +216,7 @@ impl FromStr for Message {
} else {
""
}.len();
let suffix = if state.contains(" :") {
let suffix = state.find(" :").map(|i| &state[i + 2..state.len() - line_ending_len]);
state = state.find(" :").map_or("", |i| &state[..i + 1]);
@ -217,24 +225,33 @@ impl FromStr for Message {
state = &state[..state.len() - line_ending_len];
None
};
let command = match state.find(' ').map(|i| &state[..i]) {
Some(cmd) => {
state = state.find(' ').map_or("", |i| &state[i + 1..]);
cmd
}
// If there's no arguments but the "command" starts with colon, it's not a command.
None if state.starts_with(':') => return Err(ErrorKind::InvalidCommand.into()),
None if state.starts_with(':') => return Err(IrcError::InvalidMessage {
string: s.to_owned(),
cause: MessageParseError::InvalidCommand,
}),
// If there's no arguments following the command, the rest of the state is the command.
None => {
let cmd = state;
state = "";
cmd
},
};
let args: Vec<_> = state.splitn(14, ' ').filter(|s| !s.is_empty()).collect();
Message::with_tags(tags, prefix, command, args, suffix)
.map_err(|_| ErrorKind::InvalidCommand.into())
Message::with_tags(tags, prefix, command, args, suffix).map_err(|e| {
IrcError::InvalidMessage {
string: s.to_owned(),
cause: e,
}
})
}
}

View file

@ -1,7 +1,9 @@
//! A module defining an API for IRC user and channel modes.
use std::fmt;
use error;
use error::MessageParseError;
use error::MessageParseError::InvalidModeString;
use error::ModeParseError::*;
use proto::Command;
/// A marker trait for different kinds of Modes.
@ -48,10 +50,10 @@ impl ModeType for UserMode {
}
impl UserMode {
fn from_char(c: char) -> error::Result<UserMode> {
fn from_char(c: char) -> UserMode {
use self::UserMode::*;
Ok(match c {
match c {
'a' => Away,
'i' => Invisible,
'w' => Wallops,
@ -61,7 +63,7 @@ impl UserMode {
's' => ServerNotices,
'x' => MaskedHost,
_ => Unknown(c),
})
}
}
}
@ -141,10 +143,10 @@ impl ModeType for ChannelMode {
}
impl ChannelMode {
fn from_char(c: char) -> error::Result<ChannelMode> {
fn from_char(c: char) -> ChannelMode {
use self::ChannelMode::*;
Ok(match c {
match c {
'b' => Ban,
'e' => Exception,
'l' => Limit,
@ -162,7 +164,7 @@ impl ChannelMode {
'h' => Halfop,
'v' => Voice,
_ => Unknown(c),
})
}
}
}
@ -242,7 +244,7 @@ enum PlusMinus {
impl Mode<UserMode> {
// TODO: turning more edge cases into errors.
/// Parses the specified mode string as user modes.
pub fn as_user_modes(s: &str) -> error::Result<Vec<Mode<UserMode>>> {
pub fn as_user_modes(s: &str) -> Result<Vec<Mode<UserMode>>, MessageParseError> {
use self::PlusMinus::*;
let mut res = vec![];
@ -255,11 +257,18 @@ impl Mode<UserMode> {
let init = match chars.next() {
Some('+') => Plus,
Some('-') => Minus,
_ => return Err(error::ErrorKind::ModeParsingFailed.into()),
Some(c) => return Err(InvalidModeString {
string: s.to_owned(),
cause: InvalidModeModifier { modifier: c },
}),
None => return Err(InvalidModeString {
string: s.to_owned(),
cause: MissingModeModifier,
}),
};
for c in chars {
let mode = UserMode::from_char(c)?;
let mode = UserMode::from_char(c);
let arg = if mode.takes_arg() {
pieces.next()
} else {
@ -281,7 +290,7 @@ impl Mode<UserMode> {
impl Mode<ChannelMode> {
// TODO: turning more edge cases into errors.
/// Parses the specified mode string as channel modes.
pub fn as_channel_modes(s: &str) -> error::Result<Vec<Mode<ChannelMode>>> {
pub fn as_channel_modes(s: &str) -> Result<Vec<Mode<ChannelMode>>, MessageParseError> {
use self::PlusMinus::*;
let mut res = vec![];
@ -294,11 +303,18 @@ impl Mode<ChannelMode> {
let init = match chars.next() {
Some('+') => Plus,
Some('-') => Minus,
_ => return Err(error::ErrorKind::ModeParsingFailed.into()),
Some(c) => return Err(InvalidModeString {
string: s.to_owned(),
cause: InvalidModeModifier { modifier: c },
}),
None => return Err(InvalidModeString {
string: s.to_owned(),
cause: MissingModeModifier,
}),
};
for c in chars {
let mode = ChannelMode::from_char(c)?;
let mode = ChannelMode::from_char(c);
let arg = if mode.takes_arg() {
pieces.next()
} else {