Merge branch 'use-failure' into develop
This commit is contained in:
commit
e4d441695e
14 changed files with 513 additions and 269 deletions
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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()))
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
276
src/error.rs
276
src/error.rs
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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") {
|
||||
|
|
|
@ -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<()> {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Reference in a new issue