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 }