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