async_graphql_parser/parse/
utils.rs1use 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 let lines: Vec<_> = raw
31 .split("\r\n")
32 .flat_map(|s| s.split(['\r', '\n'].as_ref()))
33 .collect();
34
35 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 .map(|(i, line)| {
65 if i != 0 && line.len() >= common_indent {
66 &line[common_indent..]
67 } else {
68 line
69 }
70 })
71 .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}