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 						static if(!isType!(__traits(getMember, Type, mem))) {
271 							tmp[Constants.typenameOrig] =
272 								typeToParameterTypeName!(
273 								//typeToTypeName!(
274 									typeof(__traits(getMember, Type, mem))
275 								);
276 						}
277 					}
278 					ret ~= tmp;
279 				}
280 			}
281 		}}
282 	}}
283 	//writefln("%s %s", __LINE__, ret.toPrettyString());
284 	return ret;
285 }
286 
287 Json inputFields(Type)() {
288 	Json ret = Json.emptyArray();
289 	alias types = FieldTypeTuple!Type;
290 	alias names = FieldNameTuple!Type;
291 	static foreach(idx; 0 .. types.length) {{
292 		enum GQLDUdaData udaData = getUdaData!(types[idx]);
293 		Json tmp = Json.emptyObject();
294 		tmp[Constants.name] = names[idx];
295 		tmp[Constants.description] = udaData.description.getText().empty
296 				? Json(null)
297 				: Json(udaData.description.getText());
298 
299 		// needed for interfacesForType
300 		tmp[Constants.__typename] = Constants.__InputValue;
301 
302 		//tmp[Constants.typenameOrig] = typeToTypeName!(types[idx]);
303 		tmp[Constants.typenameOrig] = typeToParameterTypeName!(types[idx]);
304 		auto t = __traits(getMember, Type.init, names[idx]);
305 		tmp[Constants.defaultValue] = serializeToJson(t);
306 		ret ~= tmp;
307 	}}
308 	return ret;
309 }
310 
311 Json emptyType() {
312 	Json ret = Json.emptyObject();
313 	ret[Constants.name] = Json(null);
314 	ret[Constants.description] = Json(null);
315 	ret[Constants.fields] = Json(null);
316 	ret[Constants.interfacesNames] = Json(null);
317 	ret[Constants.possibleTypesNames] = Json(null);
318 	ret[Constants.enumValues] = Json(null);
319 	ret["ofType"] = Json(null);
320 	return ret;
321 }
322 
323 Json removeNonNullAndList(Json j) {
324 	immutable string t = j["kind"].get!string();
325 	if(t == "NON_NULL" || t == "LIST") {
326 		return removeNonNullAndList(j["ofType"]);
327 	} else {
328 		return j;
329 	}
330 }
331 
332 // remove the top nullable to find out if we have a NON_NULL or not
333 Json typeToJson(Type,Schema)() {
334 	static if(is(Type : Nullable!F, F)) {
335 		return typeToJson1!(F,Schema,Type)();
336 	} else static if(is(Type : NullableStore!F, F)) {
337 		return typeToJson1!(Type.TypeValue,Schema,Type)();
338 	} else {
339 		Json ret = emptyType();
340 		ret["kind"] = "NON_NULL";
341 		ret[Constants.__typename] = "__Type";
342 		ret["ofType"] = typeToJson1!(Type,Schema,Type)();
343 		return ret;
344 	}
345 }
346 
347 // remove the array is present
348 Json typeToJson1(Type,Schema,Orig)() {
349 	static if(isArray!Type && !isSomeString!Type && !is(Type == enum)) {
350 		Json ret = emptyType();
351 		ret["kind"] = "LIST";
352 		ret[Constants.__typename] = "__Type";
353 		ret["ofType"] = typeToJson2!(ElementEncodingType!Type, Schema, Orig)();
354 		return ret;
355 	} else {
356 		return typeToJsonImpl!(Type, Schema, Orig)();
357 	}
358 }
359 
360 // remove another nullable
361 Json typeToJson2(Type,Schema,Orig)() {
362 	static if(is(Type : Nullable!F, F)) {
363 		return typeToJsonImpl!(F, Schema, Orig)();
364 	} else static if(is(Type : NullableStore!F, F)) {
365 		return typeToJsonImpl!(Type.TypeValue, Schema, Orig)();
366 	} else {
367 		Json ret = emptyType();
368 		ret["kind"] = "NON_NULL";
369 		ret[Constants.__typename] = "__Type";
370 		ret["ofType"] = typeToJsonImpl!(Type, Schema, Orig)();
371 		return ret;
372 	}
373 }
374 
375 template notNullOrArray(T,S) {
376 	static if(is(Nullable!F : T, F)) {
377 		alias notNullOrArray = S;
378 	} else static if(isArray!T) {
379 		alias notNullOrArray = S;
380 	} else {
381 		alias notNullOrArray = T;
382 	}
383 }
384 
385 Json typeToJsonImpl(Type,Schema,Orig)() {
386 	Json ret = Json.emptyObject();
387 	enum string kind = typeToTypeEnum!(stripArrayAndNullable!Type);
388 	ret["kind"] = kind;
389 	ret[Constants.__typename] = "__Type";
390 	ret[Constants.name] = typeToTypeName!Type;
391 
392 	alias TypeOrig = notNullOrArray!(Type,Orig);
393 	enum GQLDUdaData udaData = getUdaData!(TypeOrig);
394 	enum des = udaData.description.text;
395 	ret[Constants.description] = des.empty
396 			? Json(null)
397 			: Json(des);
398 
399 	ret[Constants.isDeprecated] =
400 		udaData.deprecationInfo.isDeprecated == IsDeprecated.yes
401 			? true
402 			: false;
403 
404 	ret[Constants.deprecationReason] =
405 		udaData.deprecationInfo.isDeprecated == IsDeprecated.yes
406 			? Json(udaData.deprecationInfo.deprecationReason)
407 			: Json(null);
408 
409 	// fields
410 	static if((is(Type == class) || is(Type == interface) || is(Type == struct))
411 			&& !is(Type : Nullable!K, K) && !is(Type : NullableStore!K, K)
412 			&& !is(Type : GQLDCustomLeaf!Ks, Ks...))
413 	{
414 		ret[Constants.fields] = typeFields!Type();
415 	} else {
416 		ret[Constants.fields] = Json(null);
417 	}
418 
419 	// inputFields
420 	static if(kind == Constants.INPUT_OBJECT) {
421 		ret[Constants.inputFields] = inputFields!Type();
422 	} else {
423 		ret[Constants.inputFields] = Json(null);
424 	}
425 
426 	// needed to resolve interfaces
427 	static if(is(Type == class) || is(Type == interface)) {
428 		ret[Constants.interfacesNames] = Json.emptyArray();
429 		static foreach(interfaces; InheritedClasses!Type) {{
430 			ret[Constants.interfacesNames] ~= interfaces.stringof;
431 		}}
432 	} else {
433 		ret[Constants.interfacesNames] = Json(null);
434 	}
435 
436 	// needed to resolve possibleTypes
437 	static if(is(Type == class) || is(Type == union) || is(Type == interface)) {
438 		static if(is(Type == union)) {
439 			ret[Constants.possibleTypesNames] = Json.emptyArray();
440 			static foreach(pt; Filter!(isAggregateType, FieldTypeTuple!Type)) {
441 				ret[Constants.possibleTypesNames] ~= pt.stringof;
442 			}
443 		} else {
444 			import graphql.reflection;
445 			// need to search for all types that we support that are derived
446 			// from this type
447 			ret[Constants.possibleTypesNames] = Json.emptyArray();
448 			foreach(tname;
449 					SchemaReflection!Schema.instance.derivatives.get(typeid(Type),
450 																	 null))
451 			{
452 				ret[Constants.possibleTypesNames] ~= tname;
453 			}
454 		}
455 	} else {
456 		ret[Constants.possibleTypesNames] = Json(null);
457 	}
458 
459 	// enumValues
460 	static if(is(Type == enum)) {
461 		ret[Constants.enumValues] = Json.emptyArray();
462 		static foreach(mem; EnumMembers!Type) {{
463 			enum memUdaData = getUdaData!(Type, to!string(mem));
464 			Json tmp = Json.emptyObject();
465 			tmp[Constants.__TypeKind] = Constants.__EnumValue;
466 			tmp[Constants.name] = Json(to!string(mem));
467 			tmp[Constants.description] = memUdaData.description.text;
468 			tmp[Constants.isDeprecated] = (memUdaData.deprecationInfo.isDeprecated == IsDeprecated.yes);
469 			tmp[Constants.deprecationReason] = (memUdaData.deprecationInfo.isDeprecated == IsDeprecated.yes)
470 							? Json(memUdaData.deprecationInfo.deprecationReason)
471 							: Json(null);
472 			ret[Constants.enumValues] ~= tmp;
473 		}}
474 	} else {
475 		ret[Constants.enumValues] = Json(null);
476 	}
477 
478 	// needed to resolve ofType
479 	static if(is(Type : Nullable!F, F)) {
480 		ret[Constants.ofTypeName] = F.stringof;
481 	} else static if(is(Type : NullableStore!F, F)) {
482 		ret[Constants.ofTypeName] = F.stringof;
483 	} else static if(is(Type : GQLDCustomLeaf!Fs, Fs...)) {
484 		ret[Constants.ofTypeName] = Fs[0].stringof;
485 	} else static if(isArray!Type) {
486 		ret[Constants.ofTypeName] = ElementEncodingType!(Type).stringof;
487 	}
488 
489 	return ret;
490 }
491 
492 @safe unittest {
493 	import std.format : format;
494 	Json r = typeToJson!(string,void)();
495 	Json e = parseJsonString(`
496 		{
497 			"__typename": "__Type",
498 			"possibleTypesNames": null,
499 			"enumValues": null,
500 			"interfacesNames": null,
501 			"kind": "NON_NULL",
502 			"name": null,
503 			"ofType": {
504 				"__typename": "__Type",
505 				"possibleTypesNames": null,
506 				"enumValues": null,
507 				"interfacesNames": null,
508 				"kind": "SCALAR",
509 				"isDeprecated": false,
510 				"deprecationReason": null,
511 				"name": "String",
512 				"description": null,
513 				"inputFields": null,
514 				"ofTypeName": "immutable(char)",
515 				"fields": null
516 			},
517 			"description": null,
518 			"fields": null
519 		}
520 		`);
521 	assert(r == e, format("exp:\n%s\ngot:\n%s", e.toPrettyString(),
522 				r.toPrettyString()));
523 }
524 
525 @safe unittest {
526 	enum FooBar {
527 		@GQLDUda(GQLDDescription("important"))
528 		foo,
529 		@GQLDUda(
530 			GQLDDeprecated(IsDeprecated.yes, "not foo enough"),
531 			GQLDDescription("unimportant")
532 		)
533 		bar
534 	}
535 
536 	import std.format : format;
537 	Json r = typeToJson!(FooBar,void)();
538 	Json e = parseJsonString(`
539 {
540 	"__typename": "__Type",
541 	"possibleTypesNames": null,
542 	"enumValues": null,
543 	"interfacesNames": null,
544 	"kind": "NON_NULL",
545 	"name": null,
546 	"ofType": {
547 		"__typename": "__Type",
548 		"possibleTypesNames": null,
549 		"enumValues": [
550 			{
551 				"description": "important",
552 				"deprecationReason": null,
553 				"__TypeKind": "__EnumValue",
554 				"isDeprecated": false,
555 				"name": "foo"
556 			},
557 			{
558 				"description": "unimportant",
559 				"deprecationReason": "not foo enough",
560 				"__TypeKind": "__EnumValue",
561 				"isDeprecated": true,
562 				"name": "bar"
563 			}
564 		],
565 		"interfacesNames": null,
566 		"kind": "ENUM",
567 		"isDeprecated": false,
568 		"deprecationReason": null,
569 		"name": "FooBar",
570 		"description": null,
571 		"inputFields": null,
572 		"fields": null
573 	},
574 	"description": null,
575 	"fields": null
576 }
577 		`);
578 	assert(r == e, format("exp:\n%s\ngot:\n%s", e.toPrettyString(),
579 				r.toPrettyString()));
580 }
581 @safe unittest {
582 	import std.format : format;
583 	Json r = typeToJson!(Nullable!string,void)();
584 	Json e = parseJsonString(`
585 		{
586 			"__typename": "__Type",
587 			"possibleTypesNames": null,
588 			"enumValues": null,
589 			"interfacesNames": null,
590 			"kind": "SCALAR",
591 			"isDeprecated": false,
592 			"deprecationReason": null,
593 			"name": "String",
594 			"description": null,
595 			"inputFields": null,
596 			"ofTypeName": "immutable(char)",
597 			"fields": null
598 		}
599 		`);
600 	assert(r == e, format("exp:\n%s\ngot:\n%s", e.toPrettyString(),
601 				r.toPrettyString()));
602 }
603 
604 @safe unittest {
605 	import std.format : format;
606 	Json r = typeToJson!(Nullable!string,void)();
607 	Json e = parseJsonString(`
608 		{
609 			"__typename": "__Type",
610 			"possibleTypesNames": null,
611 			"enumValues": null,
612 			"interfacesNames": null,
613 			"kind": "SCALAR",
614 			"isDeprecated": false,
615 			"deprecationReason": null,
616 			"name": "String",
617 			"description": null,
618 			"inputFields": null,
619 			"ofTypeName": "immutable(char)",
620 			"fields": null
621 		}
622 		`);
623 	assert(r == e, format("exp:\n%s\ngot:\n%s", e.toPrettyString(),
624 				r.toPrettyString()));
625 }
626 
627 Json directivesToJson(Directives)() {
628 	import std.string : stripLeft;
629 	Json ret = Json.emptyArray();
630 	alias TplusParents = AliasSeq!(Directives, InheritedClasses!Directives);
631 	static foreach(Type; TplusParents) {{
632 		static foreach(mem; __traits(allMembers, Type)) {{
633 			static if(!canFind(memsToIgnore, mem)) {
634 				Json tmp = Json.emptyObject();
635 				enum GQLDUdaData udaData = getUdaData!(Type, mem);
636 				tmp[Constants.name] = mem;
637 				// needed for interfacesForType
638 				tmp[Constants.__typename] = Constants.__Directive;
639 				tmp[Constants.description] = udaData.description.getText().empty
640 						? Json(null)
641 						: Json(udaData.description.text);
642 
643 				tmp[Constants.isDeprecated] =
644 					udaData.deprecationInfo.isDeprecated == IsDeprecated.yes
645 						? true
646 						: false;
647 
648 				tmp[Constants.deprecationReason] =
649 					udaData.deprecationInfo.isDeprecated == IsDeprecated.yes
650 						? Json(udaData.deprecationInfo.deprecationReason)
651 						: Json(null);
652 
653 				//tmp[Constants.description] = Json(null);
654 				tmp[Constants.locations] = Json.emptyArray();
655 				tmp[Constants.args] = Json.emptyArray();
656 				static if(isCallable!(__traits(getMember, Type, mem))) {
657 					// InputValue
658 					alias paraNames = ParameterIdentifierTuple!(
659 							__traits(getMember, Type, mem)
660 						);
661 					alias paraTypes = Parameters!(
662 							__traits(getMember, Type, mem)
663 						);
664 					alias paraDefs = ParameterDefaults!(
665 							__traits(getMember, Type, mem)
666 						);
667 					static foreach(idx; 0 .. paraNames.length) {{
668 						Json iv = Json.emptyObject();
669 						// TODO remove the strip left. Its in because the
670 						// two default directives of GraphQL skip and include
671 						// both have one parameter named "if".
672 						iv[Constants.name] = stripLeft(paraNames[idx], "_");
673 						iv[Constants.description] = Json(null);
674 						// needed for interfacesForType
675 						iv[Constants.__typename] = Constants.__InputValue;
676 						iv[Constants.typenameOrig] = typeToTypeName!(paraTypes[idx]);
677 						//iv[Constants.typenameOrig] = typeToParameterTypeName!(paraTypes[idx]);
678 						static if(!is(paraDefs[idx] == void)) {
679 							iv[Constants.defaultValue] = serializeToJson(paraDefs[idx])
680 								.toString();
681 						} else {
682 							iv[Constants.defaultValue] = Json(null);
683 						}
684 						tmp[Constants.args] ~= iv;
685 					}}
686 				}
687 				ret ~= tmp;
688 			}
689 		}}
690 	}}
691 	return ret;
692 }
693 
694 Json getField(Json j, string name) {
695 	import graphql.constants;
696 
697 	if(j.type != Json.Type.object || Constants.fields !in j
698 			|| j[Constants.fields].type != Json.Type.array)
699 	{
700 		return Json.init;
701 	}
702 
703 	foreach(it; j[Constants.fields].byValue) {
704 		immutable string itName = it[Constants.name].get!string();
705 		if(itName == name) {
706 			return it;
707 		}
708 	}
709 	return Json.init;
710 }
711 
712 Json getIntrospectionField(string name) {
713 	import std.format : format;
714 	Json ret = Json.emptyObject();
715 	ret[Constants.typenameOrig] = name == Constants.__typename
716 		? "String"
717 		: name == Constants.__schema
718 			? "__Schema"
719 			: name == Constants.__type
720 				? "__Type"
721 				: format("Not known introspection name '%s'", name);
722 	return ret;
723 }