fuel_indexer_graphql_parser/
common.rs

1use std::convert::TryInto;
2use std::{collections::BTreeMap, fmt};
3
4use combine::combinator::{choice, many, many1, optional, position};
5use combine::easy::Error;
6use combine::error::StreamError;
7use combine::{parser, ParseResult, Parser};
8
9use crate::helpers::{ident, kind, name, punct};
10use crate::position::Pos;
11use crate::tokenizer::{Kind as T, Token, TokenStream};
12
13/// Text abstracts over types that hold a string value.
14/// It is used to make the AST generic over the string type.
15pub trait Text<'a>: 'a {
16    type Value: 'a
17        + From<&'a str>
18        + AsRef<str>
19        + std::borrow::Borrow<str>
20        + PartialEq
21        + Eq
22        + PartialOrd
23        + Ord
24        + fmt::Debug
25        + Clone;
26}
27
28impl<'a> Text<'a> for &'a str {
29    type Value = Self;
30}
31
32impl<'a> Text<'a> for String {
33    type Value = String;
34}
35
36impl<'a> Text<'a> for std::borrow::Cow<'a, str> {
37    type Value = Self;
38}
39
40#[derive(Debug, Clone, PartialEq)]
41pub struct Directive<'a, T: Text<'a>> {
42    pub position: Pos,
43    pub name: T::Value,
44    pub arguments: Vec<(T::Value, Value<'a, T>)>,
45}
46
47/// This represents integer number
48///
49/// But since there is no definition on limit of number in spec
50/// (only in implemetation), we do a trick similar to the one
51/// in `serde_json`: encapsulate value in new-type, allowing type
52/// to be extended later.
53#[derive(Debug, Clone, PartialEq)]
54pub struct Number(pub(crate) u64);
55
56#[derive(Debug, Clone, PartialEq)]
57pub struct BigNumber(pub(crate) u128);
58
59#[derive(Debug, Clone, PartialEq)]
60pub enum Value<'a, T: Text<'a>> {
61    Variable(T::Value),
62    BigInt(BigNumber),
63    Int(Number),
64    Float(f64),
65    String(String),
66    Boolean(bool),
67    Null,
68    Enum(T::Value),
69    List(Vec<Value<'a, T>>),
70    Object(BTreeMap<T::Value, Value<'a, T>>),
71}
72
73impl<'a, T: Text<'a>> Value<'a, T> {
74    pub fn into_static(&self) -> Value<'static, String> {
75        match self {
76            Self::Variable(v) => Value::Variable(v.as_ref().into()),
77            Self::BigInt(i) => Value::BigInt(i.clone()),
78            Self::Int(i) => Value::Int(i.clone()),
79            Self::Float(f) => Value::Float(*f),
80            Self::String(s) => Value::String(s.clone()),
81            Self::Boolean(b) => Value::Boolean(*b),
82            Self::Null => Value::Null,
83            Self::Enum(v) => Value::Enum(v.as_ref().into()),
84            Self::List(l) => Value::List(l.iter().map(|e| e.into_static()).collect()),
85            Self::Object(o) => Value::Object(
86                o.iter()
87                    .map(|(k, v)| (k.as_ref().into(), v.into_static()))
88                    .collect(),
89            ),
90        }
91    }
92}
93
94#[derive(Debug, Clone, PartialEq)]
95pub enum Type<'a, T: Text<'a>> {
96    NamedType(T::Value),
97    ListType(Box<Type<'a, T>>),
98    NonNullType(Box<Type<'a, T>>),
99}
100
101impl BigNumber {
102    /// Returns a number as i64 if it fits the type
103    pub fn as_u64(&self) -> Option<u64> {
104        if let Ok(n) = TryInto::<u64>::try_into(self.0) {
105            Some(n)
106        } else {
107            None
108        }
109    }
110
111    pub fn as_u128(&self) -> u128 {
112        self.0
113    }
114}
115
116impl Number {
117    /// Returns a number as i64 if it fits the type
118    pub fn as_i64(&self) -> Option<i64> {
119        if let Ok(n) = TryInto::<i64>::try_into(self.0) {
120            Some(n)
121        } else {
122            None
123        }
124    }
125
126    pub fn as_u64(&self) -> u64 {
127        self.0
128    }
129}
130
131pub fn directives<'a, T>(
132    input: &mut TokenStream<'a>,
133) -> ParseResult<Vec<Directive<'a, T>>, TokenStream<'a>>
134where
135    T: Text<'a>,
136{
137    many(
138        position()
139            .skip(punct("@"))
140            .and(name::<'a, T>())
141            .and(parser(arguments))
142            .map(|((position, name), arguments)| Directive {
143                position,
144                name,
145                arguments,
146            }),
147    )
148    .parse_stream(input)
149}
150
151#[allow(clippy::type_complexity)]
152pub fn arguments<'a, T>(
153    input: &mut TokenStream<'a>,
154) -> ParseResult<Vec<(T::Value, Value<'a, T>)>, TokenStream<'a>>
155where
156    T: Text<'a>,
157{
158    optional(
159        punct("(")
160            .with(many1(name::<'a, T>().skip(punct(":")).and(parser(value))))
161            .skip(punct(")")),
162    )
163    .map(|opt| opt.unwrap_or_default())
164    .parse_stream(input)
165}
166
167pub fn bigint_value<'a, S>(
168    input: &mut TokenStream<'a>,
169) -> ParseResult<Value<'a, S>, TokenStream<'a>>
170where
171    S: Text<'a>,
172{
173    kind(T::BigIntValue)
174        .and_then(|tok| tok.value.parse())
175        .map(BigNumber)
176        .map(Value::BigInt)
177        .parse_stream(input)
178}
179
180pub fn int_value<'a, S>(
181    input: &mut TokenStream<'a>,
182) -> ParseResult<Value<'a, S>, TokenStream<'a>>
183where
184    S: Text<'a>,
185{
186    kind(T::IntValue)
187        .and_then(|tok| tok.value.parse())
188        .map(Number)
189        .map(Value::Int)
190        .parse_stream(input)
191}
192
193pub fn float_value<'a, S>(
194    input: &mut TokenStream<'a>,
195) -> ParseResult<Value<'a, S>, TokenStream<'a>>
196where
197    S: Text<'a>,
198{
199    kind(T::FloatValue)
200        .and_then(|tok| tok.value.parse())
201        .map(Value::Float)
202        .parse_stream(input)
203}
204
205fn unquote_block_string(src: &str) -> Result<String, Error<Token<'_>, Token<'_>>> {
206    debug_assert!(src.starts_with("\"\"\"") && src.ends_with("\"\"\""));
207    let indent = src[3..src.len() - 3]
208        .lines()
209        .skip(1)
210        .filter_map(|line| {
211            let trimmed = line.trim_start().len();
212            if trimmed > 0 {
213                Some(line.len() - trimmed)
214            } else {
215                None // skip whitespace-only lines
216            }
217        })
218        .min()
219        .unwrap_or(0);
220    let mut result = String::with_capacity(src.len() - 6);
221    let mut lines = src[3..src.len() - 3].lines();
222    if let Some(first) = lines.next() {
223        let stripped = first.trim();
224        if !stripped.is_empty() {
225            result.push_str(stripped);
226            result.push('\n');
227        }
228    }
229    let mut last_line = 0;
230    for line in lines {
231        last_line = result.len();
232        if line.len() > indent {
233            result.push_str(&line[indent..].replace(r#"\""""#, r#"""""#));
234        }
235        result.push('\n');
236    }
237    if result[last_line..].trim().is_empty() {
238        result.truncate(last_line);
239    }
240
241    Ok(result)
242}
243
244fn unquote_string(s: &str) -> Result<String, Error<Token, Token>> {
245    let mut res = String::with_capacity(s.len());
246    debug_assert!(s.starts_with('"') && s.ends_with('"'));
247    let mut chars = s[1..s.len() - 1].chars();
248    let mut temp_code_point = String::with_capacity(4);
249    while let Some(c) = chars.next() {
250        match c {
251            '\\' => {
252                match chars.next().expect("slash cant be at the end") {
253                    c @ '"' | c @ '\\' | c @ '/' => res.push(c),
254                    'b' => res.push('\u{0010}'),
255                    'f' => res.push('\u{000C}'),
256                    'n' => res.push('\n'),
257                    'r' => res.push('\r'),
258                    't' => res.push('\t'),
259                    'u' => {
260                        temp_code_point.clear();
261                        for _ in 0..4 {
262                            match chars.next() {
263                                Some(inner_c) => temp_code_point.push(inner_c),
264                                None => {
265                                    return Err(Error::unexpected_message(format_args!(
266                                        "\\u must have 4 characters after it, only found '{}'",
267                                        temp_code_point
268                                    )))
269                                }
270                            }
271                        }
272
273                        // convert our hex string into a u32, then convert that into a char
274                        match u32::from_str_radix(&temp_code_point, 16)
275                            .map(std::char::from_u32)
276                        {
277                            Ok(Some(unicode_char)) => res.push(unicode_char),
278                            _ => {
279                                return Err(Error::unexpected_message(format_args!(
280                                    "{} is not a valid unicode code point",
281                                    temp_code_point
282                                )))
283                            }
284                        }
285                    }
286                    c => {
287                        return Err(Error::unexpected_message(format_args!(
288                            "bad escaped char {:?}",
289                            c
290                        )));
291                    }
292                }
293            }
294            c => res.push(c),
295        }
296    }
297
298    Ok(res)
299}
300
301pub fn string<'a>(input: &mut TokenStream<'a>) -> ParseResult<String, TokenStream<'a>> {
302    choice((
303        kind(T::StringValue).and_then(|tok| unquote_string(tok.value)),
304        kind(T::BlockString).and_then(|tok| unquote_block_string(tok.value)),
305    ))
306    .parse_stream(input)
307}
308
309pub fn string_value<'a, S>(
310    input: &mut TokenStream<'a>,
311) -> ParseResult<Value<'a, S>, TokenStream<'a>>
312where
313    S: Text<'a>,
314{
315    kind(T::StringValue)
316        .and_then(|tok| unquote_string(tok.value))
317        .map(Value::String)
318        .parse_stream(input)
319}
320
321pub fn block_string_value<'a, S>(
322    input: &mut TokenStream<'a>,
323) -> ParseResult<Value<'a, S>, TokenStream<'a>>
324where
325    S: Text<'a>,
326{
327    kind(T::BlockString)
328        .and_then(|tok| unquote_block_string(tok.value))
329        .map(Value::String)
330        .parse_stream(input)
331}
332
333pub fn plain_value<'a, T>(
334    input: &mut TokenStream<'a>,
335) -> ParseResult<Value<'a, T>, TokenStream<'a>>
336where
337    T: Text<'a>,
338{
339    ident("true")
340        .map(|_| Value::Boolean(true))
341        .or(ident("false").map(|_| Value::Boolean(false)))
342        .or(ident("null").map(|_| Value::Null))
343        .or(name::<'a, T>().map(Value::Enum))
344        .or(parser(int_value))
345        .or(parser(float_value))
346        .or(parser(bigint_value))
347        .or(parser(string_value))
348        .or(parser(block_string_value))
349        .parse_stream(input)
350}
351
352pub fn value<'a, T>(
353    input: &mut TokenStream<'a>,
354) -> ParseResult<Value<'a, T>, TokenStream<'a>>
355where
356    T: Text<'a>,
357{
358    parser(plain_value)
359        .or(punct("$").with(name::<'a, T>()).map(Value::Variable))
360        .or(punct("[")
361            .with(many(parser(value)))
362            .skip(punct("]"))
363            .map(Value::List))
364        .or(punct("{")
365            .with(many(name::<'a, T>().skip(punct(":")).and(parser(value))))
366            .skip(punct("}"))
367            .map(Value::Object))
368        .parse_stream(input)
369}
370
371pub fn default_value<'a, T>(
372    input: &mut TokenStream<'a>,
373) -> ParseResult<Value<'a, T>, TokenStream<'a>>
374where
375    T: Text<'a>,
376{
377    parser(plain_value)
378        .or(punct("[")
379            .with(many(parser(default_value)))
380            .skip(punct("]"))
381            .map(Value::List))
382        .or(punct("{")
383            .with(many(
384                name::<'a, T>().skip(punct(":")).and(parser(default_value)),
385            ))
386            .skip(punct("}"))
387            .map(Value::Object))
388        .parse_stream(input)
389}
390
391pub fn parse_type<'a, T>(
392    input: &mut TokenStream<'a>,
393) -> ParseResult<Type<'a, T>, TokenStream<'a>>
394where
395    T: Text<'a>,
396{
397    name::<'a, T>()
398        .map(Type::NamedType)
399        .or(punct("[")
400            .with(parser(parse_type))
401            .skip(punct("]"))
402            .map(Box::new)
403            .map(Type::ListType))
404        .and(optional(punct("!")).map(|v| v.is_some()))
405        .map(|(typ, strict)| {
406            if strict {
407                Type::NonNullType(Box::new(typ))
408            } else {
409                typ
410            }
411        })
412        .parse_stream(input)
413}
414
415#[cfg(test)]
416mod tests {
417    use super::unquote_string;
418
419    #[test]
420    fn unquote_unicode_string() {
421        // basic tests
422        assert_eq!(unquote_string(r#""\u0009""#).expect(""), "\u{0009}");
423        assert_eq!(unquote_string(r#""\u000A""#).expect(""), "\u{000A}");
424        assert_eq!(unquote_string(r#""\u000D""#).expect(""), "\u{000D}");
425        assert_eq!(unquote_string(r#""\u0020""#).expect(""), "\u{0020}");
426        assert_eq!(unquote_string(r#""\uFFFF""#).expect(""), "\u{FFFF}");
427
428        // a more complex string
429        assert_eq!(
430            unquote_string(r#""\u0009 hello \u000A there""#).expect(""),
431            "\u{0009} hello \u{000A} there"
432        );
433    }
434}