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