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