graphql_parser/
common.rs

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