1 module graphql.schema.types; 2 3 import std.conv : to; 4 import std.array; 5 import std.meta; 6 import std.traits; 7 import std.typecons; 8 import std.algorithm.iteration : map, joiner; 9 import std.algorithm.searching : canFind; 10 import std.range : ElementEncodingType; 11 import std.format; 12 import std.string : strip; 13 import std.experimental.logger; 14 15 import vibe.data.json; 16 17 import nullablestore; 18 19 import graphql.helper; 20 import graphql.traits; 21 import graphql.constants; 22 import graphql.uda; 23 24 @safe: 25 26 enum GQLDKind { 27 String, 28 Float, 29 Int, 30 Bool, 31 CustomLeaf, 32 Object_, 33 List, 34 Enum, 35 Map, 36 Nullable, 37 NonNull, 38 Union, 39 Query, 40 Mutation, 41 Subscription, 42 Schema 43 } 44 45 abstract class GQLDType { 46 const GQLDKind kind; 47 string name; 48 49 this(GQLDKind kind) { 50 this.kind = kind; 51 } 52 53 override string toString() const { 54 return "GQLDType"; 55 } 56 57 string toShortString() const { 58 return this.toString(); 59 } 60 } 61 62 class GQLDScalar : GQLDType { 63 this(GQLDKind kind) { 64 super(kind); 65 } 66 } 67 68 class GQLDLeaf : GQLDScalar { 69 this(string name) { 70 super(GQLDKind.CustomLeaf); 71 super.name = name; 72 } 73 } 74 75 class GQLDString : GQLDScalar { 76 this() { 77 super(GQLDKind.String); 78 super.name = "String"; 79 } 80 81 override string toString() const { 82 return "String"; 83 } 84 } 85 86 class GQLDFloat : GQLDScalar { 87 this() { 88 super(GQLDKind.Float); 89 super.name = "Float"; 90 } 91 92 override string toString() const { 93 return "Float"; 94 } 95 } 96 97 class GQLDInt : GQLDScalar { 98 this() { 99 super(GQLDKind.Int); 100 super.name = "Int"; 101 } 102 103 override string toString() const { 104 return "Int"; 105 } 106 } 107 108 class GQLDEnum : GQLDScalar { 109 string enumName; 110 this(string enumName) { 111 super(GQLDKind.Enum); 112 this.enumName = enumName; 113 super.name = enumName; 114 } 115 116 override string toString() const { 117 return this.enumName; 118 } 119 } 120 121 class GQLDBool : GQLDScalar { 122 this() { 123 super(GQLDKind.Bool); 124 super.name = "Boolean"; 125 } 126 127 override string toString() const { 128 return "Boolean"; 129 } 130 } 131 132 class GQLDMap : GQLDType { 133 GQLDType[string] member; 134 GQLDMap[] derivatives; 135 136 this() { 137 super(GQLDKind.Map); 138 } 139 this(GQLDKind kind) { 140 super(kind); 141 } 142 143 void addDerivative(GQLDMap d) { 144 if(!canFind!((a,b) => a is b)(this.derivatives, d)) { 145 this.derivatives ~= d; 146 } 147 } 148 149 override string toString() const { 150 auto app = appender!string(); 151 foreach(key, value; this.member) { 152 formattedWrite(app, "%s: %s\n", key, value.toString()); 153 } 154 return app.data; 155 } 156 } 157 158 class GQLDObject : GQLDMap { 159 GQLDObject base; 160 161 this(string name) { 162 super(GQLDKind.Object_); 163 super.name = name; 164 } 165 166 override string toString() const { 167 return format( 168 "Object %s(%s))\n\t\t\t\tBase(%s)\n\t\t\t\tDerivaties(%s)", 169 this.name, 170 this.member 171 .byKeyValue 172 .map!(kv => format("%s %s", kv.key, 173 kv.value.toShortString())) 174 .joiner(",\n\t\t\t\t"), 175 (this.base !is null ? this.base.toShortString() : "null"), 176 this.derivatives.map!(d => d.toShortString()) 177 ); 178 } 179 180 override string toShortString() const { 181 return format("%s", super.name); 182 } 183 } 184 185 class GQLDUnion : GQLDMap { 186 this(string name) { 187 super(GQLDKind.Union); 188 super.name = name; 189 } 190 191 override string toString() const { 192 return format("Union %s(%s))\n\t\t\t\tDerivaties(%s)", 193 this.name, 194 this.member 195 .byKeyValue 196 .map!(kv => format("%s %s", kv.key, 197 kv.value.toShortString())) 198 .joiner(",\n\t\t\t\t"), 199 this.derivatives.map!(d => d.toShortString()) 200 ); 201 } 202 } 203 204 class GQLDList : GQLDType { 205 GQLDType elementType; 206 207 this(GQLDType elemType) { 208 super(GQLDKind.List); 209 super.name = "List"; 210 this.elementType = elemType; 211 } 212 213 override string toString() const { 214 return format("List(%s)", this.elementType.toShortString()); 215 } 216 217 override string toShortString() const { 218 return format("List(%s)", this.elementType.toShortString()); 219 } 220 } 221 222 class GQLDNonNull : GQLDType { 223 GQLDType elementType; 224 225 this(GQLDType elemType) { 226 super(GQLDKind.NonNull); 227 super.name = "NonNull"; 228 this.elementType = elemType; 229 } 230 231 override string toString() const { 232 return format("NonNull(%s)", this.elementType.toShortString()); 233 } 234 235 override string toShortString() const { 236 return format("NonNull(%s)", this.elementType.toShortString()); 237 } 238 } 239 240 class GQLDNullable : GQLDType { 241 GQLDType elementType; 242 243 this(GQLDType elemType) { 244 super(GQLDKind.Nullable); 245 super.name = "Nullable"; 246 this.elementType = elemType; 247 } 248 249 override string toString() const { 250 return format("Nullable(%s)", this.elementType.toShortString()); 251 } 252 253 override string toShortString() const { 254 return format("Nullable(%s)", this.elementType.toShortString()); 255 } 256 } 257 258 class GQLDOperation : GQLDType { 259 GQLDType returnType; 260 string returnTypeName; 261 262 GQLDType[string] parameters; 263 264 this(GQLDKind kind) { 265 super(kind); 266 } 267 268 override string toString() const { 269 return format("%s %s(%s)", super.kind, returnType.toShortString(), 270 this.parameters 271 .byKeyValue 272 .map!(kv => 273 format("%s %s", kv.key, kv.value.toShortString()) 274 ) 275 .joiner(", ") 276 ); 277 } 278 } 279 280 class GQLDQuery : GQLDOperation { 281 this() { 282 super(GQLDKind.Query); 283 super.name = "Query"; 284 } 285 } 286 287 class GQLDMutation : GQLDOperation { 288 this() { 289 super(GQLDKind.Mutation); 290 super.name = "Mutation"; 291 } 292 } 293 294 class GQLDSubscription : GQLDOperation { 295 this() { 296 super(GQLDKind.Subscription); 297 super.name = "Subscription"; 298 } 299 } 300 301 class GQLDSchema(Type) : GQLDMap { 302 GQLDType[string] types; 303 304 GQLDObject __schema; 305 GQLDObject __type; 306 GQLDObject __field; 307 GQLDObject __inputValue; 308 GQLDObject __enumValue; 309 GQLDObject __directives; 310 311 GQLDNonNull __nonNullType; 312 GQLDNullable __nullableType; 313 GQLDNullable __listOfNonNullType; 314 GQLDNonNull __nonNullListOfNonNullType; 315 GQLDNonNull __nonNullField; 316 GQLDNullable __listOfNonNullField; 317 GQLDNonNull __nonNullInputValue; 318 GQLDList __listOfNonNullInputValue; 319 GQLDNonNull __nonNullListOfNonNullInputValue; 320 GQLDList __listOfNonNullEnumValue; 321 322 this() { 323 super(GQLDKind.Schema); 324 super.name = "Schema"; 325 this.createInbuildTypes(); 326 this.createIntrospectionTypes(); 327 } 328 329 void createInbuildTypes() { 330 this.types["string"] = new GQLDString(); 331 foreach(t; ["String", "Int", "Float", "Boolean"]) { 332 GQLDObject tmp = new GQLDObject(t); 333 this.types[t] = tmp; 334 tmp.member[Constants.name] = new GQLDString(); 335 tmp.member[Constants.description] = new GQLDString(); 336 tmp.member[Constants.kind] = new GQLDEnum(Constants.__TypeKind); 337 //tmp.resolver = buildTypeResolver!(Type,Con)(); 338 } 339 } 340 341 void createIntrospectionTypes() { 342 // build base types 343 auto str = new GQLDString(); 344 auto nnStr = new GQLDNonNull(str); 345 auto nllStr = new GQLDNullable(str); 346 347 auto b = new GQLDBool(); 348 auto nnB = new GQLDNonNull(b); 349 this.__schema = new GQLDObject(Constants.__schema); 350 this.__type = new GQLDObject(Constants.__Type); 351 this.__nullableType = new GQLDNullable(this.__type); 352 this.__schema.member["mutationType"] = this.__nullableType; 353 this.__schema.member["subscriptionType"] = this.__nullableType; 354 355 this.__type.member[Constants.ofType] = this.__nullableType; 356 this.__type.member[Constants.kind] = new GQLDEnum(Constants.__TypeKind); 357 this.__type.member[Constants.name] = nllStr; 358 this.__type.member[Constants.description] = nllStr; 359 360 this.__nonNullType = new GQLDNonNull(this.__type); 361 this.__schema.member["queryType"] = this.__nonNullType; 362 auto lNNTypes = new GQLDList(this.__nonNullType); 363 auto nlNNTypes = new GQLDNullable(lNNTypes); 364 this.__listOfNonNullType = new GQLDNullable(lNNTypes); 365 this.__type.member[Constants.interfaces] = new GQLDNonNull(lNNTypes); 366 this.__type.member["possibleTypes"] = nlNNTypes; 367 368 this.__nonNullListOfNonNullType = new GQLDNonNull(lNNTypes); 369 this.__schema.member["types"] = this.__nonNullListOfNonNullType; 370 371 this.__field = new GQLDObject("__Field"); 372 this.__field.member[Constants.name] = nnStr; 373 this.__field.member[Constants.description] = nllStr; 374 this.__field.member[Constants.type] = this.__nonNullType; 375 this.__field.member[Constants.isDeprecated] = nnB; 376 this.__field.member[Constants.deprecationReason] = nllStr; 377 378 this.__nonNullField = new GQLDNonNull(this.__field); 379 auto lNNFields = new GQLDList(this.__nonNullField); 380 this.__listOfNonNullField = new GQLDNullable(lNNFields); 381 this.__type.member[Constants.fields] = this.__listOfNonNullField; 382 383 this.__inputValue = new GQLDObject(Constants.__InputValue); 384 this.__inputValue.member[Constants.name] = nnStr; 385 this.__inputValue.member[Constants.description] = nllStr; 386 this.__inputValue.member["defaultValue"] = nllStr; 387 this.__inputValue.member[Constants.type] = this.__nonNullType; 388 389 this.__nonNullInputValue = new GQLDNonNull(this.__inputValue); 390 this.__listOfNonNullInputValue = new GQLDList( 391 this.__nonNullInputValue 392 ); 393 auto nlNNInputValue = new GQLDNullable( 394 this.__listOfNonNullInputValue 395 ); 396 397 this.__type.member["inputFields"] = nlNNInputValue; 398 399 this.__nonNullListOfNonNullInputValue = new GQLDNonNull( 400 this.__listOfNonNullInputValue 401 ); 402 403 this.__field.member[Constants.args] = this.__nonNullListOfNonNullInputValue; 404 405 this.__enumValue = new GQLDObject(Constants.__EnumValue); 406 this.__enumValue.member[Constants.name] = nnStr; 407 this.__enumValue.member[Constants.description] = nllStr; 408 this.__enumValue.member[Constants.isDeprecated] = nnB; 409 this.__enumValue.member[Constants.deprecationReason] = nllStr; 410 411 this.__listOfNonNullEnumValue = new GQLDList(new GQLDNonNull( 412 this.__enumValue 413 )); 414 415 auto nnListOfNonNullEnumValue = new 416 GQLDNullable(this.__listOfNonNullEnumValue); 417 418 //this.__type.member[Constants.enumValues] = this.__listOfNonNullEnumValue; 419 this.__type.member[Constants.enumValues] = nnListOfNonNullEnumValue; 420 421 this.__directives = new GQLDObject(Constants.__Directive); 422 this.__directives.member[Constants.name] = nnStr; 423 this.__directives.member[Constants.description] = str; 424 this.__directives.member[Constants.args] = 425 this.__nonNullListOfNonNullInputValue; 426 this.__directives.member[Constants.locations] = new GQLDNonNull( 427 this.__listOfNonNullEnumValue 428 ); 429 430 this.__schema.member[Constants.directives] = new GQLDNonNull( 431 new GQLDList(new GQLDNonNull(this.__directives)) 432 ); 433 434 435 foreach(t; ["String", "Int", "Float", "Boolean"]) { 436 this.types[t].toObject().member[Constants.fields] = 437 this.__listOfNonNullField; 438 } 439 } 440 441 override string toString() const { 442 auto app = appender!string(); 443 formattedWrite(app, "Operation\n"); 444 foreach(key, value; super.member) { 445 formattedWrite(app, "%s: %s\n", key, value.toString()); 446 } 447 448 formattedWrite(app, "Types\n"); 449 foreach(key, value; this.types) { 450 formattedWrite(app, "%s: %s\n", key, value.toString()); 451 } 452 return app.data; 453 } 454 455 GQLDType getReturnType(GQLDType t, string field) { 456 GQLDType ret; 457 GQLDObject ob = t.toObject(); 458 if(auto s = t.toScalar()) { 459 ret = s; 460 goto retLabel; 461 } else if(auto op = t.toOperation()) { 462 ret = op.returnType; 463 goto retLabel; 464 } else if(auto map = t.toMap()) { 465 if(field in map.member) { 466 auto tmp = map.member[field]; 467 if(auto op = tmp.toOperation()) { 468 ret = op.returnType; 469 goto retLabel; 470 } else { 471 ret = tmp; 472 goto retLabel; 473 } 474 } else if(ob && ob.base && field in ob.base.member) { 475 return ob.base.member[field]; 476 } else if(field == "__typename") { 477 // the type of the field __typename is always a string 478 ret = this.types["string"]; 479 goto retLabel; 480 } else { 481 // if we couldn't find it in the passed map, maybe it is in some 482 // of its derivatives 483 foreach(deriv; map.derivatives) { 484 if(field in deriv.member) { 485 return deriv.member[field]; 486 } 487 } 488 return null; 489 } 490 } else { 491 ret = t; 492 } 493 retLabel: 494 return ret; 495 } 496 } 497 498 GQLDObject toObject(GQLDType t) { 499 return cast(typeof(return))t; 500 } 501 502 GQLDMap toMap(GQLDType t) { 503 return cast(typeof(return))t; 504 } 505 506 GQLDScalar toScalar(GQLDType t) { 507 return cast(typeof(return))t; 508 } 509 510 GQLDOperation toOperation(GQLDType t) { 511 return cast(typeof(return))t; 512 } 513 514 GQLDList toList(GQLDType t) { 515 return cast(typeof(return))t; 516 } 517 518 GQLDNullable toNullable(GQLDType t) { 519 return cast(typeof(return))t; 520 } 521 522 GQLDNonNull toNonNull(GQLDType t) { 523 return cast(typeof(return))t; 524 } 525 526 unittest { 527 auto str = new GQLDString(); 528 assert(str.name == "String"); 529 530 auto map = str.toMap(); 531 assert(map is null); 532 } 533 534 string toShortString(const(GQLDType) e) { 535 if(auto o = cast(const(GQLDObject))e) { 536 return o.name; 537 } else if(auto u = cast(const(GQLDUnion))e) { 538 return u.name; 539 } else { 540 return e.toString(); 541 } 542 } 543 544 GQLDType typeToGQLDType(Type, SCH)(ref SCH ret) { 545 static if(is(Type == enum)) { 546 GQLDEnum r; 547 if(Type.stringof in ret.types) { 548 r = cast(GQLDEnum)ret.types[Type.stringof]; 549 } else { 550 r = new GQLDEnum(Type.stringof); 551 ret.types[Type.stringof] = r; 552 } 553 return r; 554 } else static if(is(Type == bool)) { 555 return new GQLDBool(); 556 } else static if(isFloatingPoint!(Type)) { 557 return new GQLDFloat(); 558 } else static if(isIntegral!(Type)) { 559 return new GQLDInt(); 560 } else static if(isSomeString!Type) { 561 return new GQLDString(); 562 } else static if(is(Type == union)) { 563 GQLDUnion r; 564 if(Type.stringof in ret.types) { 565 r = cast(GQLDUnion)ret.types[Type.stringof]; 566 } else { 567 r = new GQLDUnion(Type.stringof); 568 ret.types[Type.stringof] = r; 569 570 alias fieldNames = FieldNameTuple!(Type); 571 alias fieldTypes = Fields!(Type); 572 static foreach(idx; 0 .. fieldNames.length) {{ 573 static if(fieldNames[idx] != Constants.directives) {{ 574 auto tmp = typeToGQLDType!(fieldTypes[idx])(ret); 575 r.member[fieldNames[idx]] = tmp; 576 577 if(GQLDMap tmpMap = tmp.toMap()) { 578 r.addDerivative(tmpMap); 579 } 580 }} 581 }} 582 } 583 return r; 584 } else static if(is(Type : Nullable!F, F)) { 585 return new GQLDNullable(typeToGQLDType!(F)(ret)); 586 } else static if(is(Type : GQLDCustomLeaf!F, F)) { 587 return new GQLDLeaf(F.stringof); 588 } else static if(is(Type : NullableStore!F, F)) { 589 return new GQLDNullable(typeToGQLDType!(F)(ret)); 590 } else static if(isArray!Type) { 591 return new GQLDList(typeToGQLDType!(ElementEncodingType!Type)(ret) 592 ); 593 } else static if(isAggregateType!Type) { 594 GQLDObject r; 595 if(Type.stringof in ret.types) { 596 r = cast(GQLDObject)ret.types[Type.stringof]; 597 } else { 598 r = new GQLDObject(Type.stringof); 599 ret.types[Type.stringof] = r; 600 601 alias fieldNames = FieldNameTuple!(Type); 602 alias fieldTypes = Fields!(Type); 603 static foreach(idx; 0 .. fieldNames.length) {{ 604 enum uda = getUdaData!(Type, fieldNames[idx]); 605 static if(uda.ignore != Ignore.yes) { 606 static if(fieldNames[idx] != Constants.directives) {{ 607 r.member[fieldNames[idx]] = 608 typeToGQLDType!(fieldTypes[idx])(ret); 609 }} 610 } 611 }} 612 613 static if(is(Type == class)) { 614 alias bct = BaseClassesTuple!(Type); 615 static if(bct.length > 1) { 616 auto d = cast(GQLDObject)typeToGQLDType!(bct[0])( 617 ret 618 ); 619 r.base = d; 620 d.addDerivative(r); 621 622 } 623 assert(bct.length > 1 ? r.base !is null : true); 624 } 625 626 static foreach(mem; __traits(allMembers, Type)) { 627 static if(!is(__traits(getMember, Type, mem))) {{ // not a type 628 enum uda = getUdaData!(Type, mem); 629 alias MemType = typeof(__traits(getMember, Type, mem)); 630 static if(uda.ignore != Ignore.yes) { 631 static if(isCallable!MemType) {{ 632 GQLDOperation op = new GQLDQuery(); 633 r.member[mem] = op; 634 op.returnType = 635 typeToGQLDType!(ReturnType!(MemType))(ret); 636 637 alias paraNames = ParameterIdentifierTuple!( 638 __traits(getMember, Type, mem) 639 ); 640 alias paraTypes = Parameters!( 641 __traits(getMember, Type, mem) 642 ); 643 static foreach(idx; 0 .. paraNames.length) { 644 op.parameters[paraNames[idx]] = 645 typeToGQLDType!(paraTypes[idx])(ret); 646 } 647 }} 648 } 649 }} 650 } 651 } 652 return r; 653 } else { 654 static assert(false, Type.stringof); 655 } 656 }