1 module graphql.starwars.introspection; 2 3 import std.typecons : Nullable, nullable; 4 import std.format : format; 5 import std.experimental.logger; 6 import std.stdio; 7 8 import vibe.data.json; 9 10 import graphql.constants; 11 import graphql.parser; 12 import graphql.builder; 13 import graphql.lexer; 14 import graphql.ast; 15 import graphql.graphql; 16 import graphql.helper; 17 import graphql.starwars.data; 18 import graphql.starwars.schema; 19 import graphql.starwars.types; 20 import graphql.validation.querybased; 21 import graphql.validation.schemabased; 22 23 @safe: 24 25 Json query(string s) { 26 return query(s, Json.init); 27 } 28 29 Json query(string s, Json args) { 30 auto graphqld = new GraphQLD!(StarWarsSchema); 31 //auto lo = new std.experimental.logger.FileLogger("query.log"); 32 //graphqld.defaultResolverLog = lo; 33 //graphqld.executationTraceLog = lo; 34 //graphqld.resolverLog = lo; 35 36 graphqld.setResolver("queryType", "human", 37 delegate(string name, Json parent, Json args, 38 ref DefaultContext con) @safe 39 { 40 auto idPtr = "id" in args; 41 Json ret = Json.emptyObject(); 42 if(idPtr) { 43 string id = idPtr.to!string(); 44 Human h = getHuman(id); 45 if(h is null) { 46 ret["data"] = Json(null); 47 return ret; 48 } 49 Json hj = toGraphqlJson!StarWarsSchema(h); 50 Json cj = toGraphqlJson!StarWarsSchema(cast(Character)h); 51 cj.remove("__typename"); 52 ret["data"] = joinJson(hj, cj); 53 } 54 return ret; 55 } 56 ); 57 58 graphqld.setResolver("queryType", "hero", 59 delegate(string name, Json parent, Json args, 60 ref DefaultContext con) @safe 61 { 62 import std.conv : to; 63 auto e = "episode" in args; 64 Json ret = Json.emptyObject(); 65 ret["data"] = toGraphqlJson!StarWarsSchema(getHero( 66 e ? nullable((*e).to!string().to!Episode()) 67 : Nullable!(Episode).init 68 )); 69 return ret; 70 } 71 ); 72 73 graphqld.setResolver("Character", "secretBackstory", 74 delegate(string name, Json parent, Json args, 75 ref DefaultContext con) @safe 76 { 77 Json ret = Json.emptyObject(); 78 ret[Constants.data] = Json(null); 79 ret[Constants.errors] = Json.emptyArray(); 80 ret.insertError("secretBackstory is secret"); 81 return ret; 82 } 83 ); 84 85 graphqld.setResolver("Character", "friends", 86 delegate(string name, Json parent, Json args, 87 ref DefaultContext con) @safe 88 { 89 auto idPtr = "id" in parent; 90 Json ret = Json.emptyObject(); 91 ret["data"] = Json.emptyArray(); 92 if(idPtr) { 93 string id = idPtr.to!string(); 94 foreach(it; getFriends(getCharacter(id))) { 95 ret["data"] ~= toGraphqlJson!StarWarsSchema(it); 96 } 97 } 98 return ret; 99 } 100 ); 101 102 auto l = Lexer(s); 103 auto p = Parser(l); 104 105 Document d = p.parseDocument(); 106 const(Document) cd = d; 107 QueryValidator fv = new QueryValidator(d); 108 fv.accept(cd); 109 noCylces(fv.fragmentChildren); 110 allFragmentsReached(fv); 111 SchemaValidator!StarWarsSchema sv = new SchemaValidator!StarWarsSchema(d, 112 graphqld.schema 113 ); 114 sv.accept(cd); 115 DefaultContext con; 116 Json gqld = graphqld.execute(d, args, con); 117 return gqld; 118 } 119 120 @safe unittest { 121 const Json rslt = query(` 122 query IntrospectionTypeQuery { 123 __schema { 124 types { 125 name 126 } 127 } 128 } 129 `); 130 131 string s = `{ 132 "data" : { 133 "__schema" : { 134 "types" : [ 135 { 136 "name": "Query" 137 }, 138 { 139 "name": "Episode" 140 }, 141 { 142 "name": "Character" 143 }, 144 { 145 "name": "String" 146 }, 147 { 148 "name": "Human" 149 }, 150 { 151 "name": "Droid" 152 }, 153 { 154 "name": "__Schema" 155 }, 156 { 157 "name": "__Type" 158 }, 159 { 160 "name": "__TypeKind" 161 }, 162 { 163 "name": "Boolean" 164 }, 165 { 166 "name": "__Field" 167 }, 168 { 169 "name": "__InputValue" 170 }, 171 { 172 "name": "__EnumValue" 173 }, 174 { 175 "name": "__Directive" 176 }, 177 { 178 "name": "__DirectiveLocation" 179 } 180 ] 181 } 182 } 183 }`; 184 immutable Json exp = parseJson(s); 185 // TODO write a json array compare that does not depend on the order 186 //assert(rslt == exp, format("exp:\n%s\ngot:\n%s", exp.toPrettyString(), 187 // rslt.toPrettyString())); 188 } 189 190 @safe unittest { 191 Json rslt = query(` 192 query IntrospectionQueryTypeQuery { 193 __schema { 194 queryType { 195 name 196 } 197 } 198 } 199 `); 200 201 string s = `{ 202 "data" : { 203 "__schema" : { 204 "queryType" : { 205 "name" : "StarWarsQuery" 206 } 207 } 208 } 209 } 210 `; 211 Json exp = parseJson(s); 212 assert(rslt == exp, format("exp:\n%s\ngot:\n%s", exp.toPrettyString(), 213 rslt.toPrettyString())); 214 } 215 216 @safe unittest { 217 Json rslt = query(` 218 query IntrospectionDroidTypeQuery { 219 __type(name: "Droid") { 220 name 221 } 222 } 223 `); 224 225 string s = `{ 226 "data" : { 227 "__type" : { 228 "name" : "Droid" 229 } 230 } 231 } 232 `; 233 Json exp = parseJson(s); 234 assert(rslt == exp, format("exp:\n%s\ngot:\n%s", exp.toPrettyString(), 235 rslt.toPrettyString())); 236 } 237 238 @safe unittest { 239 Json rslt = query(` 240 query IntrospectionDroidTypeQuery { 241 __type(name: "Droid") { 242 name 243 kind 244 } 245 } 246 `); 247 248 string s = `{ 249 "data" : { 250 "__type" : { 251 "name" : "Droid", 252 "kind" : "OBJECT" 253 } 254 } 255 } 256 `; 257 Json exp = parseJson(s); 258 assert(rslt == exp, format("exp:\n%s\ngot:\n%s", exp.toPrettyString(), 259 rslt.toPrettyString())); 260 } 261 262 @safe unittest { 263 Json rslt = query(` 264 query IntrospectionCharacterKindQuery { 265 __type(name: "Character") { 266 name 267 kind 268 } 269 } 270 `); 271 272 string s = `{ 273 "data" : { 274 "__type" : { 275 "name" : "Character", 276 "kind" : "INTERFACE" 277 } 278 } 279 } 280 `; 281 Json exp = parseJson(s); 282 assert(rslt == exp, format("exp:\n%s\ngot:\n%s", exp.toPrettyString(), 283 rslt.toPrettyString())); 284 } 285 286 @safe unittest { 287 Json rslt = query(` 288 query IntrospectionDroidFieldsQuery { 289 __type(name: "Droid") { 290 name 291 fields { 292 name 293 type { 294 name 295 kind 296 } 297 } 298 } 299 } 300 `); 301 302 string s = `{ 303 "data" : { 304 "__type" : { 305 "name" : "Droid", 306 "fields" : [ 307 { 308 "name": "primaryFunction", 309 "type": { 310 "name": "String", 311 "kind": "SCALAR" 312 } 313 }, 314 { 315 "name": "id", 316 "type": { 317 "name": null, 318 "kind": "NON_NULL" 319 } 320 }, 321 { 322 "name": "name", 323 "type": { 324 "name": "String", 325 "kind": "SCALAR" 326 } 327 }, 328 { 329 "name": "friends", 330 "type": { 331 "name": null, 332 "kind": "LIST" 333 } 334 }, 335 { 336 "name": "appearsIn", 337 "type": { 338 "name": null, 339 "kind": "LIST" 340 } 341 }, 342 { 343 "name": "secretBackstory", 344 "type": { 345 "name": "String", 346 "kind": "SCALAR" 347 } 348 }, 349 ] 350 } 351 } 352 } 353 `; 354 Json exp = parseJson(s); 355 assert(rslt == exp, format("exp:\n%s\ngot:\n%s", exp.toPrettyString(), 356 rslt.toPrettyString())); 357 } 358 359 @safe unittest { 360 Json rslt = query(` 361 query IntrospectionDroidNestedFieldsQuery { 362 __type(name: "Droid") { 363 name 364 fields { 365 name 366 type { 367 name 368 kind 369 ofType { 370 name 371 kind 372 } 373 } 374 } 375 } 376 } 377 `); 378 379 string s = `{ 380 "data" : { 381 "__type" : { 382 "name" : "Droid", 383 "fields" : [ 384 { 385 "name": "primaryFunction", 386 "type": { 387 "name": "String", 388 "kind": "SCALAR", 389 "ofType": null 390 } 391 }, 392 { 393 "name": "id", 394 "type": { 395 "name": null, 396 "kind": "NON_NULL", 397 "ofType": { 398 "name": "String", 399 "kind": "SCALAR" 400 } 401 } 402 }, 403 { 404 "name": "name", 405 "type": { 406 "name": "String", 407 "kind": "SCALAR", 408 "ofType": null 409 } 410 }, 411 { 412 "name": "friends", 413 "type": { 414 "name": null, 415 "kind": "LIST", 416 "ofType": { 417 "name": "Character", 418 "kind": "INTERFACE" 419 } 420 } 421 }, 422 { 423 "name": "appearsIn", 424 "type": { 425 "name": null, 426 "kind": "LIST", 427 "ofType": { 428 "name": "Episode", 429 "kind": "ENUM" 430 } 431 } 432 }, 433 { 434 "name": "secretBackstory", 435 "type": { 436 "name": "String", 437 "kind": "SCALAR", 438 "ofType": null 439 } 440 }, 441 ] 442 } 443 } 444 } 445 `; 446 Json exp = parseJson(s); 447 assert(rslt == exp, format("exp:\n%s\ngot:\n%s", exp.toPrettyString(), 448 rslt.toPrettyString())); 449 } 450 451 @safe unittest { 452 Json rslt = query(` 453 query IntrospectionQueryTypeQuery { 454 __schema { 455 queryType { 456 fields { 457 name 458 args { 459 name 460 description 461 type { 462 name 463 kind 464 ofType { 465 name 466 kind 467 } 468 } 469 defaultValue 470 } 471 } 472 } 473 } 474 } 475 `); 476 477 string s = ` { 478 "data" : { 479 "__schema": { 480 "queryType": { 481 "fields": [ 482 { 483 "name": "hero", 484 "args": [ 485 { 486 "defaultValue": null, 487 "description": 488 "If omitted, returns the hero of the whole saga. If provided, returns the hero of that particular episode.", 489 "name": "episode", 490 "type": { 491 "kind": "ENUM", 492 "name": "Episode", 493 "ofType": null 494 } 495 } 496 ] 497 }, 498 { 499 "name": "human", 500 "args": [ 501 { 502 "name": "id", 503 "description": "id of the human", 504 "type": { 505 "kind": "NON_NULL", 506 "name": null, 507 "ofType": { 508 "kind": "SCALAR", 509 "name": "String" 510 } 511 }, 512 "defaultValue": null 513 } 514 ] 515 }, 516 { 517 "name": "droid", 518 "args": [ 519 { 520 "name": "id", 521 "description": "id of the droid", 522 "type": { 523 "kind": "NON_NULL", 524 "name": null, 525 "ofType": { 526 "kind": "SCALAR", 527 "name": "String" 528 } 529 }, 530 "defaultValue": null 531 } 532 ] 533 } 534 ] 535 } 536 } 537 } 538 }`; 539 Json exp = parseJson(s); 540 string extS = exp.toPrettyString(); 541 string rsltS = rslt.toPrettyString(); 542 assert(rslt == exp, format("exp:\n%s\ngot:\n%s", extS, rsltS)); 543 } 544 545 @safe unittest { 546 Json rslt = query(` 547 { 548 __type(name: "Droid") { 549 name 550 description 551 } 552 }`); 553 554 string s = `{ 555 "data" : { 556 "__type": { 557 "name" : "Droid", 558 "description" : "A mechanical creature in the Star Wars universe." 559 } 560 } 561 }`; 562 Json exp = parseJson(s); 563 string extS = exp.toPrettyString(); 564 string rsltS = rslt.toPrettyString(); 565 assert(rslt == exp, format("exp:\n%s\ngot:\n%s", extS, rsltS)); 566 } 567 568 @safe unittest { 569 Json rslt = query(` 570 { 571 __type(name: "Character") { 572 fields { 573 name 574 description 575 } 576 } 577 }`); 578 579 string s = `{ 580 "data": { 581 "__type": { 582 "fields": [ 583 { 584 "description": "The id of the character", 585 "name": "id" 586 }, 587 { 588 "description": "The name of the character", 589 "name": "name" 590 }, 591 { 592 "description": "The friends of the character, or an empty list if they have none.", 593 "name": "friends" 594 }, 595 { 596 "description": "Which movies they appear in.", 597 "name": "appearsIn" 598 }, 599 { 600 "description": "Where are they from and how they came to be who they are.", 601 "name": "secretBackstory" 602 } 603 ] 604 } 605 } 606 }`; 607 Json exp = parseJson(s); 608 string extS = exp.toPrettyString(); 609 string rsltS = rslt.toPrettyString(); 610 assert(rslt == exp, format("exp:\n%s\ngot:\n%s", extS, rsltS)); 611 }