From 32e59aa3cc6567b909ab995a4e69beb12bdf0322 Mon Sep 17 00:00:00 2001 From: Jokler Date: Tue, 13 Feb 2018 21:37:58 +0100 Subject: Add download function & fromurl command --- src/plugins/factoids/utils.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/plugins/factoids/utils.rs (limited to 'src/plugins/factoids/utils.rs') diff --git a/src/plugins/factoids/utils.rs b/src/plugins/factoids/utils.rs new file mode 100644 index 0000000..ad25eef --- /dev/null +++ b/src/plugins/factoids/utils.rs @@ -0,0 +1,13 @@ +extern crate reqwest; + +use utils; +use super::rlua::prelude::*; + +use self::LuaError::RuntimeError; + +pub fn download(_: &Lua, url: String) -> Result { + match utils::download(1024, &url) { + Some(v) => Ok(v), + None => Err(RuntimeError(format!("Failed to download {}", url))), + } +} -- cgit v1.2.3-70-g09d2 From 297ceaa0899a6228f47f1f14e4bd261ec4cc6619 Mon Sep 17 00:00:00 2001 From: Jokler Date: Fri, 23 Feb 2018 18:29:20 +0100 Subject: Add possibly unsafe timeout hook to factoids --- Cargo.lock | 38 +++++++++++++++++++++++++++++++++++--- Cargo.toml | 2 +- src/plugins/factoids/mod.rs | 9 +++++---- src/plugins/factoids/sandbox.lua | 17 +++++++++++++++++ src/plugins/factoids/utils.rs | 8 ++++++++ 5 files changed, 66 insertions(+), 8 deletions(-) (limited to 'src/plugins/factoids/utils.rs') diff --git a/Cargo.lock b/Cargo.lock index 6b471e8..118e68f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -341,6 +341,25 @@ dependencies = [ "backtrace 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "failure" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "failure_derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", + "synstructure 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "foreign-types" version = "0.3.2" @@ -372,7 +391,7 @@ dependencies = [ "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "reqwest 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rlua 0.9.7 (registry+https://github.com/rust-lang/crates.io-index)", + "rlua 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)", "select 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", @@ -952,9 +971,10 @@ dependencies = [ [[package]] name = "rlua" -version = "0.9.7" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ + "failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1158,6 +1178,15 @@ dependencies = [ "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "synstructure" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "take" version = "0.1.0" @@ -1451,6 +1480,8 @@ dependencies = [ "checksum encoding-index-tradchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18" "checksum encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" "checksum error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9435d864e017c3c6afeac1654189b06cdb491cf2ff73dbf0d73b0f292f42ff8" +"checksum failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "934799b6c1de475a012a02dab0ace1ace43789ee4b99bcfbf1a2e3e8ced5de82" +"checksum failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c7cdda555bb90c9bb67a3b670a0f42de8e73f5981524123ad8578aafec8ddb8b" "checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" "checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" "checksum fuchsia-zircon 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f6c0581a4e363262e52b87f59ee2afe3415361c6ec35e665924eb08afe8ff159" @@ -1517,7 +1548,7 @@ dependencies = [ "checksum regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad890a5eef7953f55427c50575c680c42841653abd2b028b68cd223d157f62db" "checksum relay 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f301bafeb60867c85170031bdb2fcf24c8041f33aee09e7b116a58d4e9f781c5" "checksum reqwest 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3161ca63fd11ce36c7718af239e6492a25a3dbfcec54240f959b9d816cf42413" -"checksum rlua 0.9.7 (registry+https://github.com/rust-lang/crates.io-index)" = "fb7eec2f28b4812553f10b2e69888a5a674b2e761f5a39731c14541c32bdb6d8" +"checksum rlua 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4d6a9d2d1da31dd5cb4878789b924e46a600bdca4895b30f2efd6370d0dfc80e" "checksum rustc-demangle 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "aee45432acc62f7b9a108cc054142dac51f979e69e71ddce7d6fc7adf29e817e" "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" "checksum safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f" @@ -1543,6 +1574,7 @@ dependencies = [ "checksum string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b1884d1bc09741d466d9b14e6d37ac89d6909cbcac41dd9ae982d4d063bbedfc" "checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" +"checksum synstructure 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3a761d12e6d8dcb4dcf952a7a89b475e3a9d69e4a69307e01a470977642914bd" "checksum take 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b157868d8ac1f56b64604539990685fa7611d8fa9e5476cf0c02cf34d32917c5" "checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6" "checksum tendril 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1b72f8e2f5b73b65c315b1a70c730f24b9d7a25f39e98de8acbe2bb795caea" diff --git a/Cargo.toml b/Cargo.toml index 1775455..aadd44c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ tokio-core = "0.1.10" futures = "0.1.16" log = "0.3.8" time = "0.1" -rlua = "0.9.3" +rlua = "0.12.2" reqwest = "0.8.0" select = "0.4.2" regex = "0.2.2" diff --git a/src/plugins/factoids/mod.rs b/src/plugins/factoids/mod.rs index 252ba83..08e8f12 100644 --- a/src/plugins/factoids/mod.rs +++ b/src/plugins/factoids/mod.rs @@ -15,7 +15,7 @@ pub mod database; use self::database::{Database, DbResponse}; mod utils; -use self::utils::download; +use self::utils::*; static LUA_SANDBOX: &'static str = include_str!("sandbox.lua"); @@ -247,16 +247,17 @@ impl Factoids { .map(ToOwned::to_owned) .collect::>(); - let lua = Lua::new(); + let lua = unsafe { Lua::new_with_debug() }; let globals = lua.globals(); globals.set("factoid", code)?; - globals.set("download", lua.create_function(download))?; + globals.set("download", lua.create_function(download)?)?; + globals.set("sleep", lua.create_function(sleep)?)?; globals.set("args", args)?; globals.set("input", command.tokens.join(" "))?; globals.set("user", command.source.clone())?; globals.set("channel", command.target.clone())?; - globals.set("output", lua.create_table())?; + globals.set("output", lua.create_table()?)?; lua.exec::<()>(LUA_SANDBOX, Some(name))?; let output: Vec = globals.get::<_, Vec>("output")?; diff --git a/src/plugins/factoids/sandbox.lua b/src/plugins/factoids/sandbox.lua index 8ebf518..3fde65e 100644 --- a/src/plugins/factoids/sandbox.lua +++ b/src/plugins/factoids/sandbox.lua @@ -24,9 +24,26 @@ local env = { print = send, table = table, string = string, tostring = tostring, + tonumber = tonumber, math = math } local f, e = load(factoid, nil, nil, env) + +-- Check if the factoid timed out +function checktime(event, line) + if os.time() - time >= timeout then + error("Timed out after " .. timeout .. " seconds", 0) + else + -- Limit the cpu usage of factoids + sleep(1) + end +end + +-- Add timeout hook +time = os.time() +timeout = 30 +debug.sethook(checktime, "l") + if f then f() else diff --git a/src/plugins/factoids/utils.rs b/src/plugins/factoids/utils.rs index ad25eef..fc86fb3 100644 --- a/src/plugins/factoids/utils.rs +++ b/src/plugins/factoids/utils.rs @@ -1,5 +1,8 @@ extern crate reqwest; +use std::thread; +use std::time::Duration; + use utils; use super::rlua::prelude::*; @@ -11,3 +14,8 @@ pub fn download(_: &Lua, url: String) -> Result { None => Err(RuntimeError(format!("Failed to download {}", url))), } } + +pub fn sleep(_: &Lua, dur: u64) -> Result<(), LuaError> { + thread::sleep(Duration::from_millis(dur)); + Ok(()) +} -- cgit v1.2.3-70-g09d2 From b40a984ed9b6a948265e1287ecc08f4e16c64ecd Mon Sep 17 00:00:00 2001 From: Jokler Date: Wed, 28 Feb 2018 01:21:25 +0100 Subject: Create errors with failure The plugins are mostly not using the new errors yet and the error handling in the Factoids plugin is just temporary. --- Cargo.lock | 1 + Cargo.toml | 1 + src/error.rs | 98 ++++++++++++++++++++++++ src/lib.rs | 26 +++---- src/main.rs | 28 +++++-- src/plugins/factoids/mod.rs | 174 +++++++++++++++++------------------------- src/plugins/factoids/utils.rs | 4 +- src/plugins/url.rs | 37 ++++----- src/utils.rs | 101 +++++++++++------------- 9 files changed, 262 insertions(+), 208 deletions(-) create mode 100644 src/error.rs (limited to 'src/plugins/factoids/utils.rs') diff --git a/Cargo.lock b/Cargo.lock index d823d90..dcbaad5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -391,6 +391,7 @@ dependencies = [ "diesel_infer_schema 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "diesel_migrations 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "dotenv 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "frippy_derive 0.1.0", "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "humantime 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 961ffb9..2d4087a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ serde = "1.0.27" serde_json = "1.0.9" chrono = "0.4.0" glob = "0.2.11" +failure = "0.1.1" frippy_derive = { path = "frippy_derive" } diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..9ada5b6 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,98 @@ +//! Errors for `frippy` crate using `failure`. + +use std::io::Error as IoError; +use std::str::Utf8Error; +use irc::error::IrcError; +use reqwest::Error as ReqwestError; + +/// The main crate-wide error type. +#[derive(Debug, Fail)] +pub enum FrippyError { + /// A plugin error + #[fail(display = "A plugin error occured")] + Plugin(#[cause] PluginError), + + /// An IRC error + #[fail(display = "An IRC error occured")] + Irc(#[cause] IrcError), + + /// Missing config error + #[fail(display = "No config file was found")] + MissingConfig, + + /// A reqwest error + #[fail(display = "A reqwest error occured")] + Reqwest(#[cause] ReqwestError), + + /// An I/O error + #[fail(display = "An I/O error occured")] + Io(#[cause] IoError), + + /// A UTF8 error + #[fail(display = "A UTF8 error occured")] + Utf8(#[cause] Utf8Error), + + /// Reached download limit error + #[fail(display = "Reached download limit of {} KiB", limit)] + DownloadLimit { limit: usize }, +} + +/// Errors related to plugins +#[derive(Debug, Fail)] +pub enum PluginError { + /// A Url error + #[fail(display = "A Url error occured")] + Url(#[cause] UrlError), + + /// A Factoids error + #[fail(display = "{}", error)] + Factoids { error: String }, +} + +/// A URL plugin error +#[derive(Debug, Fail)] +pub enum UrlError { + /// Missing URL error + #[fail(display = "No URL was found")] + MissingUrl, + + /// Missing title error + #[fail(display = "No title was found")] + MissingTitle, +} + +impl From for FrippyError { + fn from(e: UrlError) -> FrippyError { + FrippyError::Plugin(PluginError::Url(e)) + } +} + +impl From for FrippyError { + fn from(e: PluginError) -> FrippyError { + FrippyError::Plugin(e) + } +} + +impl From for FrippyError { + fn from(e: IrcError) -> FrippyError { + FrippyError::Irc(e) + } +} + +impl From for FrippyError { + fn from(e: ReqwestError) -> FrippyError { + FrippyError::Reqwest(e) + } +} + +impl From for FrippyError { + fn from(e: IoError) -> FrippyError { + FrippyError::Io(e) + } +} + +impl From for FrippyError { + fn from(e: Utf8Error) -> FrippyError { + FrippyError::Utf8(e) + } +} diff --git a/src/lib.rs b/src/lib.rs index 657b0eb..42b0089 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,6 +37,8 @@ extern crate r2d2; #[cfg(feature = "mysql")] extern crate r2d2_diesel; +#[macro_use] +extern crate failure; #[macro_use] extern crate frippy_derive; #[macro_use] @@ -47,11 +49,13 @@ extern crate log; extern crate chrono; extern crate humantime; extern crate irc; +extern crate reqwest; extern crate time; pub mod plugin; pub mod plugins; pub mod utils; +pub mod error; use std::collections::HashMap; use std::fmt; @@ -60,6 +64,7 @@ use std::sync::Arc; pub use irc::client::prelude::*; pub use irc::error::IrcError; +use error::FrippyError; use plugin::*; @@ -142,20 +147,15 @@ impl Bot { /// reactor.run().unwrap(); /// # } /// ``` - pub fn connect(&self, reactor: &mut IrcReactor, config: &Config) -> Result<(), String> { + pub fn connect(&self, reactor: &mut IrcReactor, config: &Config) -> Result<(), FrippyError> { info!("Plugins loaded: {}", self.plugins); - let client = match reactor.prepare_client_and_connect(config) { - Ok(v) => v, - Err(e) => return Err(format!("Failed to connect: {}", e)), - }; + let client = reactor.prepare_client_and_connect(config)?; info!("Connected to IRC server"); - match client.identify() { - Ok(_) => info!("Identified"), - Err(e) => return Err(format!("Failed to identify: {}", e)), - }; + client.identify()?; + info!("Identified"); // TODO Verify if we actually need to clone plugins twice let plugins = self.plugins.clone(); @@ -253,10 +253,10 @@ impl ThreadedPlugins { &mut self, server: &IrcClient, mut command: PluginCommand, - ) -> Result<(), IrcError> { + ) -> Result<(), FrippyError> { if !command.tokens.iter().any(|s| !s.is_empty()) { let help = format!("Use \"{} help\" to get help", server.current_nickname()); - return server.send_notice(&command.source, &help); + server.send_notice(&command.source, &help)?; } // Check if the command is for this plugin @@ -284,7 +284,7 @@ impl ThreadedPlugins { command.tokens[0] ); - server.send_notice(&command.source, &help) + Ok(server.send_notice(&command.source, &help)?) } } } @@ -293,7 +293,7 @@ impl fmt::Display for ThreadedPlugins { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let plugin_names = self.plugins .iter() - .map(|(_, p)| p.name().to_string()) + .map(|(_, p)| p.name().to_owned()) .collect::>(); write!(f, "{}", plugin_names.join(", ")) } diff --git a/src/main.rs b/src/main.rs index ec04d33..461387d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ extern crate frippy; extern crate glob; extern crate irc; extern crate time; +extern crate failure; #[cfg(feature = "mysql")] extern crate diesel; @@ -26,9 +27,11 @@ use log::{Level, LevelFilter, Metadata, Record}; use irc::client::reactor::IrcReactor; use glob::glob; +use failure::Fail; use frippy::plugins; use frippy::Config; +use frippy::error::FrippyError; #[cfg(feature = "mysql")] embed_migrations!(); @@ -67,6 +70,18 @@ impl log::Log for Logger { static LOGGER: Logger = Logger; fn main() { + // Print any errors that caused frippy to shut down + if let Err(e) = run() { + let mut causes = e.causes(); + + error!("{}", causes.next().unwrap()); + for cause in causes { + error!("caused by: {}", cause); + } + }; +} + +fn run() -> Result<(), FrippyError> { log::set_max_level(if cfg!(debug_assertions) { LevelFilter::Debug } else { @@ -92,12 +107,11 @@ fn main() { // Without configs the bot would just idle if configs.is_empty() { - error!("No config file found"); - return; + return Err(FrippyError::MissingConfig); } // Create an event loop to run the connections on. - let mut reactor = IrcReactor::new().unwrap(); + let mut reactor = IrcReactor::new()?; // Open a connection and add work for each config for config in configs { @@ -127,8 +141,7 @@ fn main() { let manager = ConnectionManager::::new(url.clone()); match r2d2::Pool::builder().build(manager) { - Ok(pool) => match embedded_migrations::run(&*pool.get() - .expect("Failed to get connection")) + Ok(pool) => match embedded_migrations::run(&*pool.get()?) { Ok(_) => { let pool = Arc::new(pool); @@ -166,10 +179,9 @@ fn main() { } } - bot.connect(&mut reactor, &config) - .expect("Failed to connect"); + bot.connect(&mut reactor, &config)?; } // Run the bots until they throw an error - an error could be loss of connection - reactor.run().unwrap(); + Ok(reactor.run()?) } diff --git a/src/plugins/factoids/mod.rs b/src/plugins/factoids/mod.rs index d6abb1d..3f89943 100644 --- a/src/plugins/factoids/mod.rs +++ b/src/plugins/factoids/mod.rs @@ -6,6 +6,9 @@ use std::sync::Mutex; use self::rlua::prelude::*; use irc::client::prelude::*; use irc::error::IrcError; +use error::FrippyError; +use error::PluginError; +use failure::Fail; use time; use chrono::NaiveDateTime; @@ -40,8 +43,8 @@ impl Factoids { } } - fn create_factoid(&self, name: &str, content: &str, author: &str) -> Result<&str, &str> { - let count = try_lock!(self.factoids).count_factoids(name)?; + fn create_factoid(&self, name: &str, content: &str, author: &str) -> Result<&str, FrippyError> { + let count = try_lock!(self.factoids).count_factoids(name).map_err(|e| PluginError::Factoids { error: e.to_owned() })?; let tm = time::now().to_timespec(); let factoid = database::NewFactoid { @@ -54,74 +57,60 @@ impl Factoids { match try_lock!(self.factoids).insert_factoid(&factoid) { DbResponse::Success => Ok("Successfully added"), - DbResponse::Failed(e) => Err(e), + DbResponse::Failed(e) => Err(PluginError::Factoids { error: e.to_owned() })?, } } - fn add(&self, client: &IrcClient, command: &mut PluginCommand) -> Result<(), IrcError> { + fn add(&self, client: &IrcClient, command: &mut PluginCommand) -> Result<&str, FrippyError> { if command.tokens.len() < 2 { - return self.invalid_command(client, command); + return Ok(self.invalid_command(client, command).map(|()| "")?); } let name = command.tokens.remove(0); let content = command.tokens.join(" "); - match self.create_factoid(&name, &content, &command.source) { - Ok(v) => client.send_notice(&command.source, v), - Err(e) => client.send_notice(&command.source, e), - } + Ok(self.create_factoid(&name, &content, &command.source)?) } - fn save_from_url( + fn add_from_url( &self, client: &IrcClient, command: &mut PluginCommand, - ) -> Result<(), IrcError> { + ) -> Result<&str, FrippyError> { if command.tokens.len() < 2 { - return self.invalid_command(client, command); + return Ok(self.invalid_command(client, command).map(|()| "")?); } let name = command.tokens.remove(0); let url = &command.tokens[0]; - if let Some(content) = ::utils::download(1024, url) { - match self.create_factoid(&name, &content, &command.source) { - Ok(v) => client.send_notice(&command.source, v), - Err(e) => client.send_notice(&command.source, e), - } - } else { - client.send_notice(&command.source, "Failed to download.") - } + let content = ::utils::download(1024, url)?; + + Ok(self.create_factoid(&name, &content, &command.source)?) } - fn remove(&self, client: &IrcClient, command: &mut PluginCommand) -> Result<(), IrcError> { + fn remove(&self, client: &IrcClient, command: &mut PluginCommand) -> Result<&str, FrippyError> { if command.tokens.len() < 1 { - return self.invalid_command(client, command); + return Ok(self.invalid_command(client, command).map(|()| "")?); } let name = command.tokens.remove(0); - let count = match try_lock!(self.factoids).count_factoids(&name) { - Ok(c) => c, - Err(e) => return client.send_notice(&command.source, e), - }; + let count = try_lock!(self.factoids).count_factoids(&name).map_err(|e| PluginError::Factoids { error: e.to_owned() } )?; match try_lock!(self.factoids).delete_factoid(&name, count - 1) { - DbResponse::Success => client.send_notice(&command.source, "Successfully removed"), - DbResponse::Failed(e) => client.send_notice(&command.source, e), + DbResponse::Success => Ok("Successfully removed"), + DbResponse::Failed(e) => Err(PluginError::Factoids { error: e.to_owned() })?, } } - fn get(&self, client: &IrcClient, command: &PluginCommand) -> Result<(), IrcError> { + fn get(&self, client: &IrcClient, command: &PluginCommand) -> Result { let (name, idx) = match command.tokens.len() { - 0 => return self.invalid_command(client, command), + 0 => return Ok(self.invalid_command(client, command).map(|()| String::new())?), 1 => { let name = &command.tokens[0]; - let count = match try_lock!(self.factoids).count_factoids(name) { - Ok(c) => c, - Err(e) => return client.send_notice(&command.source, e), - }; + let count = try_lock!(self.factoids).count_factoids(name).map_err(|e| PluginError::Factoids { error: e.to_owned() } )?; if count < 1 { - return client.send_notice(&command.source, &format!("{} does not exist", name)); + Err(PluginError::Factoids { error: format!("{} does not exist", name) })?; } (name, count - 1) @@ -130,7 +119,7 @@ impl Factoids { let name = &command.tokens[0]; let idx = match i32::from_str(&command.tokens[1]) { Ok(i) => i, - Err(_) => return client.send_notice(&command.source, "Invalid index"), + Err(_) => Err(PluginError::Factoids { error: String::from("Invalid index") })?, }; (name, idx) @@ -139,61 +128,37 @@ impl Factoids { let factoid = match try_lock!(self.factoids).get_factoid(name, idx) { Some(v) => v, - None => { - return client - .send_notice(&command.source, &format!("{}~{} does not exist", name, idx)) - } + None => Err(PluginError::Factoids { error: format!("{}~{} does not exist", name, idx) })?, }; let message = factoid.content.replace("\n", "|").replace("\r", ""); - client.send_privmsg(&command.target, &format!("{}: {}", factoid.name, message)) + Ok(format!("{}: {}", factoid.name, message)) } - fn info(&self, client: &IrcClient, command: &PluginCommand) -> Result<(), IrcError> { + fn info(&self, client: &IrcClient, command: &PluginCommand) -> Result { match command.tokens.len() { - 0 => self.invalid_command(client, command), + 0 => Ok(self.invalid_command(client, command).map(|()| String::new())?), 1 => { let name = &command.tokens[0]; - let count = match try_lock!(self.factoids).count_factoids(name) { - Ok(c) => c, - Err(e) => return client.send_notice(&command.source, e), - }; + let count = try_lock!(self.factoids).count_factoids(name).map_err(|e| PluginError::Factoids { error: e.to_owned() } )?; - match count { - 0 => client.send_notice(&command.source, &format!("{} does not exist", name)), - 1 => client - .send_privmsg(&command.target, &format!("There is 1 version of {}", name)), - _ => client.send_privmsg( - &command.target, - &format!("There are {} versions of {}", count, name), - ), - } + Ok(match count { + 0 => Err(PluginError::Factoids { error: format!("{} does not exist", name) })?, + 1 => format!("There is 1 version of {}", name), + _ => format!("There are {} versions of {}", count, name), + }) } _ => { let name = &command.tokens[0]; - let idx = match i32::from_str(&command.tokens[1]) { - Ok(i) => i, - Err(_) => return client.send_notice(&command.source, "Invalid index"), - }; + let idx = i32::from_str(&command.tokens[1]).map_err(|_| PluginError::Factoids { error: String::from("Invalid index") })?; let factoid = match try_lock!(self.factoids).get_factoid(name, idx) { Some(v) => v, - None => { - return client.send_notice( - &command.source, - &format!("{}~{} does not exist", name, idx), - ) - } + None => return Ok(format!("{}~{} does not exist", name, idx)), }; - client.send_privmsg( - &command.target, - &format!( - "{}: Added by {} at {} UTC", - name, factoid.author, factoid.created - ), - ) + Ok(format!("{}: Added by {} at {} UTC", name, factoid.author, factoid.created)) } } } @@ -202,39 +167,31 @@ impl Factoids { &self, client: &IrcClient, mut command: PluginCommand, - error: bool, - ) -> Result<(), IrcError> { + ) -> Result { if command.tokens.len() < 1 { - self.invalid_command(client, &command) + Ok(self.invalid_command(client, &command).map(|()| String::new())?) } else { let name = command.tokens.remove(0); - let count = match try_lock!(self.factoids).count_factoids(&name) { - Ok(c) => c, - Err(e) => return client.send_notice(&command.source, e), - }; - - let factoid = match try_lock!(self.factoids).get_factoid(&name, count - 1) { - Some(v) => v.content, - None if error => return self.invalid_command(client, &command), - None => return Ok(()), - }; + let count = try_lock!(self.factoids).count_factoids(&name).map_err(|e| PluginError::Factoids { error: e.to_owned() } )?; + let factoid = try_lock!(self.factoids).get_factoid(&name, count - 1).ok_or(PluginError::Factoids { error: format!("The factoid \"{}\" does not exist", name) })?; - let value = &if factoid.starts_with('>') { - let factoid = String::from(&factoid[1..]); + let content = factoid.content; + let value = if content.starts_with('>') { + let content = String::from(&content[1..]); - if factoid.starts_with('>') { - factoid + if content.starts_with('>') { + content } else { - match self.run_lua(&name, &factoid, &command) { + match self.run_lua(&name, &content, &command) { Ok(v) => v, Err(e) => format!("\"{}\"", e), } } } else { - factoid + content }; - client.send_privmsg(&command.target, &value.replace("\n", "|").replace("\r", "")) + Ok(value.replace("\n", "|").replace("\r", "")) } } @@ -293,12 +250,15 @@ impl Plugin for Factoids { let t: Vec = content.split(' ').map(ToOwned::to_owned).collect(); let c = PluginCommand { - source: message.source_nickname().unwrap().to_string(), - target: message.response_target().unwrap().to_string(), + source: message.source_nickname().unwrap().to_owned(), + target: message.response_target().unwrap().to_owned(), tokens: t, }; - self.exec(client, c, false) + match self.exec(client, c) { + Ok(f) => client.send_privmsg(&message.response_target().unwrap(), &f), + Err(_) => Ok(()), + } } else { Ok(()) } @@ -309,16 +269,24 @@ impl Plugin for Factoids { return self.invalid_command(client, &command); } + let target = command.target.clone(); + let source = command.source.clone(); + let sub_command = command.tokens.remove(0); - match sub_command.as_ref() { - "add" => self.add(client, &mut command), - "fromurl" => self.save_from_url(client, &mut command), - "remove" => self.remove(client, &mut command), + let result = match sub_command.as_ref() { + "add" => self.add(client, &mut command).map(|s| s.to_owned()), + "fromurl" => self.add_from_url(client, &mut command).map(|s| s.to_owned()), + "remove" => self.remove(client, &mut command).map(|s| s.to_owned()), "get" => self.get(client, &command), "info" => self.info(client, &command), - "exec" => self.exec(client, command, true), - _ => self.invalid_command(client, &command), - } + "exec" => self.exec(client, command), + _ => self.invalid_command(client, &command).map(|()| String::new()).map_err(|e| e.into()), + }; + + Ok(match result { + Ok(v) => client.send_privmsg(&target, &v), + Err(e) => client.send_notice(&source, &e.cause().unwrap().to_string()), + }?) } fn evaluate(&self, _: &IrcClient, _: PluginCommand) -> Result { diff --git a/src/plugins/factoids/utils.rs b/src/plugins/factoids/utils.rs index fc86fb3..036dcc6 100644 --- a/src/plugins/factoids/utils.rs +++ b/src/plugins/factoids/utils.rs @@ -10,8 +10,8 @@ use self::LuaError::RuntimeError; pub fn download(_: &Lua, url: String) -> Result { match utils::download(1024, &url) { - Some(v) => Ok(v), - None => Err(RuntimeError(format!("Failed to download {}", url))), + Ok(v) => Ok(v), + Err(e) => Err(RuntimeError(format!("Failed to download {} - {}", url, e.to_string()))), } } diff --git a/src/plugins/url.rs b/src/plugins/url.rs index b19d4e5..af6f36f 100644 --- a/src/plugins/url.rs +++ b/src/plugins/url.rs @@ -11,6 +11,9 @@ use self::select::predicate::Name; use plugin::*; use utils; +use error::FrippyError; +use error::UrlError; +use failure::Fail; lazy_static! { static ref RE: Regex = Regex::new(r"(^|\s)(https?://\S+)").unwrap(); @@ -28,14 +31,10 @@ impl Url { } fn grep_url(&self, msg: &str) -> Option { - match RE.captures(msg) { - Some(captures) => { - debug!("Url captures: {:?}", captures); + let captures = RE.captures(msg)?; + debug!("Url captures: {:?}", captures); - Some(captures.get(2)?.as_str().to_string()) - } - None => None, - } + Some(captures.get(2)?.as_str().to_owned()) } fn get_title(&self, body: &str) -> Option { @@ -49,21 +48,11 @@ impl Url { Some(title_text) } - fn url(&self, text: &str) -> Result { - let url = match self.grep_url(text) { - Some(url) => url, - None => return Err("No Url was found."), - }; - - match utils::download(self.max_kib, &url) { - Some(body) => { - match self.get_title(&body) { - Some(title) => Ok(title), - None => Err("No title was found.") - } - } - None => Err("Failed to download document."), - } + fn url(&self, text: &str) -> Result { + let url = self.grep_url(text).ok_or(UrlError::MissingUrl)?; + let body = utils::download(self.max_kib, &url)?; + + Ok(self.get_title(&body).ok_or(UrlError::MissingTitle)?) } } @@ -86,7 +75,7 @@ impl Plugin for Url { Err(e) => { error!("Url plugin error: {}", e); Ok(()) - }, + } }, _ => Ok(()), } @@ -100,7 +89,7 @@ impl Plugin for Url { } fn evaluate(&self, _: &IrcClient, command: PluginCommand) -> Result { - self.url(&command.tokens[0]).map_err(String::from) + self.url(&command.tokens[0]).map_err(|e| e.cause().unwrap().to_string()) } } diff --git a/src/utils.rs b/src/utils.rs index 99b04c4..68ad4d8 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,73 +1,58 @@ -extern crate reqwest; - use std::str; use std::io::{self, Read}; -use self::reqwest::Client; -use self::reqwest::header::Connection; +use reqwest::Client; +use reqwest::header::Connection; -pub fn download(max_kib: usize, url: &str) -> Option { - let response = Client::new().get(url).header(Connection::close()).send(); +use error::FrippyError; - match response { - Ok(mut response) => { - let mut body = String::new(); +pub fn download(max_kib: usize, url: &str) -> Result { + let mut response = Client::new().get(url).header(Connection::close()).send()?; - // 100 kibibyte buffer - let mut buf = [0; 100 * 1024]; - let mut written = 0; - let mut vec = Vec::new(); - let mut end_of_valid = None; + let mut body = String::new(); - // Read until we reach EOF or max_kib KiB - loop { - if let Some(eov) = end_of_valid { - vec = vec[..eov].to_vec(); - } + // 100 kibibyte buffer + let mut buf = [0; 100 * 1024]; + let mut written = 0; + let mut vec = Vec::new(); + let mut end_of_valid = None; - let len = match response.read(&mut buf) { - Ok(0) => break, - Ok(len) => len, - Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue, - Err(e) => { - debug!("Download from {:?} failed: {}", url, e); - return None; - } - }; - vec.extend_from_slice(&buf); + // Read until we reach EOF or max_kib KiB + loop { + if let Some(eov) = end_of_valid { + vec = vec[..eov].to_vec(); + } - end_of_valid = None; - let body_slice = match str::from_utf8(&vec[..len]) { - Ok(slice) => slice, - Err(e) => { - let valid = e.valid_up_to(); - if valid == 0 { - error!("Failed to read bytes from {:?} as UTF8: {}", url, e); - return None; - } - end_of_valid = Some(valid); + let len = match response.read(&mut buf) { + Ok(0) => break, + Ok(len) => len, + Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue, + Err(e) => Err(e)?, + }; + vec.extend_from_slice(&buf); + + end_of_valid = None; + let body_slice = match str::from_utf8(&vec[..len]) { + Ok(slice) => slice, + Err(e) => { + let valid = e.valid_up_to(); + if valid == 0 { + Err(e)?; + } + end_of_valid = Some(valid); - str::from_utf8(&buf[..valid]).unwrap() - } - }; + str::from_utf8(&buf[..valid])? + } + }; - body.push_str(body_slice); - written += len; + body.push_str(body_slice); + written += len; - // Check if the file is too large to download - if written > max_kib * 1024 { - debug!( - "Stopping download - File from {:?} is larger than {} KiB", - url, max_kib - ); - return None; - } - } - Some(body) - } - Err(e) => { - debug!("Bad response from {:?}: ({})", url, e); - None + // Check if the file is too large to download + if written > max_kib * 1024 { + Err(FrippyError::DownloadLimit { limit: max_kib })?; } } + + Ok(body) } -- cgit v1.2.3-70-g09d2 From d8406b107c651321c9e166abc36fc66730d965a1 Mon Sep 17 00:00:00 2001 From: Jokler Date: Thu, 1 Mar 2018 18:34:03 +0100 Subject: Use lossy UTF8 conversion and add a log_error function --- src/error.rs | 11 ----------- src/main.rs | 9 +-------- src/plugins/factoids/mod.rs | 2 +- src/plugins/factoids/utils.rs | 2 +- src/plugins/url.rs | 7 ++----- src/utils.rs | 26 +++++++++++++++++++++----- 6 files changed, 26 insertions(+), 31 deletions(-) (limited to 'src/plugins/factoids/utils.rs') diff --git a/src/error.rs b/src/error.rs index 674faaa..fa232be 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,7 +1,6 @@ //! Errors for `frippy` crate using `failure`. use std::io::Error as IoError; -use std::str::Utf8Error; use irc::error::IrcError; use reqwest::Error as ReqwestError; #[cfg(feature = "mysql")] @@ -30,10 +29,6 @@ pub enum FrippyError { #[fail(display = "An I/O error occured")] Io(#[cause] IoError), - /// A decoding error - #[fail(display = "Failed to decode bytes")] - Decoding(#[cause] Utf8Error), - /// An r2d2 error #[cfg(feature = "mysql")] #[fail(display = "An r2d2 error occured")] @@ -98,12 +93,6 @@ impl From for FrippyError { } } -impl From for FrippyError { - fn from(e: Utf8Error) -> FrippyError { - FrippyError::Decoding(e) - } -} - #[cfg(feature = "mysql")] impl From for FrippyError { fn from(e: R2d2Error) -> FrippyError { diff --git a/src/main.rs b/src/main.rs index 461387d..3432e3e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,6 @@ extern crate frippy; extern crate glob; extern crate irc; extern crate time; -extern crate failure; #[cfg(feature = "mysql")] extern crate diesel; @@ -27,7 +26,6 @@ use log::{Level, LevelFilter, Metadata, Record}; use irc::client::reactor::IrcReactor; use glob::glob; -use failure::Fail; use frippy::plugins; use frippy::Config; @@ -72,12 +70,7 @@ static LOGGER: Logger = Logger; fn main() { // Print any errors that caused frippy to shut down if let Err(e) = run() { - let mut causes = e.causes(); - - error!("{}", causes.next().unwrap()); - for cause in causes { - error!("caused by: {}", cause); - } + frippy::utils::log_error(e); }; } diff --git a/src/plugins/factoids/mod.rs b/src/plugins/factoids/mod.rs index 3f89943..806bb7e 100644 --- a/src/plugins/factoids/mod.rs +++ b/src/plugins/factoids/mod.rs @@ -83,7 +83,7 @@ impl Factoids { let name = command.tokens.remove(0); let url = &command.tokens[0]; - let content = ::utils::download(1024, url)?; + let content = ::utils::download(url, Some(1024))?; Ok(self.create_factoid(&name, &content, &command.source)?) } diff --git a/src/plugins/factoids/utils.rs b/src/plugins/factoids/utils.rs index 036dcc6..009b46b 100644 --- a/src/plugins/factoids/utils.rs +++ b/src/plugins/factoids/utils.rs @@ -9,7 +9,7 @@ use super::rlua::prelude::*; use self::LuaError::RuntimeError; pub fn download(_: &Lua, url: String) -> Result { - match utils::download(1024, &url) { + match utils::download(&url, Some(1024)) { Ok(v) => Ok(v), Err(e) => Err(RuntimeError(format!("Failed to download {} - {}", url, e.to_string()))), } diff --git a/src/plugins/url.rs b/src/plugins/url.rs index af6f36f..6f00466 100644 --- a/src/plugins/url.rs +++ b/src/plugins/url.rs @@ -50,7 +50,7 @@ impl Url { fn url(&self, text: &str) -> Result { let url = self.grep_url(text).ok_or(UrlError::MissingUrl)?; - let body = utils::download(self.max_kib, &url)?; + let body = utils::download(&url, Some(self.max_kib))?; Ok(self.get_title(&body).ok_or(UrlError::MissingTitle)?) } @@ -72,10 +72,7 @@ impl Plugin for Url { match message.command { Command::PRIVMSG(_, ref content) => match self.url(content) { Ok(title) => client.send_privmsg(message.response_target().unwrap(), &title), - Err(e) => { - error!("Url plugin error: {}", e); - Ok(()) - } + Err(e) => Ok(utils::log_error(e)), }, _ => Ok(()), } diff --git a/src/utils.rs b/src/utils.rs index 1f3a117..cf91b37 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -4,9 +4,14 @@ use std::io::{self, Read}; use reqwest::Client; use reqwest::header::Connection; +use failure::Fail; use error::FrippyError; -pub fn download(max_kib: usize, url: &str) -> Result { +/// Downloads the file and converts it to a String. +/// Any invalid bytes are converted to a replacement character. +/// +/// The error indicated either a failed download or that the DownloadLimit was reached +pub fn download(url: &str, max_kib: Option) -> Result { let mut response = Client::new().get(url).header(Connection::close()).send()?; // 100 kibibyte buffer @@ -27,11 +32,22 @@ pub fn download(max_kib: usize, url: &str) -> Result { written += len; // Check if the file is too large to download - if written > max_kib * 1024 { - Err(FrippyError::DownloadLimit { limit: max_kib })?; + if let Some(max_kib) = max_kib { + if written > max_kib * 1024 { + Err(FrippyError::DownloadLimit { limit: max_kib })?; + } } } - let body = str::from_utf8(&bytes)?; - Ok(body.to_string()) + Ok(String::from_utf8_lossy(&bytes).into_owned()) +} + + +pub fn log_error(e: FrippyError) { + let mut causes = e.causes(); + + error!("{}", causes.next().unwrap()); + for cause in causes { + error!("caused by: {}", cause); + } } -- cgit v1.2.3-70-g09d2 From 0b4131e8cf91ed10f24d3faed341034d518aea53 Mon Sep 17 00:00:00 2001 From: Jokler Date: Fri, 2 Mar 2018 22:11:21 +0100 Subject: Use Error & ErrorKind pair instead of simple enums Each plugin should define its own errors with a respective variant in the main ErrorKind of frippy. A new procedural macro was added to reduce the boilerplate required for new error system. It can be used by deriving "Error" and adding a name for the Error via the "error" attribute. So far non of the plugins except for Url and Factoids use their own errors yet. --- Cargo.lock | 40 ++++++++- frippy_derive/Cargo.toml | 5 +- frippy_derive/src/lib.rs | 86 +++++++++++++++++- src/error.rs | 114 +++++------------------- src/lib.rs | 56 ++++++------ src/main.rs | 51 ++++++----- src/plugin.rs | 21 +++-- src/plugins/currency.rs | 28 +++--- src/plugins/emoji.rs | 25 +++--- src/plugins/factoids/database.rs | 88 ++++++++---------- src/plugins/factoids/mod.rs | 187 +++++++++++++++++++++++---------------- src/plugins/factoids/utils.rs | 6 +- src/plugins/help.rs | 16 ++-- src/plugins/keepnick.rs | 29 +++--- src/plugins/mod.rs | 23 ++--- src/plugins/tell/mod.rs | 32 ++++--- src/plugins/url.rs | 63 ++++++++----- src/utils.rs | 34 ++++--- 18 files changed, 528 insertions(+), 376 deletions(-) (limited to 'src/plugins/factoids/utils.rs') diff --git a/Cargo.lock b/Cargo.lock index dcbaad5..acb6f80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -414,8 +414,9 @@ dependencies = [ name = "frippy_derive" version = "0.1.0" dependencies = [ - "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.12.13 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -925,6 +926,14 @@ name = "precomputed-hash" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "proc-macro2" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "pulldown-cmark" version = "0.0.15" @@ -949,6 +958,14 @@ name = "quote" version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "quote" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "r2d2" version = "0.8.2" @@ -1246,6 +1263,16 @@ dependencies = [ "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "syn" +version = "0.12.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "synom" version = "0.11.3" @@ -1427,6 +1454,11 @@ name = "unicode-xid" version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "unicode_names" version = "0.1.7" @@ -1636,10 +1668,12 @@ dependencies = [ "checksum phf_shared 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)" = "07e24b0ca9643bdecd0632f2b3da6b1b89bbb0030e0b992afc1113b23a7bc2f2" "checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903" "checksum precomputed-hash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +"checksum proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cd07deb3c6d1d9ff827999c7f9b04cdfd66b1b17ae508e14fe47b620f2282ae0" "checksum pulldown-cmark 0.0.15 (registry+https://github.com/rust-lang/crates.io-index)" = "378e941dbd392c101f2cb88097fa4d7167bc421d4b88de3ff7dbee503bc3233b" "checksum quick-error 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eda5fe9b71976e62bc81b781206aaa076401769b2143379d3eb2118388babac4" "checksum quine-mc_cluskey 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "07589615d719a60c8dd8a4622e7946465dfef20d1a428f969e3443e7386d5f45" "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" +"checksum quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1eca14c727ad12702eb4b6bfb5a232287dcf8385cb8ca83a3eeaf6519c44c408" "checksum r2d2 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f9078ca6a8a5568ed142083bb2f7dc9295b69d16f867ddcc9849e51b17d8db46" "checksum r2d2-diesel 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "eb9c29bad92da76d02bc2c020452ebc3a3fe6fa74cfab91e711c43116e4fb1a3" "checksum rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "15a732abf9d20f0ad8eeb6f909bf6868722d9a06e1e50802b6a70351f40b4eb1" @@ -1675,6 +1709,7 @@ dependencies = [ "checksum string_cache_codegen 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "479cde50c3539481f33906a387f2bd17c8e87cb848c35b6021d41fb81ff9b4d7" "checksum string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b1884d1bc09741d466d9b14e6d37ac89d6909cbcac41dd9ae982d4d063bbedfc" "checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" +"checksum syn 0.12.13 (registry+https://github.com/rust-lang/crates.io-index)" = "517f6da31bc53bf080b9a77b29fbd0ff8da2f5a2ebd24c73c2238274a94ac7cb" "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" "checksum synstructure 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3a761d12e6d8dcb4dcf952a7a89b475e3a9d69e4a69307e01a470977642914bd" "checksum take 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b157868d8ac1f56b64604539990685fa7611d8fa9e5476cf0c02cf34d32917c5" @@ -1695,6 +1730,7 @@ dependencies = [ "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" "checksum unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "51ccda9ef9efa3f7ef5d91e8f9b83bbe6955f9bf86aec89d5cce2c874625920f" "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" +"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" "checksum unicode_names 0.1.7 (git+https://github.com/Jokler/unicode_names?branch=update-to-latest-unicode)" = "" "checksum unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1f2ae5ddb18e1c92664717616dd9549dde73f539f01bd7b77c2edb2446bdff91" "checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" diff --git a/frippy_derive/Cargo.toml b/frippy_derive/Cargo.toml index 937afba..b258f57 100644 --- a/frippy_derive/Cargo.toml +++ b/frippy_derive/Cargo.toml @@ -7,5 +7,6 @@ authors = ["Jokler "] proc-macro = true [dependencies] -syn = "0.11.11" -quote = "0.3.15" +syn = "0.12.13" +quote = "0.4.2" +failure = "0.1.1" diff --git a/frippy_derive/src/lib.rs b/frippy_derive/src/lib.rs index 2622f0b..efa349a 100644 --- a/frippy_derive/src/lib.rs +++ b/frippy_derive/src/lib.rs @@ -1,18 +1,20 @@ - //! Provides the plugin derive macro +#![recursion_limit="128"] extern crate proc_macro; extern crate syn; #[macro_use] extern crate quote; +extern crate failure; + use proc_macro::TokenStream; #[proc_macro_derive(PluginName)] pub fn derive_plugin(data: TokenStream) -> TokenStream { - let ast = syn::parse_derive_input(&data.to_string()).unwrap(); + let ast = syn::parse(data).unwrap(); let gen = expand_plugin(&ast); - gen.parse().unwrap() + gen.into() } fn expand_plugin(ast: &syn::DeriveInput) -> quote::Tokens { @@ -27,3 +29,81 @@ fn expand_plugin(ast: &syn::DeriveInput) -> quote::Tokens { } } } + +#[proc_macro_derive(Error, attributes(error))] +pub fn derive_error(data: TokenStream) -> TokenStream { + let ast = syn::parse(data).unwrap(); + let tokens = expand_error(&ast); + // panic!("{}", tokens.to_string()); + tokens.into() +} + +fn expand_error(ast: &syn::DeriveInput) -> quote::Tokens { + if let syn::Data::Enum(_) = ast.data { + } else { + panic!("Error should only be derived on ErrorKind enums"); + }; + + let mut name = None; + for attr in &ast.attrs { + if let Some(syn::Meta::NameValue(name_value)) = attr.interpret_meta() { + if "error" == name_value.ident.to_string() { + if let syn::Lit::Str(lit) = name_value.lit { + name = Some(lit.value()); + } + } + } + }; + + let struct_name = if let Some(name) = name { + syn::Ident::from(name) + } else { + panic!("Define the error attribute for all Error derives"); + }; + + let enum_name = &ast.ident; + + quote! { + #[derive(Debug)] + pub struct #struct_name { + inner: ::failure::Context<#enum_name>, + } + + impl #struct_name { + pub fn kind(&self) -> #enum_name { + *self.inner.get_context() + } + } + + impl From<#enum_name> for #struct_name { + fn from(kind: #enum_name) -> #struct_name { + #struct_name { + inner: ::failure::Context::new(kind), + } + } + } + + impl From<::failure::Context<#enum_name>> for #struct_name { + fn from(inner: ::failure::Context<#enum_name>) -> #struct_name { + #struct_name { inner: inner } + } + } + + impl ::failure::Fail for #struct_name { + fn cause(&self) -> Option<&::failure::Fail> { + self.inner.cause() + } + + fn backtrace(&self) -> Option<&::failure::Backtrace> { + self.inner.backtrace() + } + } + + impl ::std::fmt::Display for #struct_name { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + use std::fmt; + fmt::Display::fmt(&self.inner, f) + } + } + } +} diff --git a/src/error.rs b/src/error.rs index fa232be..36d5724 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,101 +1,31 @@ //! Errors for `frippy` crate using `failure`. -use std::io::Error as IoError; -use irc::error::IrcError; -use reqwest::Error as ReqwestError; -#[cfg(feature = "mysql")] -use r2d2::Error as R2d2Error; +use failure::Fail; -/// The main crate-wide error type. -#[derive(Debug, Fail)] -pub enum FrippyError { - /// A plugin error - #[fail(display = "A plugin error occured")] - Plugin(#[cause] PluginError), - - /// An IRC error - #[fail(display = "An IRC error occured")] - Irc(#[cause] IrcError), - - /// Missing config error - #[fail(display = "No config file was found")] - MissingConfig, - - /// A reqwest error - #[fail(display = "A reqwest error occured")] - Reqwest(#[cause] ReqwestError), - - /// An I/O error - #[fail(display = "An I/O error occured")] - Io(#[cause] IoError), - - /// An r2d2 error - #[cfg(feature = "mysql")] - #[fail(display = "An r2d2 error occured")] - R2d2(#[cause] R2d2Error), - - /// Reached download limit error - #[fail(display = "Reached download limit of {} KiB", limit)] - DownloadLimit { limit: usize }, +pub fn log_error(e: FrippyError) { + let text = e.causes() + .skip(1) + .fold(format!("{}", e), |acc, err| format!("{}: {}", acc, err)); + error!("{}", text); } -/// Errors related to plugins -#[derive(Debug, Fail)] -pub enum PluginError { - /// A Url error - #[fail(display = "A Url error occured")] - Url(#[cause] UrlError), - - /// A Factoids error - #[fail(display = "{}", error)] - Factoids { error: String }, -} - -/// A URL plugin error -#[derive(Debug, Fail)] -pub enum UrlError { - /// Missing URL error - #[fail(display = "No URL was found")] - MissingUrl, - - /// Missing title error - #[fail(display = "No title was found")] - MissingTitle, -} - -impl From for FrippyError { - fn from(e: UrlError) -> FrippyError { - FrippyError::Plugin(PluginError::Url(e)) - } -} - -impl From for FrippyError { - fn from(e: PluginError) -> FrippyError { - FrippyError::Plugin(e) - } -} - -impl From for FrippyError { - fn from(e: IrcError) -> FrippyError { - FrippyError::Irc(e) - } -} +/// The main crate-wide error type. +#[derive(Copy, Clone, Eq, PartialEq, Debug, Fail, Error)] +#[error = "FrippyError"] +pub enum ErrorKind { + /// Connection error + #[fail(display = "A connection error occured")] + Connection, -impl From for FrippyError { - fn from(e: ReqwestError) -> FrippyError { - FrippyError::Reqwest(e) - } -} + /// A Url error + #[fail(display = "A Url error has occured")] + Url, -impl From for FrippyError { - fn from(e: IoError) -> FrippyError { - FrippyError::Io(e) - } -} + /// A Tell error + #[fail(display = "A Tell error has occured")] + Tell, -#[cfg(feature = "mysql")] -impl From for FrippyError { - fn from(e: R2d2Error) -> FrippyError { - FrippyError::R2d2(e) - } + /// A Factoids error + #[fail(display = "A Factoids error has occured")] + Factoids, } diff --git a/src/lib.rs b/src/lib.rs index 42b0089..8cf1e2f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,7 +64,8 @@ use std::sync::Arc; pub use irc::client::prelude::*; pub use irc::error::IrcError; -use error::FrippyError; +use error::*; +use failure::ResultExt; use plugin::*; @@ -150,11 +151,13 @@ impl Bot { pub fn connect(&self, reactor: &mut IrcReactor, config: &Config) -> Result<(), FrippyError> { info!("Plugins loaded: {}", self.plugins); - let client = reactor.prepare_client_and_connect(config)?; + let client = reactor + .prepare_client_and_connect(config) + .context(ErrorKind::Connection)?; info!("Connected to IRC server"); - client.identify()?; + client.identify().context(ErrorKind::Connection)?; info!("Identified"); // TODO Verify if we actually need to clone plugins twice @@ -169,25 +172,25 @@ impl Bot { } fn process_msg( - server: &IrcClient, + client: &IrcClient, mut plugins: ThreadedPlugins, message: Message, ) -> Result<(), IrcError> { // Log any channels we join if let Command::JOIN(ref channel, _, _) = message.command { - if message.source_nickname().unwrap() == server.current_nickname() { + if message.source_nickname().unwrap() == client.current_nickname() { info!("Joined {}", channel); } } // Check for possible command and save the result for later - let command = PluginCommand::from(&server.current_nickname().to_lowercase(), &message); + let command = PluginCommand::from(&client.current_nickname().to_lowercase(), &message); - plugins.execute_plugins(server, message); + plugins.execute_plugins(client, message); // If the message contained a command, handle it if let Some(command) = command { - if let Err(e) = plugins.handle_command(server, command) { + if let Err(e) = plugins.handle_command(client, command) { error!("Failed to handle command: {}", e); } } @@ -218,12 +221,12 @@ impl ThreadedPlugins { self.plugins.remove(&name.to_lowercase()).map(|_| ()) } - pub fn execute_plugins(&mut self, server: &IrcClient, message: Message) { + pub fn execute_plugins(&mut self, client: &IrcClient, message: Message) { let message = Arc::new(message); for (name, plugin) in self.plugins.clone() { // Send the message to the plugin if the plugin needs it - match plugin.execute(server, &message) { + match plugin.execute(client, &message) { ExecutionStatus::Done => (), ExecutionStatus::Err(e) => error!("Error in {} - {}", name, e), ExecutionStatus::RequiresThread => { @@ -233,15 +236,15 @@ impl ThreadedPlugins { message.to_string().replace("\r\n", "") ); - // Clone everything before the move - the server uses an Arc internally too + // Clone everything before the move - the client uses an Arc internally too let plugin = Arc::clone(&plugin); let message = Arc::clone(&message); - let server = server.clone(); + let client = client.clone(); // Execute the plugin in another thread spawn(move || { - if let Err(e) = plugin.execute_threaded(&server, &message) { - error!("Error in {} - {}", name, e); + if let Err(e) = plugin.execute_threaded(&client, &message) { + log_error(e); }; }); } @@ -251,12 +254,14 @@ impl ThreadedPlugins { pub fn handle_command( &mut self, - server: &IrcClient, + client: &IrcClient, mut command: PluginCommand, ) -> Result<(), FrippyError> { if !command.tokens.iter().any(|s| !s.is_empty()) { - let help = format!("Use \"{} help\" to get help", server.current_nickname()); - server.send_notice(&command.source, &help)?; + let help = format!("Use \"{} help\" to get help", client.current_nickname()); + client + .send_notice(&command.source, &help) + .context(ErrorKind::Connection)?; } // Check if the command is for this plugin @@ -266,12 +271,12 @@ impl ThreadedPlugins { debug!("Sending command \"{:?}\" to {}", command, name); - // Clone for the move - the server uses an Arc internally - let server = server.clone(); + // Clone for the move - the client uses an Arc internally + let client = client.clone(); let plugin = Arc::clone(plugin); spawn(move || { - if let Err(e) = plugin.command(&server, command) { - error!("Error in {} command - {}", name, e); + if let Err(e) = plugin.command(&client, command) { + log_error(e); }; }); @@ -280,11 +285,13 @@ impl ThreadedPlugins { let help = format!( "\"{} {}\" is not a command, \ try \"{0} help\" instead.", - server.current_nickname(), + client.current_nickname(), command.tokens[0] ); - Ok(server.send_notice(&command.source, &help)?) + Ok(client + .send_notice(&command.source, &help) + .context(ErrorKind::Connection)?) } } } @@ -298,6 +305,3 @@ impl fmt::Display for ThreadedPlugins { write!(f, "{}", plugin_names.join(", ")) } } - -#[cfg(test)] -mod tests {} diff --git a/src/main.rs b/src/main.rs index 3432e3e..b9a4b8f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,6 +16,8 @@ extern crate r2d2; #[cfg(feature = "mysql")] extern crate r2d2_diesel; +#[macro_use] +extern crate failure; #[macro_use] extern crate log; @@ -27,9 +29,16 @@ use log::{Level, LevelFilter, Metadata, Record}; use irc::client::reactor::IrcReactor; use glob::glob; -use frippy::plugins; +pub use frippy::plugins::help::Help; +pub use frippy::plugins::url::Url; +pub use frippy::plugins::emoji::Emoji; +pub use frippy::plugins::tell::Tell; +pub use frippy::plugins::currency::Currency; +pub use frippy::plugins::keepnick::KeepNick; +pub use frippy::plugins::factoids::Factoids; + use frippy::Config; -use frippy::error::FrippyError; +use failure::Error; #[cfg(feature = "mysql")] embed_migrations!(); @@ -70,11 +79,14 @@ static LOGGER: Logger = Logger; fn main() { // Print any errors that caused frippy to shut down if let Err(e) = run() { - frippy::utils::log_error(e); + let text = e.causes() + .skip(1) + .fold(format!("{}", e), |acc, err| format!("{}: {}", acc, err)); + error!("{}", text); }; } -fn run() -> Result<(), FrippyError> { +fn run() -> Result<(), Error> { log::set_max_level(if cfg!(debug_assertions) { LevelFilter::Debug } else { @@ -100,7 +112,7 @@ fn run() -> Result<(), FrippyError> { // Without configs the bot would just idle if configs.is_empty() { - return Err(FrippyError::MissingConfig); + bail!("No config file was found"); } // Create an event loop to run the connections on. @@ -119,11 +131,11 @@ fn run() -> Result<(), FrippyError> { } let mut bot = frippy::Bot::new(); - bot.add_plugin(plugins::Help::new()); - bot.add_plugin(plugins::Url::new(1024)); - bot.add_plugin(plugins::Emoji::new()); - bot.add_plugin(plugins::Currency::new()); - bot.add_plugin(plugins::KeepNick::new()); + bot.add_plugin(Help::new()); + bot.add_plugin(Url::new(1024)); + bot.add_plugin(Emoji::new()); + bot.add_plugin(Currency::new()); + bot.add_plugin(KeepNick::new()); #[cfg(feature = "mysql")] { @@ -134,25 +146,24 @@ fn run() -> Result<(), FrippyError> { let manager = ConnectionManager::::new(url.clone()); match r2d2::Pool::builder().build(manager) { - Ok(pool) => match embedded_migrations::run(&*pool.get()?) - { + Ok(pool) => match embedded_migrations::run(&*pool.get()?) { Ok(_) => { let pool = Arc::new(pool); - bot.add_plugin(plugins::Factoids::new(pool.clone())); - bot.add_plugin(plugins::Tell::new(pool.clone())); + bot.add_plugin(Factoids::new(pool.clone())); + bot.add_plugin(Tell::new(pool.clone())); info!("Connected to MySQL server") } Err(e) => { - bot.add_plugin(plugins::Factoids::new(HashMap::new())); - bot.add_plugin(plugins::Tell::new(HashMap::new())); + bot.add_plugin(Factoids::new(HashMap::new())); + bot.add_plugin(Tell::new(HashMap::new())); error!("Failed to run migrations: {}", e); } }, Err(e) => error!("Failed to connect to database: {}", e), } } else { - bot.add_plugin(plugins::Factoids::new(HashMap::new())); - bot.add_plugin(plugins::Tell::new(HashMap::new())); + bot.add_plugin(Factoids::new(HashMap::new())); + bot.add_plugin(Tell::new(HashMap::new())); } } #[cfg(not(feature = "mysql"))] @@ -160,8 +171,8 @@ fn run() -> Result<(), FrippyError> { if mysql_url.is_some() { error!("frippy was not built with the mysql feature") } - bot.add_plugin(plugins::Factoids::new(HashMap::new())); - bot.add_plugin(plugins::Tell::new(HashMap::new())); + bot.add_plugin(Factoids::new(HashMap::new())); + bot.add_plugin(Tell::new(HashMap::new())); } if let Some(disabled_plugins) = disabled_plugins { diff --git a/src/plugin.rs b/src/plugin.rs index f33fa80..e57f072 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -2,16 +2,17 @@ use std::fmt; use irc::client::prelude::*; -use irc::error::IrcError; +use error::FrippyError; /// Describes if a [`Plugin`](trait.Plugin.html) is done working on a /// [`Message`](../../irc/proto/message/struct.Message.html) or if another thread is required. #[derive(Debug)] pub enum ExecutionStatus { - /// The [`Plugin`](trait.Plugin.html) does not need to do any more work on this [`Message`](../../irc/proto/message/struct.Message.html). + /// The [`Plugin`](trait.Plugin.html) does not need to do any more work on this + /// [`Message`](../../irc/proto/message/struct.Message.html). Done, /// An error occured during the execution. - Err(Box), + Err(FrippyError), /// The execution needs to be done by [`execute_threaded()`](trait.Plugin.html#tymethod.execute_threaded). RequiresThread, } @@ -19,15 +20,17 @@ pub enum ExecutionStatus { /// `Plugin` has to be implemented for any struct that should be usable /// as a `Plugin` in frippy. pub trait Plugin: PluginName + Send + Sync + fmt::Debug { - /// Handles messages which are not commands or returns [`RequiresThread`](enum.ExecutionStatus.html#variant.RequiresThread) + /// Handles messages which are not commands or returns + /// [`RequiresThread`](enum.ExecutionStatus.html#variant.RequiresThread) /// if [`execute_threaded()`](trait.Plugin.html#tymethod.execute_threaded) should be used instead. - fn execute(&self, server: &IrcClient, message: &Message) -> ExecutionStatus; + fn execute(&self, client: &IrcClient, message: &Message) -> ExecutionStatus; /// Handles messages which are not commands in a new thread. - fn execute_threaded(&self, server: &IrcClient, message: &Message) -> Result<(), IrcError>; + fn execute_threaded(&self, client: &IrcClient, message: &Message) -> Result<(), FrippyError>; /// Handles any command directed at this plugin. - fn command(&self, server: &IrcClient, command: PluginCommand) -> Result<(), IrcError>; - /// Similar to [`command()`](trait.Plugin.html#tymethod.command) but return a String instead of sending messages directly to IRC. - fn evaluate(&self, server: &IrcClient, command: PluginCommand) -> Result; + fn command(&self, client: &IrcClient, command: PluginCommand) -> Result<(), FrippyError>; + /// Similar to [`command()`](trait.Plugin.html#tymethod.command) but return a String instead of + /// sending messages directly to IRC. + fn evaluate(&self, client: &IrcClient, command: PluginCommand) -> Result; } /// `PluginName` is required by [`Plugin`](trait.Plugin.html). diff --git a/src/plugins/currency.rs b/src/plugins/currency.rs index 958c8e2..53a245c 100644 --- a/src/plugins/currency.rs +++ b/src/plugins/currency.rs @@ -6,7 +6,6 @@ use std::io::Read; use std::num::ParseFloatError; use irc::client::prelude::*; -use irc::error::IrcError; use self::reqwest::Client; use self::reqwest::header::Connection; @@ -14,6 +13,10 @@ use self::serde_json::Value; use plugin::*; +use error::FrippyError; +use error::ErrorKind as FrippyErrorKind; +use failure::ResultExt; + #[derive(PluginName, Default, Debug)] pub struct Currency; @@ -124,20 +127,28 @@ impl Plugin for Currency { ExecutionStatus::Done } - fn execute_threaded(&self, _: &IrcClient, _: &Message) -> Result<(), IrcError> { + fn execute_threaded(&self, _: &IrcClient, _: &Message) -> Result<(), FrippyError> { panic!("Currency does not implement the execute function!") } - fn command(&self, client: &IrcClient, mut command: PluginCommand) -> Result<(), IrcError> { + fn command(&self, client: &IrcClient, mut command: PluginCommand) -> Result<(), FrippyError> { if command.tokens.is_empty() { - return client.send_notice(&command.source, &self.invalid_command(client)); + return Ok(client + .send_notice(&command.source, &self.invalid_command(client)) + .context(FrippyErrorKind::Connection)?); } match command.tokens[0].as_ref() { - "help" => client.send_notice(&command.source, &self.help(client)), + "help" => Ok(client + .send_notice(&command.source, &self.help(client)) + .context(FrippyErrorKind::Connection)?), _ => match self.convert(client, &mut command) { - Ok(msg) => client.send_privmsg(&command.target, &msg), - Err(msg) => client.send_notice(&command.source, &msg), + Ok(msg) => Ok(client + .send_privmsg(&command.target, &msg) + .context(FrippyErrorKind::Connection)?), + Err(msg) => Ok(client + .send_notice(&command.source, &msg) + .context(FrippyErrorKind::Connection)?), }, } } @@ -153,6 +164,3 @@ impl Plugin for Currency { } } } - -#[cfg(test)] -mod tests {} diff --git a/src/plugins/emoji.rs b/src/plugins/emoji.rs index a4276f4..f1d9376 100644 --- a/src/plugins/emoji.rs +++ b/src/plugins/emoji.rs @@ -3,10 +3,14 @@ extern crate unicode_names; use std::fmt; use irc::client::prelude::*; -use irc::error::IrcError; use plugin::*; +use error::FrippyError; +use error::ErrorKind as FrippyErrorKind; +use failure::Fail; +use failure::ResultExt; + struct EmojiHandle { symbol: char, count: i32, @@ -100,21 +104,23 @@ impl Plugin for Emoji { .send_privmsg(message.response_target().unwrap(), &self.emoji(content)) { Ok(_) => ExecutionStatus::Done, - Err(e) => ExecutionStatus::Err(Box::new(e)), + Err(e) => ExecutionStatus::Err(e.context(FrippyErrorKind::Connection).into()), }, _ => ExecutionStatus::Done, } } - fn execute_threaded(&self, _: &IrcClient, _: &Message) -> Result<(), IrcError> { + fn execute_threaded(&self, _: &IrcClient, _: &Message) -> Result<(), FrippyError> { panic!("Emoji should not use threading") } - fn command(&self, client: &IrcClient, command: PluginCommand) -> Result<(), IrcError> { - client.send_notice( - &command.source, - "This Plugin does not implement any commands.", - ) + fn command(&self, client: &IrcClient, command: PluginCommand) -> Result<(), FrippyError> { + Ok(client + .send_notice( + &command.source, + "This Plugin does not implement any commands.", + ) + .context(FrippyErrorKind::Connection)?) } fn evaluate(&self, _: &IrcClient, command: PluginCommand) -> Result { @@ -126,6 +132,3 @@ impl Plugin for Emoji { } } } - -#[cfg(test)] -mod tests {} diff --git a/src/plugins/factoids/database.rs b/src/plugins/factoids/database.rs index 88aa0fd..b1fe8dd 100644 --- a/src/plugins/factoids/database.rs +++ b/src/plugins/factoids/database.rs @@ -13,13 +13,12 @@ use diesel::mysql::MysqlConnection; use r2d2::Pool; #[cfg(feature = "mysql")] use r2d2_diesel::ConnectionManager; +#[cfg(feature = "mysql")] +use failure::ResultExt; use chrono::NaiveDateTime; -pub enum DbResponse { - Success, - Failed(&'static str), -} +use super::error::*; #[cfg_attr(feature = "mysql", derive(Queryable))] #[derive(Clone, Debug)] @@ -42,15 +41,15 @@ pub struct NewFactoid<'a> { } pub trait Database: Send { - fn insert_factoid(&mut self, factoid: &NewFactoid) -> DbResponse; - fn get_factoid(&self, name: &str, idx: i32) -> Option; - fn delete_factoid(&mut self, name: &str, idx: i32) -> DbResponse; - fn count_factoids(&self, name: &str) -> Result; + fn insert_factoid(&mut self, factoid: &NewFactoid) -> Result<(), FactoidsError>; + fn get_factoid(&self, name: &str, idx: i32) -> Result; + fn delete_factoid(&mut self, name: &str, idx: i32) -> Result<(), FactoidsError>; + fn count_factoids(&self, name: &str) -> Result; } // HashMap impl Database for HashMap<(String, i32), Factoid> { - fn insert_factoid(&mut self, factoid: &NewFactoid) -> DbResponse { + fn insert_factoid(&mut self, factoid: &NewFactoid) -> Result<(), FactoidsError> { let factoid = Factoid { name: String::from(factoid.name), idx: factoid.idx, @@ -61,23 +60,25 @@ impl Database for HashMap<(String, i32), Factoid> { let name = factoid.name.clone(); match self.insert((name, factoid.idx), factoid) { - None => DbResponse::Success, - Some(_) => DbResponse::Failed("Factoid was overwritten"), + None => Ok(()), + Some(_) => Err(ErrorKind::Duplicate)?, } } - fn get_factoid(&self, name: &str, idx: i32) -> Option { - self.get(&(String::from(name), idx)).cloned() + fn get_factoid(&self, name: &str, idx: i32) -> Result { + Ok(self.get(&(String::from(name), idx)) + .cloned() + .ok_or(ErrorKind::NotFound)?) } - fn delete_factoid(&mut self, name: &str, idx: i32) -> DbResponse { + fn delete_factoid(&mut self, name: &str, idx: i32) -> Result<(), FactoidsError> { match self.remove(&(String::from(name), idx)) { - Some(_) => DbResponse::Success, - None => DbResponse::Failed("Factoid not found"), + Some(_) => Ok(()), + None => Err(ErrorKind::NotFound)?, } } - fn count_factoids(&self, name: &str) -> Result { + fn count_factoids(&self, name: &str) -> Result { Ok(self.iter().filter(|&(&(ref n, _), _)| n == name).count() as i32) } } @@ -102,38 +103,31 @@ use self::schema::factoids; #[cfg(feature = "mysql")] impl Database for Arc>> { - fn insert_factoid(&mut self, factoid: &NewFactoid) -> DbResponse { + fn insert_factoid(&mut self, factoid: &NewFactoid) -> Result<(), FactoidsError> { use diesel; - let conn = &*self.get().expect("Failed to get connection"); - match diesel::insert_into(factoids::table) + let conn = &*self.get().context(ErrorKind::NoConnection)?; + diesel::insert_into(factoids::table) .values(factoid) .execute(conn) - { - Ok(_) => DbResponse::Success, - Err(e) => { - error!("DB Insertion Error: {}", e); - DbResponse::Failed("Failed to add factoid") - } - } + .context(ErrorKind::MysqlError)?; + + Ok(()) } - fn get_factoid(&self, name: &str, idx: i32) -> Option { - let conn = &*self.get().expect("Failed to get connection"); - match factoids::table.find((name, idx)).first(conn) { - Ok(f) => Some(f), - Err(e) => { - error!("DB Count Error: {}", e); - None - } - } + fn get_factoid(&self, name: &str, idx: i32) -> Result { + let conn = &*self.get().context(ErrorKind::NoConnection)?; + Ok(factoids::table + .find((name, idx)) + .first(conn) + .context(ErrorKind::MysqlError)?) } - fn delete_factoid(&mut self, name: &str, idx: i32) -> DbResponse { + fn delete_factoid(&mut self, name: &str, idx: i32) -> Result<(), FactoidsError> { use diesel; use self::factoids::columns; - let conn = &*self.get().expect("Failed to get connection"); + let conn = &*self.get().context(ErrorKind::NoConnection)?; match diesel::delete( factoids::table .filter(columns::name.eq(name)) @@ -142,22 +136,19 @@ impl Database for Arc>> { { Ok(v) => { if v > 0 { - DbResponse::Success + Ok(()) } else { - DbResponse::Failed("Could not find any factoid with that name") + Err(ErrorKind::NotFound)? } } - Err(e) => { - error!("DB Deletion Error: {}", e); - DbResponse::Failed("Failed to delete factoid") - } + Err(e) => Err(e).context(ErrorKind::MysqlError)?, } } - fn count_factoids(&self, name: &str) -> Result { + fn count_factoids(&self, name: &str) -> Result { use diesel; - let conn = &*self.get().expect("Failed to get connection"); + let conn = &*self.get().context(ErrorKind::NoConnection)?; let count: Result = factoids::table .filter(factoids::columns::name.eq(name)) .count() @@ -166,10 +157,7 @@ impl Database for Arc>> { match count { Ok(c) => Ok(c as i32), Err(diesel::NotFound) => Ok(0), - Err(e) => { - error!("DB Count Error: {}", e); - Err("Database Error") - } + Err(e) => Err(e).context(ErrorKind::MysqlError)?, } } } diff --git a/src/plugins/factoids/mod.rs b/src/plugins/factoids/mod.rs index 806bb7e..2f3690f 100644 --- a/src/plugins/factoids/mod.rs +++ b/src/plugins/factoids/mod.rs @@ -5,21 +5,22 @@ use std::str::FromStr; use std::sync::Mutex; use self::rlua::prelude::*; use irc::client::prelude::*; -use irc::error::IrcError; -use error::FrippyError; -use error::PluginError; -use failure::Fail; use time; use chrono::NaiveDateTime; use plugin::*; pub mod database; -use self::database::{Database, DbResponse}; +use self::database::Database; mod utils; use self::utils::*; +use failure::ResultExt; +use error::ErrorKind as FrippyErrorKind; +use error::FrippyError; +use self::error::*; + static LUA_SANDBOX: &'static str = include_str!("sandbox.lua"); #[derive(PluginName)] @@ -43,8 +44,13 @@ impl Factoids { } } - fn create_factoid(&self, name: &str, content: &str, author: &str) -> Result<&str, FrippyError> { - let count = try_lock!(self.factoids).count_factoids(name).map_err(|e| PluginError::Factoids { error: e.to_owned() })?; + fn create_factoid( + &self, + name: &str, + content: &str, + author: &str, + ) -> Result<&str, FactoidsError> { + let count = try_lock!(self.factoids).count_factoids(name)?; let tm = time::now().to_timespec(); let factoid = database::NewFactoid { @@ -55,15 +61,14 @@ impl Factoids { created: NaiveDateTime::from_timestamp(tm.sec, 0u32), }; - match try_lock!(self.factoids).insert_factoid(&factoid) { - DbResponse::Success => Ok("Successfully added"), - DbResponse::Failed(e) => Err(PluginError::Factoids { error: e.to_owned() })?, - } + Ok(try_lock!(self.factoids) + .insert_factoid(&factoid) + .map(|()| "Successfully added!")?) } - fn add(&self, client: &IrcClient, command: &mut PluginCommand) -> Result<&str, FrippyError> { + fn add(&self, command: &mut PluginCommand) -> Result<&str, FactoidsError> { if command.tokens.len() < 2 { - return Ok(self.invalid_command(client, command).map(|()| "")?); + Err(ErrorKind::InvalidCommand)?; } let name = command.tokens.remove(0); @@ -72,45 +77,41 @@ impl Factoids { Ok(self.create_factoid(&name, &content, &command.source)?) } - fn add_from_url( - &self, - client: &IrcClient, - command: &mut PluginCommand, - ) -> Result<&str, FrippyError> { + fn add_from_url(&self, command: &mut PluginCommand) -> Result<&str, FactoidsError> { if command.tokens.len() < 2 { - return Ok(self.invalid_command(client, command).map(|()| "")?); + Err(ErrorKind::InvalidCommand)?; } let name = command.tokens.remove(0); let url = &command.tokens[0]; - let content = ::utils::download(url, Some(1024))?; + let content = ::utils::download(url, Some(1024)).context(ErrorKind::Download)?; Ok(self.create_factoid(&name, &content, &command.source)?) } - fn remove(&self, client: &IrcClient, command: &mut PluginCommand) -> Result<&str, FrippyError> { + fn remove(&self, command: &mut PluginCommand) -> Result<&str, FactoidsError> { if command.tokens.len() < 1 { - return Ok(self.invalid_command(client, command).map(|()| "")?); + Err(ErrorKind::InvalidCommand)?; } let name = command.tokens.remove(0); - let count = try_lock!(self.factoids).count_factoids(&name).map_err(|e| PluginError::Factoids { error: e.to_owned() } )?; + let count = try_lock!(self.factoids).count_factoids(&name)?; match try_lock!(self.factoids).delete_factoid(&name, count - 1) { - DbResponse::Success => Ok("Successfully removed"), - DbResponse::Failed(e) => Err(PluginError::Factoids { error: e.to_owned() })?, + Ok(()) => Ok("Successfully removed"), + Err(e) => Err(e)?, } } - fn get(&self, client: &IrcClient, command: &PluginCommand) -> Result { + fn get(&self, command: &PluginCommand) -> Result { let (name, idx) = match command.tokens.len() { - 0 => return Ok(self.invalid_command(client, command).map(|()| String::new())?), + 0 => Err(ErrorKind::InvalidCommand)?, 1 => { let name = &command.tokens[0]; - let count = try_lock!(self.factoids).count_factoids(name).map_err(|e| PluginError::Factoids { error: e.to_owned() } )?; + let count = try_lock!(self.factoids).count_factoids(name)?; if count < 1 { - Err(PluginError::Factoids { error: format!("{} does not exist", name) })?; + Err(ErrorKind::NotFound)?; } (name, count - 1) @@ -119,61 +120,55 @@ impl Factoids { let name = &command.tokens[0]; let idx = match i32::from_str(&command.tokens[1]) { Ok(i) => i, - Err(_) => Err(PluginError::Factoids { error: String::from("Invalid index") })?, + Err(_) => Err(ErrorKind::InvalidCommand)?, }; (name, idx) } }; - let factoid = match try_lock!(self.factoids).get_factoid(name, idx) { - Some(v) => v, - None => Err(PluginError::Factoids { error: format!("{}~{} does not exist", name, idx) })?, - }; + let factoid = try_lock!(self.factoids) + .get_factoid(name, idx) + .context(ErrorKind::NotFound)?; let message = factoid.content.replace("\n", "|").replace("\r", ""); Ok(format!("{}: {}", factoid.name, message)) } - fn info(&self, client: &IrcClient, command: &PluginCommand) -> Result { + fn info(&self, command: &PluginCommand) -> Result { match command.tokens.len() { - 0 => Ok(self.invalid_command(client, command).map(|()| String::new())?), + 0 => Err(ErrorKind::InvalidCommand)?, 1 => { let name = &command.tokens[0]; - let count = try_lock!(self.factoids).count_factoids(name).map_err(|e| PluginError::Factoids { error: e.to_owned() } )?; + let count = try_lock!(self.factoids).count_factoids(name)?; Ok(match count { - 0 => Err(PluginError::Factoids { error: format!("{} does not exist", name) })?, + 0 => Err(ErrorKind::NotFound)?, 1 => format!("There is 1 version of {}", name), _ => format!("There are {} versions of {}", count, name), }) } _ => { let name = &command.tokens[0]; - let idx = i32::from_str(&command.tokens[1]).map_err(|_| PluginError::Factoids { error: String::from("Invalid index") })?; - - let factoid = match try_lock!(self.factoids).get_factoid(name, idx) { - Some(v) => v, - None => return Ok(format!("{}~{} does not exist", name, idx)), - }; + let idx = i32::from_str(&command.tokens[1]).context(ErrorKind::InvalidIndex)?; + let factoid = try_lock!(self.factoids).get_factoid(name, idx)?; - Ok(format!("{}: Added by {} at {} UTC", name, factoid.author, factoid.created)) + Ok(format!( + "{}: Added by {} at {} UTC", + name, factoid.author, factoid.created + )) } } } - fn exec( - &self, - client: &IrcClient, - mut command: PluginCommand, - ) -> Result { + fn exec(&self, mut command: PluginCommand) -> Result { if command.tokens.len() < 1 { - Ok(self.invalid_command(client, &command).map(|()| String::new())?) + Err(ErrorKind::InvalidIndex)? } else { let name = command.tokens.remove(0); - let count = try_lock!(self.factoids).count_factoids(&name).map_err(|e| PluginError::Factoids { error: e.to_owned() } )?; - let factoid = try_lock!(self.factoids).get_factoid(&name, count - 1).ok_or(PluginError::Factoids { error: format!("The factoid \"{}\" does not exist", name) })?; + let count = try_lock!(self.factoids).count_factoids(&name)?; + let factoid = try_lock!(self.factoids).get_factoid(&name, count - 1)?; let content = factoid.content; let value = if content.starts_with('>') { @@ -225,10 +220,6 @@ impl Factoids { Ok(output.join("|")) } - - fn invalid_command(&self, client: &IrcClient, command: &PluginCommand) -> Result<(), IrcError> { - client.send_notice(&command.source, "Invalid Command") - } } impl Plugin for Factoids { @@ -243,7 +234,7 @@ impl Plugin for Factoids { } } - fn execute_threaded(&self, client: &IrcClient, message: &Message) -> Result<(), IrcError> { + fn execute_threaded(&self, client: &IrcClient, message: &Message) -> Result<(), FrippyError> { if let Command::PRIVMSG(_, mut content) = message.command.clone() { content.remove(0); @@ -255,18 +246,22 @@ impl Plugin for Factoids { tokens: t, }; - match self.exec(client, c) { - Ok(f) => client.send_privmsg(&message.response_target().unwrap(), &f), - Err(_) => Ok(()), - } + Ok(match self.exec(c) { + Ok(f) => client + .send_privmsg(&message.response_target().unwrap(), &f) + .context(FrippyErrorKind::Connection)?, + Err(_) => (), + }) } else { Ok(()) } } - fn command(&self, client: &IrcClient, mut command: PluginCommand) -> Result<(), IrcError> { + fn command(&self, client: &IrcClient, mut command: PluginCommand) -> Result<(), FrippyError> { if command.tokens.is_empty() { - return self.invalid_command(client, &command); + return Ok(client + .send_notice(&command.target, "Invalid command") + .context(FrippyErrorKind::Connection)?); } let target = command.target.clone(); @@ -274,19 +269,27 @@ impl Plugin for Factoids { let sub_command = command.tokens.remove(0); let result = match sub_command.as_ref() { - "add" => self.add(client, &mut command).map(|s| s.to_owned()), - "fromurl" => self.add_from_url(client, &mut command).map(|s| s.to_owned()), - "remove" => self.remove(client, &mut command).map(|s| s.to_owned()), - "get" => self.get(client, &command), - "info" => self.info(client, &command), - "exec" => self.exec(client, command), - _ => self.invalid_command(client, &command).map(|()| String::new()).map_err(|e| e.into()), + "add" => self.add(&mut command).map(|s| s.to_owned()), + "fromurl" => self.add_from_url(&mut command).map(|s| s.to_owned()), + "remove" => self.remove(&mut command).map(|s| s.to_owned()), + "get" => self.get(&command), + "info" => self.info(&command), + "exec" => self.exec(command), + _ => Err(ErrorKind::InvalidCommand.into()), }; Ok(match result { - Ok(v) => client.send_privmsg(&target, &v), - Err(e) => client.send_notice(&source, &e.cause().unwrap().to_string()), - }?) + Ok(v) => client + .send_privmsg(&target, &v) + .context(FrippyErrorKind::Connection)?, + Err(e) => { + let message = e.to_string(); + client + .send_notice(&source, &message) + .context(FrippyErrorKind::Connection)?; + Err(e).context(FrippyErrorKind::Factoids)? + } + }) } fn evaluate(&self, _: &IrcClient, _: PluginCommand) -> Result { @@ -301,3 +304,39 @@ impl fmt::Debug for Factoids { write!(f, "Factoids {{ ... }}") } } + +pub mod error { + #[derive(Copy, Clone, Eq, PartialEq, Debug, Fail, Error)] + #[error = "FactoidsError"] + pub enum ErrorKind { + /// Invalid command error + #[fail(display = "Invalid Command")] + InvalidCommand, + + /// Invalid index error + #[fail(display = "Invalid index")] + InvalidIndex, + + /// Download error + #[fail(display = "Download failed")] + Download, + + /// Duplicate error + #[fail(display = "Entry already exists")] + Duplicate, + + /// Not found error + #[fail(display = "Factoid was not found")] + NotFound, + + /// MySQL error + #[cfg(feature = "mysql")] + #[fail(display = "Failed to execute MySQL Query")] + MysqlError, + + /// No connection error + #[cfg(feature = "mysql")] + #[fail(display = "No connection to the database")] + NoConnection, + } +} diff --git a/src/plugins/factoids/utils.rs b/src/plugins/factoids/utils.rs index 009b46b..70ac8a7 100644 --- a/src/plugins/factoids/utils.rs +++ b/src/plugins/factoids/utils.rs @@ -11,7 +11,11 @@ use self::LuaError::RuntimeError; pub fn download(_: &Lua, url: String) -> Result { match utils::download(&url, Some(1024)) { Ok(v) => Ok(v), - Err(e) => Err(RuntimeError(format!("Failed to download {} - {}", url, e.to_string()))), + Err(e) => Err(RuntimeError(format!( + "Failed to download {} - {}", + url, + e.to_string() + ))), } } diff --git a/src/plugins/help.rs b/src/plugins/help.rs index 4dd93d7..7e3658d 100644 --- a/src/plugins/help.rs +++ b/src/plugins/help.rs @@ -1,8 +1,11 @@ use irc::client::prelude::*; -use irc::error::IrcError; use plugin::*; +use error::FrippyError; +use error::ErrorKind as FrippyErrorKind; +use failure::ResultExt; + #[derive(PluginName, Default, Debug)] pub struct Help; @@ -17,18 +20,17 @@ impl Plugin for Help { ExecutionStatus::Done } - fn execute_threaded(&self, _: &IrcClient, _: &Message) -> Result<(), IrcError> { + fn execute_threaded(&self, _: &IrcClient, _: &Message) -> Result<(), FrippyError> { panic!("Help should not use threading") } - fn command(&self, client: &IrcClient, command: PluginCommand) -> Result<(), IrcError> { - client.send_notice(&command.source, "Help has not been added yet.") + fn command(&self, client: &IrcClient, command: PluginCommand) -> Result<(), FrippyError> { + Ok(client + .send_notice(&command.source, "Help has not been added yet.") + .context(FrippyErrorKind::Connection)?) } fn evaluate(&self, _: &IrcClient, _: PluginCommand) -> Result { Err(String::from("Help has not been added yet.")) } } - -#[cfg(test)] -mod tests {} diff --git a/src/plugins/keepnick.rs b/src/plugins/keepnick.rs index bdabd90..58ac167 100644 --- a/src/plugins/keepnick.rs +++ b/src/plugins/keepnick.rs @@ -1,8 +1,11 @@ use irc::client::prelude::*; -use irc::error::IrcError; use plugin::*; +use error::FrippyError; +use error::ErrorKind as FrippyErrorKind; +use failure::ResultExt; + #[derive(PluginName, Default, Debug)] pub struct KeepNick; @@ -25,9 +28,12 @@ impl KeepNick { if client_nick != cfg_nick { info!("Trying to switch nick from {} to {}", client_nick, cfg_nick); - match client.send(Command::NICK(cfg_nick)) { + match client + .send(Command::NICK(cfg_nick)) + .context(FrippyErrorKind::Connection) + { Ok(_) => ExecutionStatus::Done, - Err(e) => ExecutionStatus::Err(Box::new(e)), + Err(e) => ExecutionStatus::Err(e.into()), } } else { ExecutionStatus::Done @@ -45,21 +51,20 @@ impl Plugin for KeepNick { } } - fn execute_threaded(&self, _: &IrcClient, _: &Message) -> Result<(), IrcError> { + fn execute_threaded(&self, _: &IrcClient, _: &Message) -> Result<(), FrippyError> { panic!("Tell should not use threading") } - fn command(&self, client: &IrcClient, command: PluginCommand) -> Result<(), IrcError> { - client.send_notice( - &command.source, - "This Plugin does not implement any commands.", - ) + fn command(&self, client: &IrcClient, command: PluginCommand) -> Result<(), FrippyError> { + Ok(client + .send_notice( + &command.source, + "This Plugin does not implement any commands.", + ) + .context(FrippyErrorKind::Connection)?) } fn evaluate(&self, _: &IrcClient, _: PluginCommand) -> Result { Err(String::from("This Plugin does not implement any commands.")) } } - -#[cfg(test)] -mod tests {} diff --git a/src/plugins/mod.rs b/src/plugins/mod.rs index 5b32efd..9a3ba2f 100644 --- a/src/plugins/mod.rs +++ b/src/plugins/mod.rs @@ -1,17 +1,8 @@ //! Collection of plugins included -mod help; -mod url; -mod emoji; -mod tell; -mod currency; -mod factoids; -mod keepnick; - -pub use self::help::Help; -pub use self::url::Url; -pub use self::emoji::Emoji; -pub use self::tell::Tell; -pub use self::currency::Currency; -pub use self::factoids::Factoids; -pub use self::factoids::database; -pub use self::keepnick::KeepNick; +pub mod help; +pub mod url; +pub mod emoji; +pub mod tell; +pub mod currency; +pub mod factoids; +pub mod keepnick; diff --git a/src/plugins/tell/mod.rs b/src/plugins/tell/mod.rs index 3a8feeb..7052b3e 100644 --- a/src/plugins/tell/mod.rs +++ b/src/plugins/tell/mod.rs @@ -1,5 +1,4 @@ use irc::client::prelude::*; -use irc::error::IrcError; use std::time::Duration; use std::sync::Mutex; @@ -10,6 +9,11 @@ use humantime::format_duration; use plugin::*; +use error::FrippyError; +use error::ErrorKind as FrippyErrorKind; +use failure::Fail; +use failure::ResultExt; + pub mod database; use self::database::{Database, DbResponse}; @@ -84,7 +88,7 @@ impl Tell { tell.sender, human_dur, tell.message ), ) { - return ExecutionStatus::Err(Box::new(e)); + return ExecutionStatus::Err(e.context(FrippyErrorKind::Connection).into()); } debug!( "Sent {:?} from {:?} to {:?}", @@ -123,22 +127,30 @@ impl Plugin for Tell { } } - fn execute_threaded(&self, _: &IrcClient, _: &Message) -> Result<(), IrcError> { + fn execute_threaded(&self, _: &IrcClient, _: &Message) -> Result<(), FrippyError> { panic!("Tell should not use threading") } - fn command(&self, client: &IrcClient, command: PluginCommand) -> Result<(), IrcError> { + fn command(&self, client: &IrcClient, command: PluginCommand) -> Result<(), FrippyError> { if command.tokens.is_empty() { - return client.send_notice(&command.source, &self.invalid_command(client)); + return Ok(client + .send_notice(&command.source, &self.invalid_command(client)) + .context(FrippyErrorKind::Connection)?); } - match command.tokens[0].as_ref() { - "help" => client.send_notice(&command.source, &self.help(client)), + Ok(match command.tokens[0].as_ref() { + "help" => client + .send_notice(&command.source, &self.help(client)) + .context(FrippyErrorKind::Connection), _ => match self.tell_command(client, &command) { - Ok(msg) => client.send_notice(&command.source, msg), - Err(msg) => client.send_notice(&command.source, &msg), + Ok(msg) => client + .send_notice(&command.source, msg) + .context(FrippyErrorKind::Connection), + Err(msg) => client + .send_notice(&command.source, &msg) + .context(FrippyErrorKind::Connection), }, - } + }?) } fn evaluate(&self, _: &IrcClient, _: PluginCommand) -> Result { diff --git a/src/plugins/url.rs b/src/plugins/url.rs index 6f00466..fa4c6f4 100644 --- a/src/plugins/url.rs +++ b/src/plugins/url.rs @@ -2,7 +2,6 @@ extern crate regex; extern crate select; use irc::client::prelude::*; -use irc::error::IrcError; use self::regex::Regex; @@ -11,9 +10,12 @@ use self::select::predicate::Name; use plugin::*; use utils; + +use self::error::*; use error::FrippyError; -use error::UrlError; +use error::ErrorKind as FrippyErrorKind; use failure::Fail; +use failure::ResultExt; lazy_static! { static ref RE: Regex = Regex::new(r"(^|\s)(https?://\S+)").unwrap(); @@ -48,11 +50,11 @@ impl Url { Some(title_text) } - fn url(&self, text: &str) -> Result { - let url = self.grep_url(text).ok_or(UrlError::MissingUrl)?; - let body = utils::download(&url, Some(self.max_kib))?; + fn url(&self, text: &str) -> Result { + let url = self.grep_url(text).ok_or(ErrorKind::MissingUrl)?; + let body = utils::download(&url, Some(self.max_kib)).context(ErrorKind::Download)?; - Ok(self.get_title(&body).ok_or(UrlError::MissingTitle)?) + Ok(self.get_title(&body).ok_or(ErrorKind::MissingTitle)?) } } @@ -68,27 +70,48 @@ impl Plugin for Url { } } - fn execute_threaded(&self, client: &IrcClient, message: &Message) -> Result<(), IrcError> { - match message.command { + fn execute_threaded(&self, client: &IrcClient, message: &Message) -> Result<(), FrippyError> { + Ok(match message.command { Command::PRIVMSG(_, ref content) => match self.url(content) { - Ok(title) => client.send_privmsg(message.response_target().unwrap(), &title), - Err(e) => Ok(utils::log_error(e)), + Ok(title) => client + .send_privmsg(message.response_target().unwrap(), &title) + .context(FrippyErrorKind::Connection)?, + Err(e) => Err(e).context(FrippyErrorKind::Url)?, }, - _ => Ok(()), - } + _ => (), + }) } - fn command(&self, client: &IrcClient, command: PluginCommand) -> Result<(), IrcError> { - client.send_notice( - &command.source, - "This Plugin does not implement any commands.", - ) + fn command(&self, client: &IrcClient, command: PluginCommand) -> Result<(), FrippyError> { + Ok(client + .send_notice( + &command.source, + "This Plugin does not implement any commands.", + ) + .context(FrippyErrorKind::Connection)?) } fn evaluate(&self, _: &IrcClient, command: PluginCommand) -> Result { - self.url(&command.tokens[0]).map_err(|e| e.cause().unwrap().to_string()) + self.url(&command.tokens[0]) + .map_err(|e| e.cause().unwrap().to_string()) } } -#[cfg(test)] -mod tests {} +pub mod error { + /// A URL plugin error + #[derive(Copy, Clone, Eq, PartialEq, Debug, Fail, Error)] + #[error = "UrlError"] + pub enum ErrorKind { + /// A download error + #[fail(display = "A download error occured")] + Download, + + /// Missing URL error + #[fail(display = "No URL was found")] + MissingUrl, + + /// Missing title error + #[fail(display = "No title was found")] + MissingTitle, + } +} diff --git a/src/utils.rs b/src/utils.rs index cf91b37..06156be 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -4,15 +4,19 @@ use std::io::{self, Read}; use reqwest::Client; use reqwest::header::Connection; -use failure::Fail; -use error::FrippyError; +use failure::ResultExt; +use self::error::{DownloadError, ErrorKind}; /// Downloads the file and converts it to a String. /// Any invalid bytes are converted to a replacement character. /// /// The error indicated either a failed download or that the DownloadLimit was reached -pub fn download(url: &str, max_kib: Option) -> Result { - let mut response = Client::new().get(url).header(Connection::close()).send()?; +pub fn download(url: &str, max_kib: Option) -> Result { + let mut response = Client::new() + .get(url) + .header(Connection::close()) + .send() + .context(ErrorKind::Connection)?; // 100 kibibyte buffer let mut buf = [0; 100 * 1024]; @@ -25,7 +29,7 @@ pub fn download(url: &str, max_kib: Option) -> Result break, Ok(len) => len, Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue, - Err(e) => Err(e)?, + Err(e) => Err(e).context(ErrorKind::Read)?, }; bytes.extend_from_slice(&buf); @@ -34,7 +38,7 @@ pub fn download(url: &str, max_kib: Option) -> Result max_kib * 1024 { - Err(FrippyError::DownloadLimit { limit: max_kib })?; + Err(ErrorKind::DownloadLimit)?; } } } @@ -42,12 +46,20 @@ pub fn download(url: &str, max_kib: Option) -> Result