1 module graphql.builder;
2 
3 import std.experimental.allocator;
4 import std.experimental.logger : logf;
5 import std.experimental.allocator.mallocator : Mallocator;
6 import std.exception : enforce;
7 
8 import fixedsizearray;
9 
10 import graphql.helper;
11 import graphql.ast;
12 import graphql.parser;
13 import graphql.lexer;
14 
15 @safe:
16 
17 Selections findFragment(Document doc, string name, string[] typenames) {
18 	import std.algorithm.searching : canFind;
19 	import std.experimental.logger : logf;
20 	Definitions cur = doc.defs;
21 	while(cur !is null) {
22 		enforce(cur.def !is null);
23 		if(cur.def.ruleSelection == DefinitionEnum.F) {
24 			enforce(cur.def.frag !is null);
25 			//logf("%s == %s && %s in %s", cur.def.frag.name.value, name,
26 			//		cur.def.frag.tc.value, typenames
27 			//	);
28 			if(cur.def.frag.name.value == name
29 					&& canFind(typenames, cur.def.frag.tc.value))
30 			{
31 				//logf("found it");
32 				return cur.def.frag.ss.sel;
33 			} else {
34 				//logf("not found");
35 			}
36 		}
37 		cur = cur.follow;
38 	}
39 
40 	//logf("search failed");
41 	return null;
42 }
43 
44 unittest {
45 	string s = `{
46  user(id: 1) {
47    friends {
48      name
49    }
50    name
51    age
52  }
53 }`;
54 	auto l = Lexer(s);
55 	auto p = Parser(l);
56 	auto d = p.parseDocument();
57 
58 	auto f = findFragment(d, "fooo", ["user"]);
59 	assert(f is null);
60 }
61 
62 unittest {
63 	string s = `
64 fragment fooo on Hero {
65   name
66 }`;
67 	auto l = Lexer(s);
68 	auto p = Parser(l);
69 	auto d = p.parseDocument();
70 
71 	auto f = findFragment(d, "fooo", ["Hero"]);
72 	assert(f !is null);
73 	assert(f.sel.field.name.name.value == "name");
74 
75 	f = findFragment(d, "fooo", ["Villian"]);
76 	assert(f is null);
77 }
78 
79 struct ArgumentRangeItem {
80 	Argument arg;
81 
82 	@property string name() const pure {
83 		return arg.name.value;
84 	}
85 
86 	@property ValueOrVariable value() {
87 		return arg.vv;
88 	}
89 }
90 
91 struct ArgumentRange {
92 	ArgumentList cur;
93 
94 	@property bool empty() const pure {
95 		return this.cur is null;
96 	}
97 
98 	@property ArgumentRangeItem front() {
99 		return ArgumentRangeItem(this.cur.arg);
100 	}
101 
102 	void popFront() {
103 		this.cur = this.cur.follow;
104 	}
105 
106 	@property ArgumentRange save() {
107 		return ArgumentRange(this.cur);
108 	}
109 }
110 
111 struct FieldRangeItem {
112 	Field f;
113 	Document doc;
114 
115 	@property string name() {
116 		return f.name.name.value;
117 	}
118 
119 	@property string aka() {
120 		return f.name.aka.value;
121 	}
122 
123 	ArgumentRange arguments() {
124 		if(this.f.args !is null) {
125 			return ArgumentRange(f.args.arg);
126 		} else {
127 			return ArgumentRange(null);
128 		}
129 	}
130 
131 	bool hasSelectionSet() pure @safe {
132 		return f.ss !is null;
133 	}
134 
135 	FieldRange selectionSet(string[] typenames) {
136 		return FieldRange(this.f.ss.sel, this.doc, typenames);
137 	}
138 
139 }
140 
141 struct FieldRange {
142 	FixedSizeArray!(Selections,32) cur;
143 	Document doc;
144 	string[] typenames;
145 
146 	void test() {
147 		import std.format : format;
148 		import std.conv : to;
149 		import std.algorithm.iteration : map;
150 		//logf("%s", this.cur[].map!(i => format("nn %s, ft %s", i !is null,
151 		//		i !is null ? to!string(i.sel.ruleSelection) : "null"))
152 		//	);
153 		foreach(it; this.cur[]) {
154 			assert(it !is null);
155 			assert(it.sel.ruleSelection == SelectionEnum.Field);
156 		}
157 	}
158 
159 	this(Selections sels, Document doc, string[] typenames) {
160 		this.doc = doc;
161 		this.typenames = typenames;
162 		this.cur.insertBack(sels);
163 		this.build();
164 		this.test();
165 	}
166 
167 	this(ref FieldRange old) {
168 		this.cur = old.cur;
169 		this.doc = doc;
170 	}
171 
172 	@property FieldRange save() {
173 		return FieldRange(this);
174 	}
175 
176 	@property bool empty() const pure {
177 		return this.cur.length == 0;
178 	}
179 
180 	@property FieldRangeItem front() {
181 		enforce(!this.cur.empty);
182 		enforce(this.cur.back !is null);
183 		enforce(this.cur.back.sel !is null);
184 		enforce(this.cur.back.sel.field !is null);
185 		return FieldRangeItem(this.cur.back.sel.field, this.doc);
186 	}
187 
188 	void popFront() {
189 		this.cur.back = this.cur.back.follow;
190 		this.build();
191 		this.test();
192 	}
193 
194 	void build() {
195 		if(this.cur.empty) {
196 			return;
197 		}
198 		if(this.cur.back is null) {
199 			this.cur.removeBack();
200 			this.build();
201 			this.test();
202 			return;
203 		}
204 		if(this.cur.back.sel.ruleSelection == SelectionEnum.Field) {
205 			return;
206 		} else if(this.cur.back.sel.ruleSelection == SelectionEnum.Spread
207 				|| this.cur.back.sel.ruleSelection == SelectionEnum.IFrag)
208 		{
209 			Selections follow = this.cur.back.follow;
210 			Selections f;
211 			if(this.cur.back.sel.ruleSelection == SelectionEnum.Spread) {
212 				f = findFragment(doc,
213 						this.cur.back.sel.frag.name.value, this.typenames
214 					);
215 			} else {
216 				f = resolveInlineFragment(this.cur.back.sel.ifrag,
217 						this.typenames
218 					);
219 			}
220 
221 			this.cur.removeBack();
222 
223 			if(follow !is null) {
224 				this.cur.insertBack(follow);
225 				this.build();
226 				this.test();
227 			}
228 			if(f !is null) {
229 				this.cur.insertBack(f);
230 				this.build();
231 				this.test();
232 			}
233 		}
234 	}
235 }
236 
237 Selections resolveInlineFragment(InlineFragment ilf, string[] typenames) {
238 	import std.algorithm.searching : canFind;
239 	final switch(ilf.ruleSelection) {
240 		case InlineFragmentEnum.TDS:
241 			goto case InlineFragmentEnum.TS;
242 		case InlineFragmentEnum.TS:
243 			return canFind(typenames, ilf.tc.value) ? ilf.ss.sel : null;
244 		case InlineFragmentEnum.DS:
245 			return ilf.ss.sel;
246 		case InlineFragmentEnum.S:
247 			return ilf.ss.sel;
248 	}
249 }
250 
251 FieldRange fieldRange(OperationDefinition od, Document doc,
252 		string[] typenames)
253 {
254 	return FieldRange(od.accessNN!(["ss", "sel"]), doc, typenames);
255 }
256 
257 FieldRange fieldRange(SelectionSet ss, Document doc, string[] typenames) {
258 	return FieldRange(ss.sel, doc, typenames);
259 }
260 
261 FieldRangeItem[] fieldRangeArr(Selections sel, Document doc,
262 		string[] typenames)
263 {
264 	import std.array : array;
265 	return FieldRange(sel, doc, typenames).array;
266 }
267 
268 struct OpDefRangeItem {
269 	Document doc;
270 	Definition def;
271 
272 	FieldRange fieldRange(string[] typenames) {
273 		return .fieldRange(accessNN!(["op", "ss"])(this.def), this.doc,
274 				typenames
275 			);
276 	}
277 }
278 
279 struct OpDefRange {
280 	Document doc;
281 	Definitions defs;
282 
283 	this(Document doc) {
284 		this.doc = doc;
285 		this.defs = doc.defs;
286 		this.advance();
287 	}
288 
289 	private void advance() {
290 		while(this.defs !is null
291 				&& this.defs.def.ruleSelection != DefinitionEnum.O)
292 		{
293 			this.defs = this.defs.follow;
294 		}
295 	}
296 
297 	@property bool empty() const {
298 		return this.defs is null;
299 	}
300 
301 	@property OpDefRangeItem front() {
302 		return OpDefRangeItem(this.doc, this.defs.def);
303 	}
304 
305 	void popFront() {
306 		this.defs = this.defs.follow;
307 		this.advance();
308 	}
309 
310 	@property typeof(this) save() {
311 		OpDefRange ret;
312 		ret.doc = this.doc;
313 		ret.defs = this.defs;
314 		return ret;
315 	}
316 }
317 
318 OpDefRange opDefRange(Document doc) {
319 	return OpDefRange(doc);
320 }
321 
322 unittest {
323 	string s = `{
324  user(id: 1) {
325    friends {
326      name
327    }
328    name
329    age
330  }
331 }`;
332 	auto l = Lexer(s);
333 	auto p = Parser(l);
334 	auto d = p.parseDocument();
335 
336 	FieldRange r = fieldRange(d.defs.def.op, d, ["User"]);
337 	assert(!r.empty);
338 	assert(r.front.name == "user");
339 	ArgumentRange argL = r.front.arguments();
340 	assert(!argL.empty);
341 	auto ari = argL.front;
342 	assert(ari.name == "id");
343 	argL.popFront();
344 	assert(argL.empty);
345 	assert(r.front.hasSelectionSet());
346 	auto fss = r.front.selectionSet(["User"]);
347 	assert(!fss.empty);
348 	assert(fss.front.name == "friends");
349 	fss.popFront();
350 	assert(!fss.empty);
351 	assert(fss.front.name == "name");
352 	fss.popFront();
353 	assert(!fss.empty);
354 	assert(fss.front.name == "age");
355 	fss.popFront();
356 	assert(fss.empty);
357 	r.popFront();
358 	assert(r.empty);
359 }
360 
361 unittest {
362 	string s = `{
363  user(id: 1) {
364 	 ...foo
365  }
366 }
367 
368 fragment foo on User {
369 	name
370 	age
371 }
372 `;
373 	auto l = Lexer(s);
374 	auto p = Parser(l);
375 	auto d = p.parseDocument();
376 
377 	auto f = findFragment(d, "foo", ["User"]);
378 	assert(f !is null);
379 
380 	FieldRange r = fieldRange(d.defs.def.op, d, ["User"]);
381 	assert(!r.empty);
382 	assert(r.front.name == "user");
383 	ArgumentRange argL = r.front.arguments();
384 	assert(!argL.empty);
385 	auto ari = argL.front;
386 	assert(ari.name == "id");
387 	argL.popFront();
388 	assert(argL.empty);
389 	assert(r.front.hasSelectionSet());
390 	auto fss = r.front.selectionSet(["User"]);
391 	assert(!fss.empty);
392 	assert(fss.front.name == "name", fss.front.name);
393 	fss.popFront();
394 	assert(!fss.empty);
395 	assert(fss.front.name == "age");
396 	fss.popFront();
397 	assert(fss.empty);
398 	r.popFront();
399 	assert(r.empty);
400 }
401 
402 unittest {
403 	string s = `{
404  user(id: 1) {
405 	 ...foo
406 	 ...bar
407  }
408 }
409 
410 fragment foo on User {
411 	name
412 }
413 
414 fragment bar on User {
415 	age
416 }
417 `;
418 	auto l = Lexer(s);
419 	auto p = Parser(l);
420 	auto d = p.parseDocument();
421 
422 	auto f = findFragment(d, "foo", ["User"]);
423 	assert(f !is null);
424 
425 	auto f2 = findFragment(d, "bar", ["User"]);
426 	assert(f2 !is null);
427 
428 	FieldRange r = fieldRange(d.defs.def.op, d, ["User"]);
429 	assert(!r.empty);
430 	assert(r.front.name == "user");
431 	ArgumentRange argL = r.front.arguments();
432 	assert(!argL.empty);
433 	auto ari = argL.front;
434 	assert(ari.name == "id");
435 	argL.popFront();
436 	assert(argL.empty);
437 	assert(r.front.hasSelectionSet());
438 	auto fss = r.front.selectionSet(["User"]);
439 	assert(!fss.empty);
440 	assert(fss.front.name == "name", fss.front.name);
441 	fss.popFront();
442 	assert(!fss.empty);
443 	assert(fss.front.name == "age");
444 	fss.popFront();
445 	assert(fss.empty);
446 	r.popFront();
447 	assert(r.empty);
448 }
449 
450 unittest {
451 	string s = `{
452  user(id: 1) {
453 	 ...foo
454 	 ...bar
455 	 hello
456  }
457 }
458 
459 fragment foo on User {
460 	name
461 }
462 
463 fragment bar on User {
464 	age
465 }
466 `;
467 	auto l = Lexer(s);
468 	auto p = Parser(l);
469 	auto d = p.parseDocument();
470 
471 	auto f = findFragment(d, "foo", ["User"]);
472 	assert(f !is null);
473 
474 	auto f2 = findFragment(d, "bar", ["User"]);
475 	assert(f2 !is null);
476 
477 	FieldRange r = fieldRange(d.defs.def.op, d, ["User"]);
478 	assert(!r.empty);
479 	assert(r.front.name == "user");
480 	ArgumentRange argL = r.front.arguments();
481 	assert(!argL.empty);
482 	auto ari = argL.front;
483 	assert(ari.name == "id");
484 	argL.popFront();
485 	assert(argL.empty);
486 	assert(r.front.hasSelectionSet());
487 	auto fss = r.front.selectionSet(["User"]);
488 	assert(!fss.empty);
489 	assert(fss.front.name == "name", fss.front.name);
490 	fss.popFront();
491 	assert(!fss.empty);
492 	assert(fss.front.name == "age");
493 	fss.popFront();
494 	assert(!fss.empty);
495 	assert(fss.front.name == "hello");
496 	fss.popFront();
497 	assert(fss.empty);
498 	r.popFront();
499 	assert(r.empty);
500 }
501 
502 unittest {
503 	string s = `{
504  user(id: 1) {
505 	 hello
506 	 ...foo
507 	 ...bar
508  }
509 }
510 
511 fragment foo on User {
512 	name
513 }
514 
515 fragment bar on User {
516 	age
517 }
518 `;
519 	auto l = Lexer(s);
520 	auto p = Parser(l);
521 	auto d = p.parseDocument();
522 
523 	auto f = findFragment(d, "foo", ["User"]);
524 	assert(f !is null);
525 
526 	auto f2 = findFragment(d, "bar", ["User"]);
527 	assert(f2 !is null);
528 
529 	FieldRange r = fieldRange(d.defs.def.op, d, ["User"]);
530 	assert(!r.empty);
531 	assert(r.front.name == "user");
532 	ArgumentRange argL = r.front.arguments();
533 	assert(!argL.empty);
534 	auto ari = argL.front;
535 	assert(ari.name == "id");
536 	argL.popFront();
537 	assert(argL.empty);
538 	assert(r.front.hasSelectionSet());
539 	auto fss = r.front.selectionSet(["User"]);
540 	assert(!fss.empty);
541 	assert(fss.front.name == "hello", fss.front.name);
542 	fss.popFront();
543 	assert(!fss.empty);
544 	assert(fss.front.name == "name");
545 	fss.popFront();
546 	assert(!fss.empty);
547 	assert(fss.front.name == "age");
548 	fss.popFront();
549 	assert(fss.empty);
550 	r.popFront();
551 	assert(r.empty);
552 }
553 
554 unittest {
555 	string s = `{
556  user(id: 1) {
557 	 hello
558 	 ...foo
559 	 ...bar
560  }
561 }
562 
563 fragment foo on User {
564 	name
565 }
566 
567 fragment bar on User {
568 	age
569 	...baz
570 }
571 
572 fragment baz on User {
573 	args
574 }
575 `;
576 	auto l = Lexer(s);
577 	auto p = Parser(l);
578 	auto d = p.parseDocument();
579 
580 	auto f = findFragment(d, "foo", ["User"]);
581 	assert(f !is null);
582 
583 	auto f2 = findFragment(d, "bar", ["User"]);
584 	assert(f2 !is null);
585 
586 	FieldRange r = fieldRange(d.defs.def.op, d, ["User"]);
587 	assert(!r.empty);
588 	assert(r.front.name == "user");
589 	ArgumentRange argL = r.front.arguments();
590 	assert(!argL.empty);
591 	auto ari = argL.front;
592 	assert(ari.name == "id");
593 	argL.popFront();
594 	assert(argL.empty);
595 	assert(r.front.hasSelectionSet());
596 	auto fss = r.front.selectionSet(["User"]);
597 	assert(!fss.empty);
598 	assert(fss.front.name == "hello", fss.front.name);
599 	fss.popFront();
600 	assert(!fss.empty);
601 	assert(fss.front.name == "name");
602 	fss.popFront();
603 	assert(!fss.empty);
604 	assert(fss.front.name == "age");
605 	fss.popFront();
606 	assert(!fss.empty);
607 	assert(fss.front.name == "args");
608 	fss.popFront();
609 	assert(fss.empty);
610 	r.popFront();
611 	assert(r.empty);
612 }
613 
614 unittest {
615 	string s = `{
616  user(id: 1) {
617 	 hello
618 	 ...foo
619 	 zzzz
620 	 ...bar
621  }
622 }
623 
624 fragment foo on User {
625 	name
626 }
627 
628 fragment bar on User {
629 	age
630 	...baz
631 }
632 
633 fragment baz on User {
634 	args
635 }
636 `;
637 	auto l = Lexer(s);
638 	auto p = Parser(l);
639 	auto d = p.parseDocument();
640 
641 	auto f = findFragment(d, "foo", ["User"]);
642 	assert(f !is null);
643 
644 	auto f2 = findFragment(d, "bar", ["User"]);
645 	assert(f2 !is null);
646 
647 	FieldRange r = fieldRange(d.defs.def.op, d, ["User"]);
648 	assert(!r.empty);
649 	assert(r.front.name == "user");
650 	ArgumentRange argL = r.front.arguments();
651 	assert(!argL.empty);
652 	auto ari = argL.front;
653 	assert(ari.name == "id");
654 	argL.popFront();
655 	assert(argL.empty);
656 	assert(r.front.hasSelectionSet());
657 	auto fss = r.front.selectionSet(["User"]);
658 	assert(!fss.empty);
659 	assert(fss.front.name == "hello", fss.front.name);
660 	fss.popFront();
661 	assert(!fss.empty);
662 	assert(fss.front.name == "name");
663 	fss.popFront();
664 	assert(!fss.empty);
665 	assert(fss.front.name == "zzzz");
666 	fss.popFront();
667 	assert(!fss.empty);
668 	assert(fss.front.name == "age");
669 	fss.popFront();
670 	assert(!fss.empty);
671 	assert(fss.front.name == "args");
672 	fss.popFront();
673 	assert(fss.empty);
674 	r.popFront();
675 	assert(r.empty);
676 }
677 
678 unittest {
679 	import std.format : format;
680 	import std.stdio;
681 
682 	string s = `{
683  user(id: 1) {
684 	 hello
685 	 ...foo
686 	 zzzz
687 	 ...bar
688  }
689 }
690 
691 fragment foo on User {
692 	name
693 }
694 
695 fragment bar on User {
696 	age
697 	...baz
698 }
699 
700 fragment baz on User {
701 	args
702 }
703 `;
704 	auto l = Lexer(s);
705 	auto p = Parser(l);
706 	auto d = p.parseDocument();
707 
708 	auto nn = ["hello", "name", "zzzz", "age", "args"];
709 	size_t cnt = 0;
710 	foreach(it; opDefRange(d)) {
711 		++cnt;
712 		long idx;
713 		foreach(jt; it.fieldRange(["User"])) {
714 			//writef("%s(", jt.name);
715 			foreach(var; jt.arguments()) {
716 				//writef("%s, ", var.name());
717 			}
718 			//writeln(")");
719 			foreach(kt; jt.selectionSet(["User"])) {
720 				//writeln("\t", kt.name);
721 				assert(kt.name == nn[idx],
722 						format("%s == %d(%s)", kt.name, idx, nn[idx])
723 					);
724 				++idx;
725 			}
726 		}
727 	}
728 	assert(cnt == 1);
729 }