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 }