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