Skip to content

Commit 5e11c7b

Browse files
authored
Merge pull request #2192 from Urgau/handlers-user-error
Improve and simplify commands errors
2 parents f550b35 + 61d9a80 commit 5e11c7b

File tree

10 files changed

+105
-122
lines changed

10 files changed

+105
-122
lines changed

src/github/webhook.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,11 +274,10 @@ async fn process_payload(
274274
}
275275
}
276276
if !message.is_empty() {
277+
log::info!("user error: {}", message);
277278
if let Some(issue) = event.issue() {
278279
let cmnt = ErrorComment::new(issue, message);
279280
cmnt.post(&ctx.github).await?;
280-
} else {
281-
log::error!("handling event failed: {:?}", message);
282281
}
283282
}
284283
if other_error {

src/handlers.rs

Lines changed: 61 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,16 @@ use std::fmt;
1010
use std::sync::Arc;
1111
use tracing as log;
1212

13-
#[derive(Debug)]
14-
pub enum HandlerError {
15-
Message(String),
16-
Other(anyhow::Error),
17-
}
18-
19-
impl std::error::Error for HandlerError {}
20-
21-
impl fmt::Display for HandlerError {
22-
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
23-
match self {
24-
HandlerError::Message(msg) => write!(f, "{}", msg),
25-
HandlerError::Other(_) => write!(f, "An internal error occurred."),
26-
}
27-
}
13+
/// Creates a [`UserError`] with message.
14+
///
15+
/// Should be used when an handler is in error due to the user action's (not a PR,
16+
/// not a issue, not authorized, ...).
17+
///
18+
/// Should be used like this `return user_error!("My error message.");`.
19+
macro_rules! user_error {
20+
($err:expr $(,)?) => {
21+
anyhow::Result::Err(anyhow::anyhow!(crate::handlers::UserError($err.into())))
22+
};
2823
}
2924

3025
mod assign;
@@ -61,6 +56,19 @@ mod shortcut;
6156
mod transfer;
6257
pub mod types_planning_updates;
6358

59+
pub struct Context {
60+
pub github: GithubClient,
61+
pub zulip: ZulipClient,
62+
pub team: TeamClient,
63+
pub db: crate::db::ClientPool,
64+
pub username: String,
65+
pub octocrab: Octocrab,
66+
/// Represents the workqueue (assigned open PRs) of individual reviewers.
67+
/// tokio's RwLock is used to avoid deadlocks, since we run on a single-threaded tokio runtime.
68+
pub workqueue: Arc<tokio::sync::RwLock<ReviewerWorkqueue>>,
69+
pub gha_logs: Arc<tokio::sync::RwLock<GitHubActionLogsCache>>,
70+
}
71+
6472
pub async fn handle(ctx: &Context, host: &str, event: &Event) -> Vec<HandlerError> {
6573
let config = config::get(&ctx.github, event.repo()).await;
6674
if let Err(e) = &config {
@@ -368,11 +376,15 @@ macro_rules! command_handlers {
368376
if let Some(config) = &config.$name {
369377
$name::handle_command(ctx, config, event, command)
370378
.await
371-
.unwrap_or_else(|err| {
372-
errors.push(HandlerError::Other(err.context(format!(
373-
"error when processing {} command handler",
374-
stringify!($name)
375-
))))
379+
.unwrap_or_else(|mut err| {
380+
if let Some(err) = err.downcast_mut::<UserError>() {
381+
errors.push(HandlerError::Message(std::mem::take(&mut err.0)));
382+
} else {
383+
errors.push(HandlerError::Other(err.context(format!(
384+
"error when processing {} command handler",
385+
stringify!($name)
386+
))));
387+
}
376388
});
377389
} else {
378390
errors.push(HandlerError::Message(format!(
@@ -416,15 +428,33 @@ command_handlers! {
416428
transfer: Transfer,
417429
}
418430

419-
pub struct Context {
420-
pub github: GithubClient,
421-
pub zulip: ZulipClient,
422-
pub team: TeamClient,
423-
pub db: crate::db::ClientPool,
424-
pub username: String,
425-
pub octocrab: Octocrab,
426-
/// Represents the workqueue (assigned open PRs) of individual reviewers.
427-
/// tokio's RwLock is used to avoid deadlocks, since we run on a single-threaded tokio runtime.
428-
pub workqueue: Arc<tokio::sync::RwLock<ReviewerWorkqueue>>,
429-
pub gha_logs: Arc<tokio::sync::RwLock<GitHubActionLogsCache>>,
431+
#[derive(Debug)]
432+
pub enum HandlerError {
433+
Message(String),
434+
Other(anyhow::Error),
435+
}
436+
437+
impl std::error::Error for HandlerError {}
438+
439+
impl fmt::Display for HandlerError {
440+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
441+
match self {
442+
HandlerError::Message(msg) => write!(f, "{}", msg),
443+
HandlerError::Other(_) => write!(f, "An internal error occurred."),
444+
}
445+
}
446+
}
447+
448+
/// Represent a user error.
449+
///
450+
/// The message will be shown to the user via comment posted by this bot.
451+
#[derive(Debug)]
452+
pub struct UserError(String);
453+
454+
impl std::error::Error for UserError {}
455+
456+
impl fmt::Display for UserError {
457+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
458+
f.write_str(&self.0)
459+
}
430460
}

src/handlers/assign.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -525,10 +525,7 @@ pub(super) async fn handle_command(
525525
let issue = event.issue().unwrap();
526526
if issue.is_pr() {
527527
if !issue.is_open() {
528-
issue
529-
.post_comment(&ctx.github, "Assignment is not allowed on a closed PR.")
530-
.await?;
531-
return Ok(());
528+
return user_error!("Assignment is not allowed on a closed PR.");
532529
}
533530
if matches!(
534531
event,
@@ -612,7 +609,7 @@ pub(super) async fn handle_command(
612609
AssignCommand::Claim => event.user().login.clone(),
613610
AssignCommand::AssignUser { username } => {
614611
if !is_team_member && username != event.user().login {
615-
bail!("Only Rust team members can assign other users");
612+
return user_error!("Only Rust team members can assign other users");
616613
}
617614
username.clone()
618615
}
@@ -627,7 +624,7 @@ pub(super) async fn handle_command(
627624
e.apply(&ctx.github, String::new()).await?;
628625
return Ok(());
629626
} else {
630-
bail!("Cannot release another user's assignment");
627+
return user_error!("Cannot release another user's assignment");
631628
}
632629
} else {
633630
let current = &event.user().login;
@@ -639,11 +636,13 @@ pub(super) async fn handle_command(
639636
e.apply(&ctx.github, String::new()).await?;
640637
return Ok(());
641638
} else {
642-
bail!("Cannot release unassigned issue");
639+
return user_error!("Cannot release unassigned issue");
643640
}
644641
};
645642
}
646-
AssignCommand::RequestReview { .. } => bail!("r? is only allowed on PRs."),
643+
AssignCommand::RequestReview { .. } => {
644+
return user_error!("r? is only allowed on PRs.");
645+
}
647646
};
648647
// Don't re-assign if aleady assigned, e.g. on comment edit
649648
if issue.contain_assignee(&to_assign) {

src/handlers/close.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Allows to close an issue or a PR
22
3-
use crate::{config::CloseConfig, github::Event, handlers::Context, interactions::ErrorComment};
3+
use crate::{config::CloseConfig, github::Event, handlers::Context};
44
use parser::command::close::CloseCommand;
55

66
pub(super) async fn handle_command(
@@ -16,9 +16,7 @@ pub(super) async fn handle_command(
1616
.await
1717
.unwrap_or(false);
1818
if !is_team_member {
19-
let cmnt = ErrorComment::new(&issue, "Only team members can close issues.");
20-
cmnt.post(&ctx.github).await?;
21-
return Ok(());
19+
return user_error!("Only team members can close issues.");
2220
}
2321
issue.close(&ctx.github).await?;
2422
Ok(())

src/handlers/concern.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::{
66
config::ConcernConfig,
77
github::{Event, Label},
88
handlers::Context,
9-
interactions::{EditIssueBody, ErrorComment},
9+
interactions::EditIssueBody,
1010
};
1111
use parser::command::concern::ConcernCommand;
1212

@@ -37,7 +37,7 @@ pub(super) async fn handle_command(
3737
cmd: ConcernCommand,
3838
) -> anyhow::Result<()> {
3939
let Event::IssueComment(issue_comment) = event else {
40-
bail!("concern issued on something other than a issue")
40+
return user_error!("Concerns can only be issued on an issue");
4141
};
4242
let Some(comment_url) = event.html_url() else {
4343
bail!("unable to retrieve the comment url")
@@ -81,10 +81,9 @@ pub(super) async fn handle_command(
8181
issue.number,
8282
issue_comment.comment.user,
8383
);
84-
ErrorComment::new(&issue, "Only team members in the [team repo](https://github.com/rust-lang/team) can add or resolve concerns.")
85-
.post(&ctx.github)
86-
.await?;
87-
return Ok(());
84+
return user_error!(
85+
"Only team members in the [team repo](https://github.com/rust-lang/team) can add or resolve concerns."
86+
);
8887
}
8988

9089
let mut client = ctx.db.get().await;

src/handlers/major_change.rs

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use crate::{
66
config::MajorChangeConfig,
77
github::{Event, Issue, IssuesAction, IssuesEvent, Label, ZulipGitHubReference},
88
handlers::Context,
9-
interactions::ErrorComment,
109
};
1110
use anyhow::Context as _;
1211
use async_trait::async_trait;
@@ -113,15 +112,10 @@ pub(super) async fn handle_input(
113112
.iter()
114113
.any(|l| l.name == config.enabling_label)
115114
{
116-
let cmnt = ErrorComment::new(
117-
&event.issue,
118-
format!(
119-
"This issue is not ready for proposals; it lacks the `{}` label.",
120-
config.enabling_label
121-
),
122-
);
123-
cmnt.post(&ctx.github).await?;
124-
return Ok(());
115+
return user_error!(format!(
116+
"This issue is not ready for proposals; it lacks the `{}` label.",
117+
config.enabling_label
118+
));
125119
}
126120
let (zulip_msg, label_to_add) = match cmd {
127121
Invocation::NewProposal => (
@@ -253,15 +247,10 @@ pub(super) async fn handle_command(
253247
.iter()
254248
.any(|l| l.name == config.enabling_label)
255249
{
256-
let cmnt = ErrorComment::new(
257-
&issue,
258-
&format!(
259-
"This issue cannot be seconded; it lacks the `{}` label.",
260-
config.enabling_label
261-
),
262-
);
263-
cmnt.post(&ctx.github).await?;
264-
return Ok(());
250+
return user_error!(format!(
251+
"This issue cannot be seconded; it lacks the `{}` label.",
252+
config.enabling_label
253+
));
265254
}
266255

267256
let is_team_member = event
@@ -272,9 +261,7 @@ pub(super) async fn handle_command(
272261
.unwrap_or(false);
273262

274263
if !is_team_member {
275-
let cmnt = ErrorComment::new(&issue, "Only team members can second issues.");
276-
cmnt.post(&ctx.github).await?;
277-
return Ok(());
264+
return user_error!("Only team members can second issues.");
278265
}
279266

280267
let has_concerns = if let Some(concerns_label) = &config.concerns_label {

src/handlers/nominate.rs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,9 @@ pub(super) async fn handle_command(
2121
};
2222

2323
if !is_team_member {
24-
let cmnt = ErrorComment::new(
25-
&event.issue().unwrap(),
26-
format!(
27-
"Nominating and approving issues and pull requests is restricted to members of\
28-
the Rust teams."
29-
),
24+
return user_error!(
25+
"Nominating and approving issues and pull requests is restricted to members of the Rust teams."
3026
);
31-
cmnt.post(&ctx.github).await?;
32-
return Ok(());
3327
}
3428

3529
let issue_labels = event.issue().unwrap().labels();

src/handlers/ping.rs

Lines changed: 10 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ use crate::{
88
config::PingConfig,
99
github::{self, Event},
1010
handlers::Context,
11-
interactions::ErrorComment,
1211
};
1312
use parser::command::ping::PingCommand;
1413

@@ -25,42 +24,27 @@ pub(super) async fn handle_command(
2524
};
2625

2726
if !is_team_member {
28-
let cmnt = ErrorComment::new(
29-
&event.issue().unwrap(),
30-
format!("Only Rust team members can ping teams."),
31-
);
32-
cmnt.post(&ctx.github).await?;
33-
return Ok(());
27+
return user_error!("Only Rust team members can ping teams.");
3428
}
3529

3630
let (gh_team, config) = match config.get_by_name(&team_name.team) {
3731
Some(v) => v,
3832
None => {
39-
let cmnt = ErrorComment::new(
40-
&event.issue().unwrap(),
41-
format!(
42-
"This team (`{}`) cannot be pinged via this command; \
33+
return user_error!(format!(
34+
"This team (`{}`) cannot be pinged via this command; \
4335
it may need to be added to `triagebot.toml` on the default branch.",
44-
team_name.team,
45-
),
46-
);
47-
cmnt.post(&ctx.github).await?;
48-
return Ok(());
36+
team_name.team,
37+
));
4938
}
5039
};
5140
let team = ctx.team.get_team(&gh_team).await?;
5241
let team = match team {
5342
Some(team) => team,
5443
None => {
55-
let cmnt = ErrorComment::new(
56-
&event.issue().unwrap(),
57-
format!(
58-
"This team (`{}`) does not exist in the team repository.",
59-
team_name.team,
60-
),
61-
);
62-
cmnt.post(&ctx.github).await?;
63-
return Ok(());
44+
return user_error!(format!(
45+
"This team (`{}`) does not exist in the team repository.",
46+
team_name.team,
47+
));
6448
}
6549
};
6650

@@ -76,11 +60,7 @@ pub(super) async fn handle_command(
7660
)
7761
.await
7862
{
79-
let cmnt = ErrorComment::new(
80-
&event.issue().unwrap(),
81-
format!("Error adding team label (`{}`): {:?}.", label, err),
82-
);
83-
cmnt.post(&ctx.github).await?;
63+
return user_error!(format!("Error adding team label (`{}`): {:?}.", label, err));
8464
}
8565
}
8666

0 commit comments

Comments
 (0)