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