#[derive(Debug, PartialEq)]
pub enum NameMatch {
Found(usize),
None,
Ambiguous(Vec<usize>),
}
pub fn match_name(names: &[&str], query: &str) -> NameMatch {
let q = query.trim().to_lowercase();
if q.is_empty() {
return NameMatch::None;
}
let exact: Vec<usize> = names
.iter()
.enumerate()
.filter(|(_, n)| n.to_lowercase() == q)
.map(|(i, _)| i)
.collect();
match exact.len() {
1 => return NameMatch::Found(exact[0]),
n if n > 1 => return NameMatch::Ambiguous(exact),
_ => {}
}
let partial: Vec<usize> = names
.iter()
.enumerate()
.filter(|(_, n)| n.to_lowercase().contains(&q))
.map(|(i, _)| i)
.collect();
match partial.len() {
0 => NameMatch::None,
1 => NameMatch::Found(partial[0]),
_ => NameMatch::Ambiguous(partial),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn exact_match_case_insensitive() {
let n = ["MacBook Pro", "iPhone"];
assert_eq!(match_name(&n, "macbook pro"), NameMatch::Found(0));
}
#[test]
fn partial_match_single() {
let n = ["MacBook Pro", "iPhone"];
assert_eq!(match_name(&n, "pro"), NameMatch::Found(0));
}
#[test]
fn no_match() {
let n = ["MacBook Pro", "iPhone"];
assert_eq!(match_name(&n, "speaker"), NameMatch::None);
}
#[test]
fn partial_match_ambiguous() {
let n = ["Living Room TV", "Living Room Speaker"];
assert_eq!(
match_name(&n, "living room"),
NameMatch::Ambiguous(vec![0, 1])
);
}
#[test]
fn exact_wins_over_partial() {
let n = ["Living Room", "Living Room TV"];
assert_eq!(match_name(&n, "living room"), NameMatch::Found(0));
}
#[test]
fn empty_query_matches_nothing() {
let n = ["MacBook Pro", "iPhone"];
assert_eq!(match_name(&n, " "), NameMatch::None);
}
}