use crate::id::{is_hex_prefix, is_uuid};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ResolveResult {
Single(String),
Multiple(Vec<ResolveMatch>),
None,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ResolveMatch {
pub id: String,
pub title: String,
}
pub fn resolve(input: &str, entities: &[(String, String)]) -> ResolveResult {
if is_uuid(input) {
if entities.iter().any(|(id, _)| id == input) {
return ResolveResult::Single(input.to_string());
}
return ResolveResult::None;
}
if is_hex_prefix(input) {
let normalized_input: String = input.chars().filter(|c| *c != '-').collect();
let matches: Vec<_> = entities
.iter()
.filter(|(id, _)| {
let normalized_id: String = id.chars().filter(|c| *c != '-').collect();
normalized_id
.to_lowercase()
.starts_with(&normalized_input.to_lowercase())
})
.map(|(id, title)| ResolveMatch {
id: id.clone(),
title: title.clone(),
})
.collect();
return match matches.len() {
0 => ResolveResult::None,
1 => ResolveResult::Single(matches[0].id.clone()),
_ => ResolveResult::Multiple(matches),
};
}
let input_lower = input.to_lowercase();
let matches: Vec<_> = entities
.iter()
.filter(|(_, title)| title.to_lowercase().contains(&input_lower))
.map(|(id, title)| ResolveMatch {
id: id.clone(),
title: title.clone(),
})
.collect();
match matches.len() {
0 => ResolveResult::None,
1 => ResolveResult::Single(matches[0].id.clone()),
_ => ResolveResult::Multiple(matches),
}
}
pub fn parse_entity_reference(input: &str) -> Option<(&str, &str)> {
if input.len() < 3 {
return None;
}
let (type_char, rest) = input.split_at(1);
if !rest.starts_with('/') {
return None;
}
let id = &rest[1..];
if id.is_empty() {
return None;
}
let entity_type = match type_char {
"p" => "problem",
"s" => "solution",
"c" => "critique",
"m" => "milestone",
_ => return None,
};
Some((entity_type, id))
}
#[cfg(test)]
mod tests {
use super::*;
fn test_entities() -> Vec<(String, String)> {
vec![
(
"01957d3e-a8b2-7def-8c3a-9f4e5d6c7b8a".to_string(),
"Fix auth timeout bug".to_string(),
),
(
"01957d3e-b1c4-7abc-9d2e-3f4a5b6c7d8e".to_string(),
"Auth token refresh fails".to_string(),
),
(
"02957d3e-c2d5-7fed-ae4b-5c6d7e8f9a0b".to_string(),
"Database connection pooling".to_string(),
),
]
}
#[test]
fn test_resolve_exact_uuid() {
let entities = test_entities();
match resolve("01957d3e-a8b2-7def-8c3a-9f4e5d6c7b8a", &entities) {
ResolveResult::Single(id) => assert_eq!(id, "01957d3e-a8b2-7def-8c3a-9f4e5d6c7b8a"),
other => panic!("Expected Single, got {:?}", other),
}
}
#[test]
fn test_resolve_uuid_not_found() {
let entities = test_entities();
match resolve("99999999-9999-9999-9999-999999999999", &entities) {
ResolveResult::None => {}
other => panic!("Expected None, got {:?}", other),
}
}
#[test]
fn test_resolve_prefix_unique() {
let entities = test_entities();
match resolve("02957d", &entities) {
ResolveResult::Single(id) => assert_eq!(id, "02957d3e-c2d5-7fed-ae4b-5c6d7e8f9a0b"),
other => panic!("Expected Single, got {:?}", other),
}
}
#[test]
fn test_resolve_prefix_ambiguous() {
let entities = test_entities();
match resolve("01957d", &entities) {
ResolveResult::Multiple(matches) => {
assert_eq!(matches.len(), 2);
}
other => panic!("Expected Multiple, got {:?}", other),
}
}
#[test]
fn test_resolve_title_unique() {
let entities = test_entities();
match resolve("database", &entities) {
ResolveResult::Single(id) => assert_eq!(id, "02957d3e-c2d5-7fed-ae4b-5c6d7e8f9a0b"),
other => panic!("Expected Single, got {:?}", other),
}
}
#[test]
fn test_resolve_title_ambiguous() {
let entities = test_entities();
match resolve("auth", &entities) {
ResolveResult::Multiple(matches) => {
assert_eq!(matches.len(), 2);
}
other => panic!("Expected Multiple, got {:?}", other),
}
}
#[test]
fn test_resolve_title_not_found() {
let entities = test_entities();
match resolve("nonexistent", &entities) {
ResolveResult::None => {}
other => panic!("Expected None, got {:?}", other),
}
}
#[test]
fn test_parse_entity_reference_valid() {
assert_eq!(
parse_entity_reference("p/01957d"),
Some(("problem", "01957d"))
);
assert_eq!(
parse_entity_reference("s/abc123"),
Some(("solution", "abc123"))
);
assert_eq!(parse_entity_reference("c/xyz"), Some(("critique", "xyz")));
assert_eq!(parse_entity_reference("m/123"), Some(("milestone", "123")));
}
#[test]
fn test_parse_entity_reference_invalid() {
assert_eq!(parse_entity_reference("p/"), None);
assert_eq!(parse_entity_reference("x/123"), None);
assert_eq!(parse_entity_reference("problem"), None);
assert_eq!(parse_entity_reference("p123"), None);
assert_eq!(parse_entity_reference(""), None);
assert_eq!(parse_entity_reference("p"), None);
}
}