use crate::domain::model::issue::Issue;
use crate::domain::model::record_ref::IssueRef;
use super::repository::IssueRepository;
#[derive(Debug)]
pub enum Resolved<T> {
Found(T),
NotFound,
AmbiguousPrefix { matches: Vec<String> },
}
pub fn find_issue_by_id_or_alias(
repo: &dyn IssueRepository,
raw: &str,
) -> anyhow::Result<Option<Issue>> {
match resolve_issue(repo, raw)? {
Resolved::Found(issue) => Ok(Some(issue)),
Resolved::NotFound | Resolved::AmbiguousPrefix { .. } => Ok(None),
}
}
pub fn resolve_issue(repo: &dyn IssueRepository, raw: &str) -> anyhow::Result<Resolved<Issue>> {
if let Ok(id) = raw.parse::<IssueRef>() {
if let Some(found) = repo.find_by_id(&id)? {
return Ok(Resolved::Found(found));
}
}
let issues = repo.list()?;
if let Some((prefix, suffix)) = split_for_prefix_match(raw) {
if suffix.len() >= 3 {
let needle = format!("{prefix}-{}", suffix.to_ascii_uppercase());
let mut hits = Vec::new();
let mut hit_issues = Vec::new();
for issue in &issues {
let id_str = issue.id.as_str();
if id_str.len() != needle.len() && id_str.to_ascii_uppercase().starts_with(&needle)
{
hits.push(id_str.to_string());
hit_issues.push(issue.clone());
}
}
match hits.len() {
0 => {} 1 => return Ok(Resolved::Found(hit_issues.into_iter().next().unwrap())),
_ => return Ok(Resolved::AmbiguousPrefix { matches: hits }),
}
}
}
for issue in issues {
if issue.aliases.iter().any(|a| a == raw) {
return Ok(Resolved::Found(issue));
}
}
Ok(Resolved::NotFound)
}
pub(crate) fn split_for_prefix_match(raw: &str) -> Option<(&str, &str)> {
let pos = raw.rfind('-')?;
Some((&raw[..pos], &raw[pos + 1..]))
}