graphql_tools/parser/query/
minify.rs

1use crate::parser::tokenizer::{Kind, Token, TokenStream};
2use combine::StreamOnce;
3use thiserror::Error;
4
5/// Error minifying query
6#[derive(Error, Debug)]
7#[error("query minify error: {}", _0)]
8pub struct MinifyError(String);
9
10pub fn minify_query(source: &str) -> Result<String, MinifyError> {
11    let mut bits: Vec<&str> = Vec::new();
12    let mut stream = TokenStream::new(source);
13    let mut prev_was_punctuator = false;
14
15    loop {
16        match stream.uncons() {
17            Ok(x) => {
18                let token: Token = x;
19                let is_non_punctuator = token.kind != Kind::Punctuator;
20
21                if prev_was_punctuator && is_non_punctuator {
22                    bits.push(" ");
23                }
24
25                bits.push(token.value);
26                prev_was_punctuator = is_non_punctuator;
27            }
28            Err(ref e) if e == &combine::easy::Error::end_of_input() => break,
29            Err(e) => return Err(MinifyError(e.to_string())),
30        }
31    }
32
33    Ok(bits.join(""))
34}
35
36#[cfg(test)]
37mod tests {
38    #[test]
39    fn strip_ignored_characters() {
40        let source = "
41        query SomeQuery($foo: String!, $bar: String) {
42            someField(foo: $foo, bar: $bar) {
43                a
44                b {
45                    ... on B {
46                        c
47                        d
48                    }
49                }
50            }
51        }
52        ";
53
54        let minified =
55            super::minify_query(source.to_string().as_str()).expect("minification failed");
56
57        assert_eq!(
58            &minified,
59            "query SomeQuery($foo:String!$bar:String){someField(foo:$foo bar:$bar){a b{...on B{c d}}}}"
60        );
61    }
62
63    #[test]
64    fn unexpected_token() {
65        let source = "
66        query foo {
67            bar;
68        }
69        ";
70
71        let minified = super::minify_query(source.to_string().as_str());
72
73        assert!(minified.is_err());
74
75        assert_eq!(
76            minified.unwrap_err().to_string(),
77            "query minify error: Unexpected unexpected character ';'"
78        );
79    }
80
81    #[test]
82    fn test_minify_directive_args() {
83        let source =
84            std::fs::read_to_string("src/parser/tests/queries/directive_args.graphql").unwrap();
85        let minified_query = super::minify_query(&source).unwrap();
86        insta::assert_snapshot!(minified_query, @r#"query{node@dir(a:1 b:"2" c:true d:false e:null)}"#);
87    }
88
89    #[test]
90    fn test_minify_directive_args_multiline() {
91        let source =
92            std::fs::read_to_string("src/parser/tests/queries/directive_args_multiline.graphql")
93                .unwrap();
94        let minified_query = super::minify_query(&source).unwrap();
95        insta::assert_snapshot!(minified_query, @r#"query{node@dir(a:1 b:"2" c:true d:false e:null)}"#);
96    }
97
98    #[test]
99    fn test_minify_fragment() {
100        let source = std::fs::read_to_string("src/parser/tests/queries/fragment.graphql").unwrap();
101        let minified_query = super::minify_query(&source).unwrap();
102        insta::assert_snapshot!(minified_query, @"fragment frag on Friend{node}");
103    }
104
105    #[test]
106    fn test_minify_fragment_spread() {
107        let source =
108            std::fs::read_to_string("src/parser/tests/queries/fragment_spread.graphql").unwrap();
109        let minified_query = super::minify_query(&source).unwrap();
110        insta::assert_snapshot!(minified_query, @"query{node{id...something}}");
111    }
112
113    #[test]
114    fn test_minify_inline_fragment() {
115        let source =
116            std::fs::read_to_string("src/parser/tests/queries/inline_fragment.graphql").unwrap();
117        let minified_query = super::minify_query(&source).unwrap();
118        insta::assert_snapshot!(minified_query, @"query{node{id...on User{name}}}");
119    }
120
121    #[test]
122    fn test_minify_inline_fragment_dir() {
123        let source =
124            std::fs::read_to_string("src/parser/tests/queries/inline_fragment_dir.graphql")
125                .unwrap();
126        let minified_query = super::minify_query(&source).unwrap();
127        insta::assert_snapshot!(minified_query, @"query{node{id...on User@defer{name}}}");
128    }
129
130    #[test]
131    fn test_minify_kitchen_sink() {
132        let source =
133            std::fs::read_to_string("src/parser/tests/queries/kitchen-sink.graphql").unwrap();
134        let minified_query = super::minify_query(&source).unwrap();
135        insta::assert_snapshot!(minified_query, @r#"
136        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:"""
137
138              block string uses \"""
139
140          """})}{unnamed(truthy:true falsey:false nullish:null)query}
141        "#);
142    }
143
144    #[test]
145    fn test_minify_kitchen_sink_canonical() {
146        let source =
147            std::fs::read_to_string("src/parser/tests/queries/kitchen-sink_canonical.graphql")
148                .unwrap();
149        let minified_query = super::minify_query(&source).unwrap();
150        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}"#);
151    }
152
153    #[test]
154    fn test_minify_minimal() {
155        let source = std::fs::read_to_string("src/parser/tests/queries/minimal.graphql").unwrap();
156        let minified_query = super::minify_query(&source).unwrap();
157        insta::assert_snapshot!(minified_query, @"{a}");
158    }
159
160    #[test]
161    fn test_minify_minimal_mutation() {
162        let source =
163            std::fs::read_to_string("src/parser/tests/queries/minimal_mutation.graphql").unwrap();
164        let minified_query = super::minify_query(&source).unwrap();
165        insta::assert_snapshot!(minified_query, @"mutation{notify}");
166    }
167
168    #[test]
169    fn test_minify_minimal_query() {
170        let source =
171            std::fs::read_to_string("src/parser/tests/queries/minimal_query.graphql").unwrap();
172        let minified_query = super::minify_query(&source).unwrap();
173        insta::assert_snapshot!(minified_query, @"query{node}");
174    }
175
176    #[test]
177    fn test_minify_mutation_directive() {
178        let source =
179            std::fs::read_to_string("src/parser/tests/queries/mutation_directive.graphql").unwrap();
180        let minified_query = super::minify_query(&source).unwrap();
181        insta::assert_snapshot!(minified_query, @"mutation@directive{node}");
182    }
183
184    #[test]
185    fn test_minify_mutation_nameless_vars() {
186        let source =
187            std::fs::read_to_string("src/parser/tests/queries/mutation_nameless_vars.graphql")
188                .unwrap();
189        let minified_query = super::minify_query(&source).unwrap();
190        insta::assert_snapshot!(minified_query, @"mutation($first:Int$second:Int){field1(first:$first)field2(second:$second)}");
191    }
192
193    #[test]
194    fn test_minify_named_query() {
195        let source =
196            std::fs::read_to_string("src/parser/tests/queries/named_query.graphql").unwrap();
197        let minified_query = super::minify_query(&source).unwrap();
198        insta::assert_snapshot!(minified_query, @"query Foo{field}");
199    }
200
201    #[test]
202    fn test_minify_nested_selection() {
203        let source =
204            std::fs::read_to_string("src/parser/tests/queries/nested_selection.graphql").unwrap();
205        let minified_query = super::minify_query(&source).unwrap();
206        insta::assert_snapshot!(minified_query, @"query{node{id}}");
207    }
208
209    #[test]
210    fn test_minify_query_aliases() {
211        let source =
212            std::fs::read_to_string("src/parser/tests/queries/query_aliases.graphql").unwrap();
213        let minified_query = super::minify_query(&source).unwrap();
214        insta::assert_snapshot!(minified_query, @"query{an_alias:node}");
215    }
216
217    #[test]
218    fn test_minify_query_arguments() {
219        let source =
220            std::fs::read_to_string("src/parser/tests/queries/query_arguments.graphql").unwrap();
221        let minified_query = super::minify_query(&source).unwrap();
222        insta::assert_snapshot!(minified_query, @"query{node(id:1)}");
223    }
224
225    #[test]
226    fn test_minify_query_arguments_multiline() {
227        let source =
228            std::fs::read_to_string("src/parser/tests/queries/query_arguments_multiline.graphql")
229                .unwrap();
230        let minified_query = super::minify_query(&source).unwrap();
231        insta::assert_snapshot!(minified_query, @"query{node(id:1)node(id:1 one:3)}");
232    }
233
234    #[test]
235    fn test_minify_query_array_argument_multiline() {
236        let source = std::fs::read_to_string(
237            "src/parser/tests/queries/query_array_argument_multiline.graphql",
238        )
239        .unwrap();
240        let minified_query = super::minify_query(&source).unwrap();
241        insta::assert_snapshot!(minified_query, @"query{node(id:[5 6 7])}");
242    }
243
244    #[test]
245    fn test_minify_query_directive() {
246        let source =
247            std::fs::read_to_string("src/parser/tests/queries/query_directive.graphql").unwrap();
248        let minified_query = super::minify_query(&source).unwrap();
249        insta::assert_snapshot!(minified_query, @"query@directive{node}");
250    }
251
252    #[test]
253    fn test_minify_query_list_argument() {
254        let source =
255            std::fs::read_to_string("src/parser/tests/queries/query_list_argument.graphql")
256                .unwrap();
257        let minified_query = super::minify_query(&source).unwrap();
258        insta::assert_snapshot!(minified_query, @"query{node(id:1 list:[123 456])}");
259    }
260
261    #[test]
262    fn test_minify_query_nameless_vars() {
263        let source =
264            std::fs::read_to_string("src/parser/tests/queries/query_nameless_vars.graphql")
265                .unwrap();
266        let minified_query = super::minify_query(&source).unwrap();
267        insta::assert_snapshot!(minified_query, @"query($first:Int$second:Int){field1(first:$first)field2(second:$second)}");
268    }
269
270    #[test]
271    fn test_minify_query_nameless_vars_multiple_fields() {
272        let source = std::fs::read_to_string(
273            "src/parser/tests/queries/query_nameless_vars_multiple_fields.graphql",
274        )
275        .unwrap();
276        let minified_query = super::minify_query(&source).unwrap();
277        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}}");
278    }
279
280    #[test]
281    fn test_minify_query_nameless_vars_multiple_fields_canonical() {
282        let source = std::fs::read_to_string(
283            "src/parser/tests/queries/query_nameless_vars_multiple_fields_canonical.graphql",
284        )
285        .unwrap();
286        let minified_query = super::minify_query(&source).unwrap();
287        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}}");
288    }
289
290    #[test]
291    fn test_minify_query_object_argument() {
292        let source =
293            std::fs::read_to_string("src/parser/tests/queries/query_object_argument.graphql")
294                .unwrap();
295        let minified_query = super::minify_query(&source).unwrap();
296        insta::assert_snapshot!(minified_query, @"query{node(id:1 obj:{key1:123 key2:456})}");
297    }
298
299    #[test]
300    fn test_minify_query_object_argument_multiline() {
301        let source = std::fs::read_to_string(
302            "src/parser/tests/queries/query_object_argument_multiline.graphql",
303        )
304        .unwrap();
305        let minified_query = super::minify_query(&source).unwrap();
306        insta::assert_snapshot!(minified_query, @"query{node(id:1 obj:{key1:123 key2:456})}");
307    }
308
309    #[test]
310    fn test_minify_query_var_default_float() {
311        let source =
312            std::fs::read_to_string("src/parser/tests/queries/query_var_default_float.graphql")
313                .unwrap();
314        let minified_query = super::minify_query(&source).unwrap();
315        insta::assert_snapshot!(minified_query, @"query Foo($site:Float=0.5){field}");
316    }
317
318    #[test]
319    fn test_minify_query_var_default_list() {
320        let source =
321            std::fs::read_to_string("src/parser/tests/queries/query_var_default_list.graphql")
322                .unwrap();
323        let minified_query = super::minify_query(&source).unwrap();
324        insta::assert_snapshot!(minified_query, @"query Foo($site:[Int]=[123 456]){field}");
325    }
326
327    #[test]
328    fn test_minify_query_var_default_object() {
329        let source =
330            std::fs::read_to_string("src/parser/tests/queries/query_var_default_object.graphql")
331                .unwrap();
332        let minified_query = super::minify_query(&source).unwrap();
333        insta::assert_snapshot!(minified_query, @"query Foo($site:Site={url:null}){field}");
334    }
335
336    #[test]
337    fn test_minify_query_var_default_string() {
338        let source =
339            std::fs::read_to_string("src/parser/tests/queries/query_var_default_string.graphql")
340                .unwrap();
341        let minified_query = super::minify_query(&source).unwrap();
342        insta::assert_snapshot!(minified_query, @r#"query Foo($site:String="string"){field}"#);
343    }
344
345    #[test]
346    fn test_minify_query_var_defaults() {
347        let source =
348            std::fs::read_to_string("src/parser/tests/queries/query_var_defaults.graphql").unwrap();
349        let minified_query = super::minify_query(&source).unwrap();
350        insta::assert_snapshot!(minified_query, @"query Foo($site:Site=MOBILE){field}");
351    }
352
353    #[test]
354    fn test_minify_query_vars() {
355        let source =
356            std::fs::read_to_string("src/parser/tests/queries/query_vars.graphql").unwrap();
357        let minified_query = super::minify_query(&source).unwrap();
358        insta::assert_snapshot!(minified_query, @"query Foo($arg:SomeType){field}");
359    }
360
361    #[test]
362    fn test_minify_string_literal() {
363        let source =
364            std::fs::read_to_string("src/parser/tests/queries/string_literal.graphql").unwrap();
365        let minified_query = super::minify_query(&source).unwrap();
366        insta::assert_snapshot!(minified_query, @r#"query{node(id:"hello")}"#);
367    }
368
369    #[test]
370    fn test_minify_subscription_directive() {
371        let source =
372            std::fs::read_to_string("src/parser/tests/queries/subscription_directive.graphql")
373                .unwrap();
374        let minified_query = super::minify_query(&source).unwrap();
375        insta::assert_snapshot!(minified_query, @"subscription@directive{node}");
376    }
377
378    #[test]
379    fn test_minify_triple_quoted_literal() {
380        let source =
381            std::fs::read_to_string("src/parser/tests/queries/triple_quoted_literal.graphql")
382                .unwrap();
383        let minified_query = super::minify_query(&source).unwrap();
384        insta::assert_snapshot!(minified_query, @r#"
385        query{node(id:"""
386            Hello,
387              world!
388          """)}
389        "#);
390    }
391}