graphql_tools/parser/query/
minify.rs1use crate::parser::tokenizer::{Kind, Token, TokenStream};
2use combine::StreamOnce;
3use thiserror::Error;
4
5#[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}