apollo_federation/error/
suggestion.rs1use itertools::Itertools;
2use levenshtein::levenshtein;
3
4use crate::utils::human_readable;
5
6pub(crate) fn suggestion_list(
7 input: &str,
8 options: impl IntoIterator<Item = String>,
9) -> Vec<String> {
10 let threshold = 1 + (input.len() as f64 * 0.4).floor() as usize;
11 let input_lowercase = input.to_lowercase();
12 let mut result = Vec::new();
13 for option in options {
14 let distance = if input_lowercase == option.to_lowercase() {
17 1
18 } else {
19 levenshtein(input, &option)
20 };
21 if distance <= threshold {
22 result.push((option, distance));
23 }
24 }
25 result.sort_by(|x, y| x.1.cmp(&y.1));
26 result.into_iter().map(|(s, _)| s.to_string()).collect()
27}
28
29const MAX_SUGGESTIONS: usize = 5;
30
31pub(crate) fn did_you_mean(suggestions: impl IntoIterator<Item = String>) -> String {
34 const MESSAGE: &str = "Did you mean ";
35 let suggestions = suggestions
36 .into_iter()
37 .take(MAX_SUGGESTIONS)
38 .map(|s| format!("\"{s}\""))
39 .collect_vec();
40 let last_separator = if suggestions.len() > 2 {
41 Some(", or ")
42 } else {
43 Some(" or ")
44 };
45 let suggestion_str = human_readable::join_strings(
46 suggestions.iter(),
47 human_readable::JoinStringsOptions {
48 separator: ", ",
49 first_separator: None,
50 last_separator,
51 output_length_limit: None,
52 },
53 );
54 format!("{MESSAGE}{suggestion_str}?")
55}