1use crate::error::{Error, Result};
4
5pub fn quote_ident(name: &str) -> Result<String> {
12 if name.is_empty() {
13 return Err(Error::invalid_ident("identifier is empty"));
14 }
15 for (i, ch) in name.chars().enumerate() {
16 let ok =
17 ch.is_ascii_alphabetic() || ch == '_' || ch.is_ascii_digit() || (ch == '-' && i != 0);
18 if !ok {
19 return Err(Error::invalid_ident(format!(
20 "identifier contains forbidden character {:?}",
21 ch
22 )));
23 }
24 }
25 Ok(name.to_string())
26}
27
28pub fn quote_string(s: &str) -> Result<String> {
35 for ch in s.chars() {
36 let c = ch as u32;
37 if c < 0x20 || c == 0x7f {
38 return Err(Error::invalid_string(
39 "string literal contains a control character",
40 ));
41 }
42 }
43 let escaped = s.replace('\\', "\\\\").replace('\'', "''");
45 Ok(format!("'{}'", escaped))
46}
47
48pub fn sanitize_graph_name(name: &str) -> String {
51 name.chars()
52 .filter(|ch| ch.is_ascii_alphanumeric() || *ch == '_' || *ch == '-')
53 .collect()
54}
55
56#[cfg(test)]
57mod tests {
58 use super::*;
59 use crate::error::Error;
60
61 #[test]
62 fn test_quote_ident_valid() {
63 for name in [
64 "abc",
65 "ABC",
66 "_foo",
67 "foo_bar",
68 "foo-bar",
69 "foo9",
70 "9starts",
71 "005_add_index",
72 ] {
73 assert_eq!(quote_ident(name).unwrap(), name);
74 }
75 }
76
77 #[test]
78 fn test_quote_ident_invalid() {
79 for name in ["", "-starts", "has space", "has;semi", "has\0null"] {
80 assert!(
81 matches!(quote_ident(name), Err(Error::InvalidIdent(_))),
82 "name={:?}",
83 name
84 );
85 }
86 }
87
88 #[test]
89 fn test_quote_string_basic() {
90 assert_eq!(quote_string("hello").unwrap(), "'hello'");
91 assert_eq!(quote_string("it's").unwrap(), "'it''s'");
92 }
93
94 #[test]
95 fn test_quote_string_backslash_then_quote() {
96 assert_eq!(quote_string("back\\slash").unwrap(), "'back\\\\slash'");
98 assert_eq!(quote_string("back\\'quote").unwrap(), "'back\\\\''quote'");
99 }
100
101 #[test]
102 fn test_quote_string_rejects_control_chars() {
103 for s in ["a\nb", "a\rb", "a\tb", "a\0b", "a\x1fb", "a\x7fb"] {
104 assert!(
105 matches!(quote_string(s), Err(Error::InvalidString(_))),
106 "s={:?}",
107 s
108 );
109 }
110 }
111
112 #[test]
113 fn test_sanitize_graph_name() {
114 assert_eq!(sanitize_graph_name("myGraph"), "myGraph");
115 assert_eq!(sanitize_graph_name("my-graph_01"), "my-graph_01");
116 assert_eq!(sanitize_graph_name("bad name!"), "badname");
117 assert_eq!(sanitize_graph_name(""), "");
118 assert_eq!(sanitize_graph_name("有害"), "");
119 assert_eq!(sanitize_graph_name(" "), "");
120 }
121}