1 module graphql.schema.typeconversions; 2 3 import std.array : empty; 4 import std.algorithm.searching : canFind; 5 import std.conv : to; 6 import std.stdio; 7 import std.traits; 8 import std.meta; 9 import std.typecons : Nullable, nullable; 10 import std.range : ElementEncodingType; 11 12 import vibe.data.json; 13 14 import nullablestore; 15 16 import graphql.schema.types; 17 import graphql.traits; 18 import graphql.uda; 19 import graphql.constants; 20 21 @safe: 22 23 template typeToTypeEnum(Type) { 24 static if(isAggregateType!Type && hasUDA!(Type, GQLDUdaData) 25 && getUDAs!(Type, GQLDUdaData)[0].typeKind != TypeKind.UNDEFINED) 26 { 27 enum udas = getUDAs!(Type, GQLDUdaData); 28 static assert(udas.length == 1); 29 enum GQLDUdaData u = udas[0]; 30 enum typeToTypeEnum = to!string(u.typeKind); 31 } else static if(is(Type == enum)) { 32 enum typeToTypeEnum = "ENUM"; 33 } else static if(is(Type == bool)) { 34 enum typeToTypeEnum = "SCALAR"; 35 } else static if(is(Type : GQLDCustomLeaf!F, F)) { 36 enum typeToTypeEnum = "SCALAR"; 37 } else static if(isFloatingPoint!(Type)) { 38 enum typeToTypeEnum = "SCALAR"; 39 } else static if(isIntegral!(Type)) { 40 enum typeToTypeEnum = "SCALAR"; 41 } else static if(isSomeString!Type) { 42 enum typeToTypeEnum = "SCALAR"; 43 } else static if(is(Type == void)) { 44 enum typeToTypeEnum = "SCALAR"; 45 } else static if(is(Type == union)) { 46 enum typeToTypeEnum = "UNION"; 47 } else static if(isAggregateType!Type) { 48 enum typeToTypeEnum = "OBJECT"; 49 } else { 50 static assert(false, Type.stringof ~ " not handled"); 51 } 52 } 53 54 template typeToTypeName(Type) { 55 import graphql.uda : GQLDCustomLeaf; 56 static if(is(Type == enum)) { 57 enum typeToTypeName = Type.stringof; 58 } else static if(is(Type == bool)) { 59 enum typeToTypeName = "Boolean"; 60 } else static if(is(Type == GQLDCustomLeaf!Fs, Fs...)) { 61 enum typeToTypeName = Fs[0].stringof; 62 } else static if(isFloatingPoint!(Type)) { 63 enum typeToTypeName = "Float"; 64 } else static if(isIntegral!(Type)) { 65 enum typeToTypeName = "Int"; 66 } else static if(isSomeString!Type) { 67 enum typeToTypeName = "String"; 68 } else { 69 enum typeToTypeName = Type.stringof; 70 } 71 } 72 73 unittest { 74 import std.datetime : Date; 75 static assert(typeToTypeName!(GQLDCustomLeaf!Date) == "Date"); 76 } 77 78 template typeToParameterTypeName(Type) { 79 template level2(Type) { 80 static if(is(Type : Nullable!F, F)) { 81 enum level2 = typeToTypeName!F; 82 } else { 83 enum level2 = typeToTypeName!Type ~ "!"; 84 } 85 } 86 87 template level1(Type) { 88 static if(isArray!Type && !isSomeString!Type) { 89 enum level1 = "[" ~ level2!(ElementEncodingType!Type) ~ "]"; 90 } else { 91 enum level1 = typeToTypeName!Type; 92 } 93 } 94 95 template level0(Type) { 96 static if(is(Type : Nullable!F, F)) { 97 enum level0 = level1!F; 98 } else { 99 enum level0 = level1!Type ~ "!"; 100 } 101 } 102 103 template levelM1(Type) { 104 static if(is(Type : NullableStore!F, F)) { 105 enum levelM1 = level0!F; 106 } else { 107 enum levelM1 = level0!Type; 108 } 109 } 110 111 enum typeToParameterTypeName = levelM1!Type; 112 } 113 114 unittest { 115 import std.datetime : Date; 116 static assert(typeToParameterTypeName!(int) == "Int!"); 117 static assert(typeToParameterTypeName!(Nullable!int) == "Int"); 118 static assert(typeToParameterTypeName!(double) == "Float!"); 119 static assert(typeToParameterTypeName!(Nullable!double) == "Float"); 120 static assert(typeToParameterTypeName!(double[]) == "[Float!]!"); 121 static assert(typeToParameterTypeName!(Nullable!(double)[]) == "[Float]!"); 122 static assert(typeToParameterTypeName!(Nullable!(Nullable!(double)[])) == 123 "[Float]"); 124 static assert(typeToParameterTypeName!(GQLDCustomLeaf!Date) == "Date!"); 125 } 126 127 unittest { 128 enum AEnum { 129 one, 130 two, 131 three 132 } 133 static assert(typeToParameterTypeName!(AEnum) == "AEnum!"); 134 static assert(typeToParameterTypeName!(Nullable!AEnum) == "AEnum"); 135 static assert(typeToParameterTypeName!(Nullable!(AEnum)[]) == "[AEnum]!"); 136 static assert(typeToParameterTypeName!(Nullable!(Nullable!(AEnum)[])) == 137 "[AEnum]"); 138 } 139 140 template isScalarType(Type) { 141 static if(is(Type == bool)) { 142 enum isScalarType = true; 143 } else static if(is(Type == GQLDCustomLeaf!Fs, Fs...)) { 144 enum isScalarType = true; 145 } else static if(isFloatingPoint!(Type)) { 146 enum isScalarType = true; 147 } else static if(isIntegral!(Type)) { 148 enum isScalarType = true; 149 } else static if(isSomeString!Type) { 150 enum isScalarType = true; 151 } else static if(is(Type == enum)) { 152 enum isScalarType = true; 153 } else { 154 enum isScalarType = false; 155 } 156 } 157 158 template typeToFieldType(Type) { 159 static if(isArray!Type && !isSomeString!Type) { 160 enum typeToFieldType = "__listType"; 161 } else static if(is(Type : Nullable!F, F)) { 162 enum typeToFieldType = F.stringof; 163 } else static if(is(Type : NullableStore!F, F)) { 164 enum typeToFieldType = Type.TypeValue.stringof; 165 } else { 166 enum typeToFieldType = "__nonNullType"; 167 } 168 } 169 170 Json typeFields(T)() { 171 import graphql.uda; 172 static enum memsToIgnore = ["__ctor", "toString", "toHash", "opCmp", 173 "opEquals", "Monitor", "factory"]; 174 Json ret = Json.emptyArray(); 175 bool[string] fieldsAlreadyIn; 176 alias TplusParents = AliasSeq!(T, InheritedClasses!T); 177 static foreach(Type; TplusParents) {{ 178 static foreach(mem; __traits(allMembers, Type)) {{ 179 enum GQLDUdaData udaData = getUdaData!(Type, mem); 180 static if(!canFind(memsToIgnore, mem) 181 && udaData.ignore != Ignore.yes) 182 { 183 if(mem !in fieldsAlreadyIn) { 184 fieldsAlreadyIn[mem] = true; 185 Json tmp = Json.emptyObject(); 186 tmp[Constants.name] = mem; 187 // needed for interfacesForType 188 tmp[Constants.__typename] = "__Field"; 189 tmp[Constants.description] = 190 udaData.description.getText().empty 191 ? Json(null) 192 : Json(udaData.description.getText()); 193 194 tmp[Constants.isDeprecated] = 195 udaData.deprecationInfo.isDeprecated == IsDeprecated.yes 196 ? true 197 : false; 198 199 tmp[Constants.deprecationReason] = 200 udaData.deprecationInfo.isDeprecated == IsDeprecated.yes 201 ? Json(udaData.deprecationInfo.deprecationReason) 202 : Json(null); 203 204 tmp[Constants.args] = Json.emptyArray(); 205 static if(isCallable!(__traits(getMember, Type, mem))) { 206 alias RT = ReturnType!(__traits(getMember, Type, mem)); 207 alias RTS = stripArrayAndNullable!RT; 208 //tmp[Constants.typenameOrig] = typeToTypeName!(RT); 209 tmp[Constants.typenameOrig] = typeToParameterTypeName!(RT); 210 211 // InputValue 212 alias paraNames = ParameterIdentifierTuple!( 213 __traits(getMember, Type, mem) 214 ); 215 alias paraTypes = Parameters!( 216 __traits(getMember, Type, mem) 217 ); 218 alias paraDefs = ParameterDefaults!( 219 __traits(getMember, Type, mem) 220 ); 221 static foreach(idx; 0 .. paraNames.length) {{ 222 Json iv = Json.emptyObject(); 223 iv[Constants.name] = paraNames[idx]; 224 // needed for interfacesForType 225 iv[Constants.__typename] = Constants.__InputValue; 226 iv[Constants.description] = Json(null); 227 static if(__traits(compiles, __traits(getAttributes, 228 Parameters!(__traits(getMember, Type, 229 mem))[idx .. idx + 1]))) 230 { 231 iv[Constants.description] = Json(null); 232 alias udad = __traits(getAttributes, 233 Parameters!(__traits(getMember, Type, 234 mem))[idx .. idx + 1]); 235 static if(udad.length == 1) { 236 enum F = udad[0]; 237 static if(is(typeof(F) == GQLDUdaData)) { 238 iv[Constants.description] = 239 F.description.text; 240 } 241 } 242 } 243 iv[Constants.typenameOrig] = 244 typeToParameterTypeName!(paraTypes[idx]); 245 static if(!is(paraDefs[idx] == void)) { 246 iv[Constants.defaultValue] = serializeToJson( 247 paraDefs[idx] 248 ) 249 .toString(); 250 } else { 251 iv[Constants.defaultValue] = Json(null); 252 } 253 tmp[Constants.args] ~= iv; 254 }} 255 } else { 256 tmp[Constants.typenameOrig] = 257 typeToParameterTypeName!( 258 //typeToTypeName!( 259 typeof(__traits(getMember, Type, mem)) 260 ); 261 } 262 ret ~= tmp; 263 } 264 } 265 }} 266 }} 267 //writefln("%s %s", __LINE__, ret.toPrettyString()); 268 return ret; 269 } 270 271 Json inputFields(Type)() { 272 Json ret = Json.emptyArray(); 273 alias types = FieldTypeTuple!Type; 274 alias names = FieldNameTuple!Type; 275 static foreach(idx; 0 .. types.length) {{ 276 enum GQLDUdaData udaData = getUdaData!(types[idx]); 277 Json tmp = Json.emptyObject(); 278 tmp[Constants.name] = names[idx]; 279 tmp[Constants.description] = udaData.description.getText().empty 280 ? Json(null) 281 : Json(udaData.description.getText()); 282 283 // needed for interfacesForType 284 tmp[Constants.__typename] = Constants.__InputValue; 285 286 //tmp[Constants.typenameOrig] = typeToTypeName!(types[idx]); 287 tmp[Constants.typenameOrig] = typeToParameterTypeName!(types[idx]); 288 auto t = __traits(getMember, Type.init, names[idx]); 289 tmp[Constants.defaultValue] = serializeToJson(t); 290 ret ~= tmp; 291 }} 292 return ret; 293 } 294 295 Json emptyType() { 296 Json ret = Json.emptyObject(); 297 ret[Constants.name] = Json(null); 298 ret[Constants.description] = Json(null); 299 ret[Constants.fields] = Json(null); 300 ret[Constants.interfacesNames] = Json(null); 301 ret[Constants.possibleTypesNames] = Json(null); 302 ret[Constants.enumValues] = Json(null); 303 ret["ofType"] = Json(null); 304 return ret; 305 } 306 307 Json removeNonNullAndList(Json j) { 308 string t = j["kind"].get!string(); 309 if(t == "NON_NULL" || t == "LIST") { 310 return removeNonNullAndList(j["ofType"]); 311 } else { 312 return j; 313 } 314 } 315 316 // remove the top nullable to find out if we have a NON_NULL or not 317 Json typeToJson(Type,Schema)() { 318 static if(is(Type : Nullable!F, F)) { 319 return typeToJson1!(F,Schema,Type)(); 320 } else static if(is(Type : NullableStore!F, F)) { 321 return typeToJson1!(Type.TypeValue,Schema,Type)(); 322 } else { 323 Json ret = emptyType(); 324 ret["kind"] = "NON_NULL"; 325 ret[Constants.__typename] = "__Type"; 326 ret["ofType"] = typeToJson1!(Type,Schema,Type)(); 327 return ret; 328 } 329 } 330 331 // remove the array is present 332 Json typeToJson1(Type,Schema,Orig)() { 333 static if(isArray!Type && !isSomeString!Type && !is(Type == enum)) { 334 Json ret = emptyType(); 335 ret["kind"] = "LIST"; 336 ret[Constants.__typename] = "__Type"; 337 ret["ofType"] = typeToJson2!(ElementEncodingType!Type, Schema, Orig)(); 338 return ret; 339 } else { 340 return typeToJsonImpl!(Type, Schema, Orig)(); 341 } 342 } 343 344 // remove another nullable 345 Json typeToJson2(Type,Schema,Orig)() { 346 static if(is(Type : Nullable!F, F)) { 347 return typeToJsonImpl!(F, Schema, Orig)(); 348 } else static if(is(Type : NullableStore!F, F)) { 349 return typeToJsonImpl!(Type.TypeValue, Schema, Orig)(); 350 } else { 351 Json ret = emptyType(); 352 ret["kind"] = "NON_NULL"; 353 ret[Constants.__typename] = "__Type"; 354 ret["ofType"] = typeToJsonImpl!(Type, Schema, Orig)(); 355 return ret; 356 } 357 } 358 359 Json typeToJsonImpl(Type,Schema,Orig)() { 360 Json ret = Json.emptyObject(); 361 enum string kind = typeToTypeEnum!(stripArrayAndNullable!Type); 362 ret["kind"] = kind; 363 ret[Constants.__typename] = "__Type"; 364 ret[Constants.name] = typeToTypeName!Type; 365 366 enum GQLDUdaData udaData = getUdaData!(Orig); 367 enum des = udaData.description.text; 368 ret[Constants.description] = des.empty 369 ? Json(null) 370 : Json(des); 371 372 ret[Constants.isDeprecated] = 373 udaData.deprecationInfo.isDeprecated == IsDeprecated.yes 374 ? true 375 : false; 376 377 ret[Constants.deprecationReason] = 378 udaData.deprecationInfo.isDeprecated == IsDeprecated.yes 379 ? Json(udaData.deprecationInfo.deprecationReason) 380 : Json(null); 381 382 // fields 383 static if((is(Type == class) || is(Type == interface) || is(Type == struct)) 384 && !is(Type : Nullable!K, K) && !is(Type : NullableStore!K, K) 385 && !is(Type : GQLDCustomLeaf!Ks, Ks...)) 386 { 387 ret[Constants.fields] = typeFields!Type(); 388 } else { 389 ret[Constants.fields] = Json(null); 390 } 391 392 // inputFields 393 static if(kind == Constants.INPUT_OBJECT) { 394 ret[Constants.inputFields] = inputFields!Type(); 395 } else { 396 ret[Constants.inputFields] = Json(null); 397 } 398 399 // needed to resolve interfaces 400 static if(is(Type == class) || is(Type == interface)) { 401 ret[Constants.interfacesNames] = Json.emptyArray(); 402 static foreach(interfaces; InheritedClasses!Type) {{ 403 ret[Constants.interfacesNames] ~= interfaces.stringof; 404 }} 405 } else { 406 ret[Constants.interfacesNames] = Json(null); 407 } 408 409 // needed to resolve possibleTypes 410 static if(is(Type == class) || is(Type == union) || is(Type == interface)) { 411 static if(is(Type == union)) { 412 ret[Constants.possibleTypesNames] = Json.emptyArray(); 413 static foreach(pt; Filter!(isAggregateType, FieldTypeTuple!Type)) { 414 ret[Constants.possibleTypesNames] ~= pt.stringof; 415 } 416 } else { 417 import graphql.reflection; 418 // need to search for all types that we support that are derived 419 // from this type 420 ret[Constants.possibleTypesNames] = Json.emptyArray(); 421 foreach(tname; 422 SchemaReflection!Schema.instance.derivatives.get(typeid(Type), 423 null)) 424 { 425 ret[Constants.possibleTypesNames] ~= tname; 426 } 427 } 428 } else { 429 ret[Constants.possibleTypesNames] = Json(null); 430 } 431 432 // enumValues 433 static if(is(Type == enum)) { 434 ret[Constants.enumValues] = Json.emptyArray(); 435 static foreach(mem; EnumMembers!Type) {{ 436 Json tmp = Json.emptyObject(); 437 tmp[Constants.__TypeKind] = Constants.__EnumValue; 438 tmp[Constants.name] = Json(to!string(mem)); 439 tmp[Constants.description] = "ENUM_DESCRIPTION_TODO"; 440 tmp[Constants.isDeprecated] = false; 441 tmp[Constants.deprecationReason] = "ENUM_DEPRECATIONREASON_TODO"; 442 ret[Constants.enumValues] ~= tmp; 443 }} 444 } else { 445 ret[Constants.enumValues] = Json(null); 446 } 447 448 // needed to resolve ofType 449 static if(is(Type : Nullable!F, F)) { 450 ret[Constants.ofTypeName] = F.stringof; 451 } else static if(is(Type : NullableStore!F, F)) { 452 ret[Constants.ofTypeName] = F.stringof; 453 } else static if(is(Type : GQLDCustomLeaf!Fs, Fs...)) { 454 ret[Constants.ofTypeName] = Fs[0].stringof; 455 } else static if(isArray!Type) { 456 ret[Constants.ofTypeName] = ElementEncodingType!(Type).stringof; 457 } 458 459 return ret; 460 } 461 462 @safe unittest { 463 import std.format : format; 464 Json r = typeToJson!(string,void)(); 465 Json e = parseJsonString(` 466 { 467 "__typename": "__Type", 468 "possibleTypesNames": null, 469 "enumValues": null, 470 "interfacesNames": null, 471 "kind": "NON_NULL", 472 "name": null, 473 "ofType": { 474 "__typename": "__Type", 475 "possibleTypesNames": null, 476 "enumValues": null, 477 "interfacesNames": null, 478 "kind": "SCALAR", 479 "isDeprecated": false, 480 "deprecationReason": null, 481 "name": "String", 482 "description": null, 483 "inputFields": null, 484 "ofTypeName": "immutable(char)", 485 "fields": null 486 }, 487 "description": null, 488 "fields": null 489 } 490 `); 491 assert(r == e, format("exp:\n%s\ngot:\n%s", e.toPrettyString(), 492 r.toPrettyString())); 493 } 494 495 @safe unittest { 496 enum FooBar { 497 foo, 498 bar 499 } 500 501 import std.format : format; 502 Json r = typeToJson!(FooBar,void)(); 503 Json e = parseJsonString(` 504 { 505 "__typename": "__Type", 506 "possibleTypesNames": null, 507 "enumValues": null, 508 "interfacesNames": null, 509 "kind": "NON_NULL", 510 "name": null, 511 "ofType": { 512 "__typename": "__Type", 513 "possibleTypesNames": null, 514 "enumValues": [ 515 { 516 "description": "ENUM_DESCRIPTION_TODO", 517 "deprecationReason": "ENUM_DEPRECATIONREASON_TODO", 518 "__TypeKind": "__EnumValue", 519 "isDeprecated": false, 520 "name": "foo" 521 }, 522 { 523 "description": "ENUM_DESCRIPTION_TODO", 524 "deprecationReason": "ENUM_DEPRECATIONREASON_TODO", 525 "__TypeKind": "__EnumValue", 526 "isDeprecated": false, 527 "name": "bar" 528 } 529 ], 530 "interfacesNames": null, 531 "kind": "ENUM", 532 "isDeprecated": false, 533 "deprecationReason": null, 534 "name": "FooBar", 535 "description": null, 536 "inputFields": null, 537 "fields": null 538 }, 539 "description": null, 540 "fields": null 541 } 542 `); 543 assert(r == e, format("exp:\n%s\ngot:\n%s", e.toPrettyString(), 544 r.toPrettyString())); 545 } 546 @safe unittest { 547 import std.format : format; 548 Json r = typeToJson!(Nullable!string,void)(); 549 Json e = parseJsonString(` 550 { 551 "__typename": "__Type", 552 "possibleTypesNames": null, 553 "enumValues": null, 554 "interfacesNames": null, 555 "kind": "SCALAR", 556 "isDeprecated": false, 557 "deprecationReason": null, 558 "name": "String", 559 "description": null, 560 "inputFields": null, 561 "ofTypeName": "immutable(char)", 562 "fields": null 563 } 564 `); 565 assert(r == e, format("exp:\n%s\ngot:\n%s", e.toPrettyString(), 566 r.toPrettyString())); 567 } 568 569 @safe unittest { 570 import std.format : format; 571 Json r = typeToJson!(Nullable!string,void)(); 572 Json e = parseJsonString(` 573 { 574 "__typename": "__Type", 575 "possibleTypesNames": null, 576 "enumValues": null, 577 "interfacesNames": null, 578 "kind": "SCALAR", 579 "isDeprecated": false, 580 "deprecationReason": null, 581 "name": "String", 582 "description": null, 583 "inputFields": null, 584 "ofTypeName": "immutable(char)", 585 "fields": null 586 } 587 `); 588 assert(r == e, format("exp:\n%s\ngot:\n%s", e.toPrettyString(), 589 r.toPrettyString())); 590 } 591 592 Json directivesToJson(Directives)() { 593 import std.string : stripLeft; 594 Json ret = Json.emptyArray(); 595 static enum memsToIgnore = ["__ctor", "toString", "toHash", "opCmp", 596 "opEquals", "Monitor", "factory"]; 597 alias TplusParents = AliasSeq!(Directives, InheritedClasses!Directives); 598 static foreach(Type; TplusParents) {{ 599 static foreach(mem; __traits(allMembers, Type)) {{ 600 static if(!canFind(memsToIgnore, mem)) { 601 Json tmp = Json.emptyObject(); 602 enum GQLDUdaData udaData = getUdaData!(Type, mem); 603 tmp[Constants.name] = mem; 604 // needed for interfacesForType 605 tmp[Constants.__typename] = Constants.__Directive; 606 tmp[Constants.description] = udaData.description.getText().empty 607 ? Json(null) 608 : Json(udaData.description.text); 609 610 tmp[Constants.isDeprecated] = 611 udaData.deprecationInfo.isDeprecated == IsDeprecated.yes 612 ? true 613 : false; 614 615 tmp[Constants.deprecationReason] = 616 udaData.deprecationInfo.isDeprecated == IsDeprecated.yes 617 ? Json(udaData.deprecationInfo.deprecationReason) 618 : Json(null); 619 620 //tmp[Constants.description] = Json(null); 621 tmp[Constants.locations] = Json.emptyArray(); 622 tmp[Constants.args] = Json.emptyArray(); 623 static if(isCallable!(__traits(getMember, Type, mem))) { 624 // InputValue 625 alias paraNames = ParameterIdentifierTuple!( 626 __traits(getMember, Type, mem) 627 ); 628 alias paraTypes = Parameters!( 629 __traits(getMember, Type, mem) 630 ); 631 alias paraDefs = ParameterDefaults!( 632 __traits(getMember, Type, mem) 633 ); 634 static foreach(idx; 0 .. paraNames.length) {{ 635 Json iv = Json.emptyObject(); 636 // TODO remove the strip left. Its in because the 637 // two default directives of GraphQL skip and include 638 // both have one parameter named "if". 639 iv[Constants.name] = stripLeft(paraNames[idx], "_"); 640 iv[Constants.description] = Json(null); 641 // needed for interfacesForType 642 iv[Constants.__typename] = Constants.__InputValue; 643 iv[Constants.typenameOrig] = typeToTypeName!(paraTypes[idx]); 644 //iv[Constants.typenameOrig] = typeToParameterTypeName!(paraTypes[idx]); 645 static if(!is(paraDefs[idx] == void)) { 646 iv[Constants.defaultValue] = serializeToJson(paraDefs[idx]) 647 .toString(); 648 } else { 649 iv[Constants.defaultValue] = Json(null); 650 } 651 tmp[Constants.args] ~= iv; 652 }} 653 } 654 ret ~= tmp; 655 } 656 }} 657 }} 658 return ret; 659 } 660 661 Json getField(Json j, string name) { 662 import graphql.constants; 663 664 if(j.type != Json.Type.object || Constants.fields !in j 665 || j[Constants.fields].type != Json.Type.array) 666 { 667 return Json.init; 668 } 669 670 foreach(it; j[Constants.fields].byValue) { 671 string itName = it[Constants.name].get!string(); 672 if(itName == name) { 673 return it; 674 } 675 } 676 return Json.init; 677 } 678 679 Json getIntrospectionField(string name) { 680 import std.format : format; 681 Json ret = Json.emptyObject(); 682 ret[Constants.typenameOrig] = name == Constants.__typename 683 ? "String" 684 : name == Constants.__schema 685 ? "__Schema" 686 : name == Constants.__type 687 ? "__Type" 688 : format("Not known introspection name '%s'", name); 689 return ret; 690 }