1 module graphql.schema.typeconversions;
2 
3 import std.array : empty;
4 import std.algorithm.searching : canFind;
5 import std.conv : to;
6 import std.stdio;
7 import std.traits;
8 import std.meta;
9 import std.typecons : Nullable, nullable;
10 import std.range : ElementEncodingType;
11 
12 import vibe.data.json;
13 
14 import nullablestore;
15 
16 import graphql.schema.types;
17 import graphql.traits;
18 import graphql.uda;
19 import graphql.constants;
20 
21 @safe:
22 
23 template typeToTypeEnum(Type) {
24 	static if(isAggregateType!Type && hasUDA!(Type, GQLDUdaData)
25 			&& getUDAs!(Type, GQLDUdaData)[0].typeKind != TypeKind.UNDEFINED)
26 	{
27 		enum udas = getUDAs!(Type, GQLDUdaData);
28 		static assert(udas.length == 1);
29 		enum GQLDUdaData u = udas[0];
30 		enum typeToTypeEnum = to!string(u.typeKind);
31 	} else static if(is(Type == enum)) {
32 		enum typeToTypeEnum = "ENUM";
33 	} else static if(is(Type == bool)) {
34 		enum typeToTypeEnum = "SCALAR";
35 	} else static if(is(Type : GQLDCustomLeaf!F, F)) {
36 		enum typeToTypeEnum = "SCALAR";
37 	} else static if(isFloatingPoint!(Type)) {
38 		enum typeToTypeEnum = "SCALAR";
39 	} else static if(isIntegral!(Type)) {
40 		enum typeToTypeEnum = "SCALAR";
41 	} else static if(isSomeString!Type) {
42 		enum typeToTypeEnum = "SCALAR";
43 	} else static if(is(Type == void)) {
44 		enum typeToTypeEnum = "SCALAR";
45 	} else static if(is(Type == union)) {
46 		enum typeToTypeEnum = "UNION";
47 	} else static if(isAggregateType!Type) {
48 		enum typeToTypeEnum = "OBJECT";
49 	} else {
50 		static assert(false, Type.stringof ~ " not handled");
51 	}
52 }
53 
54 template typeToTypeName(Type) {
55 	import graphql.uda : GQLDCustomLeaf;
56 	static if(is(Type == enum)) {
57 		enum typeToTypeName = Type.stringof;
58 	} else static if(is(Type == bool)) {
59 		enum typeToTypeName = "Boolean";
60 	} else static if(is(Type == GQLDCustomLeaf!Fs, Fs...)) {
61 		enum typeToTypeName = Fs[0].stringof;
62 	} else static if(isFloatingPoint!(Type)) {
63 		enum typeToTypeName = "Float";
64 	} else static if(isIntegral!(Type)) {
65 		enum typeToTypeName = "Int";
66 	} else static if(isSomeString!Type) {
67 		enum typeToTypeName = "String";
68 	} else {
69 		enum typeToTypeName = Type.stringof;
70 	}
71 }
72 
73 unittest {
74 	import std.datetime : Date;
75 	static assert(typeToTypeName!(GQLDCustomLeaf!Date) == "Date");
76 }
77 
78 template typeToParameterTypeName(Type) {
79 	template level2(Type) {
80 		static if(is(Type : Nullable!F, F)) {
81 			enum level2 = typeToTypeName!F;
82 		} else {
83 			enum level2 = typeToTypeName!Type ~ "!";
84 		}
85 	}
86 
87 	template level1(Type) {
88 		static if(isArray!Type && !isSomeString!Type) {
89 			enum level1 = "[" ~ level2!(ElementEncodingType!Type) ~ "]";
90 		} else {
91 			enum level1 = typeToTypeName!Type;
92 		}
93 	}
94 
95 	template level0(Type) {
96 		static if(is(Type : Nullable!F, F)) {
97 			enum level0 = level1!F;
98 		} else {
99 			enum level0 = level1!Type ~ "!";
100 		}
101 	}
102 
103 	template levelM1(Type) {
104 		static if(is(Type : NullableStore!F, F)) {
105 			enum levelM1 = level0!F;
106 		} else {
107 			enum levelM1 = level0!Type;
108 		}
109 	}
110 
111 	enum typeToParameterTypeName = levelM1!Type;
112 }
113 
114 unittest {
115 	import std.datetime : Date;
116 	static assert(typeToParameterTypeName!(int) == "Int!");
117 	static assert(typeToParameterTypeName!(Nullable!int) == "Int");
118 	static assert(typeToParameterTypeName!(double) == "Float!");
119 	static assert(typeToParameterTypeName!(Nullable!double) == "Float");
120 	static assert(typeToParameterTypeName!(double[]) == "[Float!]!");
121 	static assert(typeToParameterTypeName!(Nullable!(double)[]) == "[Float]!");
122 	static assert(typeToParameterTypeName!(Nullable!(Nullable!(double)[])) ==
123 			"[Float]");
124 	static assert(typeToParameterTypeName!(GQLDCustomLeaf!Date) == "Date!");
125 }
126 
127 unittest {
128 	enum AEnum {
129 		one,
130 		two,
131 		three
132 	}
133 	static assert(typeToParameterTypeName!(AEnum) == "AEnum!");
134 	static assert(typeToParameterTypeName!(Nullable!AEnum) == "AEnum");
135 	static assert(typeToParameterTypeName!(Nullable!(AEnum)[]) == "[AEnum]!");
136 	static assert(typeToParameterTypeName!(Nullable!(Nullable!(AEnum)[])) ==
137 			"[AEnum]");
138 }
139 
140 template isScalarType(Type) {
141 	static if(is(Type == bool)) {
142 		enum isScalarType = true;
143 	} else static if(is(Type == GQLDCustomLeaf!Fs, Fs...)) {
144 		enum isScalarType = true;
145 	} else static if(isFloatingPoint!(Type)) {
146 		enum isScalarType = true;
147 	} else static if(isIntegral!(Type)) {
148 		enum isScalarType = true;
149 	} else static if(isSomeString!Type) {
150 		enum isScalarType = true;
151 	} else static if(is(Type == enum)) {
152 		enum isScalarType = true;
153 	} else {
154 		enum isScalarType = false;
155 	}
156 }
157 
158 template typeToFieldType(Type) {
159 	static if(isArray!Type && !isSomeString!Type) {
160 		enum typeToFieldType = "__listType";
161 	} else static if(is(Type : Nullable!F, F)) {
162 		enum typeToFieldType = F.stringof;
163 	} else static if(is(Type : NullableStore!F, F)) {
164 		enum typeToFieldType = Type.TypeValue.stringof;
165 	} else {
166 		enum typeToFieldType = "__nonNullType";
167 	}
168 }
169 
170 Json typeFields(T)() {
171 	import graphql.uda;
172 	static enum memsToIgnore = ["__ctor", "toString", "toHash", "opCmp",
173 			"opEquals", "Monitor", "factory"];
174 	Json ret = Json.emptyArray();
175 	bool[string] fieldsAlreadyIn;
176 	alias TplusParents = AliasSeq!(T, InheritedClasses!T);
177 	static foreach(Type; TplusParents) {{
178 		static foreach(mem; __traits(allMembers, Type)) {{
179 			enum GQLDUdaData udaData = getUdaData!(Type, mem);
180 			static if(!canFind(memsToIgnore, mem)
181 					&& udaData.ignore != Ignore.yes)
182 			{
183 				if(mem !in fieldsAlreadyIn) {
184 					fieldsAlreadyIn[mem] = true;
185 					Json tmp = Json.emptyObject();
186 					tmp[Constants.name] = mem;
187 					// needed for interfacesForType
188 					tmp[Constants.__typename] = "__Field";
189 					tmp[Constants.description] = udaData.description.getText()
190 						.empty
191 							? Json(null)
192 							: Json(udaData.description.getText());
193 
194 					tmp[Constants.isDeprecated] =
195 						udaData.deprecationInfo.isDeprecated == IsDeprecated.yes
196 							? true
197 							: false;
198 
199 					tmp[Constants.deprecationReason] =
200 						udaData.deprecationInfo.isDeprecated == IsDeprecated.yes
201 							? Json(udaData.deprecationInfo.deprecationReason)
202 							: Json(null);
203 
204 					tmp[Constants.args] = Json.emptyArray();
205 					static if(isCallable!(__traits(getMember, Type, mem))) {
206 						alias RT = ReturnType!(__traits(getMember, Type, mem));
207 						alias RTS = stripArrayAndNullable!RT;
208 						//tmp[Constants.typenameOrig] = typeToTypeName!(RT);
209 						tmp[Constants.typenameOrig] = typeToParameterTypeName!(RT);
210 
211 						// InputValue
212 						alias paraNames = ParameterIdentifierTuple!(
213 								__traits(getMember, Type, mem)
214 							);
215 						alias paraTypes = Parameters!(
216 								__traits(getMember, Type, mem)
217 							);
218 						alias paraDefs = ParameterDefaults!(
219 								__traits(getMember, Type, mem)
220 							);
221 						static foreach(idx; 0 .. paraNames.length) {{
222 							Json iv = Json.emptyObject();
223 							iv[Constants.name] = paraNames[idx];
224 							// needed for interfacesForType
225 							iv[Constants.__typename] = Constants.__InputValue;
226 							iv[Constants.description] = Json(null);
227 							static if(__traits(compiles, __traits(getAttributes,
228 									Parameters!(__traits(getMember, Type,
229 										mem))[idx ..  idx + 1])))
230 							{
231 								iv[Constants.description] = Json(null);
232 								alias udad = __traits(getAttributes,
233 										Parameters!(__traits(getMember, Type,
234 											mem))[idx ..  idx + 1]);
235 								static if(udad.length == 1) {
236 									enum F = udad[0];
237 									static if(is(typeof(F) == GQLDUdaData)) {
238 										iv[Constants.description] =
239 											F.description.text;
240 									}
241 								}
242 							}
243 							iv[Constants.typenameOrig] =
244 								typeToParameterTypeName!(paraTypes[idx]);
245 							static if(!is(paraDefs[idx] == void)) {
246 								iv[Constants.defaultValue] = serializeToJson(
247 										paraDefs[idx]
248 									)
249 									.toString();
250 							} else {
251 								iv[Constants.defaultValue] = Json(null);
252 							}
253 							tmp[Constants.args] ~= iv;
254 						}}
255 					} else {
256 						tmp[Constants.typenameOrig] =
257 							typeToParameterTypeName!(
258 							//typeToTypeName!(
259 								typeof(__traits(getMember, Type, mem))
260 							);
261 					}
262 					ret ~= tmp;
263 				}
264 			}
265 		}}
266 	}}
267 	//writefln("%s %s", __LINE__, ret.toPrettyString());
268 	return ret;
269 }
270 
271 Json inputFields(Type)() {
272 	Json ret = Json.emptyArray();
273 	alias types = FieldTypeTuple!Type;
274 	alias names = FieldNameTuple!Type;
275 	static foreach(idx; 0 .. types.length) {{
276 		enum GQLDUdaData udaData = getUdaData!(types[idx]);
277 		Json tmp = Json.emptyObject();
278 		tmp[Constants.name] = names[idx];
279 		tmp[Constants.description] = udaData.description.getText().empty
280 				? Json(null)
281 				: Json(udaData.description.getText());
282 
283 		// needed for interfacesForType
284 		tmp[Constants.__typename] = Constants.__InputValue;
285 
286 		//tmp[Constants.typenameOrig] = typeToTypeName!(types[idx]);
287 		tmp[Constants.typenameOrig] = typeToParameterTypeName!(types[idx]);
288 		tmp[Constants.defaultValue] = serializeToJson(
289 				__traits(getMember, Type.init, names[idx])
290 			);
291 		ret ~= tmp;
292 	}}
293 	return ret;
294 }
295 
296 Json emptyType() {
297 	Json ret = Json.emptyObject();
298 	ret[Constants.name] = Json(null);
299 	ret[Constants.description] = Json(null);
300 	ret[Constants.fields] = Json(null);
301 	ret[Constants.interfacesNames] = Json(null);
302 	ret[Constants.possibleTypesNames] = Json(null);
303 	ret[Constants.enumValues] = Json(null);
304 	ret["ofType"] = Json(null);
305 	return ret;
306 }
307 
308 Json removeNonNullAndList(Json j) {
309 	string t = j["kind"].get!string();
310 	if(t == "NON_NULL" || t == "LIST") {
311 		return removeNonNullAndList(j["ofType"]);
312 	} else {
313 		return j;
314 	}
315 }
316 
317 // remove the top nullable to find out if we have a NON_NULL or not
318 Json typeToJson(Type,Schema)() {
319 	static if(is(Type : Nullable!F, F)) {
320 		return typeToJson1!(F,Schema,Type)();
321 	} else static if(is(Type : NullableStore!F, F)) {
322 		return typeToJson1!(Type.TypeValue,Schema,Type)();
323 	} else {
324 		Json ret = emptyType();
325 		ret["kind"] = "NON_NULL";
326 		ret[Constants.__typename] = "__Type";
327 		ret["ofType"] = typeToJson1!(Type,Schema,Type)();
328 		return ret;
329 	}
330 }
331 
332 // remove the array is present
333 Json typeToJson1(Type,Schema,Orig)() {
334 	static if(isArray!Type && !isSomeString!Type && !is(Type == enum)) {
335 		Json ret = emptyType();
336 		ret["kind"] = "LIST";
337 		ret[Constants.__typename] = "__Type";
338 		ret["ofType"] = typeToJson2!(ElementEncodingType!Type, Schema, Orig)();
339 		return ret;
340 	} else {
341 		return typeToJsonImpl!(Type, Schema, Orig)();
342 	}
343 }
344 
345 // remove another nullable
346 Json typeToJson2(Type,Schema,Orig)() {
347 	static if(is(Type : Nullable!F, F)) {
348 		return typeToJsonImpl!(F, Schema, Orig)();
349 	} else static if(is(Type : NullableStore!F, F)) {
350 		return typeToJsonImpl!(Type.TypeValue, Schema, Orig)();
351 	} else {
352 		Json ret = emptyType();
353 		ret["kind"] = "NON_NULL";
354 		ret[Constants.__typename] = "__Type";
355 		ret["ofType"] = typeToJsonImpl!(Type, Schema, Orig)();
356 		return ret;
357 	}
358 }
359 
360 Json typeToJsonImpl(Type,Schema,Orig)() {
361 	Json ret = Json.emptyObject();
362 	enum string kind = typeToTypeEnum!(stripArrayAndNullable!Type);
363 	ret["kind"] = kind;
364 	ret[Constants.__typename] = "__Type";
365 	ret[Constants.name] = typeToTypeName!Type;
366 
367 	enum GQLDUdaData udaData = getUdaData!(Type);
368 	enum des = udaData.description.text;
369 	ret[Constants.description] = des.empty
370 			? Json(null)
371 			: Json(des);
372 
373 	ret[Constants.isDeprecated] =
374 		udaData.deprecationInfo.isDeprecated == IsDeprecated.yes
375 			? true
376 			: false;
377 
378 	ret[Constants.deprecationReason] =
379 		udaData.deprecationInfo.isDeprecated == IsDeprecated.yes
380 			? Json(udaData.deprecationInfo.deprecationReason)
381 			: Json(null);
382 
383 	// fields
384 	static if((is(Type == class) || is(Type == interface) || is(Type == struct))
385 			&& !is(Type : Nullable!K, K) && !is(Type : NullableStore!K, K)
386 			&& !is(Type : GQLDCustomLeaf!Ks, Ks...))
387 	{
388 		ret[Constants.fields] = typeFields!Type();
389 	} else {
390 		ret[Constants.fields] = Json(null);
391 	}
392 
393 	// inputFields
394 	static if(kind == Constants.INPUT_OBJECT) {
395 		ret[Constants.inputFields] = inputFields!Type();
396 	} else {
397 		ret[Constants.inputFields] = Json(null);
398 	}
399 
400 	// needed to resolve interfaces
401 	static if(is(Type == class) || is(Type == interface)) {
402 		ret[Constants.interfacesNames] = Json.emptyArray();
403 		static foreach(interfaces; InheritedClasses!Type) {{
404 			ret[Constants.interfacesNames] ~= interfaces.stringof;
405 		}}
406 	} else {
407 		ret[Constants.interfacesNames] = Json(null);
408 	}
409 
410 	// needed to resolve possibleTypes
411 	static if(is(Type == class) || is(Type == union)
412 			|| is(Type == interface))
413 	{
414 		ret[Constants.possibleTypesNames] = Json.emptyArray();
415 		alias PT = PossibleTypes!(Type, Schema);
416 		static foreach(pt; PT) {
417 			ret[Constants.possibleTypesNames] ~= pt.stringof;
418 		}
419 	} else {
420 		ret[Constants.possibleTypesNames] = Json(null);
421 	}
422 
423 	// enumValues
424 	static if(is(Type == enum)) {
425 		ret[Constants.enumValues] = Json.emptyArray();
426 		static foreach(mem; EnumMembers!Type) {{
427 			Json tmp = Json.emptyObject();
428 			tmp[Constants.__TypeKind] = Constants.__EnumValue;
429 			tmp[Constants.name] = Json(to!string(mem));
430 			tmp[Constants.description] = "ENUM_DESCRIPTION_TODO";
431 			tmp[Constants.isDeprecated] = false;
432 			tmp[Constants.deprecationReason] = "ENUM_DEPRECATIONREASON_TODO";
433 			ret[Constants.enumValues] ~= tmp;
434 		}}
435 	} else {
436 		ret[Constants.enumValues] = Json(null);
437 	}
438 
439 	// needed to resolve ofType
440 	static if(is(Type : Nullable!F, F)) {
441 		ret[Constants.ofTypeName] = F.stringof;
442 	} else static if(is(Type : NullableStore!F, F)) {
443 		ret[Constants.ofTypeName] = F.stringof;
444 	} else static if(is(Type : GQLDCustomLeaf!Fs, Fs...)) {
445 		ret[Constants.ofTypeName] = Fs[0].stringof;
446 	} else static if(isArray!Type) {
447 		ret[Constants.ofTypeName] = ElementEncodingType!(Type).stringof;
448 	}
449 
450 	return ret;
451 }
452 
453 @safe unittest {
454 	import std.format : format;
455 	Json r = typeToJson!(string,void)();
456 	Json e = parseJsonString(`
457 		{
458 			"__typename": "__Type",
459 			"possibleTypesNames": null,
460 			"enumValues": null,
461 			"interfacesNames": null,
462 			"kind": "NON_NULL",
463 			"name": null,
464 			"ofType": {
465 				"__typename": "__Type",
466 				"possibleTypesNames": null,
467 				"enumValues": null,
468 				"interfacesNames": null,
469 				"kind": "SCALAR",
470 				"isDeprecated": false,
471 				"deprecationReason": null,
472 				"name": "String",
473 				"description": null,
474 				"inputFields": null,
475 				"ofTypeName": "immutable(char)",
476 				"fields": null
477 			},
478 			"description": null,
479 			"fields": null
480 		}
481 		`);
482 	assert(r == e, format("exp:\n%s\ngot:\n%s", e.toPrettyString(),
483 				r.toPrettyString()));
484 }
485 
486 @safe unittest {
487 	enum FooBar {
488 		foo,
489 		bar
490 	}
491 
492 	import std.format : format;
493 	Json r = typeToJson!(FooBar,void)();
494 	Json e = parseJsonString(`
495 {
496 	"__typename": "__Type",
497 	"possibleTypesNames": null,
498 	"enumValues": null,
499 	"interfacesNames": null,
500 	"kind": "NON_NULL",
501 	"name": null,
502 	"ofType": {
503 		"__typename": "__Type",
504 		"possibleTypesNames": null,
505 		"enumValues": [
506 			{
507 				"description": "ENUM_DESCRIPTION_TODO",
508 				"deprecationReason": "ENUM_DEPRECATIONREASON_TODO",
509 				"__TypeKind": "__EnumValue",
510 				"isDeprecated": false,
511 				"name": "foo"
512 			},
513 			{
514 				"description": "ENUM_DESCRIPTION_TODO",
515 				"deprecationReason": "ENUM_DEPRECATIONREASON_TODO",
516 				"__TypeKind": "__EnumValue",
517 				"isDeprecated": false,
518 				"name": "bar"
519 			}
520 		],
521 		"interfacesNames": null,
522 		"kind": "ENUM",
523 		"isDeprecated": false,
524 		"deprecationReason": null,
525 		"name": "FooBar",
526 		"description": null,
527 		"inputFields": null,
528 		"fields": null
529 	},
530 	"description": null,
531 	"fields": null
532 }
533 		`);
534 	assert(r == e, format("exp:\n%s\ngot:\n%s", e.toPrettyString(),
535 				r.toPrettyString()));
536 }
537 @safe unittest {
538 	import std.format : format;
539 	Json r = typeToJson!(Nullable!string,void)();
540 	Json e = parseJsonString(`
541 		{
542 			"__typename": "__Type",
543 			"possibleTypesNames": null,
544 			"enumValues": null,
545 			"interfacesNames": null,
546 			"kind": "SCALAR",
547 			"isDeprecated": false,
548 			"deprecationReason": null,
549 			"name": "String",
550 			"description": null,
551 			"inputFields": null,
552 			"ofTypeName": "immutable(char)",
553 			"fields": null
554 		}
555 		`);
556 	assert(r == e, format("exp:\n%s\ngot:\n%s", e.toPrettyString(),
557 				r.toPrettyString()));
558 }
559 
560 @safe unittest {
561 	import std.format : format;
562 	Json r = typeToJson!(Nullable!string,void)();
563 	Json e = parseJsonString(`
564 		{
565 			"__typename": "__Type",
566 			"possibleTypesNames": null,
567 			"enumValues": null,
568 			"interfacesNames": null,
569 			"kind": "SCALAR",
570 			"isDeprecated": false,
571 			"deprecationReason": null,
572 			"name": "String",
573 			"description": null,
574 			"inputFields": null,
575 			"ofTypeName": "immutable(char)",
576 			"fields": null
577 		}
578 		`);
579 	assert(r == e, format("exp:\n%s\ngot:\n%s", e.toPrettyString(),
580 				r.toPrettyString()));
581 }
582 
583 Json directivesToJson(Directives)() {
584 	import std.string : stripLeft;
585 	Json ret = Json.emptyArray();
586 	static enum memsToIgnore = ["__ctor", "toString", "toHash", "opCmp",
587 			"opEquals", "Monitor", "factory"];
588 	alias TplusParents = AliasSeq!(Directives, InheritedClasses!Directives);
589 	static foreach(Type; TplusParents) {{
590 		static foreach(mem; __traits(allMembers, Type)) {{
591 			static if(!canFind(memsToIgnore, mem)) {
592 				Json tmp = Json.emptyObject();
593 				enum GQLDUdaData udaData = getUdaData!(Type, mem);
594 				tmp[Constants.name] = mem;
595 				// needed for interfacesForType
596 				tmp[Constants.__typename] = Constants.__Directive;
597 				tmp[Constants.description] = udaData.description.getText().empty
598 						? Json(null)
599 						: Json(udaData.description.text);
600 
601 				tmp[Constants.isDeprecated] =
602 					udaData.deprecationInfo.isDeprecated == IsDeprecated.yes
603 						? true
604 						: false;
605 
606 				tmp[Constants.deprecationReason] =
607 					udaData.deprecationInfo.isDeprecated == IsDeprecated.yes
608 						? Json(udaData.deprecationInfo.deprecationReason)
609 						: Json(null);
610 
611 				//tmp[Constants.description] = Json(null);
612 				tmp[Constants.locations] = Json.emptyArray();
613 				tmp[Constants.args] = Json.emptyArray();
614 				static if(isCallable!(__traits(getMember, Type, mem))) {
615 					// InputValue
616 					alias paraNames = ParameterIdentifierTuple!(
617 							__traits(getMember, Type, mem)
618 						);
619 					alias paraTypes = Parameters!(
620 							__traits(getMember, Type, mem)
621 						);
622 					alias paraDefs = ParameterDefaults!(
623 							__traits(getMember, Type, mem)
624 						);
625 					static foreach(idx; 0 .. paraNames.length) {{
626 						Json iv = Json.emptyObject();
627 						// TODO remove the strip left. Its in because the
628 						// two default directives of GraphQL skip and include
629 						// both have one parameter named "if".
630 						iv[Constants.name] = stripLeft(paraNames[idx], "_");
631 						iv[Constants.description] = Json(null);
632 						// needed for interfacesForType
633 						iv[Constants.__typename] = Constants.__InputValue;
634 						iv[Constants.typenameOrig] = typeToTypeName!(paraTypes[idx]);
635 						//iv[Constants.typenameOrig] = typeToParameterTypeName!(paraTypes[idx]);
636 						static if(!is(paraDefs[idx] == void)) {
637 							iv[Constants.defaultValue] = serializeToJson(paraDefs[idx])
638 								.toString();
639 						} else {
640 							iv[Constants.defaultValue] = Json(null);
641 						}
642 						tmp[Constants.args] ~= iv;
643 					}}
644 				}
645 				ret ~= tmp;
646 			}
647 		}}
648 	}}
649 	return ret;
650 }
651 
652 Json getField(Json j, string name) {
653 	import graphql.constants;
654 
655 	if(j.type != Json.Type.object || Constants.fields !in j
656 			|| j[Constants.fields].type != Json.Type.array)
657 	{
658 		return Json.init;
659 	}
660 
661 	foreach(it; j[Constants.fields].byValue) {
662 		string itName = it[Constants.name].get!string();
663 		if(itName == name) {
664 			return it;
665 		}
666 	}
667 	return Json.init;
668 }
669 
670 Json getIntrospectionField(string name) {
671 	import std.format : format;
672 	Json ret = Json.emptyObject();
673 	ret[Constants.typenameOrig] = name == Constants.__typename
674 		? "String"
675 		: name == Constants.__schema
676 			? "__Schema"
677 			: name == Constants.__type
678 				? "__Type"
679 				: format("Not known introspection name '%s'", name);
680 	return ret;
681 }