Skip to content

Commit 3edd210

Browse files
committed
Normalize labels before addition or removal
1 parent 0f75748 commit 3edd210

File tree

1 file changed

+74
-37
lines changed

1 file changed

+74
-37
lines changed

src/github.rs

Lines changed: 74 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -565,21 +565,12 @@ impl IssueRepository {
565565
format!("{}/{}", self.organization, self.repository)
566566
}
567567

568-
async fn has_label(&self, client: &GithubClient, label: &str) -> anyhow::Result<bool> {
569-
#[allow(clippy::redundant_pattern_matching)]
570-
let url = format!("{}/labels/{}", self.url(client), label);
571-
match client.send_req(client.get(&url)).await {
572-
Ok(_) => Ok(true),
573-
Err(e) => {
574-
if e.downcast_ref::<reqwest::Error>()
575-
.map_or(false, |e| e.status() == Some(StatusCode::NOT_FOUND))
576-
{
577-
Ok(false)
578-
} else {
579-
Err(e)
580-
}
581-
}
582-
}
568+
async fn labels(&self, client: &GithubClient) -> anyhow::Result<Vec<Label>> {
569+
let url = format!("{}/labels", self.url(client));
570+
client
571+
.json(client.get(&url))
572+
.await
573+
.context("failed to get labels")
583574
}
584575
}
585576

@@ -730,8 +721,61 @@ impl Issue {
730721
Ok(())
731722
}
732723

724+
async fn normalize_and_match_labels(
725+
&self,
726+
client: &GithubClient,
727+
requested_labels: &[String],
728+
) -> anyhow::Result<Vec<String>> {
729+
let available_labels = self.repository().labels(client).await.unwrap_or_default();
730+
731+
let emoji_regex: Regex = Regex::new(r"[\p{Emoji}\p{Emoji_Presentation}]").unwrap();
732+
let normalize = |s: &str| emoji_regex.replace_all(s, "").trim().to_lowercase();
733+
734+
let mut found_labels = Vec::with_capacity(requested_labels.len());
735+
let mut unknown_labels = Vec::new();
736+
737+
for requested_label in requested_labels {
738+
// First look for an exact match
739+
if let Some(found) = available_labels.iter().find(|l| l.name == *requested_label) {
740+
found_labels.push(&found.name);
741+
continue;
742+
}
743+
744+
// Try normalizing requested label (remove emoji, case insensitive, trim whitespace)
745+
let normalized_requested = normalize(requested_label);
746+
if let Some(found) = available_labels
747+
.iter()
748+
.find(|l| normalize(&l.name) == normalized_requested)
749+
{
750+
found_labels.push(&found.name);
751+
} else {
752+
unknown_labels.push(requested_label.as_str());
753+
}
754+
}
755+
756+
if !unknown_labels.is_empty() {
757+
return Err(UnknownLabels {
758+
labels: unknown_labels.into_iter().map(String::from).collect(),
759+
}
760+
.into());
761+
}
762+
763+
Ok(found_labels.into_iter().map(|s| s.clone()).collect())
764+
}
765+
733766
pub async fn remove_label(&self, client: &GithubClient, label: &str) -> anyhow::Result<()> {
734767
log::info!("remove_label from {}: {:?}", self.global_id(), label);
768+
769+
let normalized_labels = self
770+
.normalize_and_match_labels(client, &[label.to_string()])
771+
.await?;
772+
let label = normalized_labels.first().unwrap();
773+
log::info!(
774+
"remove_label from {}: matched label to {:?}",
775+
self.global_id(),
776+
label
777+
);
778+
735779
// DELETE /repos/:owner/:repo/issues/:number/labels/{name}
736780
let url = format!(
737781
"{repo_url}/issues/{number}/labels/{name}",
@@ -767,6 +811,19 @@ impl Issue {
767811
labels: Vec<Label>,
768812
) -> anyhow::Result<()> {
769813
log::info!("add_labels: {} +{:?}", self.global_id(), labels);
814+
815+
let labels = self
816+
.normalize_and_match_labels(
817+
client,
818+
&labels.into_iter().map(|l| l.name).collect::<Vec<_>>(),
819+
)
820+
.await?;
821+
log::info!(
822+
"add_labels: {} matched requested labels to +{:?}",
823+
self.global_id(),
824+
labels
825+
);
826+
770827
// POST /repos/:owner/:repo/issues/:number/labels
771828
// repo_url = https://api.github.com/repos/Codertocat/Hello-World
772829
let url = format!(
@@ -778,8 +835,7 @@ impl Issue {
778835
// Don't try to add labels already present on this issue.
779836
let labels = labels
780837
.into_iter()
781-
.filter(|l| !self.labels().contains(&l))
782-
.map(|l| l.name)
838+
.filter(|l| !self.labels().iter().any(|existing| existing.name == *l))
783839
.collect::<Vec<_>>();
784840

785841
log::info!("add_labels: {} filtered to {:?}", self.global_id(), labels);
@@ -788,32 +844,13 @@ impl Issue {
788844
return Ok(());
789845
}
790846

791-
let mut unknown_labels = vec![];
792-
let mut known_labels = vec![];
793-
for label in labels {
794-
if !self.repository().has_label(client, &label).await? {
795-
unknown_labels.push(label);
796-
} else {
797-
known_labels.push(label);
798-
}
799-
}
800-
801-
if !unknown_labels.is_empty() {
802-
return Err(UnknownLabels {
803-
labels: unknown_labels,
804-
}
805-
.into());
806-
}
807-
808847
#[derive(serde::Serialize)]
809848
struct LabelsReq {
810849
labels: Vec<String>,
811850
}
812851

813852
client
814-
.send_req(client.post(&url).json(&LabelsReq {
815-
labels: known_labels,
816-
}))
853+
.send_req(client.post(&url).json(&LabelsReq { labels }))
817854
.await
818855
.context("failed to add labels")?;
819856

0 commit comments

Comments
 (0)