1use crate::parser::{
2 query::{
3 Definition, Directive, Document, FragmentDefinition, OperationDefinition, Selection,
4 SelectionSet, Text, Type, TypeCondition, Value, VariableDefinition,
5 },
6 tokenizer::{Kind, Token, TokenStream},
7};
8use combine::StreamOnce;
9use thiserror::Error;
10
11#[derive(Error, Debug)]
13#[error("query minify error: {}", _0)]
14pub struct MinifyError(String);
15
16pub fn minify_query(source: &str) -> Result<String, MinifyError> {
17 let mut bits: Vec<&str> = Vec::new();
18 let mut stream = TokenStream::new(source);
19 let mut prev_was_punctuator = false;
20
21 loop {
22 match stream.uncons() {
23 Ok(x) => {
24 let token: Token = x;
25 let is_non_punctuator = token.kind != Kind::Punctuator;
26
27 if prev_was_punctuator && is_non_punctuator {
28 bits.push(" ");
29 }
30
31 bits.push(token.value);
32 prev_was_punctuator = is_non_punctuator;
33 }
34 Err(ref e) if e == &combine::easy::Error::end_of_input() => break,
35 Err(e) => return Err(MinifyError(e.to_string())),
36 }
37 }
38
39 Ok(bits.join(""))
40}
41
42pub fn minify_query_document<'a, T: Text<'a>>(doc: &Document<'a, T>) -> String {
44 let mut minifier = Minifier::new();
45 minifier.write_document(doc);
46 minifier.buffer
47}
48
49struct Minifier {
60 buffer: String,
62 block_indent: u16,
64 last_was_non_punctuator: bool,
68 int_buffer: itoa::Buffer,
71 floats_buffer: ryu::Buffer,
73}
74
75impl Minifier {
76 fn new() -> Self {
77 Self {
78 buffer: String::with_capacity(1024),
81 last_was_non_punctuator: false,
82 block_indent: 2,
83 int_buffer: itoa::Buffer::new(),
84 floats_buffer: ryu::Buffer::new(),
85 }
86 }
87
88 #[inline(always)]
93 fn write_non_punctuator(&mut self, s: &str) {
94 if self.last_was_non_punctuator {
95 self.buffer.push(' ');
96 }
97 self.buffer.push_str(s);
98 self.last_was_non_punctuator = true;
99 }
100
101 #[inline(always)]
108 fn write_punctuator(&mut self, s: &str) {
109 self.buffer.push_str(s);
110 self.last_was_non_punctuator = false;
111 }
112
113 #[inline(always)]
118 fn write_punctuator_char(&mut self, c: char) {
119 self.buffer.push(c);
120 self.last_was_non_punctuator = false;
121 }
122
123 #[inline]
125 fn write_document<'a, T: Text<'a>>(&mut self, doc: &Document<'a, T>) {
126 for def in &doc.definitions {
127 self.write_definition(def);
128 }
129 }
130
131 #[inline]
132 fn write_definition<'a, T: Text<'a>>(&mut self, def: &Definition<'a, T>) {
133 match def {
134 Definition::Operation(op) => self.write_operation(op),
135 Definition::Fragment(frag) => self.write_fragment(frag),
136 }
137 }
138
139 #[inline]
140 fn write_operation<'a, T: Text<'a>>(&mut self, op: &OperationDefinition<'a, T>) {
141 match op {
142 OperationDefinition::SelectionSet(set) => self.write_selection_set(set),
143 OperationDefinition::Query(q) => {
144 self.write_non_punctuator("query");
145 if let Some(ref name) = q.name {
146 self.write_non_punctuator(name.as_ref());
147 }
148 self.write_variable_definitions(&q.variable_definitions);
149 self.write_directives(&q.directives);
150 self.write_selection_set(&q.selection_set);
151 }
152 OperationDefinition::Mutation(m) => {
153 self.write_non_punctuator("mutation");
154 if let Some(ref name) = m.name {
155 self.write_non_punctuator(name.as_ref());
156 }
157 self.write_variable_definitions(&m.variable_definitions);
158 self.write_directives(&m.directives);
159 self.write_selection_set(&m.selection_set);
160 }
161 OperationDefinition::Subscription(s) => {
162 self.write_non_punctuator("subscription");
163 if let Some(ref name) = s.name {
164 self.write_non_punctuator(name.as_ref());
165 }
166 self.write_variable_definitions(&s.variable_definitions);
167 self.write_directives(&s.directives);
168 self.write_selection_set(&s.selection_set);
169 }
170 }
171 }
172
173 #[inline]
174 fn write_fragment<'a, T: Text<'a>>(&mut self, frag: &FragmentDefinition<'a, T>) {
175 self.write_non_punctuator("fragment");
176 self.write_non_punctuator(frag.name.as_ref());
177 self.write_type_condition(&frag.type_condition);
178 self.write_directives(&frag.directives);
179 self.write_selection_set(&frag.selection_set);
180 }
181
182 #[inline]
185 fn write_selection_set<'a, T: Text<'a>>(&mut self, set: &SelectionSet<'a, T>) {
186 self.write_punctuator_char('{');
187 for item in &set.items {
188 self.write_selection(item);
189 }
190 self.write_punctuator_char('}');
191 }
192
193 #[inline]
194 fn write_selection<'a, T: Text<'a>>(&mut self, selection: &Selection<'a, T>) {
195 match selection {
196 Selection::Field(f) => {
197 if let Some(ref alias) = f.alias {
198 self.write_non_punctuator(alias.as_ref());
199 self.write_punctuator_char(':');
200 }
201 self.write_non_punctuator(f.name.as_ref());
202 self.write_arguments(&f.arguments);
203 self.write_directives(&f.directives);
204 if !f.selection_set.items.is_empty() {
205 self.write_selection_set(&f.selection_set);
206 }
207 }
208 Selection::FragmentSpread(fs) => {
209 self.write_punctuator("...");
210 self.write_non_punctuator(fs.fragment_name.as_ref());
211 self.write_directives(&fs.directives);
212 }
213 Selection::InlineFragment(ifrag) => {
214 self.write_punctuator("...");
215 if let Some(ref tc) = ifrag.type_condition {
216 self.write_type_condition(tc);
217 }
218 self.write_directives(&ifrag.directives);
219 self.write_selection_set(&ifrag.selection_set);
220 }
221 }
222 }
223
224 #[inline]
225 fn write_type_condition<'a, T: Text<'a>>(&mut self, tc: &TypeCondition<'a, T>) {
226 match tc {
227 TypeCondition::On(name) => {
228 self.write_non_punctuator("on");
229 self.write_non_punctuator(name.as_ref());
230 }
231 }
232 }
233
234 #[inline]
235 fn write_variable_definitions<'a, T: Text<'a>>(&mut self, vars: &[VariableDefinition<'a, T>]) {
236 if vars.is_empty() {
237 return;
238 }
239 self.write_punctuator_char('(');
240 for var in vars {
241 self.write_punctuator_char('$');
242 self.write_non_punctuator(var.name.as_ref());
243 self.write_punctuator_char(':');
244 self.write_type(&var.var_type);
245 if let Some(ref def) = var.default_value {
246 self.write_punctuator_char('=');
247 self.write_value(def);
248 }
249 }
250 self.write_punctuator_char(')');
251 }
252
253 #[inline]
254 fn write_type<'a, T: Text<'a>>(&mut self, ty: &Type<'a, T>) {
255 match ty {
256 Type::NamedType(name) => self.write_non_punctuator(name.as_ref()),
257 Type::ListType(inner) => {
258 self.write_punctuator_char('[');
259 self.write_type(inner);
260 self.write_punctuator_char(']');
261 }
262 Type::NonNullType(inner) => {
263 self.write_type(inner);
264 self.write_punctuator_char('!');
265 }
266 }
267 }
268
269 #[inline]
270 fn write_directives<'a, T: Text<'a>>(&mut self, dirs: &[Directive<'a, T>]) {
271 for dir in dirs {
272 self.write_punctuator_char('@');
273 self.write_non_punctuator(dir.name.as_ref());
274 self.write_arguments(&dir.arguments);
275 }
276 }
277
278 #[inline]
279 fn write_arguments<'a, T: Text<'a>>(&mut self, args: &[(T::Value, Value<'a, T>)]) {
280 if args.is_empty() {
281 return;
282 }
283 self.write_punctuator_char('(');
284 for (name, val) in args {
285 self.write_non_punctuator(name.as_ref());
286 self.write_punctuator_char(':');
287 self.write_value(val);
288 }
289 self.write_punctuator_char(')');
290 }
291
292 #[inline(always)]
314 pub fn write_quoted(&mut self, s: &str) {
315 if self.last_was_non_punctuator {
316 self.buffer.push(' ');
317 }
318
319 let bytes = s.as_bytes();
320 let mut has_newline = false;
321 let mut needs_escaping = false;
322
323 for &byte in bytes {
324 if byte == b'\n' {
325 has_newline = true;
326 } else if byte == b'"' || byte == b'\\' || byte < 0x20 || byte == 0x7F {
329 needs_escaping = true;
330 }
331
332 if has_newline && needs_escaping {
333 break;
334 }
335 }
336
337 if !needs_escaping && !has_newline {
338 self.buffer.reserve(s.len() + 2);
339 self.buffer.push('"');
340 self.buffer.push_str(s);
341 self.buffer.push('"');
342 self.last_was_non_punctuator = true;
343 return;
344 }
345
346 if !has_newline {
347 use std::fmt::Write;
348 self.buffer.reserve(s.len() + 16);
352 self.buffer.push('"');
353 for c in s.chars() {
354 match c {
355 '\r' => self.buffer.push_str(r"\r"),
356 '\n' => self.buffer.push_str(r"\n"),
357 '\t' => self.buffer.push_str(r"\t"),
358 '"' => self.buffer.push_str("\\\""),
359 '\\' => self.buffer.push_str(r"\\"),
360 '\u{0020}'..='\u{FFFF}' => self.buffer.push(c),
362 _ => write!(&mut self.buffer, "\\u{:04}", c as u32).unwrap(),
364 }
365 }
366 self.buffer.push('"');
367 } else {
368 self.buffer.reserve(s.len() + 32);
371 self.buffer.push_str(r#"""""#);
372 self.buffer.push('\n');
373
374 self.block_indent += 2;
375
376 for line in s.lines() {
377 if !line.trim().is_empty() {
378 self.indent();
379 let mut last_pos = 0;
380 for (pos, _) in line.match_indices(r#"""""#) {
381 self.buffer.push_str(&line[last_pos..pos]);
382 self.buffer.push_str(r#"\"""#);
383 last_pos = pos + 3;
384 }
385 self.buffer.push_str(&line[last_pos..]);
386 }
387 self.buffer.push('\n');
388 }
389
390 self.block_indent -= 2;
391 self.indent();
392
393 self.buffer.push_str(r#"""""#);
394 }
395 self.last_was_non_punctuator = true;
396 }
397
398 #[inline]
400 pub fn indent(&mut self) {
401 for _ in 0..self.block_indent {
402 self.buffer.push(' ');
403 }
404 }
405
406 #[inline]
407 fn write_value<'a, T: Text<'a>>(&mut self, val: &Value<'a, T>) {
408 match val {
409 Value::Variable(name) => {
410 self.write_punctuator_char('$');
411 self.write_non_punctuator(name.as_ref());
412 }
413 Value::Int(n) => {
414 let s = self.int_buffer.format(n.0);
418 if self.last_was_non_punctuator {
419 self.buffer.push(' ');
420 }
421 self.buffer.push_str(s);
422 self.last_was_non_punctuator = true;
423 }
424 Value::Float(f) => {
425 let s = self.floats_buffer.format(*f);
429 if self.last_was_non_punctuator {
430 self.buffer.push(' ');
431 }
432 self.buffer.push_str(s);
433 self.last_was_non_punctuator = true;
434 }
435 Value::String(s) => self.write_quoted(s),
436 Value::Boolean(b) => self.write_non_punctuator(if *b { "true" } else { "false" }),
437 Value::Null => self.write_non_punctuator("null"),
438 Value::Enum(name) => self.write_non_punctuator(name.as_ref()),
439 Value::List(items) => {
440 self.write_punctuator_char('[');
441 for item in items {
442 self.write_value(item);
443 }
444 self.write_punctuator_char(']');
445 }
446 Value::Object(fields) => {
447 self.write_punctuator_char('{');
448 for (name, val) in fields {
449 self.write_non_punctuator(name.as_ref());
450 self.write_punctuator_char(':');
451 self.write_value(val);
452 }
453 self.write_punctuator_char('}');
454 }
455 }
456 }
457}
458
459#[cfg(test)]
460mod tests {
461 #[test]
462 fn strip_ignored_characters() {
463 let source = "
464 query SomeQuery($foo: String!, $bar: String) {
465 someField(foo: $foo, bar: $bar) {
466 a
467 b {
468 ... on B {
469 c
470 d
471 }
472 }
473 }
474 }
475 ";
476
477 let minified =
478 super::minify_query(source.to_string().as_str()).expect("minification failed");
479
480 assert_eq!(
481 &minified,
482 "query SomeQuery($foo:String!$bar:String){someField(foo:$foo bar:$bar){a b{...on B{c d}}}}"
483 );
484 }
485
486 #[test]
487 fn unexpected_token() {
488 let source = "
489 query foo {
490 bar;
491 }
492 ";
493
494 let minified = super::minify_query(source.to_string().as_str());
495
496 assert!(minified.is_err());
497
498 assert_eq!(
499 minified.unwrap_err().to_string(),
500 "query minify error: Unexpected unexpected character ';'"
501 );
502 }
503
504 #[test]
505 fn minify_document_test() {
506 let source = "
507 query SomeQuery($foo: String!, $bar: String) {
508 someField(foo: $foo, bar: $bar) {
509 a
510 b {
511 ... on B {
512 c
513 d
514 }
515 }
516 }
517 }
518 ";
519
520 let doc =
521 crate::parser::query::grammar::parse_query::<String>(source).expect("parse failed");
522 let minified_doc = super::minify_query_document(&doc);
523 let minified_query = super::minify_query(source).expect("minification failed");
524
525 assert_eq!(minified_doc, minified_query);
526 }
527
528 #[test]
529 fn minify_document_complex() {
530 let source = r#"
531 mutation DoSomething($input: UpdateInput! = { a: 1, b: "foo" }) @opt(level: 1) {
532 updateItem(id: "123", data: $input) {
533 id
534 ... on Item {
535 name
536 tags
537 }
538 ...FragmentName
539 }
540 }
541 fragment FragmentName on Item {
542 owner {
543 id
544 email
545 }
546 }
547 "#;
548
549 let doc =
550 crate::parser::query::grammar::parse_query::<String>(source).expect("parse failed");
551 let minified_doc = super::minify_query_document(&doc);
552 let minified_query = super::minify_query(source).expect("minification failed");
553
554 assert_eq!(minified_doc, minified_query);
555 }
556
557 #[test]
558 fn test_minify_directive_args() {
559 let source =
560 std::fs::read_to_string("src/parser/tests/queries/directive_args.graphql").unwrap();
561 let minified_query = super::minify_query(&source).unwrap();
562 insta::assert_snapshot!(minified_query, @r#"query{node@dir(a:1 b:"2" c:true d:false e:null)}"#);
567
568 let minified_document = super::minify_query_document(
569 &crate::parser::query::grammar::parse_query::<String>(&source).unwrap(),
570 );
571 assert_eq!(
572 minified_query, minified_document,
573 "minify_query and minify_document outputs differ"
574 );
575 }
576
577 #[test]
578 fn test_minify_directive_args_multiline() {
579 let source =
580 std::fs::read_to_string("src/parser/tests/queries/directive_args_multiline.graphql")
581 .unwrap();
582 let minified_query = super::minify_query(&source).unwrap();
583 insta::assert_snapshot!(minified_query, @r#"query{node@dir(a:1 b:"2" c:true d:false e:null)}"#);
584
585 let minified_document = super::minify_query_document(
586 &crate::parser::query::grammar::parse_query::<String>(&source).unwrap(),
587 );
588 assert_eq!(
589 minified_query, minified_document,
590 "minify_query and minify_document outputs differ"
591 );
592 }
593
594 #[test]
595 fn test_minify_fragment() {
596 let source = std::fs::read_to_string("src/parser/tests/queries/fragment.graphql").unwrap();
597 let minified_query = super::minify_query(&source).unwrap();
598 insta::assert_snapshot!(minified_query, @"fragment frag on Friend{node}");
599
600 let minified_document = super::minify_query_document(
601 &crate::parser::query::grammar::parse_query::<String>(&source).unwrap(),
602 );
603 assert_eq!(
604 minified_query, minified_document,
605 "minify_query and minify_document outputs differ"
606 );
607 }
608
609 #[test]
610 fn test_minify_fragment_spread() {
611 let source =
612 std::fs::read_to_string("src/parser/tests/queries/fragment_spread.graphql").unwrap();
613 let minified_query = super::minify_query(&source).unwrap();
614 insta::assert_snapshot!(minified_query, @"query{node{id...something}}");
615
616 let minified_document = super::minify_query_document(
617 &crate::parser::query::grammar::parse_query::<String>(&source).unwrap(),
618 );
619 assert_eq!(
620 minified_query, minified_document,
621 "minify_query and minify_document outputs differ"
622 );
623 }
624
625 #[test]
626 fn test_minify_inline_fragment() {
627 let source =
628 std::fs::read_to_string("src/parser/tests/queries/inline_fragment.graphql").unwrap();
629 let minified_query = super::minify_query(&source).unwrap();
630 insta::assert_snapshot!(minified_query, @"query{node{id...on User{name}}}");
631
632 let minified_document = super::minify_query_document(
633 &crate::parser::query::grammar::parse_query::<String>(&source).unwrap(),
634 );
635 assert_eq!(
636 minified_query, minified_document,
637 "minify_query and minify_document outputs differ"
638 );
639 }
640
641 #[test]
642 fn test_minify_inline_fragment_dir() {
643 let source =
644 std::fs::read_to_string("src/parser/tests/queries/inline_fragment_dir.graphql")
645 .unwrap();
646 let minified_query = super::minify_query(&source).unwrap();
647 insta::assert_snapshot!(minified_query, @"query{node{id...on User@defer{name}}}");
648
649 let minified_document = super::minify_query_document(
650 &crate::parser::query::grammar::parse_query::<String>(&source).unwrap(),
651 );
652 assert_eq!(
653 minified_query, minified_document,
654 "minify_query and minify_document outputs differ"
655 );
656 }
657
658 #[test]
659 fn test_minify_kitchen_sink() {
660 let source =
661 std::fs::read_to_string("src/parser/tests/queries/kitchen-sink.graphql").unwrap();
662 let minified_query = super::minify_query(&source).unwrap();
663 insta::assert_snapshot!(minified_query, @r#"
664 query queryName($foo:ComplexType$site:Site=MOBILE){whoever123is:node(id:[123 456]){id...on User@defer{field2{id alias:field1(first:10 after:$foo)@include(if:$foo){id...frag}}}...@skip(unless:$foo){id}...{id}}}mutation likeStory{like(story:123)@defer{story{id}}}subscription StoryLikeSubscription($input:StoryLikeSubscribeInput){storyLikeSubscribe(input:$input){story{likers{count}likeSentence{text}}}}fragment frag on Friend{foo(size:$size bar:$b obj:{key:"value" block:"""
665
666 block string uses \"""
667
668 """})}{unnamed(truthy:true falsey:false nullish:null)query}
669 "#);
670
671 let minified_document = super::minify_query_document(
672 &crate::parser::query::grammar::parse_query::<String>(&source).unwrap(),
673 );
674
675 insta::assert_snapshot!(minified_document, @r#"query queryName($foo:ComplexType$site:Site=MOBILE){whoever123is:node(id:[123 456]){id...on User@defer{field2{id alias:field1(first:10 after:$foo)@include(if:$foo){id...frag}}}...@skip(unless:$foo){id}...{id}}}mutation likeStory{like(story:123)@defer{story{id}}}subscription StoryLikeSubscription($input:StoryLikeSubscribeInput){storyLikeSubscribe(input:$input){story{likers{count}likeSentence{text}}}}fragment frag on Friend{foo(size:$size bar:$b obj:{block:"block string uses \"\"\"" key:"value"})}{unnamed(truthy:true falsey:false nullish:null)query}"#);
676
677 }
681
682 #[test]
683 fn test_minify_kitchen_sink_canonical() {
684 let source =
685 std::fs::read_to_string("src/parser/tests/queries/kitchen-sink_canonical.graphql")
686 .unwrap();
687 let minified_query = super::minify_query(&source).unwrap();
688 insta::assert_snapshot!(minified_query, @r#"query queryName($foo:ComplexType$site:Site=MOBILE){whoever123is:node(id:[123 456]){id...on User@defer{field2{id alias:field1(first:10 after:$foo)@include(if:$foo){id...frag}}}...@skip(unless:$foo){id}...{id}}}mutation likeStory{like(story:123)@defer{story{id}}}subscription StoryLikeSubscription($input:StoryLikeSubscribeInput){storyLikeSubscribe(input:$input){story{likers{count}likeSentence{text}}}}fragment frag on Friend{foo(size:$size bar:$b obj:{block:"block string uses \"\"\"" key:"value"})}{unnamed(truthy:true falsey:false nullish:null)query}"#);
689
690 let minified_document = super::minify_query_document(
691 &crate::parser::query::grammar::parse_query::<String>(&source).unwrap(),
692 );
693 assert_eq!(
694 minified_query, minified_document,
695 "minify_query and minify_document outputs differ"
696 );
697 }
698
699 #[test]
700 fn test_minify_minimal() {
701 let source = std::fs::read_to_string("src/parser/tests/queries/minimal.graphql").unwrap();
702 let minified_query = super::minify_query(&source).unwrap();
703 insta::assert_snapshot!(minified_query, @"{a}");
704
705 let minified_document = super::minify_query_document(
706 &crate::parser::query::grammar::parse_query::<String>(&source).unwrap(),
707 );
708 assert_eq!(
709 minified_query, minified_document,
710 "minify_query and minify_document outputs differ"
711 );
712 }
713
714 #[test]
715 fn test_minify_minimal_mutation() {
716 let source =
717 std::fs::read_to_string("src/parser/tests/queries/minimal_mutation.graphql").unwrap();
718 let minified_query = super::minify_query(&source).unwrap();
719 insta::assert_snapshot!(minified_query, @"mutation{notify}");
720
721 let minified_document = super::minify_query_document(
722 &crate::parser::query::grammar::parse_query::<String>(&source).unwrap(),
723 );
724 assert_eq!(
725 minified_query, minified_document,
726 "minify_query and minify_document outputs differ"
727 );
728 }
729
730 #[test]
731 fn test_minify_minimal_query() {
732 let source =
733 std::fs::read_to_string("src/parser/tests/queries/minimal_query.graphql").unwrap();
734 let minified_query = super::minify_query(&source).unwrap();
735 insta::assert_snapshot!(minified_query, @"query{node}");
736
737 let minified_document = super::minify_query_document(
738 &crate::parser::query::grammar::parse_query::<String>(&source).unwrap(),
739 );
740 assert_eq!(
741 minified_query, minified_document,
742 "minify_query and minify_document outputs differ"
743 );
744 }
745
746 #[test]
747 fn test_minify_mutation_directive() {
748 let source =
749 std::fs::read_to_string("src/parser/tests/queries/mutation_directive.graphql").unwrap();
750 let minified_query = super::minify_query(&source).unwrap();
751 insta::assert_snapshot!(minified_query, @"mutation@directive{node}");
752
753 let minified_document = super::minify_query_document(
754 &crate::parser::query::grammar::parse_query::<String>(&source).unwrap(),
755 );
756 assert_eq!(
757 minified_query, minified_document,
758 "minify_query and minify_document outputs differ"
759 );
760 }
761
762 #[test]
763 fn test_minify_mutation_nameless_vars() {
764 let source =
765 std::fs::read_to_string("src/parser/tests/queries/mutation_nameless_vars.graphql")
766 .unwrap();
767 let minified_query = super::minify_query(&source).unwrap();
768 insta::assert_snapshot!(minified_query, @"mutation($first:Int$second:Int){field1(first:$first)field2(second:$second)}");
769
770 let minified_document = super::minify_query_document(
771 &crate::parser::query::grammar::parse_query::<String>(&source).unwrap(),
772 );
773 assert_eq!(
774 minified_query, minified_document,
775 "minify_query and minify_document outputs differ"
776 );
777 }
778
779 #[test]
780 fn test_minify_named_query() {
781 let source =
782 std::fs::read_to_string("src/parser/tests/queries/named_query.graphql").unwrap();
783 let minified_query = super::minify_query(&source).unwrap();
784 insta::assert_snapshot!(minified_query, @"query Foo{field}");
785
786 let minified_document = super::minify_query_document(
787 &crate::parser::query::grammar::parse_query::<String>(&source).unwrap(),
788 );
789 assert_eq!(
790 minified_query, minified_document,
791 "minify_query and minify_document outputs differ"
792 );
793 }
794
795 #[test]
796 fn test_minify_nested_selection() {
797 let source =
798 std::fs::read_to_string("src/parser/tests/queries/nested_selection.graphql").unwrap();
799 let minified_query = super::minify_query(&source).unwrap();
800 insta::assert_snapshot!(minified_query, @"query{node{id}}");
801
802 let minified_document = super::minify_query_document(
803 &crate::parser::query::grammar::parse_query::<String>(&source).unwrap(),
804 );
805 assert_eq!(
806 minified_query, minified_document,
807 "minify_query and minify_document outputs differ"
808 );
809 }
810
811 #[test]
812 fn test_minify_query_aliases() {
813 let source =
814 std::fs::read_to_string("src/parser/tests/queries/query_aliases.graphql").unwrap();
815 let minified_query = super::minify_query(&source).unwrap();
816 insta::assert_snapshot!(minified_query, @"query{an_alias:node}");
817
818 let minified_document = super::minify_query_document(
819 &crate::parser::query::grammar::parse_query::<String>(&source).unwrap(),
820 );
821 assert_eq!(
822 minified_query, minified_document,
823 "minify_query and minify_document outputs differ"
824 );
825 }
826
827 #[test]
828 fn test_minify_query_arguments() {
829 let source =
830 std::fs::read_to_string("src/parser/tests/queries/query_arguments.graphql").unwrap();
831 let minified_query = super::minify_query(&source).unwrap();
832 insta::assert_snapshot!(minified_query, @"query{node(id:1)}");
833
834 let minified_document = super::minify_query_document(
835 &crate::parser::query::grammar::parse_query::<String>(&source).unwrap(),
836 );
837 assert_eq!(
838 minified_query, minified_document,
839 "minify_query and minify_document outputs differ"
840 );
841 }
842
843 #[test]
844 fn test_minify_query_arguments_multiline() {
845 let source =
846 std::fs::read_to_string("src/parser/tests/queries/query_arguments_multiline.graphql")
847 .unwrap();
848 let minified_query = super::minify_query(&source).unwrap();
849 insta::assert_snapshot!(minified_query, @"query{node(id:1)node(id:1 one:3)}");
850
851 let minified_document = super::minify_query_document(
852 &crate::parser::query::grammar::parse_query::<String>(&source).unwrap(),
853 );
854 assert_eq!(
855 minified_query, minified_document,
856 "minify_query and minify_document outputs differ"
857 );
858 }
859
860 #[test]
861 fn test_minify_query_array_argument_multiline() {
862 let source = std::fs::read_to_string(
863 "src/parser/tests/queries/query_array_argument_multiline.graphql",
864 )
865 .unwrap();
866 let minified_query = super::minify_query(&source).unwrap();
867 insta::assert_snapshot!(minified_query, @"query{node(id:[5 6 7])}");
868
869 let minified_document = super::minify_query_document(
870 &crate::parser::query::grammar::parse_query::<String>(&source).unwrap(),
871 );
872 assert_eq!(
873 minified_query, minified_document,
874 "minify_query and minify_document outputs differ"
875 );
876 }
877
878 #[test]
879 fn test_minify_query_directive() {
880 let source =
881 std::fs::read_to_string("src/parser/tests/queries/query_directive.graphql").unwrap();
882 let minified_query = super::minify_query(&source).unwrap();
883 insta::assert_snapshot!(minified_query, @"query@directive{node}");
884
885 let minified_document = super::minify_query_document(
886 &crate::parser::query::grammar::parse_query::<String>(&source).unwrap(),
887 );
888 assert_eq!(
889 minified_query, minified_document,
890 "minify_query and minify_document outputs differ"
891 );
892 }
893
894 #[test]
895 fn test_minify_query_list_argument() {
896 let source =
897 std::fs::read_to_string("src/parser/tests/queries/query_list_argument.graphql")
898 .unwrap();
899 let minified_query = super::minify_query(&source).unwrap();
900 insta::assert_snapshot!(minified_query, @"query{node(id:1 list:[123 456])}");
901
902 let minified_document = super::minify_query_document(
903 &crate::parser::query::grammar::parse_query::<String>(&source).unwrap(),
904 );
905 assert_eq!(
906 minified_query, minified_document,
907 "minify_query and minify_document outputs differ"
908 );
909 }
910
911 #[test]
912 fn test_minify_query_nameless_vars() {
913 let source =
914 std::fs::read_to_string("src/parser/tests/queries/query_nameless_vars.graphql")
915 .unwrap();
916 let minified_query = super::minify_query(&source).unwrap();
917 insta::assert_snapshot!(minified_query, @"query($first:Int$second:Int){field1(first:$first)field2(second:$second)}");
918
919 let minified_document = super::minify_query_document(
920 &crate::parser::query::grammar::parse_query::<String>(&source).unwrap(),
921 );
922 assert_eq!(
923 minified_query, minified_document,
924 "minify_query and minify_document outputs differ"
925 );
926 }
927
928 #[test]
929 fn test_minify_query_nameless_vars_multiple_fields() {
930 let source = std::fs::read_to_string(
931 "src/parser/tests/queries/query_nameless_vars_multiple_fields.graphql",
932 )
933 .unwrap();
934 let minified_query = super::minify_query(&source).unwrap();
935 insta::assert_snapshot!(minified_query, @"query($houseId:String!$streetNumber:Int!){house(id:$houseId){id name lat lng}street(number:$streetNumber){id}houseStreet(id:$houseId number:$streetNumber){id}}");
936
937 let minified_document = super::minify_query_document(
938 &crate::parser::query::grammar::parse_query::<String>(&source).unwrap(),
939 );
940 assert_eq!(
941 minified_query, minified_document,
942 "minify_query and minify_document outputs differ"
943 );
944 }
945
946 #[test]
947 fn test_minify_query_nameless_vars_multiple_fields_canonical() {
948 let source = std::fs::read_to_string(
949 "src/parser/tests/queries/query_nameless_vars_multiple_fields_canonical.graphql",
950 )
951 .unwrap();
952 let minified_query = super::minify_query(&source).unwrap();
953 insta::assert_snapshot!(minified_query, @"query($houseId:String!$streetNumber:Int!){house(id:$houseId){id name lat lng}street(number:$streetNumber){id}houseStreet(id:$houseId number:$streetNumber){id}}");
954
955 let minified_document = super::minify_query_document(
956 &crate::parser::query::grammar::parse_query::<String>(&source).unwrap(),
957 );
958
959 assert_eq!(
960 minified_query, minified_document,
961 "minify_query and minify_document outputs differ"
962 );
963 }
964
965 #[test]
966 fn test_minify_query_object_argument() {
967 let source =
968 std::fs::read_to_string("src/parser/tests/queries/query_object_argument.graphql")
969 .unwrap();
970 let minified_query = super::minify_query(&source).unwrap();
971 insta::assert_snapshot!(minified_query, @"query{node(id:1 obj:{key1:123 key2:456})}");
972
973 let minified_document = super::minify_query_document(
974 &crate::parser::query::grammar::parse_query::<String>(&source).unwrap(),
975 );
976 assert_eq!(
977 minified_query, minified_document,
978 "minify_query and minify_document outputs differ"
979 );
980 }
981
982 #[test]
983 fn test_minify_query_object_argument_multiline() {
984 let source = std::fs::read_to_string(
985 "src/parser/tests/queries/query_object_argument_multiline.graphql",
986 )
987 .unwrap();
988 let minified_query = super::minify_query(&source).unwrap();
989 insta::assert_snapshot!(minified_query, @"query{node(id:1 obj:{key1:123 key2:456})}");
990
991 let minified_document = super::minify_query_document(
992 &crate::parser::query::grammar::parse_query::<String>(&source).unwrap(),
993 );
994 assert_eq!(
995 minified_query, minified_document,
996 "minify_query and minify_document outputs differ"
997 );
998 }
999
1000 #[test]
1001 fn test_minify_query_var_default_float() {
1002 let source =
1003 std::fs::read_to_string("src/parser/tests/queries/query_var_default_float.graphql")
1004 .unwrap();
1005 let minified_query = super::minify_query(&source).unwrap();
1006 insta::assert_snapshot!(minified_query, @"query Foo($site:Float=0.5){field}");
1007
1008 let minified_document = super::minify_query_document(
1009 &crate::parser::query::grammar::parse_query::<String>(&source).unwrap(),
1010 );
1011 assert_eq!(
1012 minified_query, minified_document,
1013 "minify_query and minify_document outputs differ"
1014 );
1015 }
1016
1017 #[test]
1018 fn test_minify_query_var_default_list() {
1019 let source =
1020 std::fs::read_to_string("src/parser/tests/queries/query_var_default_list.graphql")
1021 .unwrap();
1022 let minified_query = super::minify_query(&source).unwrap();
1023 insta::assert_snapshot!(minified_query, @"query Foo($site:[Int]=[123 456]){field}");
1024
1025 let minified_document = super::minify_query_document(
1026 &crate::parser::query::grammar::parse_query::<String>(&source).unwrap(),
1027 );
1028 assert_eq!(
1029 minified_query, minified_document,
1030 "minify_query and minify_document outputs differ"
1031 );
1032 }
1033
1034 #[test]
1035 fn test_minify_query_var_default_object() {
1036 let source =
1037 std::fs::read_to_string("src/parser/tests/queries/query_var_default_object.graphql")
1038 .unwrap();
1039 let minified_query = super::minify_query(&source).unwrap();
1040 insta::assert_snapshot!(minified_query, @"query Foo($site:Site={url:null}){field}");
1041
1042 let minified_document = super::minify_query_document(
1043 &crate::parser::query::grammar::parse_query::<String>(&source).unwrap(),
1044 );
1045 assert_eq!(
1046 minified_query, minified_document,
1047 "minify_query and minify_document outputs differ"
1048 );
1049 }
1050
1051 #[test]
1052 fn test_minify_query_var_default_string() {
1053 let source =
1054 std::fs::read_to_string("src/parser/tests/queries/query_var_default_string.graphql")
1055 .unwrap();
1056 let minified_query = super::minify_query(&source).unwrap();
1057 insta::assert_snapshot!(minified_query, @r#"query Foo($site:String="string"){field}"#);
1058
1059 let minified_document = super::minify_query_document(
1060 &crate::parser::query::grammar::parse_query::<String>(&source).unwrap(),
1061 );
1062 assert_eq!(
1063 minified_query, minified_document,
1064 "minify_query and minify_document outputs differ"
1065 );
1066 }
1067
1068 #[test]
1069 fn test_minify_query_var_defaults() {
1070 let source =
1071 std::fs::read_to_string("src/parser/tests/queries/query_var_defaults.graphql").unwrap();
1072 let minified_query = super::minify_query(&source).unwrap();
1073 insta::assert_snapshot!(minified_query, @"query Foo($site:Site=MOBILE){field}");
1074 }
1075
1076 #[test]
1077 fn test_minify_query_vars() {
1078 let source =
1079 std::fs::read_to_string("src/parser/tests/queries/query_vars.graphql").unwrap();
1080 let minified_query = super::minify_query(&source).unwrap();
1081 insta::assert_snapshot!(minified_query, @"query Foo($arg:SomeType){field}");
1082
1083 let minified_document = super::minify_query_document(
1084 &crate::parser::query::grammar::parse_query::<String>(&source).unwrap(),
1085 );
1086 assert_eq!(
1087 minified_query, minified_document,
1088 "minify_query and minify_document outputs differ"
1089 );
1090 }
1091
1092 #[test]
1093 fn test_minify_string_literal() {
1094 let source =
1095 std::fs::read_to_string("src/parser/tests/queries/string_literal.graphql").unwrap();
1096 let minified_query = super::minify_query(&source).unwrap();
1097 insta::assert_snapshot!(minified_query, @r#"query{node(id:"hello")}"#);
1098
1099 let minified_document = super::minify_query_document(
1100 &crate::parser::query::grammar::parse_query::<String>(&source).unwrap(),
1101 );
1102 assert_eq!(
1103 minified_query, minified_document,
1104 "minify_query and minify_document outputs differ"
1105 );
1106 }
1107
1108 #[test]
1109 fn test_minify_subscription_directive() {
1110 let source =
1111 std::fs::read_to_string("src/parser/tests/queries/subscription_directive.graphql")
1112 .unwrap();
1113 let minified_query = super::minify_query(&source).unwrap();
1114 insta::assert_snapshot!(minified_query, @"subscription@directive{node}");
1115
1116 let minified_document = super::minify_query_document(
1117 &crate::parser::query::grammar::parse_query::<String>(&source).unwrap(),
1118 );
1119 assert_eq!(
1120 minified_query, minified_document,
1121 "minify_query and minify_document outputs differ"
1122 );
1123 }
1124
1125 #[test]
1126 fn test_minify_triple_quoted_literal() {
1127 let source =
1128 std::fs::read_to_string("src/parser/tests/queries/triple_quoted_literal.graphql")
1129 .unwrap();
1130 let minified_query = super::minify_query(&source).unwrap();
1131 insta::assert_snapshot!(minified_query, @r#"
1132 query{node(id:"""
1133 Hello,
1134 world!
1135 """)}
1136 "#);
1137
1138 let minified_document = super::minify_query_document(
1139 &crate::parser::query::grammar::parse_query::<String>(&source).unwrap(),
1140 );
1141 assert_eq!(
1142 minified_query, minified_document,
1143 "minify_query and minify_document outputs differ"
1144 );
1145 }
1146}