promql_parser/util/
string.rs1pub fn unquote_string(s: &str) -> Result<String, String> {
20 let n = s.len();
21 if n < 2 {
22 return Err("invalid syntax".to_string());
23 }
24
25 let bytes = s.as_bytes();
26 let quote = bytes[0];
27 if quote != bytes[n - 1] {
28 return Err("invalid syntax".to_string());
29 }
30
31 let inner = &s[1..n - 1];
32
33 if quote == b'`' {
34 if inner.contains('`') {
35 return Err("invalid syntax".to_string());
36 }
37 return Ok(inner.to_string());
38 }
39
40 if quote != b'"' && quote != b'\'' {
41 return Err("invalid syntax".to_string());
42 }
43
44 if inner.contains('\n') {
45 return Err("invalid syntax".to_string());
46 }
47
48 if !inner.contains('\\') && !inner.contains(quote as char) {
49 return Ok(inner.to_string());
50 }
51
52 let mut res = String::with_capacity(3 * inner.len() / 2);
53 let mut rest = inner;
54
55 while !rest.is_empty() {
56 let (c, tail) = unquote_char(rest, quote)?;
57 res.push(c);
58 rest = tail;
59 }
60
61 Ok(res)
62}
63
64fn unquote_char(s: &str, quote: u8) -> Result<(char, &str), String> {
65 let bytes = s.as_bytes();
66 let c = bytes[0];
67
68 if c == quote && (quote == b'\'' || quote == b'"') {
70 return Err("invalid syntax".to_string());
71 }
72
73 if c < 0x80 {
74 if c != b'\\' {
75 return Ok((c as char, &s[1..]));
76 }
77 } else {
78 let r = s.chars().next().unwrap();
80 return Ok((r, &s[r.len_utf8()..]));
81 }
82
83 if s.len() <= 1 {
85 return Err("invalid syntax".to_string());
86 }
87
88 let c = bytes[1];
89 let mut tail = &s[2..];
90
91 let value = match c {
92 b'a' => '\x07', b'b' => '\x08', b'f' => '\x0c', b'n' => '\n',
96 b'r' => '\r',
97 b't' => '\t',
98 b'v' => '\x0b', b'x' | b'u' | b'U' => {
100 let n = match c {
101 b'x' => 2,
102 b'u' => 4,
103 b'U' => 8,
104 _ => unreachable!(),
105 };
106
107 if tail.len() < n {
108 return Err("invalid syntax".to_string());
109 }
110
111 let mut v: u32 = 0;
112 for i in 0..n {
113 let x = unhex(tail.as_bytes()[i])?;
114 v = (v << 4) | x;
115 }
116
117 tail = &tail[n..];
118
119 if c == b'x' {
120 std::char::from_u32(v).ok_or("invalid syntax")?
121 } else {
122 if v > 0x10FFFF {
123 return Err("invalid syntax".to_string());
124 }
125 std::char::from_u32(v).ok_or("invalid syntax")?
126 }
127 }
128 b'0'..=b'7' => {
129 let mut v = (c - b'0') as u32;
130 if tail.len() < 2 {
131 return Err("invalid syntax".to_string());
132 }
133 for i in 0..2 {
134 let x = (tail.as_bytes()[i] as char)
135 .to_digit(8)
136 .ok_or("invalid syntax")?;
137 v = (v << 3) | x;
138 }
139 tail = &tail[2..];
140 if v > 255 {
141 return Err("invalid syntax".to_string());
142 }
143 std::char::from_u32(v).ok_or("invalid syntax")?
144 }
145 b'\\' => '\\',
146 b'\'' | b'"' => {
147 if c != quote {
148 return Err("invalid syntax".to_string());
149 }
150 c as char
151 }
152 _ => return Err("invalid syntax".to_string()),
153 };
154
155 Ok((value, tail))
156}
157
158fn unhex(b: u8) -> Result<u32, String> {
159 let c = b as char;
160 c.to_digit(16).ok_or_else(|| "invalid syntax".to_string())
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[test]
168 fn test_unquote_string_basic() {
169 assert_eq!(unquote_string("\"hello\"").unwrap(), "hello");
171
172 assert_eq!(unquote_string("'hello'").unwrap(), "hello");
174
175 assert_eq!(unquote_string("`hello`").unwrap(), "hello");
177 }
178
179 #[test]
180 fn test_unquote_string_empty() {
181 assert_eq!(unquote_string("\"\"").unwrap(), "");
182 assert_eq!(unquote_string("''").unwrap(), "");
183 assert_eq!(unquote_string("``").unwrap(), "");
184 }
185
186 #[test]
187 fn test_unquote_string_error_cases() {
188 assert!(unquote_string("\"").is_err());
190 assert!(unquote_string("'").is_err());
191 assert!(unquote_string("`").is_err());
192
193 assert!(unquote_string("\"hello'").is_err());
195 assert!(unquote_string("'hello\"").is_err());
196 assert!(unquote_string("`hello\"").is_err());
197
198 assert!(unquote_string("#hello#").is_err());
200 assert!(unquote_string("/hello/").is_err());
201
202 assert!(unquote_string("\"hello\nworld\"").is_err());
204 assert!(unquote_string("'hello\nworld'").is_err());
205
206 assert!(unquote_string("`hello`world`").is_err());
208 }
209
210 #[test]
211 fn test_unquote_string_escaped_characters() {
212 assert_eq!(unquote_string(r#""\a""#).unwrap(), "\x07");
214 assert_eq!(unquote_string(r#""\b""#).unwrap(), "\x08");
215 assert_eq!(unquote_string(r#""\f""#).unwrap(), "\x0c");
216 assert_eq!(unquote_string(r#""\n""#).unwrap(), "\n");
217 assert_eq!(unquote_string(r#""\r""#).unwrap(), "\r");
218 assert_eq!(unquote_string(r#""\t""#).unwrap(), "\t");
219 assert_eq!(unquote_string(r#""\v""#).unwrap(), "\x0b");
220
221 assert_eq!(unquote_string(r#""\\""#).unwrap(), "\\");
223
224 assert_eq!(unquote_string(r#""\"""#).unwrap(), "\"");
226 assert_eq!(unquote_string(r#"'\''"#).unwrap(), "'");
227 assert_eq!(
228 unquote_string(r#""double-quoted raw string \" with escaped quote""#).unwrap(),
229 "double-quoted raw string \" with escaped quote"
230 );
231
232 assert_eq!(unquote_string(r#""hello\nworld""#).unwrap(), "hello\nworld");
234 assert_eq!(unquote_string(r#""hello\tworld""#).unwrap(), "hello\tworld");
235 }
236
237 #[test]
238 fn test_unquote_string_hex_escapes() {
239 assert_eq!(unquote_string(r#""\x41""#).unwrap(), "A");
241 assert_eq!(unquote_string(r#""\x61""#).unwrap(), "a");
242 assert_eq!(unquote_string(r#""\x20""#).unwrap(), " ");
243
244 assert_eq!(
246 unquote_string(r#""\x48\x65\x6c\x6c\x6f""#).unwrap(),
247 "Hello"
248 );
249
250 assert!(unquote_string(r#""\x""#).is_err()); assert!(unquote_string(r#""\x4""#).is_err()); assert!(unquote_string(r#""\x4G""#).is_err()); }
255
256 #[test]
257 fn test_unquote_string_unicode_escapes() {
258 assert_eq!(unquote_string(r#""\u0041""#).unwrap(), "A");
260 assert_eq!(unquote_string(r#""\u0061""#).unwrap(), "a");
261 assert_eq!(unquote_string(r#""\u20AC""#).unwrap(), "€"); assert_eq!(unquote_string(r#""\U00000041""#).unwrap(), "A");
265 assert_eq!(unquote_string(r#""\U00000061""#).unwrap(), "a");
266 assert_eq!(unquote_string(r#""\U000020AC""#).unwrap(), "€"); assert!(unquote_string(r#""\u""#).is_err()); assert!(unquote_string(r#""\u123""#).is_err()); assert!(unquote_string(r#""\U""#).is_err()); assert!(unquote_string(r#""\U1234567""#).is_err()); assert!(unquote_string(r#""\U11000000""#).is_err()); }
275
276 #[test]
277 fn test_unquote_string_octal_escapes() {
278 assert_eq!(unquote_string(r#""\101""#).unwrap(), "A"); assert_eq!(unquote_string(r#""\141""#).unwrap(), "a"); assert_eq!(unquote_string(r#""\040""#).unwrap(), " "); assert!(unquote_string(r#""\1""#).is_err()); assert!(unquote_string(r#""\12""#).is_err()); assert!(unquote_string(r#""\400""#).is_err()); assert!(unquote_string(r#""\8""#).is_err()); }
289
290 #[test]
291 fn test_unquote_string_utf8_characters() {
292 assert_eq!(unquote_string("\"café\"").unwrap(), "café");
294 assert_eq!(unquote_string("\"🦀\"").unwrap(), "🦀");
295 assert_eq!(unquote_string("\"こんにちは\"").unwrap(), "こんにちは");
296 }
297
298 #[test]
299 fn test_unquote_string_mixed_content() {
300 assert_eq!(
302 unquote_string(r#""Hello, \u4e16\u754c!""#).unwrap(),
303 "Hello, 世界!"
304 );
305 assert_eq!(
306 unquote_string(r#""Line1\nLine2\tEnd""#).unwrap(),
307 "Line1\nLine2\tEnd"
308 );
309 assert_eq!(
310 unquote_string(r#""Path: C:\\\\Windows\\\\System32""#).unwrap(),
311 "Path: C:\\\\Windows\\\\System32"
312 );
313 }
314
315 #[test]
316 fn test_unquote_string_edge_cases() {
317 assert_eq!(unquote_string(r#"'It"s'"#).unwrap(), "It\"s");
319
320 assert!(unquote_string(r#""\'"'"#).is_err()); assert_eq!(unquote_string(r#"'\''"#).unwrap(), "'");
325
326 assert!(unquote_string(r#""\""#).is_err());
328 }
329
330 #[test]
331 fn test_unquote_string_complex_escape_sequences() {
332 let complex = r#""Hello\x20World\n\u4e16\u754c\t\U0001F600""#;
334 let expected = "Hello World\n世界\t😀";
335 assert_eq!(unquote_string(complex).unwrap(), expected);
336 }
337
338 #[test]
339 fn test_unquote_string_backtick_edge_cases() {
340 assert_eq!(unquote_string("`hello world`").unwrap(), "hello world");
342 assert_eq!(unquote_string("`hello\nworld`").unwrap(), "hello\nworld"); assert_eq!(unquote_string("`hello\\nworld`").unwrap(), "hello\\nworld"); assert!(unquote_string("`hello`world`").is_err());
347 assert!(unquote_string("``hello`").is_err());
348 }
349}