database_mcp_sql/
identifier.rs1use database_mcp_server::AppError;
4
5#[must_use]
9pub fn quote_identifier(name: &str, quote_char: char) -> String {
10 let doubled: String = std::iter::repeat_n(quote_char, 2).collect();
11 let escaped = name.replace(quote_char, &doubled);
12 format!("{quote_char}{escaped}{quote_char}")
13}
14
15pub fn validate_identifier(name: &str) -> Result<(), AppError> {
22 if name.is_empty() || name.chars().all(char::is_whitespace) {
23 return Err(AppError::InvalidIdentifier(name.to_string()));
24 }
25 if name.chars().any(char::is_control) {
26 return Err(AppError::InvalidIdentifier(name.to_string()));
27 }
28 Ok(())
29}
30
31#[cfg(test)]
32mod tests {
33 use super::*;
34
35 #[test]
36 fn accepts_standard_names() {
37 assert!(validate_identifier("users").is_ok());
38 assert!(validate_identifier("my_table").is_ok());
39 assert!(validate_identifier("DB_123").is_ok());
40 }
41
42 #[test]
43 fn accepts_hyphenated_names() {
44 assert!(validate_identifier("eu-docker").is_ok());
45 assert!(validate_identifier("access-logs").is_ok());
46 }
47
48 #[test]
49 fn accepts_special_chars() {
50 assert!(validate_identifier("my.db").is_ok());
51 assert!(validate_identifier("123db").is_ok());
52 assert!(validate_identifier("café").is_ok());
53 assert!(validate_identifier("a b").is_ok());
54 }
55
56 #[test]
57 fn rejects_empty() {
58 assert!(validate_identifier("").is_err());
59 }
60
61 #[test]
62 fn rejects_whitespace_only() {
63 assert!(validate_identifier(" ").is_err());
64 assert!(validate_identifier("\t").is_err());
65 }
66
67 #[test]
68 fn rejects_control_chars() {
69 assert!(validate_identifier("test\x00db").is_err());
70 assert!(validate_identifier("test\ndb").is_err());
71 assert!(validate_identifier("test\x1Fdb").is_err());
72 }
73
74 #[test]
75 fn quote_with_double_quotes() {
76 assert_eq!(quote_identifier("users", '"'), "\"users\"");
77 assert_eq!(quote_identifier("eu-docker", '"'), "\"eu-docker\"");
78 assert_eq!(quote_identifier("test\"db", '"'), "\"test\"\"db\"");
79 }
80
81 #[test]
82 fn quote_with_backticks() {
83 assert_eq!(quote_identifier("users", '`'), "`users`");
84 assert_eq!(quote_identifier("test`db", '`'), "`test``db`");
85 }
86}