1 module graphql.starwars.introspection;
2 
3 import std.typecons : Nullable, nullable;
4 import std.format : format;
5 import std.stdio;
6 
7 import vibe.data.json;
8 
9 import graphql.constants;
10 import graphql.parser;
11 import graphql.builder;
12 import graphql.lexer;
13 import graphql.ast;
14 import graphql.graphql;
15 import graphql.helper;
16 import graphql.starwars.data;
17 import graphql.starwars.schema;
18 import graphql.starwars.types;
19 import graphql.validation.querybased;
20 import graphql.validation.schemabased;
21 
22 @safe:
23 
24 Json query(string s) {
25 	return query(s, Json.init);
26 }
27 
28 Json query(string s, Json args) {
29 	auto graphqld = new GraphQLD!(StarWarsSchema);
30 	//auto lo = new std.experimental.logger.FileLogger("query.log");
31 	//graphqld.defaultResolverLog = lo;
32 	//graphqld.executationTraceLog = lo;
33 	//graphqld.resolverLog = lo;
34 
35 	graphqld.setResolver("queryType", "human",
36 			delegate(string name, Json parent, Json args,
37 					ref DefaultContext con) @safe
38 			{
39 				auto idPtr = "id" in args;
40 				Json ret = Json.emptyObject();
41 				if(idPtr) {
42 					string id = idPtr.to!string();
43 					Human h = getHuman(id);
44 					if(h is null) {
45 						ret["data"] = Json(null);
46 						return ret;
47 					}
48 					Json hj = toGraphqlJson!StarWarsSchema(h);
49 					Json cj = toGraphqlJson!StarWarsSchema(cast(Character)h);
50 					cj.remove("__typename");
51 					ret["data"] = joinJson(hj, cj);
52 				}
53 				return ret;
54 			}
55 		);
56 
57 	graphqld.setResolver("queryType", "hero",
58 			delegate(string name, Json parent, Json args,
59 					ref DefaultContext con) @safe
60 			{
61 				import std.conv : to;
62 				auto e = "episode" in args;
63 				Json ret = Json.emptyObject();
64 				ret["data"] = toGraphqlJson!StarWarsSchema(getHero(
65 						e ? nullable((*e).to!string().to!Episode())
66 							: Nullable!(Episode).init
67 					));
68 				return ret;
69 			}
70 		);
71 
72 	graphqld.setResolver("Character", "secretBackstory",
73 			delegate(string name, Json parent, Json args,
74 					ref DefaultContext con) @safe
75 			{
76 				Json ret = Json.emptyObject();
77 				ret[Constants.data] = Json(null);
78 				ret[Constants.errors] = Json.emptyArray();
79 				ret.insertError("secretBackstory is secret");
80 				return ret;
81 			}
82 		);
83 
84 	graphqld.setResolver("Character", "friends",
85 			delegate(string name, Json parent, Json args,
86 					ref DefaultContext con) @safe
87 			{
88 				auto idPtr = "id" in parent;
89 				Json ret = Json.emptyObject();
90 				ret["data"] = Json.emptyArray();
91 				if(idPtr) {
92 					string id = idPtr.to!string();
93 					foreach(it; getFriends(getCharacter(id))) {
94 						ret["data"] ~= toGraphqlJson!StarWarsSchema(it);
95 					}
96 				}
97 				return ret;
98 			}
99 		);
100 
101 	auto l = Lexer(s);
102 	auto p = Parser(l);
103 
104 	Document d = p.parseDocument();
105 	const(Document) cd = d;
106 	QueryValidator fv = new QueryValidator(d);
107 	fv.accept(cd);
108 	noCylces(fv.fragmentChildren);
109 	allFragmentsReached(fv);
110 	SchemaValidator!StarWarsSchema sv = new SchemaValidator!StarWarsSchema(d,
111 			graphqld.schema
112 		);
113 	sv.accept(cd);
114 	DefaultContext con;
115 	Json gqld = graphqld.execute(d, args, con);
116 	return gqld;
117 }
118 
119 @safe unittest {
120 	Json rslt = query(`
121 		query IntrospectionTypeQuery {
122 			__schema {
123 				types {
124 					name
125 				}
126 			}
127 		}
128 		`);
129 
130 	string s = `{
131 		"data" : {
132 			"__schema" : {
133 				"types" : [
134 					{
135 						"name": "StarWarsQuery"
136 					},
137 					{
138 						"name": "Episode"
139 					},
140 					{
141 						"name": "Character"
142 					},
143 					{
144 						"name": "String"
145 					},
146 					{
147 						"name": "Human"
148 					},
149 					{
150 						"name": "Droid"
151 					},
152 					{
153 						"name": "__Schema"
154 					},
155 					{
156 						"name": "__Type"
157 					},
158 					{
159 						"name": "__TypeKind"
160 					},
161 					{
162 						"name": "Boolean"
163 					},
164 					{
165 						"name": "__Field"
166 					},
167 					{
168 						"name": "__InputValue"
169 					},
170 					{
171 						"name": "__EnumValue"
172 					},
173 					{
174 						"name": "__Directive"
175 					},
176 					{
177 						"name": "__DirectiveLocation"
178 					}
179 				]
180 			}
181 		}
182 	}`;
183 	Json exp = parseJson(s);
184 	auto cmpResult = compareJson(exp, rslt, "", true);
185 	// TODO make this test pass
186 	//assert(cmpResult.okay, format("msg: %s\npath: %--(%s,%)\nexp:\n%s\ngot:\n%s"
187 	//		, cmpResult.message, cmpResult.path, exp.toPrettyString()
188 	//		, rslt.toPrettyString()));
189 	if(!cmpResult.okay) {
190 		writefln("msg: %s\npath: %--(%s,%)\nexp:\n%s\ngot:\n%s"
191 			, cmpResult.message, cmpResult.path, exp.toPrettyString()
192 			, rslt.toPrettyString());
193 	}
194 }
195 
196 @safe unittest {
197 	Json rslt = query(`
198 		query IntrospectionQueryTypeQuery {
199 			__schema {
200 				queryType {
201 					name
202 				}
203 			}
204 		}
205 		`);
206 
207 	string s = `{
208 		"data" : {
209 			"__schema" : {
210 				"queryType" : {
211 					"name" : "StarWarsQuery"
212 				}
213 			}
214 		}
215 	}
216 	`;
217 	Json exp = parseJson(s);
218 	assert(rslt == exp, format("exp:\n%s\ngot:\n%s", exp.toPrettyString(),
219 			rslt.toPrettyString()));
220 }
221 
222 @safe unittest {
223 	Json rslt = query(`
224 		query IntrospectionDroidTypeQuery {
225 			__type(name: "Droid") {
226 				name
227 			}
228 		}
229 		`);
230 
231 	string s = `{
232 		"data" : {
233 			"__type" : {
234 				"name" : "Droid"
235 			}
236 		}
237 	}
238 	`;
239 	Json exp = parseJson(s);
240 	assert(rslt == exp, format("exp:\n%s\ngot:\n%s", exp.toPrettyString(),
241 			rslt.toPrettyString()));
242 }
243 
244 @safe unittest {
245 	Json rslt = query(`
246 		query IntrospectionDroidTypeQuery {
247 			__type(name: "Droid") {
248 				name
249 				kind
250 			}
251 		}
252 		`);
253 
254 	string s = `{
255 		"data" : {
256 			"__type" : {
257 				"name" : "Droid",
258 				"kind" : "OBJECT"
259 			}
260 		}
261 	}
262 	`;
263 	Json exp = parseJson(s);
264 	assert(rslt == exp, format("exp:\n%s\ngot:\n%s", exp.toPrettyString(),
265 			rslt.toPrettyString()));
266 }
267 
268 @safe unittest {
269 	Json rslt = query(`
270 		query IntrospectionCharacterKindQuery {
271 			__type(name: "Character") {
272 				name
273 				kind
274 			}
275 		}
276 		`);
277 
278 	string s = `{
279 		"data" : {
280 			"__type" : {
281 				"name" : "Character",
282 				"kind" : "INTERFACE"
283 			}
284 		}
285 	}
286 	`;
287 	Json exp = parseJson(s);
288 	assert(rslt == exp, format("exp:\n%s\ngot:\n%s", exp.toPrettyString(),
289 			rslt.toPrettyString()));
290 }
291 
292 @safe unittest {
293 	Json rslt = query(`
294 		query IntrospectionDroidFieldsQuery {
295 			__type(name: "Droid") {
296 				name
297 				fields {
298 					name
299 					type {
300 						name
301 						kind
302 					}
303 				}
304 			}
305 		}
306 		`);
307 
308 	string s = `{
309 		"data" : {
310 			"__type" : {
311 				"name" : "Droid",
312 				"fields" : [
313 					{
314 						"name": "primaryFunction",
315 						"type": {
316 							"name": "String",
317 							"kind": "SCALAR"
318 						}
319 					},
320 					{
321 						"name": "id",
322 						"type": {
323 							"name": null,
324 							"kind": "NON_NULL"
325 						}
326 					},
327 					{
328 						"name": "name",
329 						"type": {
330 							"name": "String",
331 							"kind": "SCALAR"
332 						}
333 					},
334 					{
335 						"name": "friends",
336 						"type": {
337 							"name": null,
338 							"kind": "LIST"
339 						}
340 					},
341 					{
342 						"name": "appearsIn",
343 						"type": {
344 							"name": null,
345 							"kind": "LIST"
346 						}
347 					},
348 					{
349 						"name": "secretBackstory",
350 						"type": {
351 							"name": "String",
352 							"kind": "SCALAR"
353 						}
354 					},
355 				]
356 			}
357 		}
358 	}
359 	`;
360 	Json exp = parseJson(s);
361 	assert(rslt == exp, format("exp:\n%s\ngot:\n%s", exp.toPrettyString(),
362 			rslt.toPrettyString()));
363 }
364 
365 @safe unittest {
366 	Json rslt = query(`
367 		query IntrospectionDroidNestedFieldsQuery {
368 			__type(name: "Droid") {
369 				name
370 				fields {
371 					name
372 					type {
373 						name
374 						kind
375 						ofType {
376 							name
377 							kind
378 						}
379 					}
380 				}
381 			}
382 		}
383 		`);
384 
385 	string s = `{
386 		"data" : {
387 			"__type" : {
388 				"name" : "Droid",
389 				"fields" : [
390 					{
391 						"name": "primaryFunction",
392 						"type": {
393 							"name": "String",
394 							"kind": "SCALAR",
395 							"ofType": null
396 						}
397 					},
398 					{
399 						"name": "id",
400 						"type": {
401 							"name": null,
402 							"kind": "NON_NULL",
403 							"ofType": {
404 								"name": "String",
405 								"kind": "SCALAR"
406 							}
407 						}
408 					},
409 					{
410 						"name": "name",
411 						"type": {
412 							"name": "String",
413 							"kind": "SCALAR",
414 							"ofType": null
415 						}
416 					},
417 					{
418 						"name": "friends",
419 						"type": {
420 							"name": null,
421 							"kind": "LIST",
422 							"ofType": {
423 								"name": "Character",
424 								"kind": "INTERFACE"
425 							}
426 						}
427 					},
428 					{
429 						"name": "appearsIn",
430 						"type": {
431 							"name": null,
432 							"kind": "LIST",
433 							"ofType": {
434 								"name": "Episode",
435 								"kind": "ENUM"
436 							}
437 						}
438 					},
439 					{
440 						"name": "secretBackstory",
441 						"type": {
442 							"name": "String",
443 							"kind": "SCALAR",
444 							"ofType": null
445 						}
446 					},
447 				]
448 			}
449 		}
450 	}
451 	`;
452 	Json exp = parseJson(s);
453 	assert(rslt == exp, format("exp:\n%s\ngot:\n%s", exp.toPrettyString(),
454 			rslt.toPrettyString()));
455 }
456 
457 @safe unittest {
458 	Json rslt = query(`
459 		query IntrospectionQueryTypeQuery {
460 			__schema {
461 				queryType {
462 					fields {
463 						name
464 						args {
465 							name
466 							description
467 							type {
468 								name
469 								kind
470 								ofType {
471 									name
472 									kind
473 								}
474 							}
475 							defaultValue
476 						}
477 					}
478 				}
479 			}
480 		}
481 		`);
482 
483 	string s = ` {
484 		"data" : {
485 			"__schema": {
486 				"queryType": {
487 					"fields": [
488 						{
489 							"name": "hero",
490 							"args": [
491 								{
492 									"defaultValue": null,
493 									"description":
494 										"If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode.",
495 									"name": "episode",
496 									"type": {
497 										"kind": "ENUM",
498 										"name": "Episode",
499 										"ofType": null
500 									}
501 								}
502 							]
503 						},
504 						{
505 							"name": "human",
506 							"args": [
507 								{
508 									"name": "id",
509 									"description": "id of the human",
510 									"type": {
511 										"kind": "NON_NULL",
512 										"name": null,
513 										"ofType": {
514 											"kind": "SCALAR",
515 											"name": "String"
516 										}
517 									},
518 									"defaultValue": null
519 								}
520 							]
521 						},
522 						{
523 							"name": "droid",
524 							"args": [
525 								{
526 									"name": "id",
527 									"description": "id of the droid",
528 									"type": {
529 										"kind": "NON_NULL",
530 										"name": null,
531 										"ofType": {
532 											"kind": "SCALAR",
533 											"name": "String"
534 										}
535 									},
536 									"defaultValue": null
537 								}
538 							]
539 						}
540 					]
541 				}
542 			}
543 		}
544 	}`;
545 	Json exp = parseJson(s);
546 	string extS = exp.toPrettyString();
547 	string rsltS = rslt.toPrettyString();
548 	assert(rslt == exp, format("exp:\n%s\ngot:\n%s", extS, rsltS));
549 }
550 
551 @safe unittest {
552 	Json rslt = query(`
553 		{
554 			__type(name: "Droid") {
555 				name
556 				description
557 			}
558 		}`);
559 
560 	string s = `{
561 		"data" : {
562 			"__type": {
563 				"name" : "Droid",
564 				"description" : "A mechanical creature in the Star Wars universe."
565 			}
566 		}
567 	}`;
568 	Json exp = parseJson(s);
569 	string extS = exp.toPrettyString();
570 	string rsltS = rslt.toPrettyString();
571 	assert(rslt == exp, format("exp:\n%s\ngot:\n%s", extS, rsltS));
572 }
573 
574 @safe unittest {
575 	Json rslt = query(`
576 		{
577 			__type(name: "Character") {
578 				fields {
579 					name
580 					description
581 				}
582 			}
583 		}`);
584 
585 	string s = `{
586 	"data": {
587 		"__type": {
588 			"fields": [
589 				{
590 					"description": "The id of the character",
591 					"name": "id"
592 				},
593 				{
594 					"description": "The name of the character",
595 					"name": "name"
596 				},
597 				{
598 					"description": "The friends of the character, or an empty list if they have none.",
599 					"name": "friends"
600 				},
601 				{
602 					"description": "Which movies they appear in.",
603 					"name": "appearsIn"
604 				},
605 				{
606 					"description": "Where are they from and how they came to be who they  are.",
607 					"name": "secretBackstory"
608 				}
609 			]
610 		}
611 	}
612 	}`;
613 	Json exp = parseJson(s);
614 	string extS = exp.toPrettyString();
615 	string rsltS = rslt.toPrettyString();
616 	assert(rslt == exp, format("exp:\n%s\ngot:\n%s", extS, rsltS));
617 }