1 module graphql.schema.resolver;
2 
3 import std.array : empty;
4 import std.format : format;
5 import std.meta;
6 import std.traits;
7 import std.typecons : Nullable;
8 import std.experimental.logger;
9 import std.stdio;
10 import std.string : capitalize;
11 
12 import vibe.data.json;
13 
14 import graphql.schema.types;
15 import graphql.schema.typeconversions;
16 import graphql.helper;
17 import graphql.traits;
18 import graphql.constants;
19 import graphql.graphql;
20 
21 @safe:
22 
23 alias QueryResolver(Con) = Json delegate(string name, Json parent,
24 		Json args, ref Con context) @safe;
25 
26 QueryResolver!(Con) buildSchemaResolver(Type, Con)() {
27 	return ret;
28 }
29 
30 QueryResolver!(Con) buildTypeResolver(Type, Con)() {
31 	return ret;
32 }
33 
34 GQLDSchema!(Type) toSchema(Type)() {
35 	typeof(return) ret = new GQLDSchema!Type();
36 
37 	static foreach(qms; ["queryType", "mutationType", "subscriptionType"]) {{
38 		GQLDMap cur = new GQLDMap();
39 		cur.name = qms;
40 		ret.member[qms] = cur;
41 		if(qms == "queryType") {
42 			cur.member["__schema"] = ret.__schema;
43 			cur.member["__type"] = ret.__nonNullType;
44 		}
45 		static if(__traits(hasMember, Type, qms)) {
46 			alias QMSType = typeof(__traits(getMember, Type, qms));
47 			static foreach(mem; __traits(allMembers, QMSType)) {{
48 				alias MemType = typeof(__traits(getMember, QMSType, mem));
49 				static if(isCallable!(MemType)) {{
50 					GQLDOperation op = qms == "queryType"
51 						? new GQLDQuery()
52 						: qms == "mutationType" ? new GQLDMutation()
53 						: qms == "subscriptionType" ? new GQLDSubscription()
54 						: null;
55 					cur.member[mem] = op;
56 					assert(op !is null);
57 					op.returnType = typeToGQLDType!(ReturnType!(MemType))(
58 							ret
59 						);
60 
61 					alias paraNames = ParameterIdentifierTuple!(
62 							__traits(getMember, QMSType, mem)
63 						);
64 					alias paraTypes = Parameters!(
65 							__traits(getMember, QMSType, mem)
66 						);
67 					static foreach(idx; 0 .. paraNames.length) {
68 						op.parameters[paraNames[idx]] =
69 							typeToGQLDType!(paraTypes[idx])(ret);
70 					}
71 				}}
72 			}}
73 		}
74 	}}
75 	return ret;
76 }
77 
78 enum __DirectiveLocation {
79 	QUERY
80 	, MUTATION
81 	, SUBSCRIPTION
82 	, FIELD
83 	, FRAGMENT_DEFINITION
84 	, FRAGMENT_SPREAD
85 	, INLINE_FRAGMENT
86 	, SCHEMA
87 	, SCALAR
88 	, OBJECT
89 	, FIELD_DEFINITION
90 	, ARGUMENT_DEFINITION
91 	, INTERFACE
92 	, UNION
93 	, ENUM
94 	, ENUM_VALUE
95 	, INPUT_OBJECT
96 	, INPUT_FIELD_DEFINITION
97 }
98 
99 class __Directive {
100 	string name;
101 	Nullable!string description;
102 	__DirectiveLocation[] locations;
103 	__InputValue[] args;
104 }
105 
106 enum __TypeKind {
107 	SCALAR
108 	, OBJECT
109 	, INTERFACE
110 	, UNION
111 	, ENUM
112 	, INPUT_OBJECT
113 	, LIST
114 	, NON_NULL
115 }
116 
117 class __EnumValue {
118 	string name;
119 	Nullable!string description;
120 	bool isDeprecated;
121 	Nullable!string deprecationReason;
122 }
123 
124 class __InputValue {
125 	string name;
126 	Nullable!string description;
127 	__Type type;
128 	Nullable!string defaultValue;
129 }
130 
131 class __Field {
132 	string name;
133 	Nullable!string description;
134 	__InputValue[] args;
135 	__Type type;
136 	bool isDeprecated;
137 	Nullable!string deprecationReason;
138 }
139 
140 class __Type {
141 	__TypeKind kind;
142 	string name;
143 	Nullable!string description;
144 	__Field[] fields(bool includeDeprecated = false) { assert(false);
145 	}
146 	Nullable!(__Type[]) interfaces;
147 	Nullable!(__Type[]) possibleTypes;
148 	Nullable!(__Field[]) enumValues(bool includeDeprecated = false) {
149 		assert(false);
150 	}
151 	Nullable!(__InputValue[]) inputFields;
152 	Nullable!(__Type) ofType;
153 }
154 
155 class __Schema {
156 	__Type types;
157 	__Directive directives;
158 }
159 
160 void setDefaultSchemaResolver(T, Con)(GraphQLD!(T,Con) graphql) {
161 
162 	Json typeResolverImpl(Type)(ref const(StringTypeStrip) stripType,
163 			Json parent)
164 	{
165 		Json ret = Json.emptyObject();
166 		const bool inner = stripType.innerNotNull;
167 		const bool outer = stripType.outerNotNull;
168 		const bool arr = stripType.arr;
169 
170 		if(inner && outer && arr) {
171 			alias PassType = Type[];
172 			ret["data"] = typeToJson!(PassType,T)();
173 		} else if(!inner && outer && arr) {
174 			static if(!is(Type == void)) {
175 				alias PassType = Nullable!(Type)[];
176 				ret["data"] = typeToJson!(PassType,T)();
177 			} else {
178 				ret.insertError(format("invalid type %s in %s",
179 							Type.stringof,
180 							parent.toPrettyString()));
181 			}
182 		} else if(inner && !outer && arr) {
183 			alias PassType = Nullable!(Type[]);
184 			ret["data"] = typeToJson!(PassType,T)();
185 		} else if(!inner && !outer && arr) {
186 			static if(!is(type == void)) {
187 				alias PassType = Nullable!(Nullable!(Type)[]);
188 				ret["data"] = typeToJson!(PassType,T)();
189 			} else {
190 				ret.insertError(format("invalid type %s in %s",
191 							Type.stringof,
192 							parent.toPrettyString()));
193 			}
194 		} else if(!inner && !arr) {
195 			static if(!is(Type == void)) {
196 				alias PassType = Nullable!(Type);
197 				ret["data"] = typeToJson!(PassType,T)();
198 			} else {
199 				ret.insertError(format("invalid type %s in %s",
200 							Type.stringof,
201 							parent.toPrettyString()));
202 			}
203 		} else if(inner && !arr) {
204 			alias PassType = Type;
205 			ret["data"] = typeToJson!(PassType,T)();
206 		} else {
207 			assert(false, format("%s", stripType));
208 		}
209 		graphql.defaultResolverLog.logf("%s %s", stripType.str, ret["data"]);
210 		return ret;
211 	}
212 
213 	auto typeResolver = delegate(string name, Json parent,
214 			Json args, ref Con context) @safe
215 		{
216 			graphql.defaultResolverLog.logf(
217 					"TTTTTTRRRRRRR name %s args %s parent %s", name, args,
218 					parent);
219 			Json ret = Json.emptyObject();
220 			string typeName;
221 			if(Constants.name in args) {
222 				typeName = args[Constants.name].get!string();
223 			}
224 			if(Constants.typenameOrig in parent) {
225 				typeName = parent[Constants.typenameOrig].get!string();
226 			} else if(Constants.name in parent) {
227 				typeName = parent[Constants.name].get!string();
228 			}
229 			string typeCap;
230 			string old;
231 			StringTypeStrip stripType;
232 			if(typeName.empty) {
233 				ret.insertError("unknown type");
234 				goto retLabel;
235 			} else {
236 				typeCap = typeName;
237 				old = typeName;
238 			}
239 			stripType = typeCap.stringTypeStrip();
240 			graphql.defaultResolverLog.logf("%s %s", __LINE__, stripType);
241 			static foreach(type; collectTypes!(T)) {{
242 				enum typeConst = typeToTypeName!(type);
243 				if(stripType.str == typeConst) {
244 					static if(!is(type == void)) {
245 						ret = typeResolverImpl!(type)(stripType, parent);
246 						goto retLabel;
247 					}
248 				} else {
249 					graphql.defaultResolverLog.logf("||||||||||| %s %s",
250 							stripType.str, typeConst
251 						);
252 				}
253 			}}
254 			if(stripType.str == "__Type") {
255 				ret = typeResolverImpl!(__Type)(stripType, parent);
256 			} else if(stripType.str == "__Field") {
257 				ret = typeResolverImpl!(__Field)(stripType, parent);
258 			} else if(stripType.str == "__InputValue") {
259 				ret = typeResolverImpl!(__InputValue)(stripType, parent);
260 			} else if(stripType.str == "__EnumValue") {
261 				ret = typeResolverImpl!(__EnumValue)(stripType, parent);
262 			} else if(stripType.str == "__TypeKind") {
263 				ret = typeResolverImpl!(__TypeKind)(stripType, parent);
264 			} else if(stripType.str == "__Directive") {
265 				ret = typeResolverImpl!(__Directive)(stripType, parent);
266 			} else if(stripType.str == "__DirectiveLocation") {
267 				ret = typeResolverImpl!(__DirectiveLocation)(stripType, parent);
268 			}
269 			retLabel:
270 			//graphql.defaultResolverLog.logf("%s", ret.toPrettyString());
271 			graphql.defaultResolverLog.logf("TTTTT____RRRR %s",
272 					ret.toPrettyString());
273 			return ret;
274 		};
275 
276 	QueryResolver!(Con) schemaResolver = delegate(string name, Json parent,
277 			Json args, ref Con context) @safe
278 		{
279 			//logf("%s %s %s", name, args, parent);
280 			StringTypeStrip stripType;
281 			Json ret = Json.emptyObject();
282 			ret["data"] = Json.emptyObject();
283 			ret["data"]["types"] = Json.emptyArray();
284 			ret["data"]["types"] ~=
285 				typeResolverImpl!(__Type)(stripType, parent)["data"];
286 			ret["data"]["types"] ~=
287 				typeResolverImpl!(__Field)(stripType, parent)["data"];
288 			ret["data"]["types"] ~=
289 				typeResolverImpl!(__InputValue)(stripType, parent)["data"];
290 			ret["data"]["types"] ~=
291 				typeResolverImpl!(__EnumValue)(stripType, parent)["data"];
292 			ret["data"]["types"] ~=
293 				typeResolverImpl!(__TypeKind)(stripType, parent)["data"];
294 			ret["data"]["types"] ~=
295 				typeResolverImpl!(__Directive)(stripType, parent)["data"];
296 			ret["data"]["types"] ~=
297 				typeResolverImpl!(__DirectiveLocation)(stripType, parent)
298 					["data"];
299 
300 			alias AllTypes = collectTypes!(T);
301 			alias NoListOrArray = staticMap!(stripArrayAndNullable, AllTypes);
302 			alias FixUp = staticMap!(fixupBasicTypes, NoListOrArray);
303 			static if(hasMember!(T, Constants.directives)) {
304 				alias NoDirectives = EraseAll!(
305 						typeof(__traits(getMember, T, Constants.directives)),
306 						FixUp
307 					);
308 			} else {
309 				alias NoDirectives = FixUp;
310 			}
311 			alias NoDup = NoDuplicates!(EraseAll!(T, NoDirectives));
312 			static foreach(type; NoDup) {{
313 				Json tmp = typeToJsonImpl!(type,T,type)();
314 				ret["data"]["types"] ~= tmp;
315 			}}
316 			static if(hasMember!(T, Constants.directives)) {
317 				ret["data"][Constants.directives] =
318 					directivesToJson!(typeof(
319 							__traits(getMember, T, Constants.directives)
320 						));
321 			}
322 
323 			static foreach(tName; ["subscriptionType",
324 					"queryType", "mutationType"])
325 			{
326 				static if(hasMember!(T, tName)) {
327 					ret["data"][tName] =
328 						typeToJsonImpl!(typeof(__traits(getMember, T, tName)),
329 								T, typeof(__traits(getMember, T, tName)));
330 				}
331 			}
332 			graphql.defaultResolverLog.logf("%s", ret.toPrettyString());
333 			return ret;
334 		};
335 
336 	graphql.setResolver("queryType", "__type",
337 			delegate(string name, Json parent, Json args, ref Con context) @safe
338 			{
339 				graphql.defaultResolverLog.logf("%s %s %s", name, parent, args);
340 				Json tr = typeResolver(name, parent, args, context);
341 				Json ret = Json.emptyObject();
342 				ret["data"] = tr["data"];
343 				graphql.defaultResolverLog.logf("%s %s", tr.toPrettyString(),
344 						ret.toPrettyString());
345 				return ret;
346 			}
347 		);
348 
349 	graphql.setResolver("queryType", "__schema", schemaResolver);
350 
351 	graphql.setResolver("__Field", "type",
352 			delegate(string name, Json parent, Json args, ref Con context) @safe
353 			{
354 				graphql.defaultResolverLog.logf("name %s, parent %s, args %s",
355 						name, parent, args
356 					);
357 				Json ret = typeResolver(name, parent, args, context);
358 				graphql.defaultResolverLog.logf("FIELDDDDD TYPPPPPE %s",
359 						ret.toPrettyString()
360 					);
361 				return ret;
362 			}
363 		);
364 
365 	graphql.setResolver("__InputValue", "type",
366 			delegate(string name, Json parent, Json args, ref Con context) @safe
367 			{
368 				graphql.defaultResolverLog.logf("%s %s %s", name, parent, args);
369 				Json tr = typeResolver(name, parent, args, context);
370 				Json ret = Json.emptyObject();
371 				Json d = tr["data"];
372 				ret["data"] = d;
373 				graphql.defaultResolverLog.logf("%s %s", tr.toPrettyString(),
374 						ret.toPrettyString()
375 					);
376 				return ret;
377 			}
378 		);
379 
380 	graphql.setResolver("__Type", "ofType",
381 			delegate(string name, Json parent, Json args, ref Con context) @safe
382 			{
383 				graphql.defaultResolverLog.logf("name %s, parent %s, args %s",
384 						name, parent, args
385 					);
386 				Json ret = Json.emptyObject();
387 				Json ofType;
388 				if(parent.hasPathTo("ofType", ofType)) {
389 					ret["data"] = ofType;
390 				} else {
391 					ret["data"] = Json(null);
392 				}
393 				graphql.defaultResolverLog.logf("%s", ret);
394 				return ret;
395 			}
396 		);
397 
398 	graphql.setResolver("__Type", "interfaces",
399 			delegate(string name, Json parent, Json args, ref Con context) @safe
400 			{
401 				graphql.defaultResolverLog.logf("name %s, parent %s, args %s",
402 						name, parent, args
403 					);
404 				Json ret = Json.emptyObject();
405 				ret["data"] = Json.emptyObject();
406 				if("kind" in parent
407 						&& parent["kind"].get!string() == "OBJECT")
408 				{
409 					ret["data"] = Json.emptyArray();
410 				}
411 				if("interfacesNames" !in parent) {
412 					return ret;
413 				}
414 				Json interNames = parent["interfacesNames"];
415 				if(interNames.type == Json.Type.array) {
416 					if(interNames.length > 0) {
417 						assert(ret["data"].type == Json.Type.array,
418 								format("%s", parent.toPrettyString())
419 							);
420 						ret["data"] = Json.emptyArray();
421 						foreach(Json it; interNames.byValue()) {
422 							string typeName = it.get!string();
423 							string typeCap = capitalize(typeName);
424 							l: switch(typeCap) {
425 								static foreach(type; collectTypes!(T)) {{
426 									case typeToTypeName!(type): {
427 										alias striped =
428 											stripArrayAndNullable!type;
429 										graphql.defaultResolverLog.logf("%s %s",
430 												typeCap, striped.stringof
431 											);
432 										ret["data"] ~=
433 											typeToJsonImpl!(striped,T,type)();
434 										break l;
435 									}
436 								}}
437 								default: break;
438 							}
439 						}
440 					}
441 				}
442 				graphql.defaultResolverLog.logf("__Type.interfaces result %s",
443 						ret
444 					);
445 				return ret;
446 			}
447 		);
448 
449 	graphql.setResolver("__Type", "possibleTypes",
450 			delegate(string name, Json parent, Json args, ref Con context) @safe
451 			{
452 				graphql.defaultResolverLog.logf("name %s, parent %s, args %s",
453 						name, parent, args
454 					);
455 				Json ret = Json.emptyObject();
456 				if("possibleTypesNames" !in parent) {
457 					ret["data"] = Json(null);
458 					return ret;
459 				}
460 				Json pTypesNames = parent["possibleTypesNames"];
461 				if(pTypesNames.type == Json.Type.array) {
462 					graphql.defaultResolverLog.log();
463 					ret["data"] = Json.emptyArray();
464 					foreach(Json it; pTypesNames.byValue()) {
465 						string typeName = it.get!string();
466 						string typeCap = capitalize(typeName);
467 						l: switch(typeCap) {
468 							static foreach(type; collectTypes!(T)) {
469 								case typeToTypeName!(type): {
470 									alias striped = stripArrayAndNullable!type;
471 									graphql.defaultResolverLog.logf("%s %s",
472 											typeCap, striped.stringof
473 										);
474 									ret["data"] ~=
475 										typeToJsonImpl!(striped,T,type)();
476 									break l;
477 								}
478 							}
479 							default: {
480 								break;
481 							}
482 						}
483 					}
484 				} else {
485 					graphql.defaultResolverLog.log();
486 					ret["data"] = Json(null);
487 				}
488 				return ret;
489 			}
490 		);
491 }