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 
9 template AllIncarnations(T, SCH...) {
10 	static if(SCH.length > 0 && is(T : SCH[0])) {
11 		alias AllIncarnations = AliasSeq!(SCH[0],
12 				.AllIncarnations!(T, SCH[1 ..  $])
13 			);
14 	} else static if(SCH.length > 0) {
15 		alias AllIncarnations = AliasSeq!(.AllIncarnations!(T, SCH[1 ..  $]));
16 	} else {
17 		alias AllIncarnations = AliasSeq!(T);
18 	}
19 }
20 
21 template InheritedClasses(T) {
22 	import std.meta : NoDuplicates, EraseAll;
23 	alias Clss = InheritedClassImpl!T;
24 	alias ND = NoDuplicates!(Clss);
25 	alias NO = EraseAll!(Object, ND);
26 	alias NOT = EraseAll!(T, NO);
27 	alias InheritedClasses = NOT;
28 }
29 
30 template InheritedClassImpl(T) {
31 	import std.meta : staticMap, AliasSeq, NoDuplicates;
32 	static if(is(T == union)) {
33 		alias fields = staticMap!(.InheritedClassImpl, FieldTypeTuple!T);
34 		alias tmp = AliasSeq!(T, fields);
35 		alias InheritedClassImpl = tmp;
36 	} else static if(is(T == class)) {
37 		alias clss = staticMap!(.InheritedClassImpl, BaseClassesTuple!T);
38 		alias interfs = staticMap!(.InheritedClassImpl, InterfacesTuple!T);
39 		alias tmp = AliasSeq!(T, clss, interfs);
40 		alias InheritedClassImpl = tmp;
41 	} else static if(is(T == interface)) {
42 		alias interfs = staticMap!(.InheritedClassImpl, InterfacesTuple!T);
43 		alias tmp = AliasSeq!(T, interfs);
44 		alias InheritedClassImpl = tmp;
45 	} else static if(is(T : Nullable!F, F)) {
46 		alias interfs = staticMap!(.InheritedClassImpl, F);
47 		alias tmp = AliasSeq!(T, interfs);
48 		alias InheritedClassImpl = tmp;
49 	} else {
50 		alias InheritedClassImpl = T;
51 	}
52 }
53 
54 unittest {
55 	alias Bases = InheritedClasses!Union;
56 	static assert(is(Bases ==
57 			AliasSeq!(Nullable!Bar, Bar, Nullable!Impl, Impl, Base))
58 		);
59 }
60 
61 unittest {
62 	interface H {
63 		int h();
64 	}
65 
66 	interface G : H {
67 		float g();
68 	}
69 
70 	abstract class I : G {
71 	}
72 
73 	abstract class J : I {
74 	}
75 
76 	alias inter = InheritedClasses!G;
77 	static assert(is(inter == AliasSeq!(H)));
78 
79 	alias inter2 = InheritedClasses!J;
80 	static assert(is(inter2 == AliasSeq!(I,G,H)));
81 }
82 
83 version(unittest) {
84 private:
85 	abstract class Base {
86 		int a;
87 	}
88 
89 	class Impl : Base {
90 		float b;
91 	}
92 
93 	class Bar {
94 		string c;
95 	}
96 
97 	union Union {
98 		Nullable!Bar foo;
99 		Nullable!Impl impl;
100 	}
101 }
102 
103 template isNotObject(Type) {
104 	enum isNotObject = !is(Type == Object);
105 }
106 
107 template collectTypesImpl(Type) {
108 	static if(is(Type == interface)) {
109 		alias RetTypes = AliasSeq!(collectReturnType!(Type,
110 				__traits(allMembers, Type))
111 			);
112 		alias ArgTypes = AliasSeq!(collectParameterTypes!(Type,
113 				__traits(allMembers, Type))
114 			);
115 		alias collectTypesImpl = AliasSeq!(Type, RetTypes, ArgTypes,
116 					InterfacesTuple!Type
117 				);
118 	} else static if(is(Type == class)) {
119 		alias RetTypes = AliasSeq!(collectReturnType!(Type,
120 				__traits(allMembers, Type))
121 			);
122 		alias ArgTypes = AliasSeq!(collectParameterTypes!(Type,
123 				__traits(allMembers, Type))
124 			);
125 		alias tmp = AliasSeq!(
126 				Fields!(Type),
127 				InheritedClasses!Type,
128 				InterfacesTuple!Type
129 			);
130 		alias collectTypesImpl = AliasSeq!(Type, tmp, RetTypes, ArgTypes);
131 	} else static if(is(Type == union)) {
132 		alias collectTypesImpl = AliasSeq!(Type, InheritedClasses!Type);
133 	} else static if(is(Type : Nullable!F, F)) {
134 		alias collectTypesImpl = .collectTypesImpl!(F);
135 	} else static if(is(Type == struct)) {
136 		alias RetTypes = AliasSeq!(collectReturnType!(Type,
137 				__traits(allMembers, Type))
138 			);
139 		alias ArgTypes = AliasSeq!(collectParameterTypes!(Type,
140 				__traits(allMembers, Type))
141 			);
142 		alias collectTypesImpl = AliasSeq!(Type, RetTypes, ArgTypes);
143 	} else static if(isSomeString!Type) {
144 		alias collectTypesImpl = string;
145 	} else static if(is(Type == bool)) {
146 		alias collectTypesImpl = bool;
147 	} else static if(is(Type == enum)) {
148 		alias collectTypesImpl = Type;
149 	} else static if(isArray!Type) {
150 		alias collectTypesImpl = .collectTypesImpl!(ElementEncodingType!Type);
151 	} else static if(isIntegral!Type) {
152 		alias collectTypesImpl = long;
153 	} else static if(isFloatingPoint!Type) {
154 		alias collectTypesImpl = float;
155 	} else {
156 		alias collectTypesImpl = AliasSeq!();
157 	}
158 }
159 
160 template collectReturnType(Type, Names...) {
161 	static if(Names.length > 0) {
162 		static if(isCallable!(__traits(getMember, Type, Names[0]))) {
163 			alias collectReturnType = AliasSeq!(
164 					ReturnType!(__traits(getMember, Type, Names[0])),
165 					.collectReturnType!(Type, Names[1 .. $])
166 				);
167 		} else {
168 			alias collectReturnType = .collectReturnType!(Type, Names[1 .. $]);
169 		}
170 	} else {
171 		alias collectReturnType = AliasSeq!();
172 	}
173 }
174 
175 template collectParameterTypes(Type, Names...) {
176 	static if(Names.length > 0) {
177 		static if(isCallable!(__traits(getMember, Type, Names[0]))) {
178 			alias ArgTypes = ParameterTypeTuple!(
179 					__traits(getMember, Type, Names[0])
180 				);
181 			alias collectParameterTypes = AliasSeq!(ArgTypes,
182 					.collectParameterTypes!(Type, Names[1 .. $])
183 				);
184 		} else {
185 			alias collectParameterTypes = .collectParameterTypes!(Type,
186 					Names[1 .. $]
187 				);
188 		}
189 	} else {
190 		alias collectParameterTypes = AliasSeq!();
191 	}
192 }
193 
194 template fixupBasicTypes(T) {
195 	static if(isSomeString!T) {
196 		alias fixupBasicTypes = string;
197 	} else static if(is(T == enum)) {
198 		alias fixupBasicTypes = T;
199 	} else static if(is(T == bool)) {
200 		alias fixupBasicTypes = bool;
201 	} else static if(isIntegral!T) {
202 		alias fixupBasicTypes = long;
203 	} else static if(isFloatingPoint!T) {
204 		alias fixupBasicTypes = float;
205 	} else static if(isArray!T) {
206 		alias ElemFix = fixupBasicTypes!(ElementEncodingType!T);
207 		alias fixupBasicTypes = ElemFix[];
208 	} else static if(is(T : Nullable!F, F)) {
209 		alias ElemFix = fixupBasicTypes!(F);
210 		alias fixupBasicTypes = Nullable!(ElemFix);
211 	} else {
212 		alias fixupBasicTypes = T;
213 	}
214 }
215 
216 template noArrayOrNullable(T) {
217 	static if(is(T : Nullable!F, F)) {
218 		enum noArrayOrNullable = false;
219 	} else static if(!isSomeString!T && isArray!T) {
220 		enum noArrayOrNullable = false;
221 	} else {
222 		enum noArrayOrNullable = true;
223 	}
224 }
225 
226 unittest {
227 	static assert(is(Nullable!int : Nullable!F, F));
228 	static assert(!is(int : Nullable!F, F));
229 	static assert( noArrayOrNullable!(int));
230 	static assert( noArrayOrNullable!(string));
231 	static assert(!noArrayOrNullable!(int[]));
232 	static assert(!noArrayOrNullable!(Nullable!int));
233 	static assert(!noArrayOrNullable!(Nullable!int));
234 }
235 
236 template collectTypes(T...) {
237 	alias oneLevelDown = NoDuplicates!(staticMap!(collectTypesImpl, T));
238 	alias basicT = staticMap!(fixupBasicTypes, oneLevelDown);
239 	alias elemTypes = Filter!(noArrayOrNullable, basicT);
240 	alias noVoid = EraseAll!(void, elemTypes);
241 	alias rslt = NoDuplicates!(EraseAll!(Object, basicT),
242 			EraseAll!(Object, noVoid)
243 		);
244 	static if(rslt.length == T.length) {
245 		alias collectTypes = rslt;
246 	} else {
247 		alias collectTypes = .collectTypes!(rslt);
248 	}
249 }
250 
251 version(unittest) {
252 package {
253 	enum Enum {
254 		one,
255 		two
256 	}
257 	class U {
258 		string f;
259 		Baz baz;
260 		Enum e;
261 	}
262 	class W {
263 		Nullable!(Nullable!(U)[]) us;
264 	}
265 	class Y {
266 		bool b;
267 		Nullable!W w;
268 	}
269 	class Z : Y {
270 		long id;
271 	}
272 	class Baz {
273 		string id;
274 		Z[] zs;
275 	}
276 	class Args {
277 		float value;
278 	}
279 	interface Foo {
280 		Baz bar();
281 		Args args();
282 	}
283 }
284 }
285 
286 unittest {
287 	alias a = collectTypes!(Enum);
288 	static assert(is(a == AliasSeq!(Enum)));
289 }
290 
291 unittest {
292 	alias ts = collectTypes!(Foo);
293 	alias expectedTypes = AliasSeq!(Foo, Baz, Args, float, Z[], Z, string,
294 			long, Y, bool, Nullable!W, W, Nullable!(Nullable!(U)[]), U, Enum);
295 
296 	template canBeFound(T) {
297 		enum tmp = staticIndexOf!(T, expectedTypes) != -1;
298 		enum canBeFound = tmp;
299 	}
300 	static assert(allSatisfy!(canBeFound, ts));
301 }
302 
303 template stripArrayAndNullable(T) {
304 	static if(is(T : Nullable!F, F)) {
305 		alias stripArrayAndNullable = .stripArrayAndNullable!F;
306 	} else static if(!isSomeString!T && isArray!T) {
307 		alias stripArrayAndNullable =
308 			.stripArrayAndNullable!(ElementEncodingType!T);
309 	} else {
310 		alias stripArrayAndNullable = T;
311 	}
312 }
313 
314 template stringofType(T) {
315 	enum stringofType = T.stringof;
316 }
317 
318 string[] interfacesForType(Schema)(string typename) {
319 	import std.algorithm.searching : canFind;
320 	alias filtered = staticMap!(stripArrayAndNullable, collectTypes!Schema);
321 	alias Types = NoDuplicates!(filtered);
322 	switch(typename) {
323 		static foreach(T; Types) {
324 			case T.stringof: {
325 				static enum ret = [NoDuplicates!(staticMap!(stringofType,
326 						EraseAll!(Object, AllIncarnations!(T, Types))))
327 					];
328 				//logf("%s %s %s", typename, T.stringof, ret);
329 				return ret;
330 			}
331 		}
332 		default:
333 			//logf("DEFAULT: '%s'", typename);
334 			if(canFind(["__Type", "__Field", "__InputValue", "__Schema",
335 						"__EnumValue", "__TypeKind", "__Directive",
336 						"__DirectiveLocation"], typename))
337 			{
338 				return [typename];
339 			}
340 			return string[].init;
341 	}
342 }
343 
344 template PossibleTypes(Type, Schema) {
345 	static if(is(Type == union)) {
346 		alias PossibleTypes = Filter!(isAggregateType, FieldTypeTuple!Type);
347 	} else static if(is(Type == interface) || is(Type == class)) {
348 		alias AllTypes = NoDuplicates!(collectTypes!Schema);
349 		alias PossibleTypes = NoDuplicates!(PossibleTypesImpl!(Type, AllTypes));
350 	}
351 }
352 
353 template PossibleTypesImpl(Type, AllTypes...) {
354 	static if(AllTypes.length == 0) {
355 		alias PossibleTypesImpl = AliasSeq!(Type);
356 	} else {
357 		static if(is(AllTypes[0] : Type)) {
358 			alias PossibleTypesImpl = AliasSeq!(AllTypes[0],
359 					.PossibleTypesImpl!(Type, AllTypes[1 .. $])
360 				);
361 		} else {
362 			alias PossibleTypesImpl = AliasSeq!(
363 					.PossibleTypesImpl!(Type, AllTypes[1 .. $])
364 				);
365 		}
366 	}
367 }