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