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.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 	auto args = `{"someId": "1000"}`;
252 	Json rslt = query(`
253 		query FetchSomeIDQuery($someId: String!) {
254 				human(id: $someId) {
255 					name
256 			}
257 		}`, parseJson(args));
258 
259 	string s = `{ "data" : { "human" : { "name" : "Luke Skywalker" } } }`;
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 	auto args = `{"someId": "1002"}`;
267 	Json rslt = query(`
268 		query FetchSomeIDQuery($someId: String!) {
269 				human(id: $someId) {
270 					name
271 			}
272 		}`, parseJson(args));
273 
274 	string s = `{ "data" : { "human" : { "name" : "Han Solo" } } }`;
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 	auto args = `{ "id" : "not a valid id" }`;
282 	Json rslt = query(`
283 		query humanQuery($id: String!) {
284 			human(id: $id) {
285 				name
286 			}
287 		}`, parseJson(args));
288 
289 	string s = `{ "data" : { "human" : null } }`;
290 	Json exp = parseJson(s);
291 	assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s",
292 			exp.toPrettyString(), rslt.toPrettyString()));
293 }
294 
295 @safe unittest {
296 	Json rslt = query(`
297 		query FetchLukeAliased {
298 			luke: human(id: "1000") {
299 				name
300 			}
301 		}`);
302 
303 	string s = `{ "data" : { "luke" : { "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 	Json rslt = query(`
311 		query FetchLukeAndLeiaAliased {
312 			luke: human(id: "1000") {
313 				name
314 			}
315 			leia: human(id: "1003") {
316 				name
317 			}
318 		}`);
319 
320 	string s = `{ "data" : { "luke" : { "name" : "Luke Skywalker" },
321 		"leia" : { "name" : "Leia Organa" } } }`;
322 	Json exp = parseJson(s);
323 	assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s",
324 			exp.toPrettyString(), rslt.toPrettyString()));
325 }
326 
327 @safe unittest {
328 	Json rslt = query(`
329 		query DuplicateFields {
330 			luke: human(id: "1000") {
331 				name
332 				homePlanet
333 			}
334 			leia: human(id: "1003") {
335 				name
336 				homePlanet
337 			}
338 		}`);
339 
340 	string s = `{ "data" :
341 		{ "luke" :
342 			{ "name" : "Luke Skywalker", "homePlanet" : "Tatooine" }
343 		, "leia" :
344 			{ "name" : "Leia Organa", "homePlanet" : "Alderaan" }
345 		}
346 	}`;
347 	Json exp = parseJson(s);
348 	assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s",
349 			exp.toPrettyString(), rslt.toPrettyString()));
350 }
351 
352 @safe unittest {
353 	Json rslt = query(`
354 		query UseFragment {
355 			luke: human(id: "1000") {
356 				...HumanFragment
357 			}
358 			leia: human(id: "1003") {
359 				...HumanFragment
360 			}
361 		}
362 		fragment HumanFragment on Human {
363 			name
364 			homePlanet
365 		}`);
366 
367 	string s = `{ "data" :
368 		{ "luke" :
369 			{ "name" : "Luke Skywalker", "homePlanet" : "Tatooine" }
370 		, "leia" :
371 			{ "name" : "Leia Organa", "homePlanet" : "Alderaan" }
372 		}
373 	}`;
374 	Json exp = parseJson(s);
375 	assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s",
376 			exp.toPrettyString(), rslt.toPrettyString()));
377 }
378 
379 @safe unittest {
380 	Json rslt = query(`
381 		 query CheckTypeOfR2 {
382 			hero {
383 				__typename
384 				name
385 			}
386 		}`);
387 
388 	string s = `{"data" : { "hero" : { "__typename": "Droid", "name": "R2-D2" }
389 	} }`;
390 	Json exp = parseJson(s);
391 	assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s",
392 			exp.toPrettyString(), rslt.toPrettyString()));
393 }
394 
395 @safe unittest {
396 	Json rslt = query(`
397 		query CheckTypeOfLuke {
398 			hero(episode: EMPIRE) {
399 				__typename
400 				name
401 			}
402 		}`);
403 
404 	string s = `{"data" : { "hero" : { "__typename": "Human",
405 		"name": "Luke Skywalker" }
406 	} }`;
407 	Json exp = parseJson(s);
408 	assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s",
409 			exp.toPrettyString(), rslt.toPrettyString()));
410 }
411 
412 @safe unittest {
413 	Json rslt = query(`
414 		query HeroNameQuery {
415 			hero {
416 				name
417 				secretBackstory
418 			}
419 		}`);
420 
421 	string s = `{"data" : { "hero" : { "name": "R2-D2",
422 				"secretBackstory": null, } }, "errors" : [ {
423 				"path": [ "HeroNameQuery", "hero", "secretBackstory"],
424 				"message": "secretBackstory is secret" } ]}`;
425 	Json exp = parseJson(s);
426 	assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s",
427 			exp.toPrettyString(), rslt.toPrettyString()));
428 }
429 
430 @safe unittest {
431 	Json rslt = query(`
432 		query HeroNameQuery {
433 			hero {
434 				name
435 				friends {
436 					name
437 					secretBackstory
438 				}
439 			}
440 		}`);
441 
442 	string s = `{
443 		"errors" : [
444 			{ "message": "secretBackstory is secret"
445 			, "path": [ "HeroNameQuery", "hero", "friends", 0, "secretBackstory"] },
446 			{ "message": "secretBackstory is secret"
447 			, "path": [ "HeroNameQuery", "hero", "friends", 1, "secretBackstory"] },
448 			{ "message": "secretBackstory is secret"
449 			, "path": [ "HeroNameQuery", "hero", "friends", 2, "secretBackstory"] }
450 		],
451 		"data": { "hero": { "name": "R2-D2",
452 				"friends": [
453 					{ "name": "Luke Skywalker", "secretBackstory": null },
454 					{ "name": "Han Solo", "secretBackstory": null },
455 					{ "name": "Leia Organa", "secretBackstory": null }
456 				]
457 			}
458 		}
459 	}`;
460 	Json exp = parseJson(s);
461 	assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s",
462 			exp.toPrettyString(), rslt.toPrettyString()));
463 }
464 
465 @safe unittest {
466 	Json rslt = query(`
467 		query HeroNameQuery {
468 			mainHero: hero {
469 				name
470 				story: secretBackstory
471 			}
472 		}`);
473 
474 	string s = `{
475 		"data": {
476 			"mainHero": {
477 				"name": "R2-D2",
478 				"story": null,
479 			},
480 		},
481 		"errors" : [
482 			{ "message": "secretBackstory is secret"
483 			, "path": [ "HeroNameQuery", "mainHero", "story"] }
484 		]
485 	}`;
486 	Json exp = parseJson(s);
487 	assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s",
488 			exp.toPrettyString(), rslt.toPrettyString()));
489 }