async_graphql_parser/parse/
utils.rs

1use pest::iterators::{Pair, Pairs};
2
3use super::Rule;
4use crate::Result;
5
6pub(super) fn next_if_rule<'a>(pairs: &mut Pairs<'a, Rule>, rule: Rule) -> Option<Pair<'a, Rule>> {
7    if pairs.peek().is_some_and(|pair| pair.as_rule() == rule) {
8        Some(pairs.next().unwrap())
9    } else {
10        None
11    }
12}
13pub(super) fn parse_if_rule<T>(
14    pairs: &mut Pairs<Rule>,
15    rule: Rule,
16    f: impl FnOnce(Pair<Rule>) -> Result<T>,
17) -> Result<Option<T>> {
18    next_if_rule(pairs, rule).map(f).transpose()
19}
20
21pub(super) fn exactly_one<T>(iter: impl IntoIterator<Item = T>) -> T {
22    let mut iter = iter.into_iter();
23    let res = iter.next().unwrap();
24    debug_assert!(iter.next().is_none());
25    res
26}
27
28pub(super) fn block_string_value(raw: &str) -> String {
29    // Split the string by either \r\n, \r or \n
30    let lines: Vec<_> = raw
31        .split("\r\n")
32        .flat_map(|s| s.split(['\r', '\n'].as_ref()))
33        .collect();
34
35    // Find the common indent
36    let common_indent = lines
37        .iter()
38        .skip(1)
39        .copied()
40        .filter_map(|line| line.find(|c| c != '\t' && c != ' '))
41        .min()
42        .unwrap_or(0);
43
44    let line_has_content = |line: &str| line.as_bytes().iter().any(|&c| c != b'\t' && c != b' ');
45
46    let first_contentful_line = lines
47        .iter()
48        .copied()
49        .position(line_has_content)
50        .unwrap_or(lines.len());
51    let ending_lines_start = lines
52        .iter()
53        .copied()
54        .rposition(line_has_content)
55        .map_or(0, |i| i + 1);
56
57    lines
58        .iter()
59        .copied()
60        .enumerate()
61        .take(ending_lines_start)
62        .skip(first_contentful_line)
63        // Remove the common indent, but not on the first line
64        .map(|(i, line)| {
65            if i != 0 && line.len() >= common_indent {
66                &line[common_indent..]
67            } else {
68                line
69            }
70        })
71        // Put a newline between each line
72        .enumerate()
73        .flat_map(|(i, line)| {
74            if i == 0 { [].as_ref() } else { ['\n'].as_ref() }
75                .iter()
76                .copied()
77                .chain(line.chars())
78        })
79        .collect()
80}
81
82#[test]
83fn test_block_string_value() {
84    assert_eq!(block_string_value(""), "");
85    assert_eq!(block_string_value("\r\n"), "");
86    assert_eq!(block_string_value("\r\r\r\r\n\n\r\n\r\r"), "");
87    assert_eq!(block_string_value("abc"), "abc");
88    assert_eq!(
89        block_string_value("line 1\r\n   line 2\n     line 3\r    line 4"),
90        "line 1\nline 2\n  line 3\n line 4"
91    );
92    assert_eq!(
93        block_string_value("\r\r  some text\r\n \n \n "),
94        "some text"
95    );
96    assert_eq!(
97        block_string_value(
98            r#"
99    a
100    b
101
102    c
103"#
104        ),
105        "a\nb\n\nc"
106    );
107}
108
109pub(super) fn string_value(s: &str) -> String {
110    let mut chars = s.chars();
111
112    std::iter::from_fn(|| {
113        Some(match chars.next()? {
114            '\\' => match chars.next().expect("backslash at end") {
115                c @ '\"' | c @ '\\' | c @ '/' => c,
116                'b' => '\x08',
117                'f' => '\x0C',
118                'n' => '\n',
119                'r' => '\r',
120                't' => '\t',
121                'u' => std::char::from_u32(
122                    (0..4)
123                        .map(|_| chars.next().unwrap().to_digit(16).unwrap())
124                        .fold(0, |acc, digit| acc * 16 + digit),
125                )
126                .unwrap(),
127                _ => unreachable!(),
128            },
129            other => other,
130        })
131    })
132    .collect()
133}
134
135#[test]
136fn test_string_value() {
137    assert_eq!(string_value("abc"), "abc");
138    assert_eq!(string_value("\\n\\b\\u2a1A"), "\n\x08\u{2A1A}");
139    assert_eq!(string_value("\\\"\\\\"), "\"\\");
140}