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 graphql.schema.types;
15 import graphql.traits;
16 import graphql.uda;
17 import graphql.constants;
18 
19 @safe:
20 
21 template typeToTypeEnum(Type) {
22 	static if(isAggregateType!Type && hasUDA!(Type, GQLDUdaData)
23 			&& getUDAs!(Type, GQLDUdaData)[0].typeKind != TypeKind.UNDEFINED)
24 	{
25 		enum udas = getUDAs!(Type, GQLDUdaData);
26 		static assert(udas.length == 1);
27 		enum GQLDUdaData u = udas[0];
28 		enum typeToTypeEnum = to!string(u.typeKind);
29 	} else static if(is(Type == enum)) {
30 		enum typeToTypeEnum = "ENUM";
31 	} else static if(is(Type == bool)) {
32 		enum typeToTypeEnum = "SCALAR";
33 	} else static if(isFloatingPoint!(Type)) {
34 		enum typeToTypeEnum = "SCALAR";
35 	} else static if(isIntegral!(Type)) {
36 		enum typeToTypeEnum = "SCALAR";
37 	} else static if(isSomeString!Type) {
38 		enum typeToTypeEnum = "SCALAR";
39 	} else static if(is(Type == void)) {
40 		enum typeToTypeEnum = "SCALAR";
41 	} else static if(is(Type == union)) {
42 		enum typeToTypeEnum = "UNION";
43 	} else static if(isAggregateType!Type) {
44 		enum typeToTypeEnum = "OBJECT";
45 	} else {
46 		static assert(false, Type.stringof ~ " not handled");
47 	}
48 }
49 
50 template typeToTypeName(Type) {
51 	static if(is(Type == enum)) {
52 		enum typeToTypeName = Type.stringof;
53 	} else static if(is(Type == bool)) {
54 		enum typeToTypeName = "Bool";
55 	} else static if(isFloatingPoint!(Type)) {
56 		enum typeToTypeName = "Float";
57 	} else static if(isIntegral!(Type)) {
58 		enum typeToTypeName = "Int";
59 	} else static if(isSomeString!Type) {
60 		enum typeToTypeName = "String";
61 	} else {
62 		enum typeToTypeName = Type.stringof;
63 	}
64 }
65 
66 template isScalarType(Type) {
67 	static if(is(Type == bool)) {
68 		enum isScalarType = true;
69 	} else static if(isFloatingPoint!(Type)) {
70 		enum isScalarType = true;
71 	} else static if(isIntegral!(Type)) {
72 		enum isScalarType = true;
73 	} else static if(isSomeString!Type) {
74 		enum isScalarType = true;
75 	} else static if(is(Type == enum)) {
76 		enum isScalarType = true;
77 	} else {
78 		enum isScalarType = false;
79 	}
80 }
81 
82 template typeToFieldType(Type) {
83 	static if(isArray!Type && !isSomeString!Type) {
84 		enum typeToFieldType = "__listType";
85 	} else static if(is(Type : Nullable!F, F)) {
86 		enum typeToFieldType = F.stringof;
87 	} else {
88 		enum typeToFieldType = "__nonNullType";
89 	}
90 }
91 
92 Json typeFields(T)() {
93 	static enum memsToIgnore = ["__ctor", "toString", "toHash", "opCmp",
94 			"opEquals", "Monitor", "factory"];
95 	Json ret = Json.emptyArray();
96 	alias TplusParents = AliasSeq!(T, InheritedClasses!T);
97 	static foreach(Type; TplusParents) {{
98 		static foreach(mem; __traits(allMembers, Type)) {{
99 			static if(!canFind(memsToIgnore, mem)) {
100 				enum GQLDUdaData udaData = getUdaData!(Type, mem);
101 				Json tmp = Json.emptyObject();
102 				tmp[Constants.name] = mem;
103 				tmp[Constants.__typename] = "__Field"; // needed for interfacesForType
104 				tmp[Constants.description] = udaData.description.text.empty
105 						? Json(null)
106 						: Json(udaData.description.text);
107 
108 				tmp[Constants.isDeprecated] =
109 					udaData.deprecationInfo.isDeprecated == IsDeprecated.yes
110 						? true
111 						: false;
112 
113 				tmp[Constants.deprecationReason] =
114 					udaData.deprecationInfo.isDeprecated == IsDeprecated.yes
115 						? Json(udaData.deprecationInfo.deprecationReason)
116 						: Json(null);
117 
118 				tmp[Constants.args] = Json.emptyArray();
119 				static if(isCallable!(__traits(getMember, Type, mem))) {
120 					alias RT = ReturnType!(__traits(getMember, Type, mem));
121 					alias RTS = stripArrayAndNullable!RT;
122 					tmp[Constants.typenameOrig] = typeToTypeName!(RT);
123 
124 					// InputValue
125 					alias paraNames = ParameterIdentifierTuple!(
126 							__traits(getMember, Type, mem)
127 						);
128 					alias paraTypes = Parameters!(
129 							__traits(getMember, Type, mem)
130 						);
131 					alias paraDefs = ParameterDefaults!(
132 							__traits(getMember, Type, mem)
133 						);
134 					static foreach(idx; 0 .. paraNames.length) {{
135 						Json iv = Json.emptyObject();
136 						iv[Constants.name] = paraNames[idx];
137 						// needed for interfacesForType
138 						iv[Constants.__typename] = Constants.__InputValue;
139 						iv[Constants.typenameOrig] = typeToTypeName!(paraTypes[idx]);
140 						static if(!is(paraDefs[idx] == void)) {
141 							iv[Constants.defaultValue] = serializeToJson(paraDefs[idx])
142 								.toString();
143 						}
144 						tmp[Constants.args] ~= iv;
145 					}}
146 				} else {
147 					tmp[Constants.typenameOrig] = typeToTypeName!(
148 							typeof(__traits(getMember, Type, mem))
149 						);
150 				}
151 				ret ~= tmp;
152 			}
153 		}}
154 	}}
155 	return ret;
156 }
157 
158 Json inputFields(Type)() {
159 	Json ret = Json.emptyArray();
160 	alias types = FieldTypeTuple!Type;
161 	alias names = FieldNameTuple!Type;
162 	static foreach(idx; 0 .. types.length) {{
163 		enum GQLDUdaData udaData = getUdaData!(types[idx]);
164 		Json tmp = Json.emptyObject();
165 		tmp[Constants.name] = names[idx];
166 		tmp[Constants.description] = udaData.description.text.empty
167 				? Json(null)
168 				: Json(udaData.description.text);
169 		tmp[Constants.__typename] = Constants.__InputValue; // needed for interfacesForType
170 		tmp[Constants.typenameOrig] = typeToTypeName!(types[idx]);
171 		tmp[Constants.defaultValue] = serializeToJson(
172 				__traits(getMember, Type.init, names[idx])
173 			);
174 		ret ~= tmp;
175 	}}
176 	return ret;
177 }
178 
179 Json emptyType() {
180 	Json ret = Json.emptyObject();
181 	ret[Constants.name] = Json(null);
182 	ret[Constants.description] = Json(null);
183 	ret[Constants.fields] = Json(null);
184 	ret[Constants.interfacesNames] = Json(null);
185 	ret[Constants.possibleTypesNames] = Json(null);
186 	ret[Constants.enumValues] = Json(null);
187 	ret["ofType"] = Json(null);
188 	return ret;
189 }
190 
191 // remove the top nullable to find out if we have a NON_NULL or not
192 Json typeToJson(Type,Schema)() {
193 	static if(is(Type : Nullable!F, F)) {
194 		return typeToJson1!(F,Schema,Type)();
195 	} else {
196 		Json ret = emptyType();
197 		ret["kind"] = "NON_NULL";
198 		ret[Constants.__typename] = "__Type";
199 		ret["ofType"] = typeToJson1!(Type,Schema,Type)();
200 		return ret;
201 	}
202 }
203 
204 // remove the array is present
205 Json typeToJson1(Type,Schema,Orig)() {
206 	static if(isArray!Type && !isSomeString!Type) {
207 		Json ret = emptyType();
208 		ret["kind"] = "LIST";
209 		ret[Constants.__typename] = "__Type";
210 		ret["ofType"] = typeToJson2!(ElementEncodingType!Type, Schema, Orig)();
211 		return ret;
212 	} else {
213 		return typeToJsonImpl!(Type, Schema, Orig)();
214 	}
215 }
216 
217 // remove another nullable
218 Json typeToJson2(Type,Schema,Orig)() {
219 	static if(is(Type : Nullable!F, F)) {
220 		return typeToJsonImpl!(F, Schema, Orig)();
221 	} else {
222 		Json ret = emptyType();
223 		ret["kind"] = "NON_NULL";
224 		ret[Constants.__typename] = "__Type";
225 		ret["ofType"] = typeToJsonImpl!(Type, Schema, Orig)();
226 		return ret;
227 	}
228 }
229 
230 Json typeToJsonImpl(Type,Schema,Orig)() {
231 	Json ret = Json.emptyObject();
232 	enum string kind = typeToTypeEnum!Type;
233 	ret["kind"] = kind;
234 	ret[Constants.__typename] = "__Type";
235 	ret[Constants.name] = typeToTypeName!Type;
236 
237 	enum GQLDUdaData udaData = getUdaData!(Orig);
238 	ret[Constants.description] = udaData.description.text.empty
239 			? Json(null)
240 			: Json(udaData.description.text);
241 
242 	ret[Constants.isDeprecated] =
243 		udaData.deprecationInfo.isDeprecated == IsDeprecated.yes
244 			? true
245 			: false;
246 
247 	ret[Constants.deprecationReason] =
248 		udaData.deprecationInfo.isDeprecated == IsDeprecated.yes
249 			? Json(udaData.deprecationInfo.deprecationReason)
250 			: Json(null);
251 
252 	// fields
253 	static if((is(Type == class) || is(Type == interface) || is(Type == struct))
254 			&& !is(Type : Nullable!K, K))
255 	{
256 		ret[Constants.fields] = typeFields!Type();
257 	} else {
258 		ret[Constants.fields] = Json(null);
259 	}
260 
261 	// inputFields
262 	static if(kind == Constants.INPUT_OBJECT) {
263 		ret[Constants.inputFields] = inputFields!Type();
264 	} else {
265 		ret[Constants.inputFields] = Json(null);
266 	}
267 
268 	// needed to resolve interfaces
269 	static if(is(Type == class) || is(Type == interface)) {
270 		ret[Constants.interfacesNames] = Json.emptyArray();
271 		static foreach(interfaces; InheritedClasses!Type) {{
272 			ret[Constants.interfacesNames] ~= interfaces.stringof;
273 		}}
274 	} else {
275 		ret[Constants.interfacesNames] = Json(null);
276 	}
277 
278 	// needed to resolve possibleTypes
279 	static if(is(Type == class) || is(Type == union)
280 			|| is(Type == interface))
281 	{
282 		ret[Constants.possibleTypesNames] = Json.emptyArray();
283 		alias PT = PossibleTypes!(Type, Schema);
284 		static foreach(pt; PT) {
285 			ret[Constants.possibleTypesNames] ~= pt.stringof;
286 		}
287 	} else {
288 		ret[Constants.possibleTypesNames] = Json(null);
289 	}
290 
291 	// enumValues
292 	static if(is(Type == enum)) {
293 		ret[Constants.enumValues] = Json.emptyArray();
294 		static foreach(mem; EnumMembers!Type) {{
295 			Json tmp = Json.emptyObject();
296 			tmp[Constants.__TypeKind] = Constants.__EnumValue;
297 			tmp[Constants.name] = Json(to!string(mem));
298 			tmp[Constants.description] = "ENUM_DESCRIPTION_TODO";
299 			tmp[Constants.isDeprecated] = false;
300 			tmp[Constants.deprecationReason] = "ENUM_DEPRECATIONREASON_TODO";
301 			ret[Constants.enumValues] ~= tmp;
302 		}}
303 	} else {
304 		ret[Constants.enumValues] = Json(null);
305 	}
306 
307 	// needed to resolve ofType
308 	static if(is(Type : Nullable!F, F)) {
309 		ret[Constants.ofTypeName] = F.stringof;
310 	} else static if(isArray!Type) {
311 		ret[Constants.ofTypeName] = ElementEncodingType!(Type).stringof;
312 	}
313 
314 	return ret;
315 }
316 
317 Json directivesToJson(Directives)() {
318 	import std.string : stripLeft;
319 	Json ret = Json.emptyArray();
320 	static enum memsToIgnore = ["__ctor", "toString", "toHash", "opCmp",
321 			"opEquals", "Monitor", "factory"];
322 	alias TplusParents = AliasSeq!(Directives, InheritedClasses!Directives);
323 	static foreach(Type; TplusParents) {{
324 		static foreach(mem; __traits(allMembers, Type)) {{
325 			static if(!canFind(memsToIgnore, mem)) {
326 				Json tmp = Json.emptyObject();
327 				enum GQLDUdaData udaData = getUdaData!(Type, mem);
328 				tmp[Constants.name] = mem;
329 				// needed for interfacesForType
330 				tmp[Constants.__typename] = Constants.__Directive;
331 				tmp[Constants.description] = udaData.description.text.empty
332 						? Json(null)
333 						: Json(udaData.description.text);
334 
335 				tmp[Constants.isDeprecated] =
336 					udaData.deprecationInfo.isDeprecated == IsDeprecated.yes
337 						? true
338 						: false;
339 
340 				tmp[Constants.deprecationReason] =
341 					udaData.deprecationInfo.isDeprecated == IsDeprecated.yes
342 						? Json(udaData.deprecationInfo.deprecationReason)
343 						: Json(null);
344 
345 				//tmp[Constants.description] = Json(null);
346 				tmp[Constants.locations] = Json.emptyArray();
347 				tmp[Constants.args] = Json.emptyArray();
348 				static if(isCallable!(__traits(getMember, Type, mem))) {
349 					// InputValue
350 					alias paraNames = ParameterIdentifierTuple!(
351 							__traits(getMember, Type, mem)
352 						);
353 					alias paraTypes = Parameters!(
354 							__traits(getMember, Type, mem)
355 						);
356 					alias paraDefs = ParameterDefaults!(
357 							__traits(getMember, Type, mem)
358 						);
359 					static foreach(idx; 0 .. paraNames.length) {{
360 						Json iv = Json.emptyObject();
361 						// TODO remove the strip left. Its in because the
362 						// two default directives of GraphQL skip and include
363 						// both have one parameter named "if".
364 						iv[Constants.name] = stripLeft(paraNames[idx], "_");
365 						// needed for interfacesForType
366 						iv[Constants.__typename] = Constants.__InputValue;
367 						iv[Constants.typenameOrig] = typeToTypeName!(paraTypes[idx]);
368 						static if(!is(paraDefs[idx] == void)) {
369 							iv[Constants.defaultValue] = serializeToJson(paraDefs[idx])
370 								.toString();
371 						}
372 						tmp[Constants.args] ~= iv;
373 					}}
374 				}
375 				ret ~= tmp;
376 			}
377 		}}
378 	}}
379 	return ret;
380 }