Skip to content

Commit

Permalink
Add distinct kind of error, user error
Browse files Browse the repository at this point in the history
  • Loading branch information
valentinegb committed Jul 30, 2024
1 parent 1b496e9 commit 3a067ff
Show file tree
Hide file tree
Showing 5 changed files with 229 additions and 138 deletions.
11 changes: 7 additions & 4 deletions src/commands/anon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

use anyhow::{bail, Context as _};
use anyhow::{anyhow, bail, Context as _};
use poise::{
command,
serenity_prelude::{
Expand All @@ -26,6 +26,7 @@ use poise::{
use crate::{
config::{get_config_key, Config},
emoji::*,
error::UserError,
persist::load_or_save_default,
Context, Error,
};
Expand All @@ -50,15 +51,17 @@ pub(crate) async fn anon(
} = load_or_save_default(ctx, &get_config_key(ctx)?)?;

if !anon_enabled {
bail!("`/anon` is not enabled, the `anon_enabled` config option is `false`");
bail!(UserError(anyhow!(
"`/anon` is not enabled, the `anon_enabled` config option is `false`",
)));
}

if let Some(anon_channel) = anon_channel {
if anon_channel != ctx.channel_id() {
bail!(
bail!(UserError(anyhow!(
"`/anon` is only allowed in {} due to the `anon_channel` config option being set",
anon_channel.mention(),
);
)));
}
}

Expand Down
28 changes: 23 additions & 5 deletions src/commands/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,17 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

use anyhow::anyhow;
use poise::command;
use anyhow::{anyhow, bail};
use poise::{command, ChoiceParameter};

use crate::{config::get_config_key, emoji::*, Context, Error};
use crate::{config::get_config_key, emoji::*, error::UserError, Context, Error};

#[derive(ChoiceParameter)]
enum ErrorKind {
User,
Command,
Internal,
}

/// Commands to aid in development of the bot
#[command(slash_command, subcommands("error", "delete_config"))]
Expand All @@ -27,8 +34,19 @@ pub(crate) async fn debug(_ctx: Context<'_>) -> Result<(), Error> {

/// Fails intentionally
#[command(slash_command)]
async fn error(_ctx: Context<'_>) -> Result<(), Error> {
Err(anyhow!("This is a test error").context("This is a wrapper test error"))
async fn error(
_ctx: Context<'_>,
#[description = "Kind of error to return"] kind: ErrorKind,
) -> Result<(), Error> {
match kind {
ErrorKind::User => bail!(UserError(
anyhow!("This is an example of a user error")
.context("This is an example of extra context")
)),
ErrorKind::Command => Err(anyhow!("This is an example of a command error")
.context("This is an example of extra context")),
ErrorKind::Internal => panic!("This is an example of an internal error"),
}
}

/// Deletes the config file for the current server
Expand Down
25 changes: 16 additions & 9 deletions src/commands/strike.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

use anyhow::{bail, Context as _};
use anyhow::{anyhow, bail, Context as _};
use chrono::Months;
use poise::{
command,
Expand All @@ -29,6 +29,7 @@ use serde::{Deserialize, Serialize};
use crate::{
config::{get_config_key, Config},
emoji::*,
error::UserError,
persist::load_or_save_default,
Context, Error,
};
Expand Down Expand Up @@ -107,7 +108,9 @@ fn pre_strike_command(ctx: Context<'_>) -> Result<Option<ChannelId>, Error> {
} = load_or_save_default(ctx, &get_config_key(ctx)?)?;

if !strikes_enabled {
bail!("Strikes are not enabled, see `/config get strikes_enabled`");
bail!(UserError(anyhow!(
"Strikes are not enabled, see `/config get strikes_enabled`",
)));
}

Ok(strikes_log_channel)
Expand Down Expand Up @@ -215,9 +218,9 @@ async fn history(
.permissions
.map_or(false, |permissions| permissions.view_audit_log())
{
bail!(
"You must have the View Audit Log permission to see the strike history of other users"
);
bail!(UserError(anyhow!(
"You must have the View Audit Log permission to see the strike history of other users",
)));
}

let strikes_key = &get_strikes_key(ctx, user.id)?;
Expand Down Expand Up @@ -286,7 +289,9 @@ async fn repeal(
strike_i: Option<usize>,
) -> Result<(), Error> {
if user == ctx.author().id {
bail!("You cannot repeal one of your own strikes");
bail!(UserError(anyhow!(
"You cannot repeal one of your own strikes",
)));
}

let log_channel = pre_strike_command(ctx)?;
Expand All @@ -295,14 +300,16 @@ async fn repeal(
let strike_i = strike_i.unwrap_or(strikes.len());
let repealer = &mut strikes
.get_mut(strike_i - 1)
.context(format!("User does not have a strike #{strike_i}"))?
.context(UserError(anyhow!(
"User does not have a strike #{strike_i}",
)))?
.repealer;

if repealer.is_some() {
bail!(
bail!(UserError(anyhow!(
"{}'s strike #{strike_i} has already been repealed",
user.mention(),
);
)));
}

*repealer = Some(ctx.author().id);
Expand Down
174 changes: 174 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// Goober Bot, Discord bot
// Copyright (C) 2024 Valentine Briese
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

use std::fmt::{self, Debug};

use poise::{
serenity_prelude::{self, Color, CreateAllowedMentions, CreateEmbed},
CreateReply, FrameworkError,
};
use tracing::{error, warn};

use crate::emoji::*;

pub(crate) type Error = anyhow::Error;

#[derive(Debug)]
pub(crate) struct UserError(pub(crate) Error);

impl fmt::Display for UserError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "User performed an action improperly")
}
}

impl std::error::Error for UserError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(self.0.as_ref())
}
}

pub(super) async fn on_error(
error: FrameworkError<'_, impl Debug, Error>,
) -> Result<(), serenity_prelude::Error> {
match error {
FrameworkError::Command { error, ctx, .. } => {
if let Some(downcasted_error) = error.downcast_ref() {
let user_error: &UserError = downcasted_error;

warn!("{error:#}");

ctx.send(
CreateReply::default()
.embed(
CreateEmbed::new()
.title(format!("User Error {A_FLOOF_LOAD}"))
.description(format!("{:?}", user_error.0))
.color(Color::GOLD),
)
.allowed_mentions(CreateAllowedMentions::new())
.ephemeral(true),
)
.await?;

return Ok(());
}

error!("An error occured in a command: {error:#?}");

ctx.send(
CreateReply::default()
.embed(
CreateEmbed::new()
.title(format!("Command Error {A_FLOOF_LOAD}"))
.description(format!("{error:?}"))
.color(Color::RED),
)
.allowed_mentions(CreateAllowedMentions::new())
.ephemeral(true),
)
.await?;
}
FrameworkError::CommandPanic {
payload: _, ctx, ..
} => {
// Not showing the payload to the user because it may contain sensitive info
ctx.send(
CreateReply::default()
.embed(
CreateEmbed::new()
.title(format!("Internal Error {FLOOF_NERVOUS}"))
.description("Something went *seriously* wrong- please join the support server and let a developer know!")
.color(Color::DARK_RED),
)
.ephemeral(true),
)
.await?;
}
FrameworkError::ArgumentParse {
error, input, ctx, ..
} => {
let for_input = match input {
Some(input) => format!(" for input \"{input}\""),
None => String::new(),
};

error!("An argument parsing error occured{for_input}: {error}: {ctx:#?}");

ctx.send(
CreateReply::default()
.embed(
CreateEmbed::new()
.title(format!("Argument Parsing Error {A_FLOOF_LOAD}"))
.description("There's probably been an update to this command recently. Please try running it again in a few seconds.")
.color(Color::RED),
)
.ephemeral(true),
)
.await?;
}
FrameworkError::MissingBotPermissions {
missing_permissions,
ctx,
..
} => {
warn!("Missing bot permissions: {missing_permissions}: {ctx:#?}");

ctx.send(
CreateReply::default()
.embed(
CreateEmbed::new()
.title(format!("Missing Bot Permissions {FLOOF_NERVOUS}"))
.description(format!("I can't execute this command because I don't have these permissions: {missing_permissions}"))
.color(Color::GOLD),
)
.ephemeral(true),
)
.await?;
}
FrameworkError::MissingUserPermissions {
missing_permissions,
ctx,
..
} => {
ctx.send(
CreateReply::default()
.embed(
CreateEmbed::new()
.title(format!("Missing User Permissions {FLOOF_NERVOUS}"))
.description(match missing_permissions {
Some(missing_permissions) => {
warn!("Missing user permissions: {missing_permissions}: {ctx:#?}");

format!("You need these permissions to use this command: {missing_permissions}")
},
None => {
warn!("Missing user permissions: {ctx:#?}");

"I'm not sure what exactly you're missing, but you're missing some permission you need for this command, so I can't let you continue. Sorry!".to_string()
},
})
.color(Color::GOLD),
)
.ephemeral(true),
)
.await?;
}
other => poise::builtins::on_error(other).await?,
}

Ok(())
}
Loading

0 comments on commit 3a067ff

Please sign in to comment.