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 	string toS(int i) {
126 		return "";
127 	}
128 	int fromS(string s) {
129 		return 0;
130 	}
131 	alias t = AliasSeq!(int, GQLDCustomLeaf!(int, toS, fromS));
132 	alias f = Filter!(isNotCustomLeaf, t);
133 	static assert(is(f == AliasSeq!(int)));
134 }
135 
136 template collectTypesImpl(Type) {
137 	import graphql.uda;
138 	static if(is(Type : GQLDCustomLeaf!Fs, Fs...)) {
139 		alias collectTypesImpl = AliasSeq!(Type);
140 	} else static if(is(Type == interface)) {
141 		alias RetTypes = AliasSeq!(collectReturnType!(Type,
142 				__traits(allMembers, Type))
143 			);
144 		alias ArgTypes = AliasSeq!(collectParameterTypes!(Type,
145 				__traits(allMembers, Type))
146 			);
147 		alias collectTypesImpl =
148 				AliasSeq!(Type, RetTypes, ArgTypes, InterfacesTuple!Type);
149 	} else static if(is(Type == class)) {
150 		alias RetTypes = AliasSeq!(collectReturnType!(Type,
151 				__traits(allMembers, Type))
152 			);
153 		alias ArgTypes = AliasSeq!(collectParameterTypes!(Type,
154 				__traits(allMembers, Type))
155 			);
156 		alias tmp = AliasSeq!(
157 				Fields!(Type),
158 				InheritedClasses!Type,
159 				InterfacesTuple!Type
160 			);
161 		//alias collectTypesImpl = Filter!(isNotCustomLeaf,
162 		alias collectTypesImpl =
163 				AliasSeq!(Type, tmp, RetTypes, ArgTypes)
164 			;
165 	} else static if(is(Type == union)) {
166 		alias collectTypesImpl = AliasSeq!(Type, InheritedClasses!Type);
167 	} else static if(is(Type : Nullable!F, F)) {
168 		alias collectTypesImpl = .collectTypesImpl!(F);
169 	} else static if(is(Type : NullableStore!F, F)) {
170 		alias collectTypesImpl = .collectTypesImpl!(Type.TypeValue);
171 	} else static if(is(Type : WrapperStore!F, F)) {
172 		alias collectTypesImpl = AliasSeq!(Type);
173 	} else static if(is(Type == struct)) {
174 		alias RetTypes = AliasSeq!(collectReturnType!(Type,
175 				__traits(allMembers, Type))
176 			);
177 		alias ArgTypes = AliasSeq!(collectParameterTypes!(Type,
178 				__traits(allMembers, Type))
179 			);
180 		alias Fi = Fields!Type;
181 		//alias collectTypesImpl = Filter!(isNotCustomLeaf,
182 		alias collectTypesImpl =
183 				AliasSeq!(Type, RetTypes, ArgTypes, Fi)
184 			;
185 	} else static if(isSomeString!Type) {
186 		alias collectTypesImpl = string;
187 	} else static if(is(Type == bool)) {
188 		alias collectTypesImpl = bool;
189 	} else static if(is(Type == enum)) {
190 		alias collectTypesImpl = Type;
191 	} else static if(isArray!Type) {
192 		alias collectTypesImpl = .collectTypesImpl!(ElementEncodingType!Type);
193 	} else static if(isIntegral!Type) {
194 		alias collectTypesImpl = long;
195 	} else static if(isFloatingPoint!Type) {
196 		alias collectTypesImpl = float;
197 	} else {
198 		alias collectTypesImpl = AliasSeq!();
199 	}
200 }
201 
202 unittest {
203 	struct Foo {
204 		int a;
205 	}
206 	alias Type = NullableStore!Foo;
207 	static if(is(Type : NullableStore!F, F)) {
208 		alias T = Type.TypeValue;
209 	} else {
210 		alias T = int;
211 	}
212 	static assert(is(T == Foo));
213 }
214 
215 template collectReturnType(Type, Names...) {
216 	import graphql.uda : getUdaData, GQLDUdaData;
217 	enum GQLDUdaData udaDataT = getUdaData!(Type);
218 	static if(Names.length > 0) {
219 		static if(__traits(getProtection, __traits(getMember, Type, Names[0]))
220 				== "public"
221 				&& isCallable!(__traits(getMember, Type, Names[0])))
222 		{
223 			alias rt = ReturnType!(__traits(getMember, Type, Names[0]));
224 			alias before = AliasSeq!(rt,
225 					.collectReturnType!(Type, Names[1 .. $])
226 				);
227 			//alias tmp = Filter!(isNotCustomLeaf, before);
228 			alias collectReturnType = before;
229 		} else {
230 			alias tmp = .collectReturnType!(Type, Names[1 .. $]);
231 			//alias collectReturnType = Filter!(isNotCustomLeaf, tmp);
232 			alias collectReturnType = tmp;
233 		}
234 	} else {
235 		alias collectReturnType = AliasSeq!();
236 	}
237 }
238 
239 template collectParameterTypes(Type, Names...) {
240 	static if(Names.length > 0) {
241 		static if(__traits(getProtection, __traits(getMember, Type, Names[0]))
242 				== "public"
243 				&& isCallable!(__traits(getMember, Type, Names[0])))
244 		{
245 			alias ArgTypes = ParameterTypeTuple!(
246 					__traits(getMember, Type, Names[0])
247 				);
248 			alias collectParameterTypes = AliasSeq!(ArgTypes,
249 					.collectParameterTypes!(Type, Names[1 .. $])
250 				);
251 		} else {
252 			alias collectParameterTypes = .collectParameterTypes!(Type,
253 					Names[1 .. $]
254 				);
255 		}
256 	} else {
257 		alias collectParameterTypes = AliasSeq!();
258 	}
259 }
260 
261 template fixupBasicTypes(T) {
262 	static if(isSomeString!T) {
263 		alias fixupBasicTypes = string;
264 	} else static if(is(T == enum)) {
265 		alias fixupBasicTypes = T;
266 	} else static if(is(T == bool)) {
267 		alias fixupBasicTypes = bool;
268 	} else static if(isIntegral!T) {
269 		alias fixupBasicTypes = long;
270 	} else static if(isFloatingPoint!T) {
271 		alias fixupBasicTypes = float;
272 	} else static if(isArray!T) {
273 		alias ElemFix = fixupBasicTypes!(ElementEncodingType!T);
274 		alias fixupBasicTypes = ElemFix[];
275 	} else static if(is(T : GQLDCustomLeaf!Fs, Fs...)) {
276 		alias ElemFix = fixupBasicTypes!(Fs[0]);
277 		alias fixupBasicTypes = GQLDCustomLeaf!(ElemFix, Fs[1 .. $]);
278 	} else static if(is(T : Nullable!F, F)) {
279 		alias ElemFix = fixupBasicTypes!(F);
280 		alias fixupBasicTypes = Nullable!(ElemFix);
281 	} else static if(is(T : NullableStore!F, F)) {
282 		alias ElemFix = fixupBasicTypes!(F);
283 		alias fixupBasicTypes = NullableStore!(ElemFix);
284 	} else {
285 		alias fixupBasicTypes = T;
286 	}
287 }
288 
289 template noArrayOrNullable(T) {
290 	static if(is(T : Nullable!F, F)) {
291 		enum noArrayOrNullable = false;
292 	} else static if(is(T : NullableStore!F, F)) {
293 		enum noArrayOrNullable = false;
294 	} else static if(!isSomeString!T && isArray!T) {
295 		enum noArrayOrNullable = false;
296 	} else {
297 		enum noArrayOrNullable = true;
298 	}
299 }
300 
301 unittest {
302 	static assert(is(Nullable!int : Nullable!F, F));
303 	static assert(!is(int : Nullable!F, F));
304 	static assert(!is(int : NullableStore!F, F));
305 	static assert( noArrayOrNullable!(int));
306 	static assert( noArrayOrNullable!(string));
307 	static assert(!noArrayOrNullable!(int[]));
308 	static assert(!noArrayOrNullable!(Nullable!int));
309 	static assert(!noArrayOrNullable!(Nullable!int));
310 	static assert(!noArrayOrNullable!(NullableStore!int));
311 }
312 
313 template collectTypes(T...) {
314 	import graphql.schema.introspectiontypes;
315 	alias oneLevelDown = NoDuplicates!(staticMap!(collectTypesImpl, T));
316 	alias basicT = staticMap!(fixupBasicTypes, oneLevelDown);
317 	alias elemTypes = Filter!(noArrayOrNullable, basicT);
318 	alias noVoid = EraseAll!(void, elemTypes);
319 	alias rslt = NoDuplicates!(EraseAll!(Object, basicT),
320 			EraseAll!(Object, noVoid)
321 		);
322 	static if(rslt.length == T.length) {
323 		alias collectTypes = rslt;
324 	} else {
325 		alias tmp = .collectTypes!(rslt);
326 		alias collectTypes = tmp;
327 	}
328 }
329 
330 template collectTypesPlusIntrospection(T) {
331 	import graphql.schema.introspectiontypes;
332 	alias collectTypesPlusIntrospection = AliasSeq!(collectTypes!T,
333 			IntrospectionTypes
334 		);
335 }
336 
337 package {
338 	enum Enum {
339 		one,
340 		two
341 	}
342 	class U {
343 		string f;
344 		Baz baz;
345 		Enum e;
346 
347 		override string toString() { return "U"; }
348 	}
349 	class W {
350 		Nullable!(Nullable!(U)[]) us;
351 		override string toString() { return "W"; }
352 	}
353 	class Y {
354 		bool b;
355 		Nullable!W w;
356 		override string toString() { return "Y"; }
357 	}
358 	class Z : Y {
359 		long id;
360 		override string toString() { return "Z"; }
361 	}
362 	class Baz {
363 		string id;
364 		Z[] zs;
365 		override string toString() { return "Baz"; }
366 	}
367 	class Args {
368 		float value;
369 		override string toString() { return "Args"; }
370 	}
371 	interface Foo {
372 		Baz bar();
373 		Args args();
374 	}
375 }
376 
377 unittest {
378 	alias a = collectTypes!(Enum);
379 	static assert(is(a == AliasSeq!(Enum)));
380 }
381 
382 unittest {
383 	alias ts = collectTypes!(Foo);
384 	alias expectedTypes = AliasSeq!(Foo, Baz, Args, float, Z[], Z, string,
385 			long, Y, bool, Nullable!W, W, Nullable!(Nullable!(U)[]), U, Enum);
386 
387 	template canBeFound(T) {
388 		enum tmp = staticIndexOf!(T, expectedTypes) != -1;
389 		enum canBeFound = tmp;
390 	}
391 	static assert(allSatisfy!(canBeFound, ts));
392 }
393 
394 unittest {
395 	import nullablestore;
396 	struct Foo {
397 		int a;
398 	}
399 
400 	struct Bar {
401 		NullableStore!Foo foo;
402 	}
403 
404 	static assert(is(collectTypes!Bar : AliasSeq!(Bar, NullableStore!Foo, Foo,
405 			long))
406 		);
407 }
408 
409 template stripArrayAndNullable(T) {
410 	static if(is(T : Nullable!F, F)) {
411 		alias stripArrayAndNullable = .stripArrayAndNullable!F;
412 	} else static if(is(T : NullableStore!F, F)) {
413 		alias stripArrayAndNullable = .stripArrayAndNullable!F;
414 	} else static if(!isSomeString!T && isArray!T) {
415 		alias stripArrayAndNullable =
416 			.stripArrayAndNullable!(ElementEncodingType!T);
417 	} else {
418 		alias stripArrayAndNullable = T;
419 	}
420 }
421 
422 template stringofType(T) {
423 	enum stringofType = T.stringof;
424 }
425 
426 string[] interfacesForType(Schema)(string typename) {
427 	import std.algorithm.searching : canFind;
428 	import graphql.reflection : SchemaReflection;
429 	if(auto result = typename in SchemaReflection!Schema.instance.bases) {
430 		return *result;
431 	}
432 	if(canFind(["__Type", "__Field", "__InputValue", "__Schema",
433 			   "__EnumValue", "__TypeKind", "__Directive",
434 			   "__DirectiveLocation"], typename))
435 	{
436 		return [typename];
437 	}
438 	return string[].init;
439 }
440 
441 template PossibleTypes(Type, Schema) {
442 	static if(is(Type == union)) {
443 		alias PossibleTypes = Filter!(isAggregateType, FieldTypeTuple!Type);
444 	} else static if(is(Type == interface) || is(Type == class)) {
445 		alias AllTypes = NoDuplicates!(collectTypes!Schema);
446 		alias PossibleTypes = NoDuplicates!(PossibleTypesImpl!(Type, AllTypes));
447 	}
448 }
449 
450 template PossibleTypesImpl(Type, AllTypes...) {
451 	static if(AllTypes.length == 0) {
452 		alias PossibleTypesImpl = AliasSeq!(Type);
453 	} else {
454 		static if(is(AllTypes[0] : Type)) {
455 			alias PossibleTypesImpl = AliasSeq!(AllTypes[0],
456 					.PossibleTypesImpl!(Type, AllTypes[1 .. $])
457 				);
458 		} else {
459 			alias PossibleTypesImpl = AliasSeq!(
460 					.PossibleTypesImpl!(Type, AllTypes[1 .. $])
461 				);
462 		}
463 	}
464 }
465 
466 // compiler has a hard time inferring safe. So we have to tag it.
467 void execForAllTypes(T, alias fn, Context...)(auto ref Context context) @safe {
468 	// establish a seen array to ensure no infinite recursion.
469 	execForAllTypesImpl!(T, fn)((bool[void*]).init, context);
470 }
471 
472 @trusted private void* keyFor(TypeInfo ti) {
473 	return cast(void*)ti;
474 }
475 
476 void execForAllTypesImpl(Type, alias fn, Context...)(
477 							bool[void*] seen, auto ref Context context) @safe
478 {
479 	alias FixedType = fixupBasicTypes!Type;
480 	static if(!is(FixedType == Type)) {
481 		return .execForAllTypesImpl!(FixedType, fn)(seen, context);
482 	} else static if(isArray!Type && !is(Type == string)) {
483 		return .execForAllTypesImpl!(typeof(Type.init[0]), fn)(seen, context);
484 	} else static if( // only process types we are interested in
485 		  isAggregateType!Type ||
486 		  is(Type == bool) ||
487 		  is(Type == enum) ||
488 		  is(Type == long) ||
489 		  is(Type == float) ||
490 		  is(Type == string))
491 	{
492 		auto tid = keyFor(typeid(Type));
493 		if(auto v = tid in seen) {
494 			// already in there
495 			return;
496 		}
497 		// store the result
498 		seen[tid] = true;
499 		fn!Type(context);
500 
501 		// now, handle the types we can get to from this type.
502 		static if(is(Type : GQLDCustomLeaf!Fs, Fs...)) {
503 			// ignore subtypes
504 		} else static if(is(Type : WrapperStore!F, F)) {
505 			// ignores subtypes
506 		} else static if(is(Type : Nullable!F, F)) {
507 			.execForAllTypesImpl!(F, fn)(seen, context);
508 		} else static if(is(Type : NullableStore!F, F)) {
509 			.execForAllTypesImpl!(Type.TypeValue, fn)(seen, context);
510 		} else static if(isAggregateType!Type) { // class, struct, interface, union
511 			// do callables first. Then do fields separately
512 			foreach(mem; __traits(allMembers, Type)) {{
513 				 static if(__traits(getProtection, __traits(getMember, Type, mem))
514 						   == "public"
515 						   && isCallable!(__traits(getMember, Type, mem)))
516 				 {
517 					 // return type
518 					 .execForAllTypesImpl!(ReturnType!(
519 									   __traits(getMember, Type, mem)), fn)
520 						 (seen, context);
521 					 // parameters
522 					 foreach(T; ParameterTypeTuple!(__traits(getMember,
523 																	Type, mem)))
524 					 {
525 						 .execForAllTypesImpl!(T, fn)(seen, context);
526 					 }
527 				 }
528 			}}
529 
530 			// now do all fields
531 			foreach(T; Fields!Type) {
532 				.execForAllTypesImpl!(T, fn)(seen, context);
533 			}
534 
535 			// do any base types (stolen from BaseTypeTuple, which annoyingly
536 			// doesn't work on all aggregates)
537 			static if(is(Type S == super)) {
538 				static foreach(T; S) {
539 					.execForAllTypesImpl!(T, fn)(seen, context);
540 				}
541 			}
542 		}
543 	}
544 	// other types we don't care about.
545 }