1 module graphql.validation.querybased;
2 
3 import std.array : array, back, empty, popBack;
4 import std.algorithm.searching : canFind;
5 import std.algorithm.sorting : sort;
6 import std.algorithm.iteration : each, uniq, filter, map, joiner;
7 import std.algorithm.setops : setDifference;
8 import std.algorithm.comparison : equal;
9 import std.conv : to;
10 import std.format : format;
11 import std.exception : enforce;
12 import std.experimental.logger;
13 
14 import vibe.data.json;
15 
16 import fixedsizearray;
17 
18 import graphql.ast;
19 import graphql.builder;
20 import graphql.helper : lexAndParse;
21 import graphql.visitor;
22 import graphql.validation.exception;
23 
24 import std.exception : assertThrown, assertNotThrown;
25 import std.stdio;
26 import graphql.lexer;
27 import graphql.parser;
28 import graphql.treevisitor;
29 
30 @safe:
31 
32 struct OperationFragVar {
33 	const(OperationDefinition) op;
34 	bool[string] fragmentsUsed;
35 	bool[string] variablesDefined;
36 	bool[string] variablesUsed;
37 
38 	this(const(OperationDefinition) op) {
39 		this.op = op;
40 	}
41 }
42 
43 class QueryValidator : ConstVisitor {
44 	import std.experimental.typecons : Final;
45 	alias enter = ConstVisitor.enter;
46 	alias exit = ConstVisitor.exit;
47 	alias accept = ConstVisitor.accept;
48 
49 	const(Document) doc;
50 
51 	this(const(Document) doc) {
52 		this.doc = doc;
53 	}
54 
55 	string[][string] fragmentChildren;
56 	FixedSizeArray!string curFragment;
57 	string[] allFrags;
58 	bool[string] reachedFragments;
59 
60 	// Lone Anonymous Operation
61 	bool laoFound;
62 
63 	// Unique Operation names
64 	bool[string] operationNames;
65 
66 	// Unique Argument names
67 	bool[string] argumentNames;
68 
69 	// Unique Variable names
70 	bool[string] variableNames;
71 
72 	// Variable usage
73 	bool variableDefinitionDone;
74 
75 	string[string] variablesUsedByFragments;
76 
77 	OperationFragVar[] operations;
78 
79 	// Unique directives per Field
80 	bool[string][] directiveNames;
81 
82 	override void exit(const(Document) doc) {
83 		foreach(ref OperationFragVar op; this.operations) {
84 			bool[string] allFragsReached = allReachable(op.fragmentsUsed,
85 					this.fragmentChildren
86 				);
87 			bool[string] varsUsedByOp = computeVariablesUsedByFragments(
88 					allFragsReached, this.variablesUsedByFragments
89 				);
90 
91 			auto allVars = op.variablesDefined.byKey().array.sort.uniq;
92 			auto varUsed = (varsUsedByOp.byKey().array ~
93 					op.variablesUsed.byKey().array)
94 					.sort.uniq;
95 
96 			enforce!VariablesUseException(equal(allVars, varUsed),
97 					format("Variables available [%(%s, %)], "
98 						~ "Variables used [%(%s, %)]", allVars, varUsed)
99 				);
100 		}
101 	}
102 
103 	override void enter(const(InlineFragment) f) {
104 		// Unique directive names
105 		bool[string] tmp;
106 		this.directiveNames ~= tmp;
107 	}
108 
109 	override void exit(const(InlineFragment) f) {
110 		this.directiveNames.popBack();
111 	}
112 
113 	override void enter(const(Field) f) {
114 		// Unique directive names
115 		bool[string] tmp;
116 		this.directiveNames ~= tmp;
117 	}
118 
119 	override void exit(const(Field) f) {
120 		this.directiveNames.popBack();
121 	}
122 
123 	override void enter(const(Definition) od) {
124 		enforce!NoTypeSystemDefinitionException(
125 				od.ruleSelection != DefinitionEnum.T);
126 	}
127 
128 	override void enter(const(Directive) dir) {
129 		enforce!DirectiveNotUnique(dir.name.value !in this.directiveNames.back,
130 			format!"Directive '%s' must be unique at %s:%s"(dir.name.value,
131 				dir.name.line, dir.name.column));
132 
133 		this.directiveNames.back[dir.name.value] = true;
134 	}
135 
136 	override void enter(const(OperationDefinition) od) {
137 		this.operations ~= OperationFragVar(od);
138 
139 		this.variableDefinitionDone = false;
140 
141 		enforce!LoneAnonymousOperationException(!this.laoFound);
142 		if(canFind([OperationDefinitionEnum.SelSet,
143 					OperationDefinitionEnum.OT,
144 					OperationDefinitionEnum.OT_D,
145 					OperationDefinitionEnum.OT_V,
146 					OperationDefinitionEnum.OT_VD] ,od.ruleSelection))
147 		{
148 			this.laoFound = true;
149 		} else {
150 			enforce!NonUniqueOperationNameException(od.name.value !in
151 					this.operationNames, format(
152 						"Operation name '%s' already present in [%(%s, %)]",
153 						od.name.value, this.operationNames.byKey()
154 					)
155 				);
156 			this.operationNames[od.name.value] = true;
157 		}
158 
159 		// Unique directive names
160 		bool[string] tmp;
161 		this.directiveNames ~= tmp;
162 	}
163 
164 	override void exit(const(OperationDefinition) od) {
165 		this.directiveNames.popBack();
166 	}
167 
168 	override void enter(const(FragmentDefinition) frag) {
169 		enforce!FragmentNameAlreadyInUseException(
170 				!canFind(this.allFrags, frag.name.value),
171 				format("Fragment names '%s' already in use '[%(%s, %)]'",
172 					frag.name.value, this.allFrags)
173 			);
174 
175 		this.allFrags ~= frag.name.value;
176 		this.curFragment.insertBack(frag.name.value);
177 
178 		// Unique directive names
179 		bool[string] tmp;
180 		this.directiveNames ~= tmp;
181 	}
182 
183 
184 	override void exit(const(FragmentDefinition) frag) {
185 		this.curFragment.removeBack();
186 
187 		this.directiveNames.popBack();
188 	}
189 
190 	override void enter(const(FragmentSpread) fragSpread) {
191 		if(!this.operations.empty) {
192 			this.operations.back.fragmentsUsed[fragSpread.name.value] = true;
193 		}
194 
195 		const(FragmentDefinition) frag = findFragment(this.doc,
196 				fragSpread.name.value
197 			);
198 
199 		enforce!FragmentNotFoundException(frag !is null,
200 				format("No Fragment with name '%s' could be found",
201 					fragSpread.name.value)
202 			);
203 
204 		this.reachedFragments[frag.name.value] = true;
205 
206 		if(!this.curFragment.empty) {
207 			string[]* children =
208 				() @trusted {
209 					return &(this.fragmentChildren.require(
210 							this.curFragment.back, new string[](0)
211 						));
212 				}();
213 			(*children) ~= frag.name.value;
214 		}
215 
216 		// Unique directive names
217 		bool[string] tmp;
218 		this.directiveNames ~= tmp;
219 	}
220 
221 	override void enter(const(Arguments) al) {
222 		() @trusted {
223 			this.argumentNames.clear();
224 		}();
225 	}
226 
227 	override void enter(const(Argument) al) {
228 		enforce!ArgumentsNotUniqueException(al.name.value !in this.argumentNames,
229 				format("Argument with name '%s' already present in [%(%s, %)]",
230 						al.name.value, this.argumentNames.byKey()
231 					)
232 			);
233 		this.argumentNames[al.name.value] = true;
234 	}
235 
236 	override void enter(const(VariableDefinitions) vd) {
237 		() @trusted {
238 			this.variableNames.clear();
239 		}();
240 	}
241 
242 	override void exit(const(VariableDefinitions) vd) {
243 		this.variableDefinitionDone = true;
244 	}
245 
246 	override void enter(const(Variable) v) {
247 		if(variableDefinitionDone) {
248 			this.operations.back.variablesUsed[v.name.value] = true;
249 			if(!this.curFragment.empty) {
250 				this.variablesUsedByFragments[this.curFragment.back] =
251 					v.name.value;
252 			}
253 		} else {
254 			enforce!VariablesNotUniqueException(
255 					v.name.value !in this.variableNames,
256 					format(
257 						"Variable with name '%s' already present in [%(%s, %)]",
258 						v.name.value, this.variableNames.byKey()
259 					)
260 				);
261 			this.variableNames[v.name.value] = true;
262 			this.operations.back.variablesDefined[v.name.value] = true;
263 		}
264 	}
265 }
266 
267 bool[string] computeVariablesUsedByFragments(bool[string] fragmentsUsed,
268 		string[string] variablesUsed)
269 {
270 	bool[string] ret;
271 	fragmentsUsed
272 		.byKey()
273 		.filter!(a => a in variablesUsed)
274 		.map!(a => variablesUsed[a])
275 		.array
276 		.sort
277 		.uniq
278 		.each!(a => ret[a] = true);
279 	return ret;
280 }
281 
282 bool noCylces(string[][string] frags) {
283 	foreach(string key; frags.byKey()) {
284 		noCylcesImpl([key], frags);
285 	}
286 	return true;
287 }
288 
289 void noCylcesImpl(string[] toFind, string[][string] frags) {
290 	auto toIter = toFind.back in frags;
291 	if(toIter is null) {
292 		return;
293 	}
294 
295 	string[] follow = *toIter;
296 	foreach(f; follow) {
297 		enforce!FragmentCycleException(!canFind(toFind, f), format(
298 				"Found a cycle in the fragments '[%(%s -> %)]'", toFind ~ f
299 			));
300 		noCylcesImpl(toFind ~ f, frags);
301 	}
302 }
303 
304 bool[string] allReachable(bool[string] reached, string[][string] fragChildren) {
305 	bool[string] ret;
306 	foreach(string key; reached.byKey()) {
307 		enforce(!key.empty);
308 		ret[key] = true;
309 	}
310 	size_t oldLen;
311 	do {
312 		oldLen = ret.length;
313 		foreach(string key; ret.byKey()) {
314 			string[]* follow = key in fragChildren;
315 			if(follow !is null) {
316 				foreach(string f; *follow) {
317 					assert(!f.empty, format("%s [%(%s, %)]", key, *follow));
318 					ret[f] = true;
319 				}
320 			}
321 		}
322 	} while(ret.length > oldLen);
323 	return ret;
324 }
325 
326 void allFragmentsReached(QueryValidator fv) {
327 	bool[string] reached = allReachable(fv.reachedFragments,
328 			fv.fragmentChildren);
329 	auto af = fv.allFrags.sort;
330 	auto r = reached.byKey().array.sort;
331 	enforce!UnusedFragmentsException(equal(af, r),
332 			format("Fragments [%(%s, %)] are unused, allFrags [%(%s, %)], "
333 					~ "reached [%(%s, %)]",
334 					setDifference(af, r), af, r
335 				)
336 		);
337 }
338 
339 unittest {
340 	string simpleCylce = `
341 fragment Frag0 on Foo {
342 	...Frag1
343 }
344 
345 fragment Frag1 on Foo {
346 	...Frag0
347 }
348 
349 query Q {
350 	...Frag0
351 }`;
352 
353 	auto doc = lexAndParse(simpleCylce);
354 	const(FragmentDefinition) f0 = findFragment(doc, "Frag0");
355 	assert(f0 !is null);
356 	const(FragmentDefinition) f1 = findFragment(doc, "Frag1");
357 	assert(f1 !is null);
358 
359 	QueryValidator fv = new QueryValidator(doc);
360 	fv.accept(doc);
361 	assertThrown!(FragmentCycleException)(noCylces(fv.fragmentChildren));
362 	assertNotThrown(allFragmentsReached(fv));
363 }
364 
365 unittest {
366 	string biggerCylce = `
367 fragment Frag0 on Foo {
368 	...Frag1
369 }
370 
371 fragment Frag1 on Foo {
372 	...Frag2
373 }
374 
375 fragment Frag2 on Foo {
376 	...Frag0
377 }
378 
379 query Q {
380 	...Frag0
381 }`;
382 
383 	auto doc = lexAndParse(biggerCylce);
384 
385 	QueryValidator fv = new QueryValidator(doc);
386 	fv.accept(doc);
387 	assertThrown!(FragmentCycleException)(noCylces(fv.fragmentChildren));
388 	assertNotThrown(allFragmentsReached(fv));
389 }
390 
391 unittest {
392 	string biggerCylce = `
393 fragment Frag0 on Foo {
394 	...Frag1
395 }
396 
397 fragment Frag1 on Foo {
398 	...Frag2
399 }
400 
401 fragment Frag2 on Foo {
402 	hello
403 }
404 
405 query Q {
406 	...Frag0
407 }`;
408 
409 	auto doc = lexAndParse(biggerCylce);
410 
411 	QueryValidator fv = new QueryValidator(doc);
412 	fv.accept(doc);
413 	assertNotThrown(noCylces(fv.fragmentChildren));
414 	assertNotThrown(allFragmentsReached(fv));
415 }
416 
417 unittest {
418 	import std.stdio;
419 	string biggerCylce = `
420 query Q {
421 	...Frag0
422 }
423 
424 fragment Frag0 on Foo {
425 	...Frag1
426 }
427 
428 fragment Frag1 on Foo {
429 	...Frag
430 }`;
431 
432 	auto doc = lexAndParse(biggerCylce);
433 
434 	QueryValidator fv = new QueryValidator(doc);
435 	assertThrown!FragmentNotFoundException(fv.accept(doc));
436 	assertNotThrown(allFragmentsReached(fv));
437 }
438 
439 unittest {
440 	string biggerCylce = `
441 fragment Frag0 on Foo {
442 	...Frag1
443 }
444 
445 fragment Frag0 on Foo {
446 	...Frag2
447 }
448 
449 fragment Frag1 on Foo {
450 	...Frag2
451 }
452 
453 fragment Frag2 on Foo {
454 	hello
455 }
456 
457 fragment Frag4 on Foo {
458 	hello
459 }
460 
461 query Q {
462 	...Frag0
463 }`;
464 
465 	auto doc = lexAndParse(biggerCylce);
466 
467 	QueryValidator fv = new QueryValidator(doc);
468 	assertThrown!FragmentNameAlreadyInUseException(fv.accept(doc));
469 	assertThrown!UnusedFragmentsException(allFragmentsReached(fv));
470 }
471 
472 unittest {
473 	string biggerCylce = `
474 mutation Q {
475 	bar
476 }
477 
478 query Q {
479 	foo
480 }`;
481 
482 	auto l = Lexer(biggerCylce);
483 	auto p = Parser(l);
484 	const(Document) doc = p.parseDocument();
485 
486 	QueryValidator fv = new QueryValidator(doc);
487 	assertThrown!NonUniqueOperationNameException(fv.accept(doc));
488 }
489 
490 unittest {
491 	string biggerCylce = `
492 {
493 	bar
494 }
495 
496 query Q {
497 	foo
498 }`;
499 
500 	auto doc = lexAndParse(biggerCylce);
501 
502 	QueryValidator fv = new QueryValidator(doc);
503 	assertThrown!LoneAnonymousOperationException(fv.accept(doc));
504 }
505 
506 unittest {
507 	string biggerCylce = `
508 {
509 	bar
510 }
511 
512 {
513 	foo
514 }`;
515 
516 	auto doc = lexAndParse(biggerCylce);
517 
518 	QueryValidator fv = new QueryValidator(doc);
519 	assertThrown!LoneAnonymousOperationException(fv.accept(doc));
520 }
521 
522 unittest {
523 	string biggerCylce = `
524 {
525 	bar
526 }
527 
528 enum Dog {
529 	foo
530 }`;
531 
532 	auto doc = lexAndParse(biggerCylce);
533 
534 	QueryValidator fv = new QueryValidator(doc);
535 	assertThrown!NoTypeSystemDefinitionException(fv.accept(doc));
536 }
537 
538 unittest {
539 	string biggerCylce = `
540 query foo($x: Int!) {
541 	bar(x: $x) {
542 		a
543 	}
544 }`;
545 
546 	auto doc = lexAndParse(biggerCylce);
547 
548 	QueryValidator fv = new QueryValidator(doc);
549 	assertNotThrown!ArgumentsNotUniqueException(fv.accept(doc));
550 }
551 
552 unittest {
553 	string biggerCylce = `
554 query foo {
555 	foo(x: 10, y: 11.1) {
556 		bar(x: $x, y: $y) {
557 			i
558 		}
559 	}
560 
561 	bar(x: 10, x: 11.1) {
562 		bar(x: $x, y: $x) {
563 			i
564 		}
565 	}
566 }`;
567 
568 	auto doc = lexAndParse(biggerCylce);
569 
570 	QueryValidator fv = new QueryValidator(doc);
571 	assertThrown!ArgumentsNotUniqueException(fv.accept(doc));
572 }
573 
574 unittest {
575 	string biggerCylce = `
576 query foo($x: Int, $y: Float) {
577 	bar(x: $x, y: $y) {
578 		i
579 	}
580 }`;
581 
582 	auto doc = lexAndParse(biggerCylce);
583 
584 	QueryValidator fv = new QueryValidator(doc);
585 	assertNotThrown!VariablesNotUniqueException(fv.accept(doc));
586 }
587 
588 unittest {
589 	string biggerCylce = `
590 query foo($x: Int, $x: Float) {
591 	bar(x: $x, y: $x) {
592 		i
593 	}
594 }`;
595 
596 	auto doc = lexAndParse(biggerCylce);
597 
598 	QueryValidator fv = new QueryValidator(doc);
599 	assertThrown!VariablesNotUniqueException(fv.accept(doc));
600 }
601 
602 unittest {
603 	string biggerCylce = `
604 query foo($x: Int, $y: Float) {
605 	bar(x: $x) {
606 		i
607 	}
608 }`;
609 
610 	auto doc = lexAndParse(biggerCylce);
611 
612 	QueryValidator fv = new QueryValidator(doc);
613 	assertThrown!VariablesUseException(fv.accept(doc));
614 }
615 
616 unittest {
617 	string biggerCylce = `
618 query foo($x: Int) {
619 	...Foo
620 }
621 
622 fragment Foo on Bar {
623 	bar(x: $x) {
624 		i
625 	}
626 }
627 `;
628 
629 	auto doc = lexAndParse(biggerCylce);
630 
631 	QueryValidator fv = new QueryValidator(doc);
632 	assertNotThrown!VariablesUseException(fv.accept(doc));
633 }
634 
635 unittest {
636 	string biggerCylce = `
637 query foo($x: Int, $y: Float) {
638 	...Foo
639 }
640 
641 fragment Foo on Bar {
642 	bar(x: $x) {
643 		i
644 		...ZZZ
645 	}
646 }
647 
648 fragment ZZZ on Bar {
649 	bar(x: $y) {
650 		i
651 	}
652 }
653 `;
654 
655 	auto doc = lexAndParse(biggerCylce);
656 
657 	QueryValidator fv = new QueryValidator(doc);
658 	assertNotThrown!VariablesUseException(fv.accept(doc));
659 }
660 
661 unittest {
662 	string biggerCylce = `
663 query foo($x: Int, $y: Float) @skip(if: true) @skip(if: false) {
664 	...Foo
665 }
666 
667 fragment Foo on Bar {
668 	bar(x: $x) {
669 		i
670 		...ZZZ
671 	}
672 }
673 
674 fragment ZZZ on Bar {
675 	bar(x: $y) {
676 		i
677 	}
678 }
679 `;
680 
681 	auto doc = lexAndParse(biggerCylce);
682 
683 	QueryValidator fv = new QueryValidator(doc);
684 	assertThrown!DirectiveNotUnique(fv.accept(doc));
685 }