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