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