1 module graphql.traits;
2 
3 import std.meta;
4 import std.range : ElementEncodingType;
5 import std.traits;
6 import std.typecons : Nullable;
7 import std.experimental.logger : logf;
8 import std.meta : AliasSeq, Filter;
9 
10 import nullablestore;
11 
12 import graphql.uda;
13 
14 template AllIncarnations(T, SCH...) {
15 	static if(SCH.length > 0 && is(T : SCH[0])) {
16 		alias AllIncarnations = AliasSeq!(SCH[0],
17 				.AllIncarnations!(T, SCH[1 ..  $])
18 			);
19 	} else static if(SCH.length > 0) {
20 		alias AllIncarnations = AliasSeq!(.AllIncarnations!(T, SCH[1 ..  $]));
21 	} else {
22 		alias AllIncarnations = AliasSeq!(T);
23 	}
24 }
25 
26 template InheritedClasses(T) {
27 	import std.meta : NoDuplicates, EraseAll;
28 	alias Clss = InheritedClassImpl!T;
29 	alias ND = NoDuplicates!(Clss);
30 	alias NO = EraseAll!(Object, ND);
31 	alias NOT = EraseAll!(T, NO);
32 	alias InheritedClasses = NOT;
33 }
34 
35 template isNotCustomLeafOrIgnore(T) {
36 	import graphql.uda;
37 	enum GQLDUdaData u = getUdaData!T;
38 	enum isNotCustomLeafOrIgnore = u.ignore != Ignore.yes;
39 }
40 
41 template InheritedClassImpl(T) {
42 	import std.meta : staticMap, AliasSeq, NoDuplicates;
43 	import std.traits : Select;
44 
45 	alias getInheritedFields() = staticMap!(.InheritedClassImpl,
46 			FieldTypeTuple!T
47 		);
48 
49 	alias ftt = Select!(is(T == union), getInheritedFields, AliasSeq);
50 
51 	alias getBaseTuple() = staticMap!(.InheritedClassImpl, BaseClassesTuple!T);
52 	alias clss = Select!(is(T == class), getBaseTuple, AliasSeq);
53 
54 	alias getInter() = staticMap!(.InheritedClassImpl, InterfacesTuple!T);
55 	alias inter = Select!(is(T == class) || is(T == interface),
56 			getInter, AliasSeq);
57 
58 	static if(is(T : Nullable!F, F) || is(T : NullableStore!F, F)) {
59 		alias interfs = staticMap!(.InheritedClassImpl, F);
60 		alias tmp = AliasSeq!(T, interfs);
61 		alias nn = tmp;
62 	} else {
63 		alias nn = T;
64 	}
65 
66 	alias InheritedClassImpl = AliasSeq!(ftt!(), clss!(), inter!(), nn);
67 }
68 
69 unittest {
70 	alias Bases = InheritedClasses!Union;
71 	static assert(is(Bases ==
72 			AliasSeq!(Nullable!Bar, Bar, Nullable!Impl, Base, Impl))
73 		);
74 }
75 
76 unittest {
77 	interface H {
78 		int h();
79 	}
80 
81 	interface G : H {
82 		float g();
83 	}
84 
85 	abstract class I : G {
86 	}
87 
88 	abstract class J : I {
89 	}
90 
91 	alias inter = InheritedClasses!G;
92 	static assert(is(inter == AliasSeq!(H)));
93 
94 	alias inter2 = InheritedClasses!J;
95 	static assert(is(inter2 == AliasSeq!(H,G,I)));
96 }
97 
98 private abstract class Base {
99 	int a;
100 }
101 
102 private class Impl : Base {
103 	float b;
104 }
105 
106 private class Bar {
107 	string c;
108 }
109 
110 private union Union {
111 	Nullable!Bar foo;
112 	Nullable!Impl impl;
113 }
114 
115 template isNotObject(Type) {
116 	enum isNotObject = !is(Type == Object);
117 }
118 
119 template isNotCustomLeaf(Type) {
120 	import graphql.uda;
121 	enum isNotCustomLeaf = !is(Type : GQLDCustomLeaf!Fs, Fs...);
122 }
123 
124 unittest {
125 	alias t = AliasSeq!(int, GQLDCustomLeaf!int);
126 	alias f = Filter!(isNotCustomLeaf, t);
127 	static assert(is(f == AliasSeq!(int)));
128 }
129 
130 template collectTypesImpl(Type) {
131 	import graphql.uda;
132 	static if(is(Type : GQLDCustomLeaf!Fs, Fs...)) {
133 		alias collectTypesImpl = AliasSeq!(Type);
134 	} else static if(is(Type == interface)) {
135 		alias RetTypes = AliasSeq!(collectReturnType!(Type,
136 				__traits(allMembers, Type))
137 			);
138 		alias ArgTypes = AliasSeq!(collectParameterTypes!(Type,
139 				__traits(allMembers, Type))
140 			);
141 		alias collectTypesImpl =
142 				AliasSeq!(Type, RetTypes, ArgTypes, InterfacesTuple!Type);
143 	} else static if(is(Type == class)) {
144 		alias RetTypes = AliasSeq!(collectReturnType!(Type,
145 				__traits(allMembers, Type))
146 			);
147 		alias ArgTypes = AliasSeq!(collectParameterTypes!(Type,
148 				__traits(allMembers, Type))
149 			);
150 		alias tmp = AliasSeq!(
151 				Fields!(Type),
152 				InheritedClasses!Type,
153 				InterfacesTuple!Type
154 			);
155 		//alias collectTypesImpl = Filter!(isNotCustomLeaf,
156 		alias collectTypesImpl =
157 				AliasSeq!(Type, tmp, RetTypes, ArgTypes)
158 			;
159 	} else static if(is(Type == union)) {
160 		alias collectTypesImpl = AliasSeq!(Type, InheritedClasses!Type);
161 	} else static if(is(Type : Nullable!F, F)) {
162 		alias collectTypesImpl = .collectTypesImpl!(F);
163 	} else static if(is(Type : NullableStore!F, F)) {
164 		alias collectTypesImpl = .collectTypesImpl!(Type.TypeValue);
165 	} else static if(is(Type : WrapperStore!F, F)) {
166 		alias collectTypesImpl = AliasSeq!(Type);
167 	} else static if(is(Type == struct)) {
168 		alias RetTypes = AliasSeq!(collectReturnType!(Type,
169 				__traits(allMembers, Type))
170 			);
171 		alias ArgTypes = AliasSeq!(collectParameterTypes!(Type,
172 				__traits(allMembers, Type))
173 			);
174 		alias Fi = Fields!Type;
175 		//alias collectTypesImpl = Filter!(isNotCustomLeaf,
176 		alias collectTypesImpl =
177 				AliasSeq!(Type, RetTypes, ArgTypes, Fi)
178 			;
179 	} else static if(isSomeString!Type) {
180 		alias collectTypesImpl = string;
181 	} else static if(is(Type == bool)) {
182 		alias collectTypesImpl = bool;
183 	} else static if(is(Type == enum)) {
184 		alias collectTypesImpl = Type;
185 	} else static if(isArray!Type) {
186 		alias collectTypesImpl = .collectTypesImpl!(ElementEncodingType!Type);
187 	} else static if(isIntegral!Type) {
188 		alias collectTypesImpl = long;
189 	} else static if(isFloatingPoint!Type) {
190 		alias collectTypesImpl = float;
191 	} else {
192 		alias collectTypesImpl = AliasSeq!();
193 	}
194 }
195 
196 unittest {
197 	struct Foo {
198 		int a;
199 	}
200 	alias Type = NullableStore!Foo;
201 	static if(is(Type : NullableStore!F, F)) {
202 		alias T = Type.TypeValue;
203 	} else {
204 		alias T = int;
205 	}
206 	static assert(is(T == Foo));
207 }
208 
209 template collectReturnType(Type, Names...) {
210 	import graphql.uda : getUdaData, GQLDUdaData;
211 	enum GQLDUdaData udaDataT = getUdaData!(Type);
212 	static if(Names.length > 0) {
213 		static if(__traits(getProtection, __traits(getMember, Type, Names[0]))
214 				== "public"
215 				&& isCallable!(__traits(getMember, Type, Names[0])))
216 		{
217 			alias rt = ReturnType!(__traits(getMember, Type, Names[0]));
218 			alias before = AliasSeq!(rt,
219 					.collectReturnType!(Type, Names[1 .. $])
220 				);
221 			//alias tmp = Filter!(isNotCustomLeaf, before);
222 			alias collectReturnType = before;
223 		} else {
224 			alias tmp = .collectReturnType!(Type, Names[1 .. $]);
225 			//alias collectReturnType = Filter!(isNotCustomLeaf, tmp);
226 			alias collectReturnType = tmp;
227 		}
228 	} else {
229 		alias collectReturnType = AliasSeq!();
230 	}
231 }
232 
233 template collectParameterTypes(Type, Names...) {
234 	static if(Names.length > 0) {
235 		static if(__traits(getProtection, __traits(getMember, Type, Names[0]))
236 				== "public"
237 				&& isCallable!(__traits(getMember, Type, Names[0])))
238 		{
239 			alias ArgTypes = ParameterTypeTuple!(
240 					__traits(getMember, Type, Names[0])
241 				);
242 			alias collectParameterTypes = AliasSeq!(ArgTypes,
243 					.collectParameterTypes!(Type, Names[1 .. $])
244 				);
245 		} else {
246 			alias collectParameterTypes = .collectParameterTypes!(Type,
247 					Names[1 .. $]
248 				);
249 		}
250 	} else {
251 		alias collectParameterTypes = AliasSeq!();
252 	}
253 }
254 
255 template fixupBasicTypes(T) {
256 	static if(isSomeString!T) {
257 		alias fixupBasicTypes = string;
258 	} else static if(is(T == enum)) {
259 		alias fixupBasicTypes = T;
260 	} else static if(is(T == bool)) {
261 		alias fixupBasicTypes = bool;
262 	} else static if(isIntegral!T) {
263 		alias fixupBasicTypes = long;
264 	} else static if(isFloatingPoint!T) {
265 		alias fixupBasicTypes = float;
266 	} else static if(isArray!T) {
267 		alias ElemFix = fixupBasicTypes!(ElementEncodingType!T);
268 		alias fixupBasicTypes = ElemFix[];
269 	} else static if(is(T : GQLDCustomLeaf!Fs, Fs...)) {
270 		alias ElemFix = fixupBasicTypes!(Fs[0]);
271 		alias fixupBasicTypes = GQLDCustomLeaf!(ElemFix, Fs[1 .. $]);
272 	} else static if(is(T : Nullable!F, F)) {
273 		alias ElemFix = fixupBasicTypes!(F);
274 		alias fixupBasicTypes = Nullable!(ElemFix);
275 	} else static if(is(T : NullableStore!F, F)) {
276 		alias ElemFix = fixupBasicTypes!(F);
277 		alias fixupBasicTypes = NullableStore!(ElemFix);
278 	} else {
279 		alias fixupBasicTypes = T;
280 	}
281 }
282 
283 template noArrayOrNullable(T) {
284 	static if(is(T : Nullable!F, F)) {
285 		enum noArrayOrNullable = false;
286 	} else static if(is(T : NullableStore!F, F)) {
287 		enum noArrayOrNullable = false;
288 	} else static if(!isSomeString!T && isArray!T) {
289 		enum noArrayOrNullable = false;
290 	} else {
291 		enum noArrayOrNullable = true;
292 	}
293 }
294 
295 unittest {
296 	static assert(is(Nullable!int : Nullable!F, F));
297 	static assert(!is(int : Nullable!F, F));
298 	static assert(!is(int : NullableStore!F, F));
299 	static assert( noArrayOrNullable!(int));
300 	static assert( noArrayOrNullable!(string));
301 	static assert(!noArrayOrNullable!(int[]));
302 	static assert(!noArrayOrNullable!(Nullable!int));
303 	static assert(!noArrayOrNullable!(Nullable!int));
304 	static assert(!noArrayOrNullable!(NullableStore!int));
305 }
306 
307 template collectTypes(T...) {
308 	import graphql.schema.introspectiontypes;
309 	alias oneLevelDown = NoDuplicates!(staticMap!(collectTypesImpl, T));
310 	alias basicT = staticMap!(fixupBasicTypes, oneLevelDown);
311 	alias elemTypes = Filter!(noArrayOrNullable, basicT);
312 	alias noVoid = EraseAll!(void, elemTypes);
313 	alias rslt = NoDuplicates!(EraseAll!(Object, basicT),
314 			EraseAll!(Object, noVoid)
315 		);
316 	static if(rslt.length == T.length) {
317 		alias collectTypes = rslt;
318 	} else {
319 		alias tmp = .collectTypes!(rslt);
320 		alias collectTypes = tmp;
321 	}
322 }
323 
324 template collectTypesPlusIntrospection(T) {
325 	import graphql.schema.introspectiontypes;
326 	alias collectTypesPlusIntrospection = AliasSeq!(collectTypes!T,
327 			IntrospectionTypes
328 		);
329 }
330 
331 package {
332 	enum Enum {
333 		one,
334 		two
335 	}
336 	class U {
337 		string f;
338 		Baz baz;
339 		Enum e;
340 
341 		override string toString() { return "U"; }
342 	}
343 	class W {
344 		Nullable!(Nullable!(U)[]) us;
345 		override string toString() { return "W"; }
346 	}
347 	class Y {
348 		bool b;
349 		Nullable!W w;
350 		override string toString() { return "Y"; }
351 	}
352 	class Z : Y {
353 		long id;
354 		override string toString() { return "Z"; }
355 	}
356 	class Baz {
357 		string id;
358 		Z[] zs;
359 		override string toString() { return "Baz"; }
360 	}
361 	class Args {
362 		float value;
363 		override string toString() { return "Args"; }
364 	}
365 	interface Foo {
366 		Baz bar();
367 		Args args();
368 	}
369 }
370 
371 unittest {
372 	alias a = collectTypes!(Enum);
373 	static assert(is(a == AliasSeq!(Enum)));
374 }
375 
376 unittest {
377 	alias ts = collectTypes!(Foo);
378 	alias expectedTypes = AliasSeq!(Foo, Baz, Args, float, Z[], Z, string,
379 			long, Y, bool, Nullable!W, W, Nullable!(Nullable!(U)[]), U, Enum);
380 
381 	template canBeFound(T) {
382 		enum tmp = staticIndexOf!(T, expectedTypes) != -1;
383 		enum canBeFound = tmp;
384 	}
385 	static assert(allSatisfy!(canBeFound, ts));
386 }
387 
388 unittest {
389 	import nullablestore;
390 	struct Foo {
391 		int a;
392 	}
393 
394 	struct Bar {
395 		NullableStore!Foo foo;
396 	}
397 
398 	static assert(is(collectTypes!Bar : AliasSeq!(Bar, NullableStore!Foo, Foo,
399 			long))
400 		);
401 }
402 
403 template stripArrayAndNullable(T) {
404 	static if(is(T : Nullable!F, F)) {
405 		alias stripArrayAndNullable = .stripArrayAndNullable!F;
406 	} else static if(is(T : NullableStore!F, F)) {
407 		alias stripArrayAndNullable = .stripArrayAndNullable!F;
408 	} else static if(!isSomeString!T && isArray!T) {
409 		alias stripArrayAndNullable =
410 			.stripArrayAndNullable!(ElementEncodingType!T);
411 	} else {
412 		alias stripArrayAndNullable = T;
413 	}
414 }
415 
416 template stringofType(T) {
417 	enum stringofType = T.stringof;
418 }
419 
420 string[] interfacesForType(Schema)(string typename) {
421 	import std.algorithm.searching : canFind;
422 	import graphql.reflection : SchemaReflection;
423 	if(auto result = typename in SchemaReflection!Schema.instance.bases) {
424 		return *result;
425 	}
426 	if(canFind(["__Type", "__Field", "__InputValue", "__Schema",
427 			   "__EnumValue", "__TypeKind", "__Directive",
428 			   "__DirectiveLocation"], typename))
429 	{
430 		return [typename];
431 	}
432 	return string[].init;
433 }
434 
435 template PossibleTypes(Type, Schema) {
436 	static if(is(Type == union)) {
437 		alias PossibleTypes = Filter!(isAggregateType, FieldTypeTuple!Type);
438 	} else static if(is(Type == interface) || is(Type == class)) {
439 		alias AllTypes = NoDuplicates!(collectTypes!Schema);
440 		alias PossibleTypes = NoDuplicates!(PossibleTypesImpl!(Type, AllTypes));
441 	}
442 }
443 
444 template PossibleTypesImpl(Type, AllTypes...) {
445 	static if(AllTypes.length == 0) {
446 		alias PossibleTypesImpl = AliasSeq!(Type);
447 	} else {
448 		static if(is(AllTypes[0] : Type)) {
449 			alias PossibleTypesImpl = AliasSeq!(AllTypes[0],
450 					.PossibleTypesImpl!(Type, AllTypes[1 .. $])
451 				);
452 		} else {
453 			alias PossibleTypesImpl = AliasSeq!(
454 					.PossibleTypesImpl!(Type, AllTypes[1 .. $])
455 				);
456 		}
457 	}
458 }
459 
460 // compiler has a hard time inferring safe. So we have to tag it.
461 void execForAllTypes(T, alias fn, Context...)(auto ref Context context) @safe {
462 	// establish a seen array to ensure no infinite recursion.
463 	execForAllTypesImpl!(T, fn)((bool[void*]).init, context);
464 }
465 
466 @trusted private void* keyFor(TypeInfo ti) {
467 	return cast(void*)ti;
468 }
469 
470 void execForAllTypesImpl(Type, alias fn, Context...)(
471 							bool[void*] seen, auto ref Context context) @safe
472 {
473 	alias FixedType = fixupBasicTypes!Type;
474 	static if(!is(FixedType == Type)) {
475 		return .execForAllTypesImpl!(FixedType, fn)(seen, context);
476 	} else static if(isArray!Type && !is(Type == string)) {
477 		return .execForAllTypesImpl!(typeof(Type.init[0]), fn)(seen, context);
478 	} else static if( // only process types we are interested in
479 		  isAggregateType!Type ||
480 		  is(Type == bool) ||
481 		  is(Type == enum) ||
482 		  is(Type == long) ||
483 		  is(Type == float) ||
484 		  is(Type == string))
485 	{
486 		auto tid = keyFor(typeid(Type));
487 		if(auto v = tid in seen) {
488 			// already in there
489 			return;
490 		}
491 		// store the result
492 		seen[tid] = true;
493 		fn!Type(context);
494 
495 		// now, handle the types we can get to from this type.
496 		static if(is(Type : GQLDCustomLeaf!Fs, Fs...)) {
497 			// ignore subtypes
498 		} else static if(is(Type : WrapperStore!F, F)) {
499 			// ignores subtypes
500 		} else static if(is(Type : Nullable!F, F)) {
501 			.execForAllTypesImpl!(F, fn)(seen, context);
502 		} else static if(is(Type : NullableStore!F, F)) {
503 			.execForAllTypesImpl!(Type.TypeValue, fn)(seen, context);
504 		} else static if(isAggregateType!Type) { // class, struct, interface, union
505 			// do callables first. Then do fields separately
506 			foreach(mem; __traits(allMembers, Type)) {{
507 				 static if(__traits(getProtection, __traits(getMember, Type, mem))
508 						   == "public"
509 						   && isCallable!(__traits(getMember, Type, mem)))
510 				 {
511 					 // return type
512 					 .execForAllTypesImpl!(ReturnType!(
513 									   __traits(getMember, Type, mem)), fn)
514 						 (seen, context);
515 					 // parameters
516 					 foreach(T; ParameterTypeTuple!(__traits(getMember,
517 																	Type, mem)))
518 					 {
519 						 .execForAllTypesImpl!(T, fn)(seen, context);
520 					 }
521 				 }
522 			}}
523 
524 			// now do all fields
525 			foreach(T; Fields!Type) {
526 				.execForAllTypesImpl!(T, fn)(seen, context);
527 			}
528 
529 			// do any base types (stolen from BaseTypeTuple, which annoyingly
530 			// doesn't work on all aggregates)
531 			static if(is(Type S == super)) {
532 				static foreach(T; S) {
533 					.execForAllTypesImpl!(T, fn)(seen, context);
534 				}
535 			}
536 		}
537 	}
538 	// other types we don't care about.
539 }