Skip to content

Commit

Permalink
feat: comprehensive error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanccn committed Oct 23, 2023
1 parent 7d162e1 commit 30fca27
Show file tree
Hide file tree
Showing 5 changed files with 203 additions and 79 deletions.
10 changes: 10 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ anyhow = "1.0.75"
chrono = "0.4.31"
dotenvy = "0.15.7"
humantime = "2.1.0"
nanoid = "0.4.0"
num = "0.4.1"
once_cell = "1.18.0"
owo-colors = { version = "3.5.0", features = ["supports-colors"] }
Expand Down
142 changes: 142 additions & 0 deletions src/handlers/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
use nanoid::nanoid;
use owo_colors::OwoColorize;
use poise::{
serenity_prelude::{ChannelId, CreateEmbed, CreateEmbedFooter, CreateMessage, Timestamp},
CreateReply, FrameworkError,
};

use crate::{Context, Data};

enum ErrorOrPanic<'a> {
Error(&'a anyhow::Error),
Panic(&'a Option<String>),
}

impl ErrorOrPanic<'_> {
fn type_(&self) -> String {
match self {
Self::Panic(_) => "panic".to_owned(),
Self::Error(_) => "error".to_owned(),
}
}
}

struct ValfiskError<'a> {
error_or_panic: ErrorOrPanic<'a>,
ctx: &'a Context<'a>,
error_id: String,
}

impl ValfiskError<'_> {
fn from_error<'a>(error: &'a anyhow::Error, ctx: &'a Context) -> ValfiskError<'a> {
ValfiskError {
error_or_panic: ErrorOrPanic::Error(&error),
ctx,
error_id: nanoid!(8),
}
}

fn from_panic<'a>(panic_payload: &'a Option<String>, ctx: &'a Context) -> ValfiskError<'a> {
ValfiskError {
error_or_panic: ErrorOrPanic::Panic(&panic_payload),
ctx,
error_id: nanoid!(8),
}
}

fn log(&self) {
eprintln!(
"{}\n {} {}\n {} {}\n{:#?}",
format!("Encountered {}!", self.error_or_panic.type_()).red(),
"ID:".dimmed(),
self.error_id,
"Command:".dimmed(),
self.ctx.invoked_command_name(),
self.error_or_panic
);
}

async fn reply(&self) {
self.ctx
.send(
CreateReply::new().embed(
CreateEmbed::new()
.title("An error occurred!")
.description("Hmm. I wonder what happened there?")
.footer(CreateEmbedFooter::new(&self.error_id))
.timestamp(Timestamp::now())
.color(0xef4444),
),
)
.await
.ok();
}

async fn post(&self) {
let channel_id = match std::env::var("ERROR_LOGS_CHANNEL") {
Ok(channel_id_str) => Some(channel_id_str.parse::<u64>()),
Err(_) => None,
};

if let Some(Ok(channel_id)) = channel_id {
let channel = ChannelId::new(channel_id);

let embed = CreateEmbed::new()
.title("An error occurred!")
.description(format!("```\n{:#?}\n```", self.error_or_panic))
.footer(CreateEmbedFooter::new(&self.error_id))
.timestamp(Timestamp::now())
.color(0xef4444);

channel
.send_message(&self.ctx, CreateMessage::new().embed(embed))
.await
.ok();
}
}

async fn handle_all(&self) {
self.log();
self.reply().await;
self.post().await;
}
}

impl std::fmt::Debug for ErrorOrPanic<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Error(e) => e.fmt(f),
Self::Panic(p) => p.fmt(f),
}
}
}

pub async fn handle_error(err: &FrameworkError<'_, Data, anyhow::Error>) {
match err {
FrameworkError::Setup { error, .. } => {
eprintln!(
"{} setting up client:\n {}",
"Encountered error".red(),
error
);
}

FrameworkError::EventHandler { error, .. } => {
eprintln!(
"{} handling event!\n{:#?}",
"Encountered error".red(),
error
);
}

FrameworkError::Command { error, ctx, .. } => {
ValfiskError::from_error(error, ctx).handle_all().await;
}

FrameworkError::CommandPanic { payload, ctx, .. } => {
ValfiskError::from_panic(payload, ctx).handle_all().await;
}

_ => {}
}
}
5 changes: 4 additions & 1 deletion src/handlers/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use anyhow::Result;
use poise::serenity_prelude as serenity;

mod error;
mod github_expansion;

pub async fn handle(message: &serenity::Message, ctx: &serenity::Context) -> Result<()> {
pub use error::handle_error;

pub async fn handle_message(message: &serenity::Message, ctx: &serenity::Context) -> Result<()> {
tokio::try_join!(github_expansion::handle(message, ctx))?;

Ok(())
Expand Down
124 changes: 46 additions & 78 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use anyhow::{Error, Result};
use owo_colors::OwoColorize;

use poise::{
serenity_prelude::{Client, CreateEmbed, FullEvent, GatewayIntents},
CreateReply, Framework, FrameworkError, FrameworkOptions,
serenity_prelude::{Client, FullEvent, GatewayIntents},
Framework, FrameworkOptions,
};

use crate::utils::Pluralize;
Expand All @@ -22,90 +22,58 @@ async fn main() -> Result<()> {
#[cfg(debug_assertions)]
dotenvy::dotenv().ok();

let mut client = Client::builder(
std::env::var("DISCORD_TOKEN").expect("missing DISCORD_TOKEN"),
GatewayIntents::all(),
)
.framework(Framework::new(
FrameworkOptions {
commands: commands::vec(),
event_handler: |ev, _, _| {
Box::pin(async move {
match ev {
FullEvent::Message { new_message, ctx } => {
handlers::handle(new_message, ctx).await?;
}
let mut client = Client::builder(std::env::var("DISCORD_TOKEN")?, GatewayIntents::all())
.framework(Framework::new(
FrameworkOptions {
commands: commands::vec(),
event_handler: |ev, _, _| {
Box::pin(async move {
match ev {
FullEvent::Message { new_message, ctx } => {
handlers::handle_message(new_message, ctx).await?;
}

FullEvent::PresenceUpdate { new_data, .. } => {
let mut presence_store = presence_api::PRESENCE_STORE.lock().await;
presence_store.insert(
new_data.user.id,
presence_api::ValfiskPresenceData::from_presence(new_data),
);
}
FullEvent::PresenceUpdate { new_data, .. } => {
let mut presence_store = presence_api::PRESENCE_STORE.lock().await;
presence_store.insert(
new_data.user.id,
presence_api::ValfiskPresenceData::from_presence(new_data),
);
}

&_ => {}
}
&_ => {}
}

Ok(())
})
Ok(())
})
},
on_error: |err| {
Box::pin(async move {
handlers::handle_error(&err).await;
})
},
..Default::default()
},
on_error: |err| {
|ctx, ready, framework| {
Box::pin(async move {
match err {
FrameworkError::Setup { error, .. } => eprintln!("{}", error),
FrameworkError::Command { error, ctx, .. } => {
eprintln!(
"Encountered error handling command {}: {}",
ctx.invoked_command_name(),
error
);
let tag = ready.user.tag();
println!("{} to Discord as {}", "Connected".green(), tag.cyan());

ctx.send(
CreateReply::new().embed(
CreateEmbed::new()
.title("An error occurred!")
.description(format!("```\n{}\n```", error)),
),
)
.await
.ok();
}
FrameworkError::EventHandler { error, .. } => {
eprintln!("{}", error);
}
FrameworkError::CommandPanic {
payload: Some(payload),
..
} => {
eprintln!("{}", payload);
}
_ => {}
}
})
},
..Default::default()
},
|ctx, ready, framework| {
Box::pin(async move {
let tag = ready.user.tag();
println!("{} to Discord as {}", "Connected".green(), tag.cyan());

let commands = &framework.options().commands;
let commands = &framework.options().commands;

poise::builtins::register_globally(&ctx, commands).await?;
println!(
"{} {} {}",
"Registered".blue(),
commands.len(),
"command".pluralize(commands.len())
);
poise::builtins::register_globally(&ctx, commands).await?;
println!(
"{} {} {}",
"Registered".blue(),
commands.len(),
"command".pluralize(commands.len())
);

Ok(Data {})
})
},
))
.await?;
Ok(Data {})
})
},
))
.await?;

tokio::select! {
result = client.start() => { result.map_err(anyhow::Error::from) },
Expand Down

0 comments on commit 30fca27

Please sign in to comment.