From 95db2a8c35e55abae493a9b32aab27a33fb1b64c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20Spie=C3=9F?= Date: Thu, 20 Jul 2023 13:46:05 +0200 Subject: [PATCH] Use anyhow and thiserror (#62) --- Cargo.lock | 11 ++++++++ Cargo.toml | 2 ++ commons/src/errors.rs | 3 --- commons/src/lib.rs | 4 +-- commons/src/util.rs | 4 +-- discord-api/Cargo.toml | 1 + discord-api/src/commands.rs | 7 +++--- discord-api/src/config.rs | 2 +- strumbot/Cargo.toml | 2 ++ strumbot/src/config.rs | 12 ++++----- strumbot/src/errors.rs | 7 +++--- strumbot/src/main.rs | 14 +++++------ strumbot/src/watcher.rs | 9 +++---- twitch-api/Cargo.toml | 2 ++ twitch-api/src/error.rs | 50 ++++++++----------------------------- twitch-api/src/lib.rs | 2 +- twitch-api/src/oauth.rs | 2 +- 17 files changed, 57 insertions(+), 77 deletions(-) delete mode 100644 commons/src/errors.rs diff --git a/Cargo.lock b/Cargo.lock index 878d56e..a59cef1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -49,6 +49,12 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +[[package]] +name = "anyhow" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" + [[package]] name = "async-compression" version = "0.3.15" @@ -238,6 +244,7 @@ dependencies = [ name = "discord-api" version = "1.2.5" dependencies = [ + "anyhow", "commons", "futures", "hashbrown 0.14.0", @@ -1181,6 +1188,7 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" name = "strumbot" version = "1.2.6" dependencies = [ + "anyhow", "commons", "database-api", "discord-api", @@ -1188,6 +1196,7 @@ dependencies = [ "futures", "serde", "serde_json", + "thiserror", "tokio", "tracing", "tracing-subscriber", @@ -1597,6 +1606,7 @@ dependencies = [ name = "twitch-api" version = "1.2.5" dependencies = [ + "anyhow", "bytes", "eos", "hashbrown 0.14.0", @@ -1606,6 +1616,7 @@ dependencies = [ "reqwest", "serde", "serde_json", + "thiserror", "tokio", "tracing", ] diff --git a/Cargo.toml b/Cargo.toml index c32fb7a..0c06c56 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,8 @@ version = "1.2.6" edition = "2021" [workspace.dependencies] +anyhow = "1.0" +thiserror = "1.0" twilight-http-ratelimiting = "0.15" twilight-model = "0.15" serde_json = "1.0" diff --git a/commons/src/errors.rs b/commons/src/errors.rs deleted file mode 100644 index fd314b2..0000000 --- a/commons/src/errors.rs +++ /dev/null @@ -1,3 +0,0 @@ -use std::error::Error; - -pub type AsyncError = Box; diff --git a/commons/src/lib.rs b/commons/src/lib.rs index af96550..870edd9 100644 --- a/commons/src/lib.rs +++ b/commons/src/lib.rs @@ -1,4 +1,2 @@ - -pub mod errors; #[macro_use] -pub mod util; \ No newline at end of file +pub mod util; diff --git a/commons/src/util.rs b/commons/src/util.rs index cf49c88..733ed09 100644 --- a/commons/src/util.rs +++ b/commons/src/util.rs @@ -5,8 +5,8 @@ use std::{num::NonZeroU64, ops::Add}; macro_rules! resolve { ($x:expr) => { match $x.await { - Ok(response) => response.model().await.map_err(commons::errors::AsyncError::from), - Err(err) => Err(commons::errors::AsyncError::from(err)), + Ok(response) => response.model().await.map_err(anyhow::Error::from), + Err(err) => Err(anyhow::Error::from(err)), } }; } diff --git a/discord-api/Cargo.toml b/discord-api/Cargo.toml index 2294d25..b83035e 100644 --- a/discord-api/Cargo.toml +++ b/discord-api/Cargo.toml @@ -4,6 +4,7 @@ version = "1.2.5" edition.workspace = true [dependencies] +anyhow = { workspace = true } twilight-http-ratelimiting = { workspace = true } twilight-model = { workspace = true } tracing = { workspace = true } diff --git a/discord-api/src/commands.rs b/discord-api/src/commands.rs index 99c5d2e..f1e4c4f 100644 --- a/discord-api/src/commands.rs +++ b/discord-api/src/commands.rs @@ -16,7 +16,6 @@ use twilight_model::{ }, }; -use commons::errors::AsyncError; use commons::resolve; use crate::config::{DiscordConfig, RoleNameConfig}; @@ -54,7 +53,7 @@ impl Gateway { } } - pub async fn run(mut self) -> Result<(), AsyncError> { + pub async fn run(mut self) -> anyhow::Result<()> { let mut shard = Shard::with_config( ShardId::ONE, ShardConfig::builder(self.http.token().unwrap().into(), Self::INTENTS) @@ -82,7 +81,7 @@ impl Gateway { } } _ => {} - } + } } log::info!("Connection terminated"); @@ -94,7 +93,7 @@ impl Gateway { (name, name) } - async fn init_roles(&mut self, config: &RoleNameConfig, guild_id: &str) -> Result { + async fn init_roles(&mut self, config: &RoleNameConfig, guild_id: &str) -> anyhow::Result { let guild_id: Id = Id::from_str(guild_id)?; let role_names = config.values(); diff --git a/discord-api/src/config.rs b/discord-api/src/config.rs index 96537dd..32706db 100644 --- a/discord-api/src/config.rs +++ b/discord-api/src/config.rs @@ -71,7 +71,7 @@ mod tests { "enabled_events": ["live", "update", "vod"], "enable_command": true }"#; - + let discord: DiscordConfig = serde_json::from_slice(file).unwrap(); assert_eq!(discord.guild_id, Some("81384788765712384".into())); diff --git a/strumbot/Cargo.toml b/strumbot/Cargo.toml index 8c6b38f..d8aef13 100644 --- a/strumbot/Cargo.toml +++ b/strumbot/Cargo.toml @@ -6,6 +6,8 @@ edition.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +thiserror = { workspace = true } +anyhow = { workspace = true } serde_json = { workspace = true } serde = { workspace = true, features = ["derive"] } tokio = { workspace = true } diff --git a/strumbot/src/config.rs b/strumbot/src/config.rs index 4e22431..b54c7ed 100644 --- a/strumbot/src/config.rs +++ b/strumbot/src/config.rs @@ -8,7 +8,7 @@ use twilight_model::guild::{Guild, Permissions}; use twilight_model::id::{marker::GuildMarker, Id}; use twitch_api::config::TwitchConfig; -use commons::{errors::AsyncError as Error, resolve}; +use commons::resolve; use crate::errors::InitError; @@ -43,15 +43,15 @@ impl Config { self.role_map.get(event).cloned() } - pub async fn init_roles(&mut self, client: &Client) -> Result<(), Error> { + pub async fn init_roles(&mut self, client: &Client) -> anyhow::Result<()> { let guild = if let Some(ref id) = self.discord.guild_id { Self::get_guild(client, id.parse()?).await? } else { let guilds = client.current_user_guilds().limit(2)?.await?.models().await?; match guilds[..] { [ref guild] => Self::get_guild(client, guild.id).await?, - [] => return Err(Box::new(InitError::NoGuilds)), - _ => return Err(Box::new(InitError::TooManyGuilds)), + [] => return Err(InitError::NoGuilds.into()), + _ => return Err(InitError::TooManyGuilds.into()), } }; @@ -59,10 +59,10 @@ impl Config { Ok(()) } - async fn get_guild(client: &Client, id: Id) -> Result { + async fn get_guild(client: &Client, id: Id) -> anyhow::Result { match client.guild(id).await { Ok(guild) => Ok(guild.model().await?), - Err(err) => Err(Box::new(err)), + Err(err) => Err(err.into()), } } diff --git a/strumbot/src/errors.rs b/strumbot/src/errors.rs index f310df7..f17f670 100644 --- a/strumbot/src/errors.rs +++ b/strumbot/src/errors.rs @@ -1,7 +1,8 @@ -use std::error::Error; use std::fmt::{self, Display, Formatter}; -#[derive(Debug)] +use thiserror::Error; + +#[derive(Error, Debug)] pub enum InitError { NoGuilds, TooManyGuilds, @@ -15,5 +16,3 @@ impl Display for InitError { } } } - -impl Error for InitError {} diff --git a/strumbot/src/main.rs b/strumbot/src/main.rs index f94343d..b04e0ec 100644 --- a/strumbot/src/main.rs +++ b/strumbot/src/main.rs @@ -1,4 +1,3 @@ -use commons::errors::AsyncError; use config::Config; use database_api::{Database, DatabaseError, FileDatabase}; use discord_api::{Gateway, WebhookClient}; @@ -21,11 +20,10 @@ mod config; mod errors; mod watcher; -type Async = Result<(), AsyncError>; type Cache = FileDatabase; #[tokio::main] -async fn main() -> Async { +async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt::init(); let config: String = match tokio::fs::read_to_string("config.json").await { @@ -152,17 +150,17 @@ fn start_watcher( break; } Err(e) => { - log::error!("[{}] Error when updating stream watcher: {}", key, e); + log::error!("[{key}] Error when updating stream watcher: {e:?}"); } Ok(WatcherState::Updated) => { if cache_enabled { // Save the current watcher state to cache file match db.save(&key, &watcher).await { Err(DatabaseError::Io(e)) => { - log::error!("[{}] Failed to save cache: {}", key, e); + log::error!("[{key}] Failed to save cache: {e:?}"); } Err(DatabaseError::Serde(e)) => { - log::error!("[{}] Could not serialize watcher: {}", key, e); + log::error!("[{key}] Could not serialize watcher: {e:?}"); } Ok(_) => {} } @@ -176,7 +174,7 @@ fn start_watcher( } if let Err(err) = db.delete(&key).await { - log::error!("{} Failed to delete database entry: {}", key, err); + log::error!("[{key}] Failed to delete database entry: {err:?}"); } receive.close(); }); @@ -194,7 +192,7 @@ async fn load_cache( client: &Arc, webhook: &Arc, db: &Arc, -) -> Async { +) -> anyhow::Result<()> { if let Ok(data) = fs::metadata(".config").await { if !data.is_dir() { log::error!("Cannot load cache: .config is not a directory"); diff --git a/strumbot/src/watcher.rs b/strumbot/src/watcher.rs index 6ace411..48ed4b7 100644 --- a/strumbot/src/watcher.rs +++ b/strumbot/src/watcher.rs @@ -1,6 +1,5 @@ use std::sync::Arc; -use commons::errors::AsyncError as Error; use commons::util::Timestamp; use discord_api::{config::EventName, WebhookClient}; use eos::DateTime; @@ -128,7 +127,7 @@ impl StreamWatcher { client: &TwitchClient, webhook: &WebhookClient, stream: StreamUpdate, - ) -> Result { + ) -> anyhow::Result { match stream { StreamUpdate::Live(stream) if self.segments.is_empty() => { self.on_go_live(client, webhook, *stream).await?; @@ -157,7 +156,7 @@ impl StreamWatcher { client: &TwitchClient, webhook: &WebhookClient, stream: Stream, - ) -> Result<(), Error> { + ) -> anyhow::Result<()> { self.offline_timestamp = None; self.start_timestamp = stream.started_at; self.user_id = stream.user_id.clone(); @@ -196,7 +195,7 @@ impl StreamWatcher { client: &TwitchClient, webhook: &WebhookClient, stream: Stream, - ) -> Result { + ) -> anyhow::Result { self.offline_timestamp = None; let old_game = match self.segments.last() { Some(seg) => seg.game.clone(), // have to clone so the borrow isn't an issue later @@ -261,7 +260,7 @@ impl StreamWatcher { Ok(true) } - async fn on_offline(&mut self, client: &TwitchClient, webhook: &WebhookClient) -> Result { + async fn on_offline(&mut self, client: &TwitchClient, webhook: &WebhookClient) -> anyhow::Result { // Check if the offline grace period is over (usually 2 minutes) match self.offline_timestamp { None => { diff --git a/twitch-api/Cargo.toml b/twitch-api/Cargo.toml index b9cb6a1..a29d055 100644 --- a/twitch-api/Cargo.toml +++ b/twitch-api/Cargo.toml @@ -4,6 +4,8 @@ version = "1.2.5" edition.workspace = true [dependencies] +anyhow = { workspace = true } +thiserror = { workspace = true } serde_json = { workspace = true } lru = { workspace = true } tracing = { workspace = true } diff --git a/twitch-api/src/error.rs b/twitch-api/src/error.rs index efc7da0..f329960 100644 --- a/twitch-api/src/error.rs +++ b/twitch-api/src/error.rs @@ -1,51 +1,29 @@ -use std::{ - error::Error, - fmt::{Display, Formatter}, -}; - use reqwest::{header::ToStrError, StatusCode}; +use thiserror::Error; -pub type AsyncError = Box; -pub type SerdeError = serde_json::Error; - -#[derive(Debug)] +#[derive(Error, Debug)] pub enum RequestError { + #[error("http request failed with code {0}")] Http(StatusCode), + #[error("request timed out")] Timeout, - Unexpected(AsyncError), - Deserialize(SerdeError), + #[error("unexpected error: {0:?}")] + Unexpected(#[from] anyhow::Error), + #[error("failed to deserialize {0:?}")] + Deserialize(#[from] serde_json::Error), + #[error("{0} not found for query {1}")] NotFound(&'static str, String), } -impl Display for RequestError { - fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { - match self { - RequestError::Http(status) => write!(f, "HTTP {}", status), - RequestError::Unexpected(e) => write!(f, "Unexpected error: {}", e), - RequestError::Deserialize(e) => write!(f, "Deserialize error: {}", e), - RequestError::Timeout => write!(f, "Timeout error"), - RequestError::NotFound(kind, query) => write!(f, "{kind} not found for query {query}"), - } - } -} - -impl std::error::Error for RequestError {} - impl From for RequestError { fn from(e: reqwest::Error) -> Self { - RequestError::Unexpected(Box::new(e)) - } -} - -impl From for RequestError { - fn from(e: AsyncError) -> Self { - RequestError::Unexpected(e) + RequestError::Unexpected(e.into()) } } impl From for RequestError { fn from(e: ToStrError) -> Self { - RequestError::Unexpected(Box::new(e)) + RequestError::Unexpected(e.into()) } } @@ -54,9 +32,3 @@ impl From for RequestError { RequestError::Http(code) } } - -impl From for RequestError { - fn from(e: SerdeError) -> Self { - RequestError::Deserialize(e) - } -} diff --git a/twitch-api/src/lib.rs b/twitch-api/src/lib.rs index 772135b..0d58370 100644 --- a/twitch-api/src/lib.rs +++ b/twitch-api/src/lib.rs @@ -5,8 +5,8 @@ pub mod model; #[macro_use] pub mod oauth; pub mod client; -pub mod error; pub mod config; +pub mod error; // Serde deserialization into Instant pub(crate) mod expires_at { diff --git a/twitch-api/src/oauth.rs b/twitch-api/src/oauth.rs index 09c65ba..6e272af 100644 --- a/twitch-api/src/oauth.rs +++ b/twitch-api/src/oauth.rs @@ -91,7 +91,7 @@ impl OauthClient { warn!("Request error: {}", err); } Err(err) => { - error!("Request failed unexpectedly: {}", err); + // error!("Request failed unexpectedly: {}", err); return Err(RequestError::from(err)); } };