Skip to main content

lutra_sql/
string.rs

1#[cfg(not(feature = "std"))]
2use alloc::string::String;
3
4use core::fmt;
5
6pub fn escape(string: &str, quote: char) -> EscapeQuotedString<'_> {
7    EscapeQuotedString { string, quote }
8}
9
10pub struct EscapeQuotedString<'a> {
11    string: &'a str,
12    quote: char,
13}
14
15impl fmt::Display for EscapeQuotedString<'_> {
16    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
17        // EscapeQuotedString doesn't know which mode of escape was
18        // chosen by the user. So this code must to correctly display
19        // strings without knowing if the strings are already escaped
20        // or not.
21        //
22        // If the quote symbol in the string is repeated twice, OR, if
23        // the quote symbol is after backslash, display all the chars
24        // without any escape. However, if the quote symbol is used
25        // just between usual chars, `fmt()` should display it twice."
26        //
27        // The following table has examples
28        //
29        // | original query | mode      | AST Node                                           | serialized   |
30        // | -------------  | --------- | -------------------------------------------------- | ------------ |
31        // | `"A""B""A"`    | no-escape | `DoubleQuotedString(String::from("A\"\"B\"\"A"))`  | `"A""B""A"`  |
32        // | `"A""B""A"`    | default   | `DoubleQuotedString(String::from("A\"B\"A"))`      | `"A""B""A"`  |
33        // | `"A\"B\"A"`    | no-escape | `DoubleQuotedString(String::from("A\\\"B\\\"A"))`  | `"A\"B\"A"`  |
34        // | `"A\"B\"A"`    | default   | `DoubleQuotedString(String::from("A\"B\"A"))`      | `"A""B""A"`  |
35        let quote = self.quote;
36        let mut previous_char = char::default();
37        let mut start_idx = 0;
38        let mut peekable_chars = self.string.char_indices().peekable();
39        while let Some(&(idx, ch)) = peekable_chars.peek() {
40            match ch {
41                char if char == quote => {
42                    if previous_char == '\\' {
43                        // the quote is already escaped with a backslash, skip
44                        peekable_chars.next();
45                        continue;
46                    }
47                    peekable_chars.next();
48                    match peekable_chars.peek() {
49                        Some((_, c)) if *c == quote => {
50                            // the quote is already escaped with another quote, skip
51                            peekable_chars.next();
52                        }
53                        _ => {
54                            // The quote is not escaped.
55                            // Including idx in the range, so the quote at idx will be printed twice:
56                            // in this call to write_str() and in the next one.
57                            f.write_str(&self.string[start_idx..=idx])?;
58                            start_idx = idx;
59                        }
60                    }
61                }
62                _ => {
63                    peekable_chars.next();
64                }
65            }
66            previous_char = ch;
67        }
68        f.write_str(&self.string[start_idx..])?;
69        Ok(())
70    }
71}