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