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