1use std::collections::HashMap;
4use strsim::levenshtein;
5
6#[derive(Debug, Clone)]
8pub struct Validator {
9 tables: Vec<String>,
10 columns: HashMap<String, Vec<String>>,
11}
12
13impl Validator {
14 pub fn new() -> Self {
16 Self {
17 tables: Vec::new(),
18 columns: HashMap::new(),
19 }
20 }
21
22 pub fn add_table(&mut self, table: &str, cols: &[&str]) {
24 self.tables.push(table.to_string());
25 self.columns.insert(
26 table.to_string(),
27 cols.iter().map(|s| s.to_string()).collect(),
28 );
29 }
30
31 pub fn validate_table(&self, table: &str) -> Result<(), String> {
33 if self.tables.contains(&table.to_string()) {
34 Ok(())
35 } else {
36 let suggestions = self.did_you_mean(table, &self.tables);
37 if let Some(sugg) = suggestions {
38 Err(format!("Table '{}' not found. Did you mean '{}'?", table, sugg))
39 } else {
40 Err(format!("Table '{}' not found.", table))
41 }
42 }
43 }
44
45 pub fn validate_column(&self, table: &str, column: &str) -> Result<(), String> {
47 if !self.tables.contains(&table.to_string()) {
49 return Ok(());
50 }
51
52 if let Some(cols) = self.columns.get(table) {
53 if cols.contains(&column.to_string()) || column == "*" {
55 return Ok(());
56 }
57
58 let suggestions = self.did_you_mean(column, cols);
60 if let Some(sugg) = suggestions {
61 Err(format!(
62 "Column '{}' not found in table '{}'. Did you mean '{}'?",
63 column, table, sugg
64 ))
65 } else {
66 Err(format!("Column '{}' not found in table '{}'.", column, table))
67 }
68 } else {
69 Ok(())
70 }
71 }
72
73 fn did_you_mean(&self, input: &str, candidates: &[impl AsRef<str>]) -> Option<String> {
75 let mut best_match = None;
76 let mut min_dist = usize::MAX;
77
78 for cand in candidates {
79 let cand_str = cand.as_ref();
80 let dist = levenshtein(input, cand_str);
81
82 let threshold = match input.len() {
84 0..=2 => 0, 3..=5 => 2, _ => 3, };
88
89 if dist <= threshold && dist < min_dist {
90 min_dist = dist;
91 best_match = Some(cand_str.to_string());
92 }
93 }
94
95 best_match
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[test]
104 fn test_did_you_mean_table() {
105 let mut v = Validator::new();
106 v.add_table("users", &["id", "name"]);
107 v.add_table("orders", &["id", "total"]);
108
109 assert!(v.validate_table("users").is_ok());
110
111 let err = v.validate_table("usr").unwrap_err();
112 assert!(err.contains("Did you mean 'users'?")); let err = v.validate_table("usrs").unwrap_err();
115 assert!(err.contains("Did you mean 'users'?")); }
117
118 #[test]
119 fn test_did_you_mean_column() {
120 let mut v = Validator::new();
121 v.add_table("users", &["email", "password"]);
122
123 assert!(v.validate_column("users", "email").is_ok());
124
125 let err = v.validate_column("users", "emial").unwrap_err();
126 assert!(err.contains("Did you mean 'email'?"));
127 }
128}