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 }