graphql_query/ast/
printer.rs

1use super::ast::*;
2use std::{fmt, fmt::Write};
3
4/// Trait for printing AST Nodes to a new String allocated on the heap.
5/// This is implemented by all AST Nodes and can hence be used to granularly print GraphQL language.
6/// However, mostly this will be used via `Document::print`.
7///
8/// This typically is the last operation that's done in a given AST context and is hence outside
9/// of its lifetime and arena.
10///
11/// For convience when debugging, AST Nodes that implement `PrintNode` also automatically
12/// implement the [`fmt::Display`] trait.
13pub trait PrintNode {
14    /// Write an AST node to a buffer implementing the [Write] trait.
15    ///
16    /// The `level` indicates the level of nesting, which increases with each [`SelectionSet`]
17    /// and is typically initialized as zero (`0`).
18    fn write_to_buffer(&self, level: usize, buffer: &mut dyn Write) -> fmt::Result;
19
20    /// Print an AST Node to source text as a String allocated on the heap.
21    ///
22    /// For convience when debugging, AST Nodes that implement `PrintNode` also automatically
23    /// implement the [`fmt::Display`] trait.
24    fn print(&self) -> String {
25        let mut buf = String::new();
26        match self.write_to_buffer(0, &mut buf) {
27            Ok(()) => buf,
28            _ => "".to_string(),
29        }
30    }
31}
32
33impl fmt::Display for dyn PrintNode {
34    #[inline]
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        self.write_to_buffer(0, f)
37    }
38}
39
40impl<'a> PrintNode for NamedType<'a> {
41    #[inline]
42    fn write_to_buffer(&self, _level: usize, buffer: &mut dyn Write) -> fmt::Result {
43        buffer.write_str(self.name)
44    }
45}
46
47impl<'a> PrintNode for Variable<'a> {
48    #[inline]
49    fn write_to_buffer(&self, _level: usize, buffer: &mut dyn Write) -> fmt::Result {
50        write!(buffer, "${}", self.name)
51    }
52}
53
54impl PrintNode for BooleanValue {
55    #[inline]
56    fn write_to_buffer(&self, _level: usize, buffer: &mut dyn Write) -> fmt::Result {
57        match self.value {
58            true => buffer.write_str("true"),
59            false => buffer.write_str("false"),
60        }
61    }
62}
63
64impl<'a> PrintNode for EnumValue<'a> {
65    #[inline]
66    fn write_to_buffer(&self, _level: usize, buffer: &mut dyn Write) -> fmt::Result {
67        buffer.write_str(self.value)
68    }
69}
70
71impl<'a> PrintNode for FloatValue<'a> {
72    #[inline]
73    fn write_to_buffer(&self, _level: usize, buffer: &mut dyn Write) -> fmt::Result {
74        buffer.write_str(self.value)
75    }
76}
77
78impl<'a> PrintNode for IntValue<'a> {
79    #[inline]
80    fn write_to_buffer(&self, _level: usize, buffer: &mut dyn Write) -> fmt::Result {
81        buffer.write_str(self.value)
82    }
83}
84
85impl<'a> PrintNode for StringValue<'a> {
86    #[inline]
87    fn write_to_buffer(&self, level: usize, buffer: &mut dyn Write) -> fmt::Result {
88        use lexical_core::*;
89        let mut buf = [b'0'; u32::FORMATTED_SIZE];
90
91        // See: https://github.com/graphql-rust/graphql-parser/blob/ff34bae/src/format.rs#L127-L167
92        if !self.is_block() {
93            buffer.write_char('"')?;
94            for c in self.value.chars() {
95                match c {
96                    '\r' => buffer.write_str(r"\r")?,
97                    '\n' => buffer.write_str(r"\n")?,
98                    '\t' => buffer.write_str(r"\t")?,
99                    '"' => buffer.write_str("\\\"")?,
100                    '\\' => buffer.write_str(r"\\")?,
101                    '\u{0020}'..='\u{FFFF}' => buffer.write_char(c)?,
102                    _ => unsafe {
103                        const FORMAT: u128 = NumberFormatBuilder::hexadecimal();
104                        const OPTIONS: WriteIntegerOptions = WriteIntegerOptions::new();
105                        let buf =
106                            write_with_options_unchecked::<_, FORMAT>(c as u32, &mut buf, &OPTIONS);
107                        write!(buffer, "\\u{:0>4}", std::str::from_utf8_unchecked(buf))?;
108                    },
109                };
110            }
111            buffer.write_char('"')
112        } else {
113            buffer.write_str("\"\"\"\n")?;
114            for line in self.value.lines() {
115                if !line.trim().is_empty() {
116                    write_indent(level, buffer)?;
117                    buffer.write_str(&line.replace(r#"""""#, r#"\""""#))?;
118                }
119                buffer.write_char('\n')?;
120            }
121            write_indent(level, buffer)?;
122            buffer.write_str("\"\"\"")
123        }
124    }
125}
126
127impl<'a> PrintNode for Value<'a> {
128    #[inline]
129    fn write_to_buffer(&self, level: usize, buffer: &mut dyn Write) -> fmt::Result {
130        match self {
131            Value::Boolean(value) => value.write_to_buffer(level, buffer),
132            Value::Enum(value) => value.write_to_buffer(level, buffer),
133            Value::Float(value) => value.write_to_buffer(level, buffer),
134            Value::Int(value) => value.write_to_buffer(level, buffer),
135            Value::String(value) => value.write_to_buffer(level, buffer),
136            Value::Variable(value) => value.write_to_buffer(level, buffer),
137            Value::Object(value) => value.write_to_buffer(level, buffer),
138            Value::List(value) => value.write_to_buffer(level, buffer),
139            Value::Null => buffer.write_str("null"),
140        }
141    }
142}
143
144impl<'a> PrintNode for ObjectField<'a> {
145    #[inline]
146    fn write_to_buffer(&self, level: usize, buffer: &mut dyn Write) -> fmt::Result {
147        write!(buffer, "{}: ", self.name)?;
148        self.value.write_to_buffer(level, buffer)
149    }
150}
151
152impl<'a> PrintNode for ObjectValue<'a> {
153    #[inline]
154    fn write_to_buffer(&self, level: usize, buffer: &mut dyn Write) -> fmt::Result {
155        buffer.write_str("{")?;
156        let mut first = true;
157        for field in self.children.iter() {
158            if first {
159                first = false;
160            } else {
161                buffer.write_str(", ")?;
162            }
163            field.write_to_buffer(level, buffer)?;
164        }
165        buffer.write_str("}")
166    }
167}
168
169impl<'a> PrintNode for ListValue<'a> {
170    #[inline]
171    fn write_to_buffer(&self, level: usize, buffer: &mut dyn Write) -> fmt::Result {
172        buffer.write_str("[")?;
173        let mut first = true;
174        for field in self.children.iter() {
175            if first {
176                first = false;
177            } else {
178                buffer.write_str(", ")?;
179            }
180            field.write_to_buffer(level, buffer)?;
181        }
182        buffer.write_str("]")
183    }
184}
185
186impl<'a> PrintNode for Argument<'a> {
187    #[inline]
188    fn write_to_buffer(&self, level: usize, buffer: &mut dyn Write) -> fmt::Result {
189        write!(buffer, "{}: ", self.name)?;
190        self.value.write_to_buffer(level, buffer)
191    }
192}
193
194impl<'a> PrintNode for Arguments<'a> {
195    #[inline]
196    fn write_to_buffer(&self, level: usize, buffer: &mut dyn Write) -> fmt::Result {
197        if !self.is_empty() {
198            buffer.write_str("(")?;
199            let mut first = true;
200            for argument in self.children.iter() {
201                if first {
202                    first = false;
203                } else {
204                    buffer.write_str(", ")?;
205                }
206                argument.write_to_buffer(level, buffer)?;
207            }
208            buffer.write_str(")")
209        } else {
210            Ok(())
211        }
212    }
213}
214
215impl<'a> PrintNode for Directive<'a> {
216    #[inline]
217    fn write_to_buffer(&self, level: usize, buffer: &mut dyn Write) -> fmt::Result {
218        write!(buffer, "@{}", self.name)?;
219        self.arguments.write_to_buffer(level, buffer)
220    }
221}
222
223impl<'a> PrintNode for Directives<'a> {
224    #[inline]
225    fn write_to_buffer(&self, level: usize, buffer: &mut dyn Write) -> fmt::Result {
226        for directive in self.children.iter() {
227            buffer.write_str(" ")?;
228            directive.write_to_buffer(level, buffer)?;
229        }
230        Ok(())
231    }
232}
233
234impl<'a> PrintNode for SelectionSet<'a> {
235    #[inline]
236    fn write_to_buffer(&self, level: usize, buffer: &mut dyn Write) -> fmt::Result {
237        if !self.is_empty() {
238            let level = level + 1;
239            buffer.write_str("{")?;
240            for selection in self.selections.iter() {
241                buffer.write_char('\n')?;
242                write_indent(level, buffer)?;
243                match selection {
244                    Selection::Field(field) => field.write_to_buffer(level, buffer)?,
245                    Selection::FragmentSpread(spread) => spread.write_to_buffer(level, buffer)?,
246                    Selection::InlineFragment(inline) => inline.write_to_buffer(level, buffer)?,
247                };
248            }
249            buffer.write_char('\n')?;
250            write_indent(level - 1, buffer)?;
251            buffer.write_char('}')
252        } else {
253            Ok(())
254        }
255    }
256}
257
258impl<'a> PrintNode for Field<'a> {
259    #[inline]
260    fn write_to_buffer(&self, level: usize, buffer: &mut dyn Write) -> fmt::Result {
261        if let Some(alias) = self.alias {
262            write!(buffer, "{}: {}", alias, self.name)?;
263        } else {
264            buffer.write_str(self.name)?;
265        };
266        self.arguments.write_to_buffer(level, buffer)?;
267        self.directives.write_to_buffer(level, buffer)?;
268        if !self.selection_set.is_empty() {
269            buffer.write_str(" ")?;
270        };
271        self.selection_set.write_to_buffer(level, buffer)
272    }
273}
274
275impl<'a> PrintNode for FragmentSpread<'a> {
276    #[inline]
277    fn write_to_buffer(&self, level: usize, buffer: &mut dyn Write) -> fmt::Result {
278        buffer.write_str("...")?;
279        self.name.write_to_buffer(level, buffer)?;
280        self.directives.write_to_buffer(level, buffer)
281    }
282}
283
284impl<'a> PrintNode for InlineFragment<'a> {
285    #[inline]
286    fn write_to_buffer(&self, level: usize, buffer: &mut dyn Write) -> fmt::Result {
287        buffer.write_str("...")?;
288        if let Some(name) = &self.type_condition {
289            buffer.write_str(" on ")?;
290            name.write_to_buffer(level, buffer)?;
291        };
292        self.directives.write_to_buffer(level, buffer)?;
293        buffer.write_str(" ")?;
294        self.selection_set.write_to_buffer(level, buffer)
295    }
296}
297
298impl<'a> PrintNode for Type<'a> {
299    #[inline]
300    fn write_to_buffer(&self, level: usize, buffer: &mut dyn Write) -> fmt::Result {
301        match self {
302            Type::NamedType(name) => name.write_to_buffer(level, buffer),
303            Type::ListType(inner) => {
304                buffer.write_str("[")?;
305                inner.write_to_buffer(level, buffer)?;
306                buffer.write_str("]")
307            }
308            Type::NonNullType(inner) => {
309                inner.write_to_buffer(level, buffer)?;
310                buffer.write_str("!")
311            }
312        }
313    }
314}
315
316impl<'a> PrintNode for VariableDefinition<'a> {
317    #[inline]
318    fn write_to_buffer(&self, level: usize, buffer: &mut dyn Write) -> fmt::Result {
319        self.variable.write_to_buffer(level, buffer)?;
320        buffer.write_str(": ")?;
321        self.of_type.write_to_buffer(level, buffer)?;
322        if self.default_value != Value::Null {
323            buffer.write_str(" = ")?;
324            self.default_value.write_to_buffer(level, buffer)?;
325        }
326        self.directives.write_to_buffer(level, buffer)
327    }
328}
329
330impl<'a> PrintNode for VariableDefinitions<'a> {
331    #[inline]
332    fn write_to_buffer(&self, level: usize, buffer: &mut dyn Write) -> fmt::Result {
333        if !self.is_empty() {
334            buffer.write_str("(")?;
335            let mut first = true;
336            for var_definition in self.children.iter() {
337                if first {
338                    first = false;
339                } else {
340                    buffer.write_str(", ")?;
341                }
342                var_definition.write_to_buffer(level, buffer)?;
343            }
344            buffer.write_str(")")
345        } else {
346            Ok(())
347        }
348    }
349}
350
351impl<'a> PrintNode for FragmentDefinition<'a> {
352    #[inline]
353    fn write_to_buffer(&self, level: usize, buffer: &mut dyn Write) -> fmt::Result {
354        buffer.write_str("fragment ")?;
355        self.name.write_to_buffer(level, buffer)?;
356        buffer.write_str(" on ")?;
357        self.type_condition.write_to_buffer(level, buffer)?;
358        self.directives.write_to_buffer(level, buffer)?;
359        buffer.write_str(" ")?;
360        self.selection_set.write_to_buffer(level, buffer)
361    }
362}
363
364impl<'a> PrintNode for OperationDefinition<'a> {
365    #[inline]
366    fn write_to_buffer(&self, level: usize, buffer: &mut dyn Write) -> fmt::Result {
367        if self.operation == OperationKind::Query
368            && self.name.is_none()
369            && self.variable_definitions.is_empty()
370            && self.directives.is_empty()
371        {
372            self.selection_set.write_to_buffer(level, buffer)
373        } else {
374            match self.operation {
375                OperationKind::Query => buffer.write_str("query")?,
376                OperationKind::Mutation => buffer.write_str("mutation")?,
377                OperationKind::Subscription => buffer.write_str("subscription")?,
378            };
379            if let Some(name) = &self.name {
380                buffer.write_str(" ")?;
381                name.write_to_buffer(level, buffer)?;
382            };
383            if self.name.is_none() && !self.variable_definitions.is_empty() {
384                buffer.write_str(" ")?;
385            }
386            self.variable_definitions.write_to_buffer(level, buffer)?;
387            self.directives.write_to_buffer(level, buffer)?;
388            buffer.write_str(" ")?;
389            self.selection_set.write_to_buffer(level, buffer)
390        }
391    }
392}
393
394impl<'a> PrintNode for Definition<'a> {
395    #[inline]
396    fn write_to_buffer(&self, level: usize, buffer: &mut dyn Write) -> fmt::Result {
397        match self {
398            Definition::Operation(operation) => operation.write_to_buffer(level, buffer),
399            Definition::Fragment(fragment) => fragment.write_to_buffer(level, buffer),
400        }
401    }
402}
403
404impl<'a> PrintNode for Document<'a> {
405    #[inline]
406    fn write_to_buffer(&self, level: usize, buffer: &mut dyn Write) -> fmt::Result {
407        let mut first = true;
408        for definition in self.definitions.iter() {
409            if first {
410                first = false;
411            } else {
412                buffer.write_str("\n\n")?;
413            }
414            definition.write_to_buffer(level, buffer)?;
415        }
416        Ok(())
417    }
418
419    #[inline]
420    fn print(&self) -> String {
421        let mut buf = String::with_capacity(self.size_hint);
422        match self.write_to_buffer(0, &mut buf) {
423            Ok(()) => buf,
424            _ => "".to_string(),
425        }
426    }
427}
428
429#[inline(always)]
430fn write_indent(level: usize, buffer: &mut dyn Write) -> fmt::Result {
431    for _ in 0..level {
432        buffer.write_str("  ")?
433    }
434    Ok(())
435}
436
437#[cfg(test)]
438mod tests {
439    use super::super::*;
440
441    #[test]
442    fn values() {
443        let ctx = ASTContext::new();
444        let ast = Value::parse(&ctx, "{ a: true, b: [1, 2] }");
445        assert_eq!(ast.unwrap().print(), "{a: true, b: [1, 2]}");
446        let ast = Value::parse(&ctx, "123.23");
447        assert_eq!(ast.unwrap().print(), "123.23");
448        let ast = Value::parse(&ctx, "123.23e20");
449        assert_eq!(ast.unwrap().print(), "123.23e20");
450    }
451
452    #[test]
453    fn arguments() {
454        let ctx = ASTContext::new();
455        let ast = Arguments::parse(&ctx, "()");
456        assert_eq!(ast.unwrap().print(), "");
457        let ast = Arguments::parse(&ctx, "(a:1)");
458        assert_eq!(ast.unwrap().print(), "(a: 1)");
459        let ast = Arguments::parse(&ctx, "(a:1 b:2)");
460        assert_eq!(ast.unwrap().print(), "(a: 1, b: 2)");
461    }
462
463    #[test]
464    fn directives() {
465        let ctx = ASTContext::new();
466        let ast = Directives::parse(&ctx, "@skip(if: true)");
467        assert_eq!(ast.unwrap().print(), " @skip(if: true)");
468        let ast = Directives::parse(&ctx, "@skip(if: true) @include(if: false)");
469        assert_eq!(ast.unwrap().print(), " @skip(if: true) @include(if: false)");
470    }
471
472    #[test]
473    fn field() {
474        let ctx = ASTContext::new();
475        let ast = Field::parse(&ctx, "field { child }");
476        assert_eq!(ast.unwrap().print(), "field {\n  child\n}");
477        let ast = Field::parse(&ctx, "field { child { child } }");
478        assert_eq!(
479            ast.unwrap().print(),
480            "field {\n  child {\n    child\n  }\n}"
481        );
482        let ast = Field::parse(&ctx, "alias : field");
483        assert_eq!(ast.unwrap().print(), "alias: field");
484        let ast = Field::parse(&ctx, "field: field");
485        assert_eq!(ast.unwrap().print(), "field: field");
486        let ast = Field::parse(&ctx, "field (test: true)");
487        assert_eq!(ast.unwrap().print(), "field(test: true)");
488        let ast = Field::parse(&ctx, "field (test: true) @test");
489        assert_eq!(ast.unwrap().print(), "field(test: true) @test");
490    }
491
492    #[test]
493    fn fragment_spread() {
494        let ctx = ASTContext::new();
495        let ast = FragmentSpread::parse(&ctx, "...Type");
496        assert_eq!(ast.unwrap().print(), "...Type");
497        let ast = FragmentSpread::parse(&ctx, "...Type @test");
498        assert_eq!(ast.unwrap().print(), "...Type @test");
499    }
500
501    #[test]
502    fn inline_fragment() {
503        let ctx = ASTContext::new();
504        let ast = InlineFragment::parse(&ctx, "... on Type { field }");
505        assert_eq!(ast.unwrap().print(), "... on Type {\n  field\n}");
506        let ast = InlineFragment::parse(&ctx, "... on Type @test{ field }");
507        assert_eq!(ast.unwrap().print(), "... on Type @test {\n  field\n}");
508        let ast = InlineFragment::parse(&ctx, "...@test { field }");
509        assert_eq!(ast.unwrap().print(), "... @test {\n  field\n}");
510    }
511
512    #[test]
513    fn _type() {
514        let ctx = ASTContext::new();
515        let ast = Type::parse(&ctx, "[Type]");
516        assert_eq!(ast.unwrap().print(), "[Type]");
517        let ast = Type::parse(&ctx, "[Type !] !");
518        assert_eq!(ast.unwrap().print(), "[Type!]!");
519        let ast = Type::parse(&ctx, "Type!");
520        assert_eq!(ast.unwrap().print(), "Type!");
521    }
522
523    #[test]
524    fn variable_definitions() {
525        let ctx = ASTContext::new();
526        let ast = VariableDefinitions::parse(&ctx, "($x : Int)");
527        assert_eq!(ast.unwrap().print(), "($x: Int)");
528        let ast = VariableDefinitions::parse(&ctx, "($x : Int = 1)");
529        assert_eq!(ast.unwrap().print(), "($x: Int = 1)");
530        let ast = VariableDefinitions::parse(&ctx, "($x : Int = 1, $y: Bool)");
531        assert_eq!(ast.unwrap().print(), "($x: Int = 1, $y: Bool)");
532    }
533
534    #[test]
535    fn strings() {
536        let ctx = ASTContext::new();
537        let ast = Value::parse(&ctx, "\"\\u0001\"");
538        assert_eq!(ast.unwrap().print(), "\"\\u0001\"");
539        let ast = Value::parse(&ctx, "\"\\u0019\"");
540        assert_eq!(ast.unwrap().print(), "\"\\u0019\"");
541        let ast = Value::parse(&ctx, "\"\0\"");
542        assert_eq!(ast.unwrap().print(), "\"\\u0000\"");
543    }
544
545    #[test]
546    fn block_strings() {
547        let ctx = ASTContext::new();
548        let ast = Value::parse(
549            &ctx,
550            r#"
551            """
552            this
553              is
554            doc
555            """
556        "#,
557        );
558        assert_eq!(ast.unwrap().print(), "\"\"\"\nthis\n  is\ndoc\n\"\"\"");
559
560        let ast = Value::parse(
561            &ctx,
562            r#"
563            """this
564              is
565            doc"""
566        "#,
567        );
568        assert_eq!(ast.unwrap().print(), "\"\"\"\nthis\n  is\ndoc\n\"\"\"");
569
570        let ast = Value::parse(
571            &ctx,
572            r#"
573            """
574              this
575              is
576              doc
577            """
578        "#,
579        );
580        assert_eq!(ast.unwrap().print(), "\"\"\"\n  this\n  is\n  doc\n\"\"\"");
581    }
582
583    #[test]
584    fn fragment_definitions() {
585        let ctx = ASTContext::new();
586        let ast = FragmentDefinition::parse(
587            &ctx,
588            r#"
589            fragment Test on Type {
590              field
591            }
592        "#,
593        );
594        assert_eq!(ast.unwrap().print(), "fragment Test on Type {\n  field\n}");
595
596        let ast = FragmentDefinition::parse(
597            &ctx,
598            r#"
599            fragment Test on Type @test {
600              field
601            }
602        "#,
603        );
604        assert_eq!(
605            ast.unwrap().print(),
606            "fragment Test on Type @test {\n  field\n}"
607        );
608    }
609
610    #[test]
611    fn operation_definition() {
612        let ctx = ASTContext::new();
613        let ast = OperationDefinition::parse(
614            &ctx,
615            r#"
616            query {
617              field
618            }
619        "#,
620        );
621        assert_eq!(ast.unwrap().print(), "{\n  field\n}");
622
623        let ast = OperationDefinition::parse(
624            &ctx,
625            r#"
626            query Name {
627              field
628            }
629        "#,
630        );
631        assert_eq!(ast.unwrap().print(), "query Name {\n  field\n}");
632
633        let ast = OperationDefinition::parse(
634            &ctx,
635            r#"
636            query Name ($var: String) {
637              field
638            }
639        "#,
640        );
641        assert_eq!(
642            ast.unwrap().print(),
643            "query Name($var: String) {\n  field\n}"
644        );
645
646        let ast = OperationDefinition::parse(
647            &ctx,
648            r#"
649            query ($var: String) {
650              field
651            }
652        "#,
653        );
654        assert_eq!(ast.unwrap().print(), "query ($var: String) {\n  field\n}");
655
656        let ast = OperationDefinition::parse(
657            &ctx,
658            r#"
659            query Name ($var: String) @defer{
660              field
661            }
662        "#,
663        );
664        assert_eq!(
665            ast.unwrap().print(),
666            "query Name($var: String) @defer {\n  field\n}"
667        );
668
669        let ast = OperationDefinition::parse(
670            &ctx,
671            r#"
672            mutation {
673              doThing
674            }
675        "#,
676        );
677        assert_eq!(ast.unwrap().print(), "mutation {\n  doThing\n}");
678    }
679
680    #[test]
681    fn kitchen_sink() {
682        let ctx = ASTContext::new();
683        let query = include_str!("../../fixture/kitchen_sink.graphql");
684        let ast = Document::parse(&ctx, query);
685        let expected = indoc::indoc! {r#"
686            query queryName($foo: ComplexType, $site: Site = MOBILE) @onQuery {
687              whoever123is: node(id: [123, 456]) {
688                id
689                ... on User @onInlineFragment {
690                  field2 {
691                    id
692                    alias: field1(first: 10, after: $foo) @include(if: $foo) {
693                      id
694                      ...frag @onFragmentSpread
695                    }
696                  }
697                }
698                ... @skip(unless: $foo) {
699                  id
700                }
701                ... {
702                  id
703                }
704              }
705            }
706
707            mutation likeStory @onMutation {
708              like(story: 123) @onField {
709                story {
710                  id @onField
711                }
712              }
713            }
714
715            subscription StoryLikeSubscription($input: StoryLikeSubscribeInput) @onSubscription {
716              storyLikeSubscribe(input: $input) {
717                story {
718                  likers {
719                    count
720                  }
721                  likeSentence {
722                    text
723                  }
724                }
725              }
726            }
727
728            fragment frag on Friend @onFragmentDefinition {
729              foo(size: $site, bar: 12, obj: {key: "value", block: """
730              block string uses \"""
731              """})
732            }
733
734            query teeny {
735              unnamed(truthy: true, falsey: false, nullish: null)
736              query
737            }
738
739            query tiny {
740              __typename
741            }"#};
742        assert_eq!(ast.unwrap().print(), expected);
743    }
744}