diff --git a/src/github.rs b/src/github.rs index 0293ec1c4..02e58ffdb 100644 --- a/src/github.rs +++ b/src/github.rs @@ -366,17 +366,36 @@ impl IssueRepository { ) } - async fn has_label(&self, client: &GithubClient, label: &str) -> bool { + async fn has_label(&self, client: &GithubClient, label: &str) -> anyhow::Result { #[allow(clippy::redundant_pattern_matching)] let url = format!("{}/labels/{}", self.url(), label); - match client.send_req(client.get(&url)).await { - Ok(_) => true, - // XXX: Error handling if the request failed for reasons beyond 'label didn't exist' - Err(_) => false, + match client._send_req(client.get(&url)).await { + Ok((_, _)) => Ok(true), + Err(e) => { + if e.downcast_ref::().map_or(false, |e| e.status() == Some(StatusCode::NOT_FOUND)) { + Ok(false) + } else { + Err(e) + } + } } } } +#[derive(Debug)] +pub(crate) struct UnknownLabels { + labels: Vec, +} + +// NOTE: This is used to post the Github comment; make sure it's valid markdown. +impl fmt::Display for UnknownLabels { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Unknown labels: {}", &self.labels.join(", ")) + } +} + +impl std::error::Error for UnknownLabels {} + impl Issue { pub fn to_zulip_github_reference(&self) -> ZulipGitHubReference { ZulipGitHubReference { @@ -519,18 +538,27 @@ impl Issue { return Ok(()); } - for label in &labels { - if !self.repository().has_label(client, &label).await { - anyhow::bail!("Label {} does not exist in {}", label, self.global_id()); + let mut unknown_labels = vec![]; + let mut known_labels = vec![]; + for label in labels { + if !self.repository().has_label(client, &label).await? { + unknown_labels.push(label); + } else { + known_labels.push(label); } } + if !unknown_labels.is_empty() { + return Err(UnknownLabels { labels: unknown_labels }.into()); + } + #[derive(serde::Serialize)] struct LabelsReq { labels: Vec, } + client - ._send_req(client.post(&url).json(&LabelsReq { labels })) + ._send_req(client.post(&url).json(&LabelsReq { labels: known_labels })) .await .context("failed to add labels")?; @@ -1382,6 +1410,12 @@ pub trait IssuesQuery { mod tests { use super::*; + #[test] + fn display_labels() { + let x = UnknownLabels { labels: vec!["A-bootstrap".into(), "xxx".into()] }; + assert_eq!(x.to_string(), "Unknown labels: A-bootstrap, xxx"); + } + #[test] fn extract_one_file() { let input = r##"\ diff --git a/src/handlers/autolabel.rs b/src/handlers/autolabel.rs index b08c5d43a..cb99489d9 100644 --- a/src/handlers/autolabel.rs +++ b/src/handlers/autolabel.rs @@ -125,7 +125,18 @@ pub(super) async fn handle_input( event: &IssuesEvent, input: AutolabelInput, ) -> anyhow::Result<()> { - event.issue.add_labels(&ctx.github, input.add).await?; + match event.issue.add_labels(&ctx.github, input.add).await { + Ok(()) => {} + Err(e) => { + use crate::github::UnknownLabels; + if let Some(err @ UnknownLabels { .. }) = e.downcast_ref() { + event.issue.post_comment(&ctx.github, &err.to_string()).await.context("failed to post missing label comment")?; + return Ok(()); + } + return Err(e); + } + } + for label in input.remove { event .issue diff --git a/src/interactions.rs b/src/interactions.rs index f5bf30868..ef1e71d47 100644 --- a/src/interactions.rs +++ b/src/interactions.rs @@ -1,6 +1,5 @@ use crate::github::{GithubClient, Issue}; use std::fmt::Write; -use tracing as log; pub struct ErrorComment<'a> { issue: &'a Issue,