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