1 module graphql.starwars.query;
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.exception;
17 import graphql.helper;
18 import graphql.starwars.data;
19 import graphql.starwars.schema;
20 import graphql.starwars.types;
21 import graphql.validation.querybased;
22 import graphql.validation.schemabased;
23 
24 @safe:
25 
26 Json query(string s) {
27 	return query(s, Json.init);
28 }
29 
30 Json query(string s, Json args) {
31 	auto graphqld = new GraphQLD!(StarWarsSchema);
32 	//auto lo = new std.experimental.logger.FileLogger("query.log");
33 	//graphqld.defaultResolverLog = lo;
34 	//graphqld.executationTraceLog = 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;
78 				if(name == "secretBackstory") {
79 					throw new GQLDExecutionException("secretBackstory is secret");
80 				}
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 	Json rslt = query(
122 		`query HeroNameQuery {
123 			hero {
124 				name
125 			}
126 		}`);
127 
128 	string s = `{ "data" : { "hero" : { "name" : "R2-D2" } } }`;
129 	Json exp = parseJson(s);
130 	assert(rslt == exp, rslt.toPrettyString() ~ "\n" ~ exp.toPrettyString());
131 }
132 
133 @safe unittest {
134 	Json rslt = query(
135 		`query HeroNameQuery {
136 			hero {
137 				name
138 			}
139 		}`);
140 	string s = `{ "data" : { "hero" : { "name" : "R2-D2" } } }`;
141 	Json exp = parseJson(s);
142 	assert(rslt == exp, rslt.toPrettyString() ~ "\n" ~ exp.toPrettyString());
143 }
144 
145 @safe unittest {
146 	Json rslt = query(
147 		`query HeroNameAndFriendsQuery {
148 			hero {
149 				id
150 				name
151 				friends {
152 					name
153 				}
154 			}
155 		}`);
156 	string s = `{
157 			"data": {
158 				"hero": {
159 					"id": "2001",
160 					"name": "R2-D2",
161 					"friends": [
162 						{
163 							"name": "Luke Skywalker",
164 						},
165 						{
166 							"name": "Han Solo",
167 						},
168 						{
169 							"name": "Leia Organa",
170 						}
171 					]
172 				}
173 			}
174 		}`;
175 	Json exp = parseJson(s);
176 	assert(rslt == exp, rslt.toPrettyString() ~ "\n" ~ exp.toPrettyString());
177 }
178 
179 @safe unittest {
180 	Json rslt = query(`
181 		query NestedQuery {
182 			hero {
183 				name
184 				friends {
185 					name
186 					appearsIn
187 					friends {
188 						name
189 					}
190 				}
191 			}
192 		}`);
193 
194 	string s = `
195 	{
196 		"data": {
197 			"hero": {
198 				"name": "R2-D2",
199 				"friends": [
200 					{
201 						"name": "Luke Skywalker",
202 						"appearsIn": ["NEWHOPE", "EMPIRE", "JEDI"],
203 						"friends": [
204 							{ "name": "Han Solo", },
205 							{ "name": "Leia Organa", },
206 							{ "name": "C-3PO", },
207 							{ "name": "R2-D2", },
208 						],
209 					},
210 					{
211 						"name": "Han Solo",
212 						"appearsIn": ["NEWHOPE", "EMPIRE", "JEDI"],
213 						"friends": [
214 							{ "name": "Luke Skywalker", },
215 							{ "name": "Leia Organa", },
216 							{ "name": "R2-D2", },
217 						],
218 					},
219 					{
220 						"name": "Leia Organa",
221 						"appearsIn": ["NEWHOPE", "EMPIRE", "JEDI"],
222 						"friends": [
223 							{ "name": "Luke Skywalker", },
224 							{ "name": "Han Solo", },
225 							{ "name": "C-3PO", },
226 							{ "name": "R2-D2", },
227 						],
228 					},
229 				],
230 			},
231 		}
232 	}`;
233 	Json exp = parseJson(s);
234 	assert(rslt == exp, rslt.toPrettyString() ~ "\n" ~ exp.toPrettyString());
235 }
236 
237 @safe unittest {
238 	Json rslt = query(`
239 		query FetchLukeQuery {
240 			human(id: "1000") {
241 				name
242 			}
243 		}`);
244 
245 	string s = `{ "data" : { "human" : { "name" : "Luke Skywalker" } } }`;
246 	Json exp = parseJson(s);
247 	assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s",
248 			exp.toPrettyString(), rslt.toPrettyString()));
249 }
250 
251 @safe unittest {
252 	string args = `{"a": false}`;
253 	Json rslt = query(`
254 		query FetchLukeQuery($a: Boolean!) {
255 			human(id: "1000") @include(if: $a) {
256 				name
257 			}
258 		}`, parseJson(args));
259 
260 	string s = `{ "data" : { } } }`;
261 	Json exp = parseJson(s);
262 	assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s",
263 			exp.toPrettyString(), rslt.toPrettyString()));
264 }
265 
266 @safe unittest {
267 	Json rslt = query(`
268 		query FetchLukeQuery() {
269 			human(id: "1000") @include(if: false) {
270 				name
271 			}
272 		}`);
273 
274 	string s = `{ "data" : { } } }`;
275 	Json exp = parseJson(s);
276 	assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s",
277 			exp.toPrettyString(), rslt.toPrettyString()));
278 }
279 
280 @safe unittest {
281 	Json rslt = query(`
282 		query FetchLukeQuery() {
283 			human(id: "1000") @include(if: true) {
284 				name
285 			}
286 		}`);
287 
288 	string s = `{ "data" : { "human" : { "name" : "Luke Skywalker" } } }`;
289 	Json exp = parseJson(s);
290 	assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s",
291 			exp.toPrettyString(), rslt.toPrettyString()));
292 }
293 
294 @safe unittest {
295 	string args = `{"a": true}`;
296 	Json rslt = query(`
297 		query FetchLukeQuery($a: Boolean!) {
298 			human(id: "1000") @include(if: $a) {
299 				name
300 			}
301 		}`, parseJson(args));
302 
303 	string s = `{ "data" : { "human" : { "name" : "Luke Skywalker" } } }`;
304 	Json exp = parseJson(s);
305 	assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s",
306 			exp.toPrettyString(), rslt.toPrettyString()));
307 }
308 
309 @safe unittest {
310 	auto args = `{"someId": "1000"}`;
311 	Json rslt = query(`
312 		query FetchSomeIDQuery($someId: String!) {
313 				human(id: $someId) {
314 					name
315 			}
316 		}`, parseJson(args));
317 
318 	string s = `{ "data" : { "human" : { "name" : "Luke Skywalker" } } }`;
319 	Json exp = parseJson(s);
320 	assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s",
321 			exp.toPrettyString(), rslt.toPrettyString()));
322 }
323 
324 @safe unittest {
325 	auto args = `{"someId": "1002"}`;
326 	Json rslt = query(`
327 		query FetchSomeIDQuery($someId: String!) {
328 				human(id: $someId) {
329 					name
330 			}
331 		}`, parseJson(args));
332 
333 	string s = `{ "data" : { "human" : { "name" : "Han Solo" } } }`;
334 	Json exp = parseJson(s);
335 	assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s",
336 			exp.toPrettyString(), rslt.toPrettyString()));
337 }
338 
339 @safe unittest {
340 	auto args = `{ "id" : "not a valid id" }`;
341 	Json rslt = query(`
342 		query humanQuery($id: String!) {
343 			human(id: $id) {
344 				name
345 			}
346 		}`, parseJson(args));
347 
348 	string s = `{ "data" : { "human" : null } }`;
349 	Json exp = parseJson(s);
350 	assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s",
351 			exp.toPrettyString(), rslt.toPrettyString()));
352 }
353 
354 @safe unittest {
355 	Json rslt = query(`
356 		query FetchLukeAliased {
357 			luke: human(id: "1000") {
358 				name
359 			}
360 		}`);
361 
362 	string s = `{ "data" : { "luke" : { "name" : "Luke Skywalker" } } }`;
363 	Json exp = parseJson(s);
364 	assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s",
365 			exp.toPrettyString(), rslt.toPrettyString()));
366 }
367 
368 @safe unittest {
369 	Json rslt = query(`
370 		query FetchLukeAndLeiaAliased {
371 			luke: human(id: "1000") {
372 				name
373 			}
374 			leia: human(id: "1003") {
375 				name
376 			}
377 		}`);
378 
379 	string s = `{ "data" : { "luke" : { "name" : "Luke Skywalker" },
380 		"leia" : { "name" : "Leia Organa" } } }`;
381 	Json exp = parseJson(s);
382 	assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s",
383 			exp.toPrettyString(), rslt.toPrettyString()));
384 }
385 
386 @safe unittest {
387 	Json rslt = query(`
388 		query DuplicateFields {
389 			luke: human(id: "1000") {
390 				name
391 				homePlanet
392 			}
393 			leia: human(id: "1003") {
394 				name
395 				homePlanet
396 			}
397 		}`);
398 
399 	string s = `{ "data" :
400 		{ "luke" :
401 			{ "name" : "Luke Skywalker", "homePlanet" : "Tatooine" }
402 		, "leia" :
403 			{ "name" : "Leia Organa", "homePlanet" : "Alderaan" }
404 		}
405 	}`;
406 	Json exp = parseJson(s);
407 	assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s",
408 			exp.toPrettyString(), rslt.toPrettyString()));
409 }
410 
411 @safe unittest {
412 	Json rslt = query(`
413 		query UseFragment {
414 			luke: human(id: "1000") {
415 				...HumanFragment
416 			}
417 			leia: human(id: "1003") {
418 				...HumanFragment
419 			}
420 		}
421 		fragment HumanFragment on Human {
422 			name
423 			homePlanet
424 		}`);
425 
426 	string s = `{ "data" :
427 		{ "luke" :
428 			{ "name" : "Luke Skywalker", "homePlanet" : "Tatooine" }
429 		, "leia" :
430 			{ "name" : "Leia Organa", "homePlanet" : "Alderaan" }
431 		}
432 	}`;
433 	Json exp = parseJson(s);
434 	assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s",
435 			exp.toPrettyString(), rslt.toPrettyString()));
436 }
437 
438 @safe unittest {
439 	Json rslt = query(`
440 		 query CheckTypeOfR2 {
441 			hero {
442 				__typename
443 				name
444 			}
445 		}`);
446 
447 	string s = `{"data" : { "hero" : { "__typename": "Droid", "name": "R2-D2" }
448 	} }`;
449 	Json exp = parseJson(s);
450 	assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s",
451 			exp.toPrettyString(), rslt.toPrettyString()));
452 }
453 
454 @safe unittest {
455 	Json rslt = query(`
456 		query CheckTypeOfLuke {
457 			hero(episode: EMPIRE) {
458 				__typename
459 				name
460 			}
461 		}`);
462 
463 	string s = `{"data" : { "hero" : { "__typename": "Human",
464 		"name": "Luke Skywalker" }
465 	} }`;
466 	Json exp = parseJson(s);
467 	assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s",
468 			exp.toPrettyString(), rslt.toPrettyString()));
469 }
470 
471 @safe unittest {
472 	Json rslt = query(`
473 		query HeroNameQuery {
474 			hero {
475 				name
476 				secretBackstory
477 			}
478 		}`);
479 
480 	string s = `{"data" : { "hero" : { "name": "R2-D2",
481 				"secretBackstory": null, } }, "errors" : [ {
482 				"path": [ "HeroNameQuery", "hero", "secretBackstory"],
483 				"message": "secretBackstory is secret" } ]}`;
484 	Json exp = parseJson(s);
485 	assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s",
486 			exp.toPrettyString(), rslt.toPrettyString()));
487 }
488 
489 @safe unittest {
490 	Json rslt = query(`
491 		query HeroNameQuery {
492 			hero {
493 				name
494 				friends {
495 					name
496 					secretBackstory
497 				}
498 			}
499 		}`);
500 
501 	string s = `{
502 		"errors" : [
503 			{ "message": "secretBackstory is secret"
504 			, "path": [ "HeroNameQuery", "hero", "friends", 0, "secretBackstory"] },
505 			{ "message": "secretBackstory is secret"
506 			, "path": [ "HeroNameQuery", "hero", "friends", 1, "secretBackstory"] },
507 			{ "message": "secretBackstory is secret"
508 			, "path": [ "HeroNameQuery", "hero", "friends", 2, "secretBackstory"] }
509 		],
510 		"data": { "hero": { "name": "R2-D2",
511 				"friends": [
512 					{ "name": "Luke Skywalker", "secretBackstory": null },
513 					{ "name": "Han Solo", "secretBackstory": null },
514 					{ "name": "Leia Organa", "secretBackstory": null }
515 				]
516 			}
517 		}
518 	}`;
519 	Json exp = parseJson(s);
520 	assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s",
521 			exp.toPrettyString(), rslt.toPrettyString()));
522 }
523 
524 @safe unittest {
525 	Json rslt = query(`
526 		query HeroNameQuery {
527 			mainHero: hero {
528 				name
529 				story: secretBackstory
530 			}
531 		}`);
532 
533 	string s = `{
534 		"data": {
535 			"mainHero": {
536 				"name": "R2-D2",
537 				"story": null,
538 			},
539 		},
540 		"errors" : [
541 			{ "message": "secretBackstory is secret"
542 			, "path": [ "HeroNameQuery", "mainHero", "story"] }
543 		]
544 	}`;
545 	Json exp = parseJson(s);
546 	assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s",
547 			exp.toPrettyString(), rslt.toPrettyString()));
548 }