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 void setDefaultSchemaResolver(T, Con)(GraphQLD!(T,Con) graphql) {
166 
167 	static Json typeResolverImpl(Type)(ref const(StringTypeStrip) stripType,
168 			Json parent, GraphQLD!(T,Con) graphql)
169 	{
170 		Json ret = Json.emptyObject();
171 		const bool inner = stripType.innerNotNull;
172 		const bool outer = stripType.outerNotNull;
173 		const bool arr = stripType.arr;
174 
175 		if(inner && outer && arr) {
176 			alias PassType = Type[];
177 			ret["data"] = typeToJson!(PassType,T)();
178 		} else if(!inner && outer && arr) {
179 			static if(!is(Type == void)) {
180 				alias PassType = Nullable!(Type)[];
181 				ret["data"] = typeToJson!(PassType,T)();
182 			} else {
183 				ret.insertError(format("invalid type %s in %s",
184 							Type.stringof,
185 							parent.toPrettyString()));
186 			}
187 		} else if(inner && !outer && arr) {
188 			alias PassType = Nullable!(Type[]);
189 			ret["data"] = typeToJson!(PassType,T)();
190 		} else if(!inner && !outer && arr) {
191 			static if(!is(type == void)) {
192 				alias PassType = Nullable!(Nullable!(Type)[]);
193 				ret["data"] = typeToJson!(PassType,T)();
194 			} else {
195 				ret.insertError(format("invalid type %s in %s",
196 							Type.stringof,
197 							parent.toPrettyString()));
198 			}
199 		} else if(!inner && !arr) {
200 			static if(!is(Type == void)) {
201 				alias PassType = Nullable!(Type);
202 				ret["data"] = typeToJson!(PassType,T)();
203 			} else {
204 				ret.insertError(format("invalid type %s in %s",
205 							Type.stringof,
206 							parent.toPrettyString()));
207 			}
208 		} else if(inner && !arr) {
209 			alias PassType = Type;
210 			ret["data"] = typeToJson!(PassType,T)();
211 		} else {
212 			assert(false, format("%s", stripType));
213 		}
214 		graphql.defaultResolverLog.logf("%s %s", stripType.str, ret["data"]);
215 		return ret;
216 	}
217 
218 	alias ResolverFunction =
219 		Json function(ref const(StringTypeStrip), Json, GraphQLD!(T,Con)) @safe;
220 
221 	static ResolverFunction[string] handlers;
222 
223 	if(handlers is null) {
224 		static void processType(type)(ref ResolverFunction[string] handlers) {
225 			static if(!is(type == void)) {
226 				enum typeConst = typeToTypeName!(type);
227 				handlers[typeConst] = &typeResolverImpl!(type);
228 			}
229 		}
230 
231 		execForAllTypes!(T, processType)(handlers);
232 
233 		foreach(t; AliasSeq!(__Type, __Field, __InputValue,
234 				__EnumValue, __TypeKind, __Directive, __DirectiveLocation))
235 		{
236 			 handlers[t.stringof] = &typeResolverImpl!t;
237 		}
238 	}
239 
240 	auto typeResolver = delegate(string name, Json parent,
241 			Json args, ref Con context) @safe
242 		{
243 			graphql.defaultResolverLog.logf(
244 					"TTTTTTRRRRRRR name %s args %s parent %s", name, args,
245 					parent);
246 			Json ret = Json.emptyObject();
247 			string typeName;
248 			if(Constants.name in args) {
249 				typeName = args[Constants.name].get!string();
250 			}
251 			if(Constants.typenameOrig in parent) {
252 				typeName = parent[Constants.typenameOrig].get!string();
253 			} else if(Constants.name in parent) {
254 				typeName = parent[Constants.name].get!string();
255 			}
256 			string typeCap;
257 			string old;
258 			StringTypeStrip stripType;
259 			if(typeName.empty) {
260 				ret.insertError("unknown type");
261 				goto retLabel;
262 			} else {
263 				typeCap = typeName;
264 				old = typeName;
265 			}
266 			stripType = typeCap.stringTypeStrip();
267 			graphql.defaultResolverLog.logf("%s %s", __LINE__, stripType);
268 
269 			if(auto h = stripType.str in handlers) {
270 				ret = (*h)(stripType, parent, graphql);
271 			}
272 			retLabel:
273 			//graphql.defaultResolverLog.logf("%s", ret.toPrettyString());
274 			graphql.defaultResolverLog.logf("TTTTT____RRRR %s",
275 					ret.toPrettyString());
276 			return ret;
277 		};
278 
279 	QueryResolver!(Con) schemaResolver = delegate(string _unused, Json parent,
280 			Json args, ref Con context) @safe
281 		{
282 			//logf("%s %s %s", name, args, parent);
283 			StringTypeStrip stripType;
284 			Json ret = Json.emptyObject();
285 			ret["data"] = Json.emptyObject();
286 			ret["data"]["types"] = Json.emptyArray();
287 			ret["data"]["types"] ~=
288 				typeResolverImpl!(__Type)(stripType, parent, graphql)["data"];
289 			ret["data"]["types"] ~=
290 				typeResolverImpl!(__Field)(stripType, parent, graphql)["data"];
291 			ret["data"]["types"] ~=
292 				typeResolverImpl!(__InputValue)(stripType, parent, graphql)["data"];
293 			ret["data"]["types"] ~=
294 				typeResolverImpl!(__EnumValue)(stripType, parent, graphql)["data"];
295 			ret["data"]["types"] ~=
296 				typeResolverImpl!(__TypeKind)(stripType, parent, graphql)["data"];
297 			ret["data"]["types"] ~=
298 				typeResolverImpl!(__Directive)(stripType, parent, graphql)["data"];
299 			ret["data"]["types"] ~=
300 				typeResolverImpl!(__DirectiveLocation)(stripType, parent,
301 														graphql)["data"];
302 
303 			Json jsonTypes;
304 			jsonTypes = Json.emptyArray;
305 			static if(hasMember!(T, Constants.directives)) {
306 				import graphql.schema.typeconversions : typeToTypeName;
307 				immutable string directiveTypeName =
308 			   	   typeToTypeName!(typeof(__traits(getMember, T,
309 												   Constants.directives)));
310 			} else {
311 				immutable string directiveTypeName = "";
312 			}
313 			foreach(n, ref tsn; SchemaReflection!T.instance.jsonTypes) {
314 				if(n != directiveTypeName && tsn.canonical)
315 					jsonTypes ~= tsn.typeJson.clone;
316 			}
317 			ret["data"]["types"] ~= jsonTypes;
318 			static if(hasMember!(T, Constants.directives)) {
319 				ret["data"][Constants.directives] =
320 					directivesToJson!(typeof(
321 							__traits(getMember, T, Constants.directives)
322 						));
323 			}
324 
325 			static foreach(tName; ["subscriptionType",
326 					"queryType", "mutationType"])
327 			{
328 				static if(hasMember!(T, tName)) {
329 					ret["data"][tName] =
330 						typeToJsonImpl!(typeof(__traits(getMember, T, tName)),
331 								T, typeof(__traits(getMember, T, tName)));
332 				}
333 			}
334 			graphql.defaultResolverLog.logf("%s", ret.toPrettyString());
335 			return ret;
336 		};
337 
338 	graphql.setResolver("queryType", "__type",
339 			delegate(string name, Json parent, Json args, ref Con context) @safe
340 			{
341 				graphql.defaultResolverLog.logf("%s %s %s", name, parent, args);
342 				Json tr = typeResolver(name, parent, args, context);
343 				Json ret = Json.emptyObject();
344 				ret["data"] = tr["data"];
345 				graphql.defaultResolverLog.logf("%s %s", tr.toPrettyString(),
346 						ret.toPrettyString());
347 				return ret;
348 			}
349 		);
350 
351 	graphql.setResolver("queryType", "__schema", schemaResolver);
352 
353 	graphql.setResolver("__Field", "type",
354 			delegate(string name, Json parent, Json args, ref Con context) @safe
355 			{
356 				graphql.defaultResolverLog.logf("name %s, parent %s, args %s",
357 						name, parent, args
358 					);
359 				Json ret = typeResolver(name, parent, args, context);
360 				graphql.defaultResolverLog.logf("FIELDDDDD TYPPPPPE %s",
361 						ret.toPrettyString()
362 					);
363 				return ret;
364 			}
365 		);
366 
367 	graphql.setResolver("__InputValue", "type",
368 			delegate(string name, Json parent, Json args, ref Con context) @safe
369 			{
370 				graphql.defaultResolverLog.logf("%s %s %s", name, parent, args);
371 				Json tr = typeResolver(name, parent, args, context);
372 				Json ret = Json.emptyObject();
373 				Json d = tr["data"];
374 				ret["data"] = d;
375 				graphql.defaultResolverLog.logf("%s %s", tr.toPrettyString(),
376 						ret.toPrettyString()
377 					);
378 				return ret;
379 			}
380 		);
381 
382 	graphql.setResolver("__Type", "ofType",
383 			delegate(string name, Json parent, Json args, ref Con context) @safe
384 			{
385 				graphql.defaultResolverLog.logf("name %s, parent %s, args %s",
386 						name, parent, args
387 					);
388 				Json ret = Json.emptyObject();
389 				Json ofType;
390 				if(parent.hasPathTo("ofType", ofType)) {
391 					ret["data"] = ofType;
392 				} else {
393 					ret["data"] = Json(null);
394 				}
395 				graphql.defaultResolverLog.logf("%s", ret);
396 				return ret;
397 			}
398 		);
399 
400 	graphql.setResolver("__Type", "interfaces",
401 			delegate(string name, Json parent, Json args, ref Con context) @safe
402 			{
403 				graphql.defaultResolverLog.logf("name %s, parent %s, args %s",
404 						name, parent, args
405 					);
406 				Json ret = Json.emptyObject();
407 				ret["data"] = Json.emptyObject();
408 				if("kind" in parent
409 						&& parent["kind"].get!string() == "OBJECT")
410 				{
411 					ret["data"] = Json.emptyArray();
412 				}
413 				if("interfacesNames" !in parent) {
414 					return ret;
415 				}
416 				Json interNames = parent["interfacesNames"];
417 				if(interNames.type == Json.Type.array) {
418 					if(interNames.length > 0) {
419 						assert(ret["data"].type == Json.Type.array,
420 								format("%s", parent.toPrettyString())
421 							);
422 						ret["data"] = Json.emptyArray();
423 						foreach(Json it; interNames.byValue()) {
424 							string typeName = it.get!string();
425 							string typeCap = capitalize(typeName);
426 							if(auto v = typeCap in
427 							   SchemaReflection!T.instance.jsonTypes)
428 							{
429 								ret["data"] ~= v.typeJson.clone;
430 								graphql.defaultResolverLog.logf("%s %s", typeCap, v.name);
431 							}
432 						}
433 					}
434 				}
435 				graphql.defaultResolverLog.logf("__Type.interfaces result %s",
436 						ret
437 					);
438 				return ret;
439 			}
440 		);
441 
442 	graphql.setResolver("__Type", "possibleTypes",
443 			delegate(string name, Json parent, Json args, ref Con context) @safe
444 			{
445 				graphql.defaultResolverLog.logf("name %s, parent %s, args %s",
446 						name, parent, args
447 					);
448 				Json ret = Json.emptyObject();
449 				if("possibleTypesNames" !in parent) {
450 					ret["data"] = Json(null);
451 					return ret;
452 				}
453 				Json pTypesNames = parent["possibleTypesNames"];
454 				if(pTypesNames.type == Json.Type.array) {
455 					graphql.defaultResolverLog.log();
456 					ret["data"] = Json.emptyArray();
457 					foreach(Json it; pTypesNames.byValue()) {
458 						string typeName = it.get!string();
459 						string typeCap = capitalize(typeName);
460 						if(auto v = typeCap in
461 						   SchemaReflection!T.instance.jsonTypes)
462 						{
463 							graphql.defaultResolverLog.logf("%s %s",
464 															typeCap, v.name);
465 							ret["data"] ~= v.typeJson.clone;
466 						}
467 					}
468 				} else {
469 					graphql.defaultResolverLog.log();
470 					ret["data"] = Json(null);
471 				}
472 				return ret;
473 			}
474 		);
475 }