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 typeof(return)();
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 void setDefaultSchemaResolver(T, Con)(GraphQLD!(T,Con) graphql) {
79 	auto typeResolver = delegate(string name, Json parent,
80 			Json args, ref Con context) @safe
81 		{
82 			graphql.defaultResolverLog.logf("%s %s %s", name, args, parent);
83 			Json ret = //returnTemplate();
84 				Json.emptyObject();
85 			string typeName;
86 			if(Constants.name in args) {
87 				typeName = args[Constants.name].get!string();
88 			}
89 			if(Constants.typenameOrig in parent) {
90 				typeName = parent[Constants.typenameOrig].get!string();
91 			} else if(Constants.name in parent) {
92 				typeName = parent[Constants.name].get!string();
93 			}
94 			string typeCap;
95 			if(typeName.empty) {
96 				ret.insertError("unknown type");
97 				goto retLabel;
98 			} else {
99 				typeCap = typeName;
100 			}
101 			typeCap = typeCap.stringTypeStrip();
102 			//pragma(msg, collectTypes!(T));
103 			static foreach(type; collectTypes!(T)) {{
104 				enum typeConst = typeToTypeName!(type);
105 				if(typeCap == typeConst) {
106 					ret["data"] = typeToJson!(type,T)();
107 					graphql.defaultResolverLog.logf("%s %s %s", typeCap,
108 							typeConst, ret["data"]
109 						);
110 					goto retLabel;
111 				} else {
112 					graphql.defaultResolverLog.logf("||||||||||| %s %s",
113 							typeCap, typeConst
114 						);
115 				}
116 			}}
117 			retLabel:
118 			graphql.defaultResolverLog.logf("%s", ret.toPrettyString());
119 			return ret;
120 		};
121 
122 	QueryResolver!(Con) schemaResolver = delegate(string name, Json parent,
123 			Json args, ref Con context) @safe
124 		{
125 			//logf("%s %s %s", name, args, parent);
126 			Json ret = //returnTemplate();
127 				Json.emptyObject();
128 			ret["data"] = Json.emptyObject();
129 			ret["data"]["types"] = Json.emptyArray();
130 			alias AllTypes = collectTypes!(T);
131 			alias NoListOrArray = staticMap!(stripArrayAndNullable, AllTypes);
132 			alias FixUp = staticMap!(fixupBasicTypes, NoListOrArray);
133 			static if(hasMember!(T, Constants.directives)) {
134 				alias NoDirectives = EraseAll!(
135 						typeof(__traits(getMember, T, Constants.directives)),
136 						FixUp
137 					);
138 			} else {
139 				alias NoDirectives = FixUp;
140 			}
141 			alias NoDup = NoDuplicates!(EraseAll!(T, NoDirectives));
142 			static foreach(type; NoDup) {{
143 				Json tmp = typeToJsonImpl!(type,T,type)();
144 				ret["data"]["types"] ~= tmp;
145 			}}
146 			static if(hasMember!(T, Constants.directives)) {
147 				ret["data"][Constants.directives] =
148 					directivesToJson!(typeof(
149 							__traits(getMember, T, Constants.directives)
150 						));
151 			}
152 
153 			static foreach(tName; ["subscriptionType",
154 					"queryType", "mutationType"])
155 			{
156 				static if(hasMember!(T, tName)) {
157 					ret["data"][tName] =
158 						typeToJsonImpl!(typeof(__traits(getMember, T, tName)),
159 								T, typeof(__traits(getMember, T, tName)));
160 				}
161 			}
162 			graphql.defaultResolverLog.logf("%s", ret.toPrettyString());
163 			return ret;
164 		};
165 
166 	graphql.setResolver("queryType", "__type",
167 			delegate(string name, Json parent, Json args, ref Con context) @safe
168 			{
169 				graphql.defaultResolverLog.logf("%s %s %s", name, parent, args);
170 				Json tr = typeResolver(name, parent, args, context);
171 				Json ret = Json.emptyObject();
172 				ret["data"] = tr["data"]["ofType"];
173 				graphql.defaultResolverLog.logf("%s %s", tr.toPrettyString(),
174 						ret.toPrettyString());
175 				return ret;
176 			}
177 		);
178 
179 	graphql.setResolver("queryType", "__schema", schemaResolver);
180 
181 	graphql.setResolver("__Field", "type",
182 			delegate(string name, Json parent, Json args, ref Con context) @safe
183 			{
184 				graphql.defaultResolverLog.logf("name %s, parent %s, args %s",
185 						name, parent, args
186 					);
187 				import std.string : capitalize;
188 				Json ret = typeResolver(name, parent, args, context);
189 				graphql.defaultResolverLog.logf("FIELDDDDD TYPPPPPE %s",
190 						ret.toPrettyString()
191 					);
192 				return ret;
193 			}
194 		);
195 
196 	graphql.setResolver("__InputValue", "type",
197 			delegate(string name, Json parent, Json args, ref Con context) @safe
198 			{
199 				graphql.defaultResolverLog.logf("%s %s %s", name, parent, args);
200 				Json tr = typeResolver(name, parent, args, context);
201 				Json ret = Json.emptyObject();
202 				ret["data"] = tr["data"]["ofType"];
203 				graphql.defaultResolverLog.logf("%s %s", tr.toPrettyString(),
204 						ret.toPrettyString()
205 					);
206 				return ret;
207 			}
208 		);
209 
210 	graphql.setResolver("__Type", "ofType",
211 			delegate(string name, Json parent, Json args, ref Con context) @safe
212 			{
213 				graphql.defaultResolverLog.logf("name %s, parent %s, args %s",
214 						name, parent, args
215 					);
216 				Json ret = Json.emptyObject();
217 				Json ofType;
218 				if(parent.hasPathTo("ofType", ofType)) {
219 					ret["data"] = ofType;
220 				}
221 				graphql.defaultResolverLog.logf("%s", ret);
222 				return ret;
223 			}
224 		);
225 
226 	graphql.setResolver("__Type", "interfaces",
227 			delegate(string name, Json parent, Json args, ref Con context) @safe
228 			{
229 				graphql.defaultResolverLog.logf("name %s, parent %s, args %s",
230 						name, parent, args
231 					);
232 				Json ret = Json.emptyObject();
233 				ret["data"] = Json.emptyObject();
234 				if("kind" in parent
235 						&& parent["kind"].get!string() == "OBJECT")
236 				{
237 					ret["data"] = Json.emptyArray();
238 				}
239 				if("interfacesNames" !in parent) {
240 					return ret;
241 				}
242 				Json interNames = parent["interfacesNames"];
243 				if(interNames.type == Json.Type.array) {
244 					if(interNames.length > 0) {
245 						assert(ret["data"].type == Json.Type.array,
246 								format("%s", parent.toPrettyString())
247 							);
248 						ret["data"] = Json.emptyArray();
249 						foreach(Json it; interNames.byValue()) {
250 							string typeName = it.get!string();
251 							string typeCap = capitalize(typeName);
252 							l: switch(typeCap) {
253 								static foreach(type; collectTypes!(T)) {{
254 									case typeToTypeName!(type): {
255 									//if(typeCap == typeToTypeName!(type)) {
256 										alias striped =
257 											stripArrayAndNullable!type;
258 										graphql.defaultResolverLog.logf("%s %s",
259 												typeCap, striped.stringof
260 											);
261 										ret["data"] ~=
262 											typeToJsonImpl!(striped,T,type)();
263 										break l;
264 									}
265 								}}
266 								default: break;
267 							}
268 						}
269 					}
270 				}
271 				graphql.defaultResolverLog.logf("__Type.interfaces result %s",
272 						ret
273 					);
274 				return ret;
275 			}
276 		);
277 
278 	graphql.setResolver("__Type", "possibleTypes",
279 			delegate(string name, Json parent, Json args, ref Con context) @safe
280 			{
281 				graphql.defaultResolverLog.logf("name %s, parent %s, args %s",
282 						name, parent, args
283 					);
284 				Json ret = Json.emptyObject();
285 				if("possibleTypesNames" !in parent) {
286 					ret["data"] = Json(null);
287 					return ret;
288 				}
289 				Json pTypesNames = parent["possibleTypesNames"];
290 				if(pTypesNames.type == Json.Type.array) {
291 					graphql.defaultResolverLog.log();
292 					ret["data"] = Json.emptyArray();
293 					foreach(Json it; pTypesNames.byValue()) {
294 						string typeName = it.get!string();
295 						string typeCap = capitalize(typeName);
296 						l: switch(typeCap) {
297 							static foreach(type; collectTypes!(T)) {
298 								//if(typeCap == typeToTypeName!(type)) {
299 								case typeToTypeName!(type): {
300 									alias striped = stripArrayAndNullable!type;
301 									graphql.defaultResolverLog.logf("%s %s",
302 											typeCap, striped.stringof
303 										);
304 									ret["data"] ~=
305 										typeToJsonImpl!(striped,T,type)();
306 									break l;
307 								}
308 							}
309 							default: {
310 								break;
311 							}
312 						}
313 					}
314 				} else {
315 					graphql.defaultResolverLog.log();
316 					ret["data"] = Json(null);
317 				}
318 				return ret;
319 			}
320 		);
321 }