1pub fn parse_column_path(path: &str) -> Result<Vec<String>, String> {
21 let mut parts = Vec::new();
22 let mut current = String::new();
23 let mut escaped = false;
24
25 for c in path.chars() {
26 if escaped {
27 match c {
28 '.' | '\\' => current.push(c),
29 _ => {
30 return Err(format!(
31 "Invalid escape sequence \\{c} in column path {path}"
32 ));
33 }
34 }
35 escaped = false;
36 continue;
37 }
38
39 match c {
40 '\\' => escaped = true,
41 '.' => {
42 parts.push(current);
43 current = String::new();
44 }
45 _ => current.push(c),
46 }
47 }
48
49 if escaped {
50 return Err(format!(
51 "Column path {path} ends with an incomplete escape sequence"
52 ));
53 }
54
55 parts.push(current);
56 Ok(parts)
57}
58
59#[cfg(test)]
60mod tests {
61 use super::*;
62
63 #[test]
64 fn parse_column_path_splits_unescaped_dots() {
65 assert_eq!(parse_column_path("a.b.c").unwrap(), vec!["a", "b", "c"]);
66 }
67
68 #[test]
69 fn parse_column_path_allows_escaped_dots() {
70 assert_eq!(parse_column_path(r"a\.b.c").unwrap(), vec!["a.b", "c"]);
71 }
72
73 #[test]
74 fn parse_column_path_allows_escaped_backslashes() {
75 assert_eq!(parse_column_path(r"a\\b.c").unwrap(), vec![r"a\b", "c"]);
76 }
77
78 #[test]
79 fn parse_column_path_invalid_escape_sequence() {
80 let err = parse_column_path(r"a.\b.c").unwrap_err();
81 assert!(err.contains("Invalid escape sequence"));
82 }
83
84 #[test]
85 fn parse_column_path_incomplete_escape_sequence() {
86 let err = parse_column_path(r"a.b.c\").unwrap_err();
87 assert!(err.contains("incomplete escape sequence"));
88 }
89}