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((map.name == "queryType" || map.name == "mutationType" 466 || map.name == "subscriptionType") 467 && field in map.member) 468 { 469 auto tmp = map.member[field]; 470 if(auto op = tmp.toOperation()) { 471 ret = op.returnType; 472 goto retLabel; 473 } else { 474 ret = tmp; 475 goto retLabel; 476 } 477 } else if(field in map.member) { 478 ret = map.member[field]; 479 goto retLabel; 480 } else if(ob && ob.base && field in ob.base.member) { 481 return ob.base.member[field]; 482 } else if(field == "__typename") { 483 // the type of the field __typename is always a string 484 ret = this.types["string"]; 485 goto retLabel; 486 } else { 487 // if we couldn't find it in the passed map, maybe it is in some 488 // of its derivatives 489 foreach(deriv; map.derivatives) { 490 if(field in deriv.member) { 491 return deriv.member[field]; 492 } 493 } 494 return null; 495 } 496 } else { 497 ret = t; 498 } 499 retLabel: 500 return ret; 501 } 502 } 503 504 GQLDObject toObject(GQLDType t) { 505 return cast(typeof(return))t; 506 } 507 508 GQLDMap toMap(GQLDType t) { 509 return cast(typeof(return))t; 510 } 511 512 GQLDScalar toScalar(GQLDType t) { 513 return cast(typeof(return))t; 514 } 515 516 GQLDOperation toOperation(GQLDType t) { 517 return cast(typeof(return))t; 518 } 519 520 GQLDList toList(GQLDType t) { 521 return cast(typeof(return))t; 522 } 523 524 GQLDNullable toNullable(GQLDType t) { 525 return cast(typeof(return))t; 526 } 527 528 GQLDNonNull toNonNull(GQLDType t) { 529 return cast(typeof(return))t; 530 } 531 532 unittest { 533 auto str = new GQLDString(); 534 assert(str.name == "String"); 535 536 auto map = str.toMap(); 537 assert(map is null); 538 } 539 540 string toShortString(const(GQLDType) e) { 541 if(auto o = cast(const(GQLDObject))e) { 542 return o.name; 543 } else if(auto u = cast(const(GQLDUnion))e) { 544 return u.name; 545 } else { 546 return e.toString(); 547 } 548 } 549 550 GQLDType typeToGQLDType(Type, SCH)(ref SCH ret) { 551 static if(is(Type == enum)) { 552 GQLDEnum r; 553 if(Type.stringof in ret.types) { 554 r = cast(GQLDEnum)ret.types[Type.stringof]; 555 } else { 556 r = new GQLDEnum(Type.stringof); 557 ret.types[Type.stringof] = r; 558 } 559 return r; 560 } else static if(is(Type == bool)) { 561 return new GQLDBool(); 562 } else static if(isFloatingPoint!(Type)) { 563 return new GQLDFloat(); 564 } else static if(isIntegral!(Type)) { 565 return new GQLDInt(); 566 } else static if(isSomeString!Type) { 567 return new GQLDString(); 568 } else static if(is(Type == union)) { 569 GQLDUnion r; 570 if(Type.stringof in ret.types) { 571 r = cast(GQLDUnion)ret.types[Type.stringof]; 572 } else { 573 r = new GQLDUnion(Type.stringof); 574 ret.types[Type.stringof] = r; 575 576 alias fieldNames = FieldNameTuple!(Type); 577 alias fieldTypes = Fields!(Type); 578 static foreach(idx; 0 .. fieldNames.length) {{ 579 static if(fieldNames[idx] != Constants.directives) {{ 580 auto tmp = typeToGQLDType!(fieldTypes[idx])(ret); 581 r.member[fieldNames[idx]] = tmp; 582 583 if(GQLDMap tmpMap = tmp.toMap()) { 584 r.addDerivative(tmpMap); 585 } 586 }} 587 }} 588 } 589 return r; 590 } else static if(is(Type : Nullable!F, F)) { 591 return new GQLDNullable(typeToGQLDType!(F)(ret)); 592 } else static if(is(Type : GQLDCustomLeaf!F, F)) { 593 return new GQLDLeaf(F.stringof); 594 } else static if(is(Type : NullableStore!F, F)) { 595 return new GQLDNullable(typeToGQLDType!(F)(ret)); 596 } else static if(isArray!Type) { 597 return new GQLDList(typeToGQLDType!(ElementEncodingType!Type)(ret) 598 ); 599 } else static if(isAggregateType!Type) { 600 GQLDObject r; 601 if(Type.stringof in ret.types) { 602 r = cast(GQLDObject)ret.types[Type.stringof]; 603 } else { 604 r = new GQLDObject(Type.stringof); 605 ret.types[Type.stringof] = r; 606 607 alias fieldNames = FieldNameTuple!(Type); 608 alias fieldTypes = Fields!(Type); 609 static foreach(idx; 0 .. fieldNames.length) {{ 610 static if(fieldNames[idx] != Constants.directives) {{ 611 r.member[fieldNames[idx]] = 612 typeToGQLDType!(fieldTypes[idx])(ret); 613 }} 614 }} 615 616 static if(is(Type == class)) { 617 alias bct = BaseClassesTuple!(Type); 618 static if(bct.length > 1) { 619 auto d = cast(GQLDObject)typeToGQLDType!(bct[0])( 620 ret 621 ); 622 r.base = d; 623 d.addDerivative(r); 624 625 } 626 assert(bct.length > 1 ? r.base !is null : true); 627 } 628 } 629 return r; 630 } else { 631 static assert(false, Type.stringof); 632 } 633 }