Refactored the whole crate to use failure.
This commit is contained in:
parent
8782f66de4
commit
5266e4098d
14 changed files with 513 additions and 270 deletions
|
@ -25,7 +25,7 @@ bufstream = "0.1"
|
||||||
bytes = "0.4"
|
bytes = "0.4"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
encoding = "0.2"
|
encoding = "0.2"
|
||||||
error-chain = "0.10"
|
failure = "0.1"
|
||||||
futures = "0.1"
|
futures = "0.1"
|
||||||
log = "0.3"
|
log = "0.3"
|
||||||
native-tls = "0.1"
|
native-tls = "0.1"
|
||||||
|
|
|
@ -16,7 +16,7 @@ fn main() {
|
||||||
let args: Vec<_> = env::args().collect();
|
let args: Vec<_> = env::args().collect();
|
||||||
match parse(&args) {
|
match parse(&args) {
|
||||||
Ok(Some((ref input, ref output))) => {
|
Ok(Some((ref input, ref output))) => {
|
||||||
let cfg = Config::load(input).unwrap();
|
let mut cfg = Config::load(input).unwrap();
|
||||||
cfg.save(output).unwrap();
|
cfg.save(output).unwrap();
|
||||||
println!("Converted {} to {}.", input, output);
|
println!("Converted {} to {}.", input, output);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
//! A module providing IRC connections for use by `IrcServer`s.
|
//! A module providing IRC connections for use by `IrcServer`s.
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::{fmt, io};
|
use std::fmt;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
|
||||||
use encoding::EncoderTrap;
|
use encoding::EncoderTrap;
|
||||||
|
@ -43,7 +43,7 @@ impl fmt::Debug for Connection {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A convenient type alias representing the `TlsStream` future.
|
/// 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`.
|
/// A future representing an eventual `Connection`.
|
||||||
pub enum ConnectionFuture<'a> {
|
pub enum ConnectionFuture<'a> {
|
||||||
|
@ -76,7 +76,7 @@ impl<'a> fmt::Debug for ConnectionFuture<'a> {
|
||||||
|
|
||||||
impl<'a> Future for ConnectionFuture<'a> {
|
impl<'a> Future for ConnectionFuture<'a> {
|
||||||
type Item = Connection;
|
type Item = Connection;
|
||||||
type Error = error::Error;
|
type Error = error::IrcError;
|
||||||
|
|
||||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||||
match *self {
|
match *self {
|
||||||
|
@ -95,21 +95,18 @@ impl<'a> Future for ConnectionFuture<'a> {
|
||||||
ConnectionFuture::Mock(ref config) => {
|
ConnectionFuture::Mock(ref config) => {
|
||||||
let enc: error::Result<_> = encoding_from_whatwg_label(
|
let enc: error::Result<_> = encoding_from_whatwg_label(
|
||||||
config.encoding()
|
config.encoding()
|
||||||
).ok_or_else(|| io::Error::new(
|
).ok_or_else(|| error::IrcError::UnknownCodec {
|
||||||
io::ErrorKind::InvalidInput,
|
codec: config.encoding().to_owned(),
|
||||||
&format!("Attempted to use unknown codec {}.", config.encoding())[..],
|
});
|
||||||
).into());
|
|
||||||
let encoding = enc?;
|
let encoding = enc?;
|
||||||
let init_str = config.mock_initial_value();
|
let init_str = config.mock_initial_value();
|
||||||
let initial: error::Result<_> = {
|
let initial: error::Result<_> = {
|
||||||
encoding.encode(init_str, EncoderTrap::Replace).map_err(
|
encoding.encode(init_str, EncoderTrap::Replace).map_err(|data| {
|
||||||
|data| {
|
error::IrcError::CodecFailed {
|
||||||
io::Error::new(
|
codec: encoding.name(),
|
||||||
io::ErrorKind::InvalidInput,
|
data: data.into_owned(),
|
||||||
&format!("Failed to encode {} as {}.", data, encoding.name())[..],
|
}
|
||||||
).into()
|
})
|
||||||
},
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let framed = MockStream::new(&initial?).framed(IrcCodec::new(config.encoding())?);
|
let framed = MockStream::new(&initial?).framed(IrcCodec::new(config.encoding())?);
|
||||||
|
@ -139,17 +136,14 @@ impl Connection {
|
||||||
info!("Added {} to trusted certificates.", cert_path);
|
info!("Added {} to trusted certificates.", cert_path);
|
||||||
}
|
}
|
||||||
let connector = builder.build()?;
|
let connector = builder.build()?;
|
||||||
let stream = Box::new(TcpStream::connect(&config.socket_addr()?, handle)
|
let stream = Box::new(TcpStream::connect(&config.socket_addr()?, handle).map_err(|e| {
|
||||||
.map_err(|e| {
|
let res: error::IrcError = e.into();
|
||||||
let res: error::Error = e.into();
|
res
|
||||||
res
|
}).and_then(move |socket| {
|
||||||
})
|
connector.connect_async(&domain, socket).map_err(
|
||||||
.and_then(move |socket| {
|
|e| e.into(),
|
||||||
connector.connect_async(&domain, socket).map_err(
|
)
|
||||||
|e| e.into(),
|
}));
|
||||||
)
|
|
||||||
}
|
|
||||||
));
|
|
||||||
Ok(ConnectionFuture::Secured(config, stream))
|
Ok(ConnectionFuture::Secured(config, stream))
|
||||||
} else {
|
} else {
|
||||||
info!("Connecting to {}.", config.server()?);
|
info!("Connecting to {}.", config.server()?);
|
||||||
|
@ -172,7 +166,7 @@ impl Connection {
|
||||||
|
|
||||||
impl Stream for Connection {
|
impl Stream for Connection {
|
||||||
type Item = Message;
|
type Item = Message;
|
||||||
type Error = error::Error;
|
type Error = error::IrcError;
|
||||||
|
|
||||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||||
match *self {
|
match *self {
|
||||||
|
@ -185,7 +179,7 @@ impl Stream for Connection {
|
||||||
|
|
||||||
impl Sink for Connection {
|
impl Sink for Connection {
|
||||||
type SinkItem = Message;
|
type SinkItem = Message;
|
||||||
type SinkError = error::Error;
|
type SinkError = error::IrcError;
|
||||||
|
|
||||||
fn start_send(&mut self, item: Self::SinkItem) -> StartSend<Self::SinkItem, Self::SinkError> {
|
fn start_send(&mut self, item: Self::SinkItem) -> StartSend<Self::SinkItem, Self::SinkError> {
|
||||||
match *self {
|
match *self {
|
||||||
|
|
|
@ -3,9 +3,8 @@ use std::borrow::ToOwned;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
use std::io::{Error, ErrorKind};
|
|
||||||
use std::net::{SocketAddr, ToSocketAddrs};
|
use std::net::{SocketAddr, ToSocketAddrs};
|
||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
#[cfg(feature = "json")]
|
#[cfg(feature = "json")]
|
||||||
use serde_json;
|
use serde_json;
|
||||||
|
@ -14,8 +13,10 @@ use serde_yaml;
|
||||||
#[cfg(feature = "toml")]
|
#[cfg(feature = "toml")]
|
||||||
use toml;
|
use toml;
|
||||||
|
|
||||||
use error;
|
#[cfg(feature = "toml")]
|
||||||
use error::{Result, ResultExt};
|
use error::TomlError;
|
||||||
|
use error::{ConfigError, Result};
|
||||||
|
use error::IrcError::InvalidConfig;
|
||||||
|
|
||||||
/// Configuration data.
|
/// Configuration data.
|
||||||
#[derive(Clone, Deserialize, Serialize, Default, PartialEq, Debug)]
|
#[derive(Clone, Deserialize, Serialize, Default, PartialEq, Debug)]
|
||||||
|
@ -88,9 +89,25 @@ pub struct Config {
|
||||||
pub channel_keys: Option<HashMap<String, String>>,
|
pub channel_keys: Option<HashMap<String, String>>,
|
||||||
/// A map of additional options to be stored in config.
|
/// A map of additional options to be stored in config.
|
||||||
pub options: Option<HashMap<String, String>>,
|
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 {
|
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
|
/// 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
|
/// 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.
|
/// its respective crate feature enabled. Only json is available by default.
|
||||||
|
@ -99,155 +116,171 @@ impl Config {
|
||||||
let mut data = String::new();
|
let mut data = String::new();
|
||||||
file.read_to_string(&mut data)?;
|
file.read_to_string(&mut data)?;
|
||||||
|
|
||||||
match path.as_ref().extension().and_then(|s| s.to_str()) {
|
let res = match path.as_ref().extension().and_then(|s| s.to_str()) {
|
||||||
Some("json") => Config::load_json(&data),
|
Some("json") => Config::load_json(&path, &data),
|
||||||
Some("toml") => Config::load_toml(&data),
|
Some("toml") => Config::load_toml(&path, &data),
|
||||||
Some("yaml") | Some("yml") => Config::load_yaml(&data),
|
Some("yaml") | Some("yml") => Config::load_yaml(&path, &data),
|
||||||
Some(ext) => Err(Error::new(
|
Some(ext) => Err(InvalidConfig {
|
||||||
ErrorKind::InvalidInput,
|
path: path.as_ref().to_string_lossy().into_owned(),
|
||||||
format!("Failed to decode configuration of unknown format {}", ext),
|
cause: ConfigError::UnknownConfigFormat {
|
||||||
).into()),
|
format: ext.to_owned(),
|
||||||
None => Err(Error::new(
|
},
|
||||||
ErrorKind::InvalidInput,
|
}),
|
||||||
"Failed to decode configuration of missing or non-unicode format.",
|
None => Err(InvalidConfig {
|
||||||
).into()),
|
path: path.as_ref().to_string_lossy().into_owned(),
|
||||||
}
|
cause: ConfigError::MissingExtension,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
res.map(|config| {
|
||||||
|
config.with_path(path)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "json")]
|
#[cfg(feature = "json")]
|
||||||
fn load_json(data: &str) -> Result<Config> {
|
fn load_json<P: AsRef<Path>>(path: &P, data: &str) -> Result<Config> {
|
||||||
serde_json::from_str(&data[..]).chain_err(|| {
|
serde_json::from_str(&data[..]).map_err(|e| {
|
||||||
let e: error::Error = Error::new(
|
InvalidConfig {
|
||||||
ErrorKind::InvalidInput,
|
path: path.as_ref().to_string_lossy().into_owned(),
|
||||||
"Failed to decode JSON configuration file.",
|
cause: ConfigError::InvalidJson(e),
|
||||||
).into();
|
}
|
||||||
e
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "json"))]
|
#[cfg(not(feature = "json"))]
|
||||||
fn load_json(_: &str) -> Result<Config> {
|
fn load_json<P: AsRef<Path>>(path: &P, _: &str) -> Result<Config> {
|
||||||
Err(Error::new(
|
Err(InvalidConfig {
|
||||||
ErrorKind::InvalidInput,
|
path: path.as_ref().to_string_lossy().into_owned(),
|
||||||
"JSON file decoding is disabled.",
|
cause: ConfigError::ConfigFormatDisabled {
|
||||||
).into())
|
format: "JSON"
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "toml")]
|
#[cfg(feature = "toml")]
|
||||||
fn load_toml(data: &str) -> Result<Config> {
|
fn load_toml<P: AsRef<Path>>(path: &P, data: &str) -> Result<Config> {
|
||||||
toml::from_str(&data[..]).chain_err(|| {
|
toml::from_str(&data[..]).map_err(|e| {
|
||||||
let e: error::Error = Error::new(
|
InvalidConfig {
|
||||||
ErrorKind::InvalidInput,
|
path: path.as_ref().to_string_lossy().into_owned(),
|
||||||
"Failed to decode TOML configuration file.",
|
cause: ConfigError::InvalidToml(TomlError::Read(e)),
|
||||||
).into();
|
}
|
||||||
e
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "toml"))]
|
#[cfg(not(feature = "toml"))]
|
||||||
fn load_toml(_: &str) -> Result<Config> {
|
fn load_toml<P: AsRef<Path>>(path: &P, _: &str) -> Result<Config> {
|
||||||
Err(Error::new(
|
Err(InvalidConfig {
|
||||||
ErrorKind::InvalidInput,
|
path: path.as_ref().to_string_lossy().into_owned(),
|
||||||
"TOML file decoding is disabled.",
|
cause: ConfigError::ConfigFormatDisabled {
|
||||||
).into())
|
format: "TOML"
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "yaml")]
|
#[cfg(feature = "yaml")]
|
||||||
fn load_yaml(data: &str) -> Result<Config> {
|
fn load_yaml<P: AsRef<Path>>(path: &P, data: &str) -> Result<Config> {
|
||||||
serde_yaml::from_str(&data[..]).chain_err(|| {
|
serde_yaml::from_str(&data[..]).map_err(|e| {
|
||||||
let e: error::Error = Error::new(
|
InvalidConfig {
|
||||||
ErrorKind::InvalidInput,
|
path: path.as_ref().to_string_lossy().into_owned(),
|
||||||
"Failed to decode YAML configuration file.",
|
cause: ConfigError::InvalidYaml(e),
|
||||||
).into();
|
}
|
||||||
e
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "yaml"))]
|
#[cfg(not(feature = "yaml"))]
|
||||||
fn load_yaml(_: &str) -> Result<Config> {
|
fn load_yaml<P: AsRef<Path>>(path: &P, _: &str) -> Result<Config> {
|
||||||
Err(Error::new(
|
Err(InvalidConfig {
|
||||||
ErrorKind::InvalidInput,
|
path: path.as_ref().to_string_lossy().into_owned(),
|
||||||
"YAML file decoding is disabled.",
|
cause: ConfigError::ConfigFormatDisabled {
|
||||||
).into())
|
format: "YAML"
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Saves a configuration to the desired path. This will use the file extension to detect
|
/// 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
|
/// 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.
|
/// 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 mut file = File::create(&path)?;
|
||||||
let data = match path.as_ref().extension().and_then(|s| s.to_str()) {
|
let data = match path.as_ref().extension().and_then(|s| s.to_str()) {
|
||||||
Some("json") => self.save_json()?,
|
Some("json") => self.save_json(&path)?,
|
||||||
Some("toml") => self.save_toml()?,
|
Some("toml") => self.save_toml(&path)?,
|
||||||
Some("yaml") | Some("yml") => self.save_yaml()?,
|
Some("yaml") | Some("yml") => self.save_yaml(&path)?,
|
||||||
Some(ext) => return Err(Error::new(
|
Some(ext) => return Err(InvalidConfig {
|
||||||
ErrorKind::InvalidInput,
|
path: path.as_ref().to_string_lossy().into_owned(),
|
||||||
format!("Failed to encode configuration of unknown format {}", ext),
|
cause: ConfigError::UnknownConfigFormat {
|
||||||
).into()),
|
format: ext.to_owned(),
|
||||||
None => return Err(Error::new(
|
},
|
||||||
ErrorKind::InvalidInput,
|
}),
|
||||||
"Failed to encode configuration of missing or non-unicode format.",
|
None => return Err(InvalidConfig {
|
||||||
).into()),
|
path: path.as_ref().to_string_lossy().into_owned(),
|
||||||
|
cause: ConfigError::MissingExtension,
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
file.write_all(data.as_bytes())?;
|
file.write_all(data.as_bytes())?;
|
||||||
|
self.path = Some(path.as_ref().to_owned());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "json")]
|
#[cfg(feature = "json")]
|
||||||
fn save_json(&self) -> Result<String> {
|
fn save_json<P: AsRef<Path>>(&self, path: &P) -> Result<String> {
|
||||||
serde_json::to_string(self).chain_err(|| {
|
serde_json::to_string(self).map_err(|e| {
|
||||||
let e: error::Error = Error::new(
|
InvalidConfig {
|
||||||
ErrorKind::InvalidInput,
|
path: path.as_ref().to_string_lossy().into_owned(),
|
||||||
"Failed to encode JSON configuration file.",
|
cause: ConfigError::InvalidJson(e),
|
||||||
).into();
|
}
|
||||||
e
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "json"))]
|
#[cfg(not(feature = "json"))]
|
||||||
fn save_json(&self) -> Result<String> {
|
fn save_json<P: AsRef<Path>>(&self, path: &P) -> Result<String> {
|
||||||
Err(Error::new(
|
Err(InvalidConfig {
|
||||||
ErrorKind::InvalidInput,
|
path: path.as_ref().to_string_lossy().into_owned(),
|
||||||
"JSON file encoding is disabled.",
|
cause: ConfigError::ConfigFormatDisabled {
|
||||||
).into())
|
format: "JSON"
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "toml")]
|
#[cfg(feature = "toml")]
|
||||||
fn save_toml(&self) -> Result<String> {
|
fn save_toml<P: AsRef<Path>>(&self, path: &P) -> Result<String> {
|
||||||
toml::to_string(self).chain_err(|| {
|
toml::to_string(self).map_err(|e| {
|
||||||
let e: error::Error = Error::new(
|
InvalidConfig {
|
||||||
ErrorKind::InvalidInput,
|
path: path.as_ref().to_string_lossy().into_owned(),
|
||||||
"Failed to encode TOML configuration file.",
|
cause: ConfigError::InvalidToml(TomlError::Write(e)),
|
||||||
).into();
|
}
|
||||||
e
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "toml"))]
|
#[cfg(not(feature = "toml"))]
|
||||||
fn save_toml(&self) -> Result<String> {
|
fn save_toml<P: AsRef<Path>>(&self, path: &P) -> Result<String> {
|
||||||
Err(Error::new(
|
Err(InvalidConfig {
|
||||||
ErrorKind::InvalidInput,
|
path: path.as_ref().to_string_lossy().into_owned(),
|
||||||
"TOML file encoding is disabled.",
|
cause: ConfigError::ConfigFormatDisabled {
|
||||||
).into())
|
format: "TOML"
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "yaml")]
|
#[cfg(feature = "yaml")]
|
||||||
fn save_yaml(&self) -> Result<String> {
|
fn save_yaml<P: AsRef<Path>>(&self, path: &P) -> Result<String> {
|
||||||
serde_yaml::to_string(self).chain_err(|| {
|
serde_yaml::to_string(self).map_err(|e| {
|
||||||
let e: error::Error = Error::new(
|
InvalidConfig {
|
||||||
ErrorKind::InvalidInput,
|
path: path.as_ref().to_string_lossy().into_owned(),
|
||||||
"Failed to encode YAML configuration file.",
|
cause: ConfigError::InvalidYaml(e),
|
||||||
).into();
|
}
|
||||||
e
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "yaml"))]
|
#[cfg(not(feature = "yaml"))]
|
||||||
fn save_yaml(&self) -> Result<String> {
|
fn save_yaml<P: AsRef<Path>>(&self, path: &P) -> Result<String> {
|
||||||
Err(Error::new(
|
Err(InvalidConfig {
|
||||||
ErrorKind::InvalidInput,
|
path: path.as_ref().to_string_lossy().into_owned(),
|
||||||
"YAML file encoding is disabled.",
|
cause: ConfigError::ConfigFormatDisabled {
|
||||||
).into())
|
format: "YAML"
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determines whether or not the nickname provided is the owner of the bot.
|
/// 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.
|
/// Gets the nickname specified in the configuration.
|
||||||
pub fn nickname(&self) -> Result<&str> {
|
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.
|
/// 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.
|
/// Gets the address of the server specified in the configuration.
|
||||||
pub fn server(&self) -> Result<&str> {
|
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.
|
/// Gets the port of the server specified in the configuration.
|
||||||
|
@ -487,31 +529,27 @@ mod test {
|
||||||
options: Some(HashMap::new()),
|
options: Some(HashMap::new()),
|
||||||
use_mock_connection: None,
|
use_mock_connection: None,
|
||||||
mock_initial_value: None,
|
mock_initial_value: None,
|
||||||
|
|
||||||
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "json")]
|
#[cfg(feature = "json")]
|
||||||
fn load() {
|
fn load_from_json() {
|
||||||
assert_eq!(Config::load(Path::new("client_config.json")).unwrap(), test_config());
|
assert_eq!(Config::load("client_config.json").unwrap(), test_config().with_path("client_config.json"));
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[cfg(feature = "json")]
|
|
||||||
fn load_from_str() {
|
|
||||||
assert_eq!(Config::load("client_config.json").unwrap(), test_config());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "toml")]
|
#[cfg(feature = "toml")]
|
||||||
fn load_from_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]
|
#[test]
|
||||||
#[cfg(feature = "yaml")]
|
#[cfg(feature = "yaml")]
|
||||||
fn load_from_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]
|
#[test]
|
||||||
|
|
|
@ -41,7 +41,7 @@ use proto::Message;
|
||||||
/// For a full example usage, see [irc::client::reactor](./index.html).
|
/// For a full example usage, see [irc::client::reactor](./index.html).
|
||||||
pub struct IrcReactor {
|
pub struct IrcReactor {
|
||||||
inner: Core,
|
inner: Core,
|
||||||
handlers: Vec<Box<Future<Item = (), Error = error::Error>>>,
|
handlers: Vec<Box<Future<Item = (), Error = error::IrcError>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IrcReactor {
|
impl IrcReactor {
|
||||||
|
@ -139,7 +139,7 @@ impl IrcReactor {
|
||||||
pub fn register_server_with_handler<F, U>(
|
pub fn register_server_with_handler<F, U>(
|
||||||
&mut self, server: IrcServer, handler: F
|
&mut self, server: IrcServer, handler: F
|
||||||
) where F: Fn(&IrcServer, Message) -> U + 'static,
|
) 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| {
|
self.handlers.push(Box::new(server.stream().for_each(move |message| {
|
||||||
handler(&server, message)
|
handler(&server, message)
|
||||||
})));
|
})));
|
||||||
|
@ -151,7 +151,7 @@ impl IrcReactor {
|
||||||
/// be sufficient for most use cases.
|
/// be sufficient for most use cases.
|
||||||
pub fn register_future<F>(
|
pub fn register_future<F>(
|
||||||
&mut self, 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()))
|
self.handlers.push(Box::new(future.into_future()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -97,7 +97,7 @@ pub mod utils;
|
||||||
/// }).unwrap();
|
/// }).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.
|
/// 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<()>
|
fn for_each_incoming<F>(self, mut f: F) -> error::Result<()>
|
||||||
where
|
where
|
||||||
|
@ -111,7 +111,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.
|
/// An interface for communicating with an IRC server.
|
||||||
pub trait Server {
|
pub trait Server {
|
||||||
|
@ -211,7 +211,7 @@ pub struct ServerStream {
|
||||||
|
|
||||||
impl Stream for ServerStream {
|
impl Stream for ServerStream {
|
||||||
type Item = Message;
|
type Item = Message;
|
||||||
type Error = error::Error;
|
type Error = error::IrcError;
|
||||||
|
|
||||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||||
match try_ready!(self.stream.poll()) {
|
match try_ready!(self.stream.poll()) {
|
||||||
|
@ -352,7 +352,6 @@ impl ServerState {
|
||||||
trace!("[RECV] {}", msg.to_string());
|
trace!("[RECV] {}", msg.to_string());
|
||||||
match msg.command {
|
match msg.command {
|
||||||
JOIN(ref chan, _, _) => self.handle_join(msg.source_nickname().unwrap_or(""), chan),
|
JOIN(ref chan, _, _) => self.handle_join(msg.source_nickname().unwrap_or(""), chan),
|
||||||
/// This will panic if not specified.
|
|
||||||
PART(ref chan, _) => self.handle_part(msg.source_nickname().unwrap_or(""), chan),
|
PART(ref chan, _) => self.handle_part(msg.source_nickname().unwrap_or(""), chan),
|
||||||
QUIT(_) => self.handle_quit(msg.source_nickname().unwrap_or("")),
|
QUIT(_) => self.handle_quit(msg.source_nickname().unwrap_or("")),
|
||||||
NICK(ref new_nick) => {
|
NICK(ref new_nick) => {
|
||||||
|
@ -442,7 +441,16 @@ impl ServerState {
|
||||||
if self.config().umodes().is_empty() {
|
if self.config().umodes().is_empty() {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} 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,
|
||||||
|
}
|
||||||
|
})?
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -721,9 +729,8 @@ impl IrcServer {
|
||||||
tx_view.send(conn.log_view()).unwrap();
|
tx_view.send(conn.log_view()).unwrap();
|
||||||
let (sink, stream) = conn.split();
|
let (sink, stream) = conn.split();
|
||||||
|
|
||||||
let outgoing_future = sink.send_all(rx_outgoing.map_err(|_| {
|
let outgoing_future = sink.send_all(rx_outgoing.map_err::<error::IrcError, _>(|_| {
|
||||||
let res: error::Error = error::ErrorKind::ChannelError.into();
|
unreachable!("futures::sync::mpsc::Receiver should never return Err");
|
||||||
res
|
|
||||||
})).map(|_| ()).map_err(|e| panic!("{}", e));
|
})).map(|_| ()).map_err(|e| panic!("{}", e));
|
||||||
|
|
||||||
// Send the stream half back to the original thread.
|
// Send the stream half back to the original thread.
|
||||||
|
@ -821,7 +828,7 @@ pub struct IrcServerFuture<'a> {
|
||||||
|
|
||||||
impl<'a> Future for IrcServerFuture<'a> {
|
impl<'a> Future for IrcServerFuture<'a> {
|
||||||
type Item = PackedIrcServer;
|
type Item = PackedIrcServer;
|
||||||
type Error = error::Error;
|
type Error = error::IrcError;
|
||||||
|
|
||||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||||
let conn = try_ready!(self.conn.poll());
|
let conn = try_ready!(self.conn.poll());
|
||||||
|
@ -829,10 +836,11 @@ impl<'a> Future for IrcServerFuture<'a> {
|
||||||
let view = conn.log_view();
|
let view = conn.log_view();
|
||||||
let (sink, stream) = conn.split();
|
let (sink, stream) = conn.split();
|
||||||
|
|
||||||
let outgoing_future = sink.send_all(self.rx_outgoing.take().unwrap().map_err(|()| {
|
let outgoing_future = sink.send_all(
|
||||||
let res: error::Error = error::ErrorKind::ChannelError.into();
|
self.rx_outgoing.take().unwrap().map_err::<error::IrcError, _>(|()| {
|
||||||
res
|
unreachable!("futures::sync::mpsc::Receiver should never return Err");
|
||||||
})).map(|_| ());
|
})
|
||||||
|
).map(|_| ());
|
||||||
|
|
||||||
let server = IrcServer {
|
let server = IrcServer {
|
||||||
state: Arc::new(ServerState::new(
|
state: Arc::new(ServerState::new(
|
||||||
|
@ -850,7 +858,7 @@ impl<'a> Future for IrcServerFuture<'a> {
|
||||||
/// This type should only be used by advanced users who are familiar with the implementation of this
|
/// 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
|
/// crate. An easy to use abstraction that does not require this knowledge is available via
|
||||||
/// [IrcReactors](../reactor/struct.IrcReactor.html).
|
/// [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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
|
|
@ -88,12 +88,12 @@ where
|
||||||
T: AsyncRead + AsyncWrite,
|
T: AsyncRead + AsyncWrite,
|
||||||
{
|
{
|
||||||
type Item = Message;
|
type Item = Message;
|
||||||
type Error = error::Error;
|
type Error = error::IrcError;
|
||||||
|
|
||||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||||
if self.ping_timed_out() {
|
if self.ping_timed_out() {
|
||||||
self.close()?;
|
self.close()?;
|
||||||
return Err(error::ErrorKind::PingTimeout.into())
|
return Err(error::IrcError::PingTimeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
let timer_poll = self.ping_timer.poll()?;
|
let timer_poll = self.ping_timer.poll()?;
|
||||||
|
@ -144,12 +144,12 @@ where
|
||||||
T: AsyncRead + AsyncWrite,
|
T: AsyncRead + AsyncWrite,
|
||||||
{
|
{
|
||||||
type SinkItem = Message;
|
type SinkItem = Message;
|
||||||
type SinkError = error::Error;
|
type SinkError = error::IrcError;
|
||||||
|
|
||||||
fn start_send(&mut self, item: Self::SinkItem) -> StartSend<Self::SinkItem, Self::SinkError> {
|
fn start_send(&mut self, item: Self::SinkItem) -> StartSend<Self::SinkItem, Self::SinkError> {
|
||||||
if self.ping_timed_out() {
|
if self.ping_timed_out() {
|
||||||
self.close()?;
|
self.close()?;
|
||||||
Err(error::ErrorKind::PingTimeout.into())
|
Err(error::IrcError::PingTimeout)
|
||||||
} else {
|
} else {
|
||||||
// Check if the oldest message in the rolling window is discounted.
|
// Check if the oldest message in the rolling window is discounted.
|
||||||
if let Async::Ready(()) = self.rolling_burst_window_front()? {
|
if let Async::Ready(()) = self.rolling_burst_window_front()? {
|
||||||
|
@ -180,7 +180,7 @@ where
|
||||||
fn poll_complete(&mut self) -> Poll<(), Self::SinkError> {
|
fn poll_complete(&mut self) -> Poll<(), Self::SinkError> {
|
||||||
if self.ping_timed_out() {
|
if self.ping_timed_out() {
|
||||||
self.close()?;
|
self.close()?;
|
||||||
Err(error::ErrorKind::PingTimeout.into())
|
Err(error::IrcError::PingTimeout)
|
||||||
} else {
|
} else {
|
||||||
Ok(self.inner.poll_complete()?)
|
Ok(self.inner.poll_complete()?)
|
||||||
}
|
}
|
||||||
|
@ -201,16 +201,12 @@ pub struct LogView {
|
||||||
impl LogView {
|
impl LogView {
|
||||||
/// Gets a read guard for all the messages sent on the transport.
|
/// Gets a read guard for all the messages sent on the transport.
|
||||||
pub fn sent(&self) -> error::Result<RwLockReadGuard<Vec<Message>>> {
|
pub fn sent(&self) -> error::Result<RwLockReadGuard<Vec<Message>>> {
|
||||||
self.sent.read().map_err(
|
self.sent.read().map_err(|_| error::IrcError::PoisonedLog)
|
||||||
|_| error::ErrorKind::PoisonedLog.into(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets a read guard for all the messages received on the transport.
|
/// Gets a read guard for all the messages received on the transport.
|
||||||
pub fn received(&self) -> error::Result<RwLockReadGuard<Vec<Message>>> {
|
pub fn received(&self) -> error::Result<RwLockReadGuard<Vec<Message>>> {
|
||||||
self.received.read().map_err(
|
self.received.read().map_err(|_| error::IrcError::PoisonedLog)
|
||||||
|_| error::ErrorKind::PoisonedLog.into(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,13 +246,13 @@ where
|
||||||
T: AsyncRead + AsyncWrite,
|
T: AsyncRead + AsyncWrite,
|
||||||
{
|
{
|
||||||
type Item = Message;
|
type Item = Message;
|
||||||
type Error = error::Error;
|
type Error = error::IrcError;
|
||||||
|
|
||||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||||
match try_ready!(self.inner.poll()) {
|
match try_ready!(self.inner.poll()) {
|
||||||
Some(msg) => {
|
Some(msg) => {
|
||||||
let recv: error::Result<_> = self.view.received.write().map_err(|_| {
|
let recv: error::Result<_> = self.view.received.write().map_err(|_| {
|
||||||
error::ErrorKind::PoisonedLog.into()
|
error::IrcError::PoisonedLog
|
||||||
});
|
});
|
||||||
recv?.push(msg.clone());
|
recv?.push(msg.clone());
|
||||||
Ok(Async::Ready(Some(msg)))
|
Ok(Async::Ready(Some(msg)))
|
||||||
|
@ -271,12 +267,12 @@ where
|
||||||
T: AsyncRead + AsyncWrite,
|
T: AsyncRead + AsyncWrite,
|
||||||
{
|
{
|
||||||
type SinkItem = Message;
|
type SinkItem = Message;
|
||||||
type SinkError = error::Error;
|
type SinkError = error::IrcError;
|
||||||
|
|
||||||
fn start_send(&mut self, item: Self::SinkItem) -> StartSend<Self::SinkItem, Self::SinkError> {
|
fn start_send(&mut self, item: Self::SinkItem) -> StartSend<Self::SinkItem, Self::SinkError> {
|
||||||
let res = self.inner.start_send(item.clone())?;
|
let res = self.inner.start_send(item.clone())?;
|
||||||
let sent: error::Result<_> = self.view.sent.write().map_err(|_| {
|
let sent: error::Result<_> = self.view.sent.write().map_err(|_| {
|
||||||
error::ErrorKind::PoisonedLog.into()
|
error::IrcError::PoisonedLog
|
||||||
});
|
});
|
||||||
sent?.push(item);
|
sent?.push(item);
|
||||||
Ok(res)
|
Ok(res)
|
||||||
|
|
276
src/error.rs
276
src/error.rs
|
@ -1,67 +1,233 @@
|
||||||
//! Errors for `irc` crate using `error_chain`.
|
//! Errors for `irc` crate using `failure`.
|
||||||
#![allow(missing_docs)]
|
|
||||||
|
|
||||||
error_chain! {
|
use std::io::Error as IoError;
|
||||||
foreign_links {
|
use std::sync::mpsc::RecvError;
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
errors {
|
use futures::sync::mpsc::SendError;
|
||||||
/// A parsing error for empty strings as messages.
|
use futures::sync::oneshot::Canceled;
|
||||||
ParseEmpty {
|
use native_tls::Error as TlsError;
|
||||||
description("Cannot parse an empty string as a message.")
|
#[cfg(feature = "json")]
|
||||||
display("Cannot parse an empty string as a message.")
|
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.
|
use proto::Message;
|
||||||
InvalidCommand {
|
|
||||||
description("Message contained a missing or invalid Command.")
|
|
||||||
display("Message contained a missing or invalid Command.")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A parsing error for failures in subcommand parsing (e.g. CAP and metadata).
|
/// A specialized `Result` type for the `irc` crate.
|
||||||
SubCommandParsingFailed {
|
pub type Result<T> = ::std::result::Result<T, IrcError>;
|
||||||
description("Failed to parse an IRC subcommand.")
|
|
||||||
display("Failed to parse an IRC subcommand.")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Failed to parse a mode correctly.
|
/// The main crate-wide error type.
|
||||||
ModeParsingFailed {
|
#[derive(Debug, Fail)]
|
||||||
description("Failed to parse a mode correctly.")
|
pub enum IrcError {
|
||||||
display("Failed to parse a mode correctly.")
|
/// 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`.
|
/// An internal TLS error.
|
||||||
ChannelError {
|
#[fail(display = "a TLS error occurred")]
|
||||||
description("An error occured on one of the IrcServer's internal channels.")
|
Tls(#[cause] TlsError),
|
||||||
display("An error occured on one of the IrcServer's internal channels.")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An error occured causing a mutex for a logged transport to be poisoned.
|
/// An internal synchronous channel closed.
|
||||||
PoisonedLog {
|
#[fail(display = "a sync channel closed")]
|
||||||
description("An error occured causing a mutex for a logged transport to be poisoned.")
|
SyncChannelClosed(#[cause] RecvError),
|
||||||
display("An error occured causing a mutex for a logged transport to be poisoned.")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Connection timed out due to no ping response.
|
/// An internal asynchronous channel closed.
|
||||||
PingTimeout {
|
#[fail(display = "an async channel closed")]
|
||||||
description("The connection timed out due to no ping response.")
|
AsyncChannelClosed(#[cause] SendError<Message>),
|
||||||
display("The connection timed out due to no ping response.")
|
|
||||||
}
|
|
||||||
|
|
||||||
NicknameNotSpecified {
|
/// An internal oneshot channel closed.
|
||||||
description("No nickname was specified for use with this IrcServer.")
|
#[fail(display = "a oneshot channel closed")]
|
||||||
display("No nickname was specified for use with this IrcServer.")
|
OneShotCanceled(#[cause] Canceled),
|
||||||
}
|
|
||||||
|
|
||||||
ServerNotSpecified {
|
/// An internal timer error.
|
||||||
description("No server was specified to connect to.")
|
#[fail(display = "timer failed")]
|
||||||
display("No server was specified to connect to.")
|
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)]
|
#![warn(missing_docs)]
|
||||||
#![recursion_limit="128"]
|
|
||||||
|
|
||||||
extern crate bufstream;
|
extern crate bufstream;
|
||||||
extern crate bytes;
|
extern crate bytes;
|
||||||
extern crate chrono;
|
extern crate chrono;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate error_chain;
|
extern crate failure;
|
||||||
extern crate encoding;
|
extern crate encoding;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate futures;
|
extern crate futures;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
use std::ascii::AsciiExt;
|
use std::ascii::AsciiExt;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use error;
|
use error::MessageParseError;
|
||||||
use proto::{ChannelExt, ChannelMode, Mode, Response, UserMode};
|
use proto::{ChannelExt, ChannelMode, Mode, Response, UserMode};
|
||||||
|
|
||||||
/// List of all client commands as defined in [RFC 2812](http://tools.ietf.org/html/rfc2812). This
|
/// List of all client commands as defined in [RFC 2812](http://tools.ietf.org/html/rfc2812). This
|
||||||
|
@ -446,7 +446,7 @@ impl<'a> From<&'a Command> for String {
|
||||||
|
|
||||||
impl Command {
|
impl Command {
|
||||||
/// Constructs a new 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") {
|
Ok(if cmd.eq_ignore_ascii_case("PASS") {
|
||||||
match suffix {
|
match suffix {
|
||||||
Some(suffix) => {
|
Some(suffix) => {
|
||||||
|
@ -1653,8 +1653,9 @@ impl CapSubCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for CapSubCommand {
|
impl FromStr for CapSubCommand {
|
||||||
type Err = error::Error;
|
type Err = MessageParseError;
|
||||||
fn from_str(s: &str) -> error::Result<CapSubCommand> {
|
|
||||||
|
fn from_str(s: &str) -> Result<CapSubCommand, Self::Err> {
|
||||||
if s.eq_ignore_ascii_case("LS") {
|
if s.eq_ignore_ascii_case("LS") {
|
||||||
Ok(CapSubCommand::LS)
|
Ok(CapSubCommand::LS)
|
||||||
} else if s.eq_ignore_ascii_case("LIST") {
|
} else if s.eq_ignore_ascii_case("LIST") {
|
||||||
|
@ -1672,7 +1673,10 @@ impl FromStr for CapSubCommand {
|
||||||
} else if s.eq_ignore_ascii_case("DEL") {
|
} else if s.eq_ignore_ascii_case("DEL") {
|
||||||
Ok(CapSubCommand::DEL)
|
Ok(CapSubCommand::DEL)
|
||||||
} else {
|
} else {
|
||||||
Err(error::ErrorKind::SubCommandParsingFailed.into())
|
Err(MessageParseError::InvalidSubcommand {
|
||||||
|
cmd: "CAP",
|
||||||
|
sub: s.to_owned(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1704,8 +1708,9 @@ impl MetadataSubCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for MetadataSubCommand {
|
impl FromStr for MetadataSubCommand {
|
||||||
type Err = error::Error;
|
type Err = MessageParseError;
|
||||||
fn from_str(s: &str) -> error::Result<MetadataSubCommand> {
|
|
||||||
|
fn from_str(s: &str) -> Result<MetadataSubCommand, Self::Err> {
|
||||||
if s.eq_ignore_ascii_case("GET") {
|
if s.eq_ignore_ascii_case("GET") {
|
||||||
Ok(MetadataSubCommand::GET)
|
Ok(MetadataSubCommand::GET)
|
||||||
} else if s.eq_ignore_ascii_case("LIST") {
|
} else if s.eq_ignore_ascii_case("LIST") {
|
||||||
|
@ -1715,7 +1720,10 @@ impl FromStr for MetadataSubCommand {
|
||||||
} else if s.eq_ignore_ascii_case("CLEAR") {
|
} else if s.eq_ignore_ascii_case("CLEAR") {
|
||||||
Ok(MetadataSubCommand::CLEAR)
|
Ok(MetadataSubCommand::CLEAR)
|
||||||
} else {
|
} else {
|
||||||
Err(error::ErrorKind::SubCommandParsingFailed.into())
|
Err(MessageParseError::InvalidSubcommand {
|
||||||
|
cmd: "METADATA",
|
||||||
|
sub: s.to_owned(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1743,8 +1751,9 @@ impl BatchSubCommand {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for BatchSubCommand {
|
impl FromStr for BatchSubCommand {
|
||||||
type Err = error::Error;
|
type Err = MessageParseError;
|
||||||
fn from_str(s: &str) -> error::Result<BatchSubCommand> {
|
|
||||||
|
fn from_str(s: &str) -> Result<BatchSubCommand, Self::Err> {
|
||||||
if s.eq_ignore_ascii_case("NETSPLIT") {
|
if s.eq_ignore_ascii_case("NETSPLIT") {
|
||||||
Ok(BatchSubCommand::NETSPLIT)
|
Ok(BatchSubCommand::NETSPLIT)
|
||||||
} else if s.eq_ignore_ascii_case("NETJOIN") {
|
} else if s.eq_ignore_ascii_case("NETJOIN") {
|
||||||
|
|
|
@ -20,7 +20,7 @@ impl IrcCodec {
|
||||||
|
|
||||||
impl Decoder for IrcCodec {
|
impl Decoder for IrcCodec {
|
||||||
type Item = Message;
|
type Item = Message;
|
||||||
type Error = error::Error;
|
type Error = error::IrcError;
|
||||||
|
|
||||||
fn decode(&mut self, src: &mut BytesMut) -> error::Result<Option<Message>> {
|
fn decode(&mut self, src: &mut BytesMut) -> error::Result<Option<Message>> {
|
||||||
self.inner.decode(src).and_then(|res| {
|
self.inner.decode(src).and_then(|res| {
|
||||||
|
@ -31,7 +31,7 @@ impl Decoder for IrcCodec {
|
||||||
|
|
||||||
impl Encoder for IrcCodec {
|
impl Encoder for IrcCodec {
|
||||||
type Item = Message;
|
type Item = Message;
|
||||||
type Error = error::Error;
|
type Error = error::IrcError;
|
||||||
|
|
||||||
|
|
||||||
fn encode(&mut self, msg: Message, dst: &mut BytesMut) -> error::Result<()> {
|
fn encode(&mut self, msg: Message, dst: &mut BytesMut) -> error::Result<()> {
|
||||||
|
|
|
@ -28,7 +28,7 @@ impl LineCodec {
|
||||||
|
|
||||||
impl Decoder for LineCodec {
|
impl Decoder for LineCodec {
|
||||||
type Item = String;
|
type Item = String;
|
||||||
type Error = error::Error;
|
type Error = error::IrcError;
|
||||||
|
|
||||||
fn decode(&mut self, src: &mut BytesMut) -> error::Result<Option<String>> {
|
fn decode(&mut self, src: &mut BytesMut) -> error::Result<Option<String>> {
|
||||||
if let Some(n) = src.as_ref().iter().position(|b| *b == b'\n') {
|
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 {
|
impl Encoder for LineCodec {
|
||||||
type Item = String;
|
type Item = String;
|
||||||
type Error = error::Error;
|
type Error = error::IrcError;
|
||||||
|
|
||||||
fn encode(&mut self, msg: String, dst: &mut BytesMut) -> error::Result<()> {
|
fn encode(&mut self, msg: String, dst: &mut BytesMut) -> error::Result<()> {
|
||||||
// Encode the message using the codec's encoding.
|
// 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 std::str::FromStr;
|
||||||
|
|
||||||
use error;
|
use error;
|
||||||
use error::{Error, ErrorKind};
|
use error::{IrcError, MessageParseError};
|
||||||
use proto::{Command, ChannelExt};
|
use proto::{Command, ChannelExt};
|
||||||
|
|
||||||
/// A data structure representing an IRC message according to the protocol specification. It
|
/// A data structure representing an IRC message according to the protocol specification. It
|
||||||
|
@ -43,7 +43,7 @@ impl Message {
|
||||||
command: &str,
|
command: &str,
|
||||||
args: Vec<&str>,
|
args: Vec<&str>,
|
||||||
suffix: Option<&str>,
|
suffix: Option<&str>,
|
||||||
) -> error::Result<Message> {
|
) -> Result<Message, MessageParseError> {
|
||||||
Message::with_tags(None, prefix, command, args, suffix)
|
Message::with_tags(None, prefix, command, args, suffix)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ impl Message {
|
||||||
command: &str,
|
command: &str,
|
||||||
args: Vec<&str>,
|
args: Vec<&str>,
|
||||||
suffix: Option<&str>,
|
suffix: Option<&str>,
|
||||||
) -> error::Result<Message> {
|
) -> Result<Message, error::MessageParseError> {
|
||||||
Ok(Message {
|
Ok(Message {
|
||||||
tags: tags,
|
tags: tags,
|
||||||
prefix: prefix.map(|s| s.to_owned()),
|
prefix: prefix.map(|s| s.to_owned()),
|
||||||
|
@ -170,13 +170,18 @@ impl From<Command> for Message {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for Message {
|
impl FromStr for Message {
|
||||||
type Err = Error;
|
type Err = IrcError;
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Message, Self::Err> {
|
fn from_str(s: &str) -> Result<Message, Self::Err> {
|
||||||
let mut state = s;
|
|
||||||
if s.is_empty() {
|
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 = if state.starts_with('@') {
|
||||||
let tags = state.find(' ').map(|i| &state[1..i]);
|
let tags = state.find(' ').map(|i| &state[1..i]);
|
||||||
state = state.find(' ').map_or("", |i| &state[i + 1..]);
|
state = state.find(' ').map_or("", |i| &state[i + 1..]);
|
||||||
|
@ -193,6 +198,7 @@ impl FromStr for Message {
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let prefix = if state.starts_with(':') {
|
let prefix = if state.starts_with(':') {
|
||||||
let prefix = state.find(' ').map(|i| &state[1..i]);
|
let prefix = state.find(' ').map(|i| &state[1..i]);
|
||||||
state = state.find(' ').map_or("", |i| &state[i + 1..]);
|
state = state.find(' ').map_or("", |i| &state[i + 1..]);
|
||||||
|
@ -200,6 +206,7 @@ impl FromStr for Message {
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let line_ending_len = if state.ends_with("\r\n") {
|
let line_ending_len = if state.ends_with("\r\n") {
|
||||||
"\r\n"
|
"\r\n"
|
||||||
} else if state.ends_with('\r') {
|
} else if state.ends_with('\r') {
|
||||||
|
@ -209,6 +216,7 @@ impl FromStr for Message {
|
||||||
} else {
|
} else {
|
||||||
""
|
""
|
||||||
}.len();
|
}.len();
|
||||||
|
|
||||||
let suffix = if state.contains(" :") {
|
let suffix = if state.contains(" :") {
|
||||||
let suffix = state.find(" :").map(|i| &state[i + 2..state.len() - line_ending_len]);
|
let suffix = state.find(" :").map(|i| &state[i + 2..state.len() - line_ending_len]);
|
||||||
state = state.find(" :").map_or("", |i| &state[..i + 1]);
|
state = state.find(" :").map_or("", |i| &state[..i + 1]);
|
||||||
|
@ -217,24 +225,33 @@ impl FromStr for Message {
|
||||||
state = &state[..state.len() - line_ending_len];
|
state = &state[..state.len() - line_ending_len];
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let command = match state.find(' ').map(|i| &state[..i]) {
|
let command = match state.find(' ').map(|i| &state[..i]) {
|
||||||
Some(cmd) => {
|
Some(cmd) => {
|
||||||
state = state.find(' ').map_or("", |i| &state[i + 1..]);
|
state = state.find(' ').map_or("", |i| &state[i + 1..]);
|
||||||
cmd
|
cmd
|
||||||
}
|
}
|
||||||
// If there's no arguments but the "command" starts with colon, it's not a command.
|
// 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.
|
// If there's no arguments following the command, the rest of the state is the command.
|
||||||
None => {
|
None => {
|
||||||
let cmd = state;
|
let cmd = state;
|
||||||
state = "";
|
state = "";
|
||||||
cmd
|
cmd
|
||||||
},
|
},
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let args: Vec<_> = state.splitn(14, ' ').filter(|s| !s.is_empty()).collect();
|
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.
|
//! A module defining an API for IRC user and channel modes.
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use error;
|
use error::MessageParseError;
|
||||||
|
use error::MessageParseError::InvalidModeString;
|
||||||
|
use error::ModeParseError::*;
|
||||||
use proto::Command;
|
use proto::Command;
|
||||||
|
|
||||||
/// A marker trait for different kinds of Modes.
|
/// A marker trait for different kinds of Modes.
|
||||||
|
@ -48,10 +50,10 @@ impl ModeType for UserMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserMode {
|
impl UserMode {
|
||||||
fn from_char(c: char) -> error::Result<UserMode> {
|
fn from_char(c: char) -> UserMode {
|
||||||
use self::UserMode::*;
|
use self::UserMode::*;
|
||||||
|
|
||||||
Ok(match c {
|
match c {
|
||||||
'a' => Away,
|
'a' => Away,
|
||||||
'i' => Invisible,
|
'i' => Invisible,
|
||||||
'w' => Wallops,
|
'w' => Wallops,
|
||||||
|
@ -61,7 +63,7 @@ impl UserMode {
|
||||||
's' => ServerNotices,
|
's' => ServerNotices,
|
||||||
'x' => MaskedHost,
|
'x' => MaskedHost,
|
||||||
_ => Unknown(c),
|
_ => Unknown(c),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,10 +143,10 @@ impl ModeType for ChannelMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ChannelMode {
|
impl ChannelMode {
|
||||||
fn from_char(c: char) -> error::Result<ChannelMode> {
|
fn from_char(c: char) -> ChannelMode {
|
||||||
use self::ChannelMode::*;
|
use self::ChannelMode::*;
|
||||||
|
|
||||||
Ok(match c {
|
match c {
|
||||||
'b' => Ban,
|
'b' => Ban,
|
||||||
'e' => Exception,
|
'e' => Exception,
|
||||||
'l' => Limit,
|
'l' => Limit,
|
||||||
|
@ -162,7 +164,7 @@ impl ChannelMode {
|
||||||
'h' => Halfop,
|
'h' => Halfop,
|
||||||
'v' => Voice,
|
'v' => Voice,
|
||||||
_ => Unknown(c),
|
_ => Unknown(c),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,7 +244,7 @@ enum PlusMinus {
|
||||||
impl Mode<UserMode> {
|
impl Mode<UserMode> {
|
||||||
// TODO: turning more edge cases into errors.
|
// TODO: turning more edge cases into errors.
|
||||||
/// Parses the specified mode string as user modes.
|
/// 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::*;
|
use self::PlusMinus::*;
|
||||||
|
|
||||||
let mut res = vec![];
|
let mut res = vec![];
|
||||||
|
@ -255,11 +257,18 @@ impl Mode<UserMode> {
|
||||||
let init = match chars.next() {
|
let init = match chars.next() {
|
||||||
Some('+') => Plus,
|
Some('+') => Plus,
|
||||||
Some('-') => Minus,
|
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 {
|
for c in chars {
|
||||||
let mode = UserMode::from_char(c)?;
|
let mode = UserMode::from_char(c);
|
||||||
let arg = if mode.takes_arg() {
|
let arg = if mode.takes_arg() {
|
||||||
pieces.next()
|
pieces.next()
|
||||||
} else {
|
} else {
|
||||||
|
@ -281,7 +290,7 @@ impl Mode<UserMode> {
|
||||||
impl Mode<ChannelMode> {
|
impl Mode<ChannelMode> {
|
||||||
// TODO: turning more edge cases into errors.
|
// TODO: turning more edge cases into errors.
|
||||||
/// Parses the specified mode string as channel modes.
|
/// 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::*;
|
use self::PlusMinus::*;
|
||||||
|
|
||||||
let mut res = vec![];
|
let mut res = vec![];
|
||||||
|
@ -294,11 +303,18 @@ impl Mode<ChannelMode> {
|
||||||
let init = match chars.next() {
|
let init = match chars.next() {
|
||||||
Some('+') => Plus,
|
Some('+') => Plus,
|
||||||
Some('-') => Minus,
|
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 {
|
for c in chars {
|
||||||
let mode = ChannelMode::from_char(c)?;
|
let mode = ChannelMode::from_char(c);
|
||||||
let arg = if mode.takes_arg() {
|
let arg = if mode.takes_arg() {
|
||||||
pieces.next()
|
pieces.next()
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Add table
Reference in a new issue