1 module graphql.starwars.query; 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.exception; 17 import graphql.helper; 18 import graphql.starwars.data; 19 import graphql.starwars.schema; 20 import graphql.starwars.types; 21 import graphql.validation.querybased; 22 import graphql.validation.schemabased; 23 24 @safe: 25 26 Json query(string s) { 27 return query(s, Json.init); 28 } 29 30 Json query(string s, Json args) { 31 auto graphqld = new GraphQLD!(StarWarsSchema); 32 //auto lo = new std.experimental.logger.FileLogger("query.log"); 33 //graphqld.defaultResolverLog = lo; 34 //graphqld.executationTraceLog = 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; 78 if(name == "secretBackstory") { 79 throw new GQLDExecutionException("secretBackstory is secret"); 80 } 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 Json rslt = query( 122 `query HeroNameQuery { 123 hero { 124 name 125 } 126 }`); 127 128 string s = `{ "data" : { "hero" : { "name" : "R2-D2" } } }`; 129 Json exp = parseJson(s); 130 assert(rslt == exp, rslt.toPrettyString() ~ "\n" ~ exp.toPrettyString()); 131 } 132 133 @safe unittest { 134 Json rslt = query( 135 `query HeroNameQuery { 136 hero { 137 name 138 } 139 }`); 140 string s = `{ "data" : { "hero" : { "name" : "R2-D2" } } }`; 141 Json exp = parseJson(s); 142 assert(rslt == exp, rslt.toPrettyString() ~ "\n" ~ exp.toPrettyString()); 143 } 144 145 @safe unittest { 146 Json rslt = query( 147 `query HeroNameAndFriendsQuery { 148 hero { 149 id 150 name 151 friends { 152 name 153 } 154 } 155 }`); 156 string s = `{ 157 "data": { 158 "hero": { 159 "id": "2001", 160 "name": "R2-D2", 161 "friends": [ 162 { 163 "name": "Luke Skywalker", 164 }, 165 { 166 "name": "Han Solo", 167 }, 168 { 169 "name": "Leia Organa", 170 } 171 ] 172 } 173 } 174 }`; 175 Json exp = parseJson(s); 176 assert(rslt == exp, rslt.toPrettyString() ~ "\n" ~ exp.toPrettyString()); 177 } 178 179 @safe unittest { 180 Json rslt = query(` 181 query NestedQuery { 182 hero { 183 name 184 friends { 185 name 186 appearsIn 187 friends { 188 name 189 } 190 } 191 } 192 }`); 193 194 string s = ` 195 { 196 "data": { 197 "hero": { 198 "name": "R2-D2", 199 "friends": [ 200 { 201 "name": "Luke Skywalker", 202 "appearsIn": ["NEWHOPE", "EMPIRE", "JEDI"], 203 "friends": [ 204 { "name": "Han Solo", }, 205 { "name": "Leia Organa", }, 206 { "name": "C-3PO", }, 207 { "name": "R2-D2", }, 208 ], 209 }, 210 { 211 "name": "Han Solo", 212 "appearsIn": ["NEWHOPE", "EMPIRE", "JEDI"], 213 "friends": [ 214 { "name": "Luke Skywalker", }, 215 { "name": "Leia Organa", }, 216 { "name": "R2-D2", }, 217 ], 218 }, 219 { 220 "name": "Leia Organa", 221 "appearsIn": ["NEWHOPE", "EMPIRE", "JEDI"], 222 "friends": [ 223 { "name": "Luke Skywalker", }, 224 { "name": "Han Solo", }, 225 { "name": "C-3PO", }, 226 { "name": "R2-D2", }, 227 ], 228 }, 229 ], 230 }, 231 } 232 }`; 233 Json exp = parseJson(s); 234 assert(rslt == exp, rslt.toPrettyString() ~ "\n" ~ exp.toPrettyString()); 235 } 236 237 @safe unittest { 238 Json rslt = query(` 239 query FetchLukeQuery { 240 human(id: "1000") { 241 name 242 } 243 }`); 244 245 string s = `{ "data" : { "human" : { "name" : "Luke Skywalker" } } }`; 246 Json exp = parseJson(s); 247 assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s", 248 exp.toPrettyString(), rslt.toPrettyString())); 249 } 250 251 @safe unittest { 252 string args = `{"a": false}`; 253 Json rslt = query(` 254 query FetchLukeQuery($a: Boolean!) { 255 human(id: "1000") @include(if: $a) { 256 name 257 } 258 }`, parseJson(args)); 259 260 string s = `{ "data" : { } } }`; 261 Json exp = parseJson(s); 262 assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s", 263 exp.toPrettyString(), rslt.toPrettyString())); 264 } 265 266 @safe unittest { 267 Json rslt = query(` 268 query FetchLukeQuery() { 269 human(id: "1000") @include(if: false) { 270 name 271 } 272 }`); 273 274 string s = `{ "data" : { } } }`; 275 Json exp = parseJson(s); 276 assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s", 277 exp.toPrettyString(), rslt.toPrettyString())); 278 } 279 280 @safe unittest { 281 Json rslt = query(` 282 query FetchLukeQuery() { 283 human(id: "1000") @include(if: true) { 284 name 285 } 286 }`); 287 288 string s = `{ "data" : { "human" : { "name" : "Luke Skywalker" } } }`; 289 Json exp = parseJson(s); 290 assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s", 291 exp.toPrettyString(), rslt.toPrettyString())); 292 } 293 294 @safe unittest { 295 string args = `{"a": true}`; 296 Json rslt = query(` 297 query FetchLukeQuery($a: Boolean!) { 298 human(id: "1000") @include(if: $a) { 299 name 300 } 301 }`, parseJson(args)); 302 303 string s = `{ "data" : { "human" : { "name" : "Luke Skywalker" } } }`; 304 Json exp = parseJson(s); 305 assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s", 306 exp.toPrettyString(), rslt.toPrettyString())); 307 } 308 309 @safe unittest { 310 auto args = `{"someId": "1000"}`; 311 Json rslt = query(` 312 query FetchSomeIDQuery($someId: String!) { 313 human(id: $someId) { 314 name 315 } 316 }`, parseJson(args)); 317 318 string s = `{ "data" : { "human" : { "name" : "Luke Skywalker" } } }`; 319 Json exp = parseJson(s); 320 assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s", 321 exp.toPrettyString(), rslt.toPrettyString())); 322 } 323 324 @safe unittest { 325 auto args = `{"someId": "1002"}`; 326 Json rslt = query(` 327 query FetchSomeIDQuery($someId: String!) { 328 human(id: $someId) { 329 name 330 } 331 }`, parseJson(args)); 332 333 string s = `{ "data" : { "human" : { "name" : "Han Solo" } } }`; 334 Json exp = parseJson(s); 335 assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s", 336 exp.toPrettyString(), rslt.toPrettyString())); 337 } 338 339 @safe unittest { 340 auto args = `{ "id" : "not a valid id" }`; 341 Json rslt = query(` 342 query humanQuery($id: String!) { 343 human(id: $id) { 344 name 345 } 346 }`, parseJson(args)); 347 348 string s = `{ "data" : { "human" : null } }`; 349 Json exp = parseJson(s); 350 assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s", 351 exp.toPrettyString(), rslt.toPrettyString())); 352 } 353 354 @safe unittest { 355 Json rslt = query(` 356 query FetchLukeAliased { 357 luke: human(id: "1000") { 358 name 359 } 360 }`); 361 362 string s = `{ "data" : { "luke" : { "name" : "Luke Skywalker" } } }`; 363 Json exp = parseJson(s); 364 assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s", 365 exp.toPrettyString(), rslt.toPrettyString())); 366 } 367 368 @safe unittest { 369 Json rslt = query(` 370 query FetchLukeAndLeiaAliased { 371 luke: human(id: "1000") { 372 name 373 } 374 leia: human(id: "1003") { 375 name 376 } 377 }`); 378 379 string s = `{ "data" : { "luke" : { "name" : "Luke Skywalker" }, 380 "leia" : { "name" : "Leia Organa" } } }`; 381 Json exp = parseJson(s); 382 assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s", 383 exp.toPrettyString(), rslt.toPrettyString())); 384 } 385 386 @safe unittest { 387 Json rslt = query(` 388 query DuplicateFields { 389 luke: human(id: "1000") { 390 name 391 homePlanet 392 } 393 leia: human(id: "1003") { 394 name 395 homePlanet 396 } 397 }`); 398 399 string s = `{ "data" : 400 { "luke" : 401 { "name" : "Luke Skywalker", "homePlanet" : "Tatooine" } 402 , "leia" : 403 { "name" : "Leia Organa", "homePlanet" : "Alderaan" } 404 } 405 }`; 406 Json exp = parseJson(s); 407 assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s", 408 exp.toPrettyString(), rslt.toPrettyString())); 409 } 410 411 @safe unittest { 412 Json rslt = query(` 413 query UseFragment { 414 luke: human(id: "1000") { 415 ...HumanFragment 416 } 417 leia: human(id: "1003") { 418 ...HumanFragment 419 } 420 } 421 fragment HumanFragment on Human { 422 name 423 homePlanet 424 }`); 425 426 string s = `{ "data" : 427 { "luke" : 428 { "name" : "Luke Skywalker", "homePlanet" : "Tatooine" } 429 , "leia" : 430 { "name" : "Leia Organa", "homePlanet" : "Alderaan" } 431 } 432 }`; 433 Json exp = parseJson(s); 434 assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s", 435 exp.toPrettyString(), rslt.toPrettyString())); 436 } 437 438 @safe unittest { 439 Json rslt = query(` 440 query CheckTypeOfR2 { 441 hero { 442 __typename 443 name 444 } 445 }`); 446 447 string s = `{"data" : { "hero" : { "__typename": "Droid", "name": "R2-D2" } 448 } }`; 449 Json exp = parseJson(s); 450 assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s", 451 exp.toPrettyString(), rslt.toPrettyString())); 452 } 453 454 @safe unittest { 455 Json rslt = query(` 456 query CheckTypeOfLuke { 457 hero(episode: EMPIRE) { 458 __typename 459 name 460 } 461 }`); 462 463 string s = `{"data" : { "hero" : { "__typename": "Human", 464 "name": "Luke Skywalker" } 465 } }`; 466 Json exp = parseJson(s); 467 assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s", 468 exp.toPrettyString(), rslt.toPrettyString())); 469 } 470 471 @safe unittest { 472 Json rslt = query(` 473 query HeroNameQuery { 474 hero { 475 name 476 secretBackstory 477 } 478 }`); 479 480 string s = `{"data" : { "hero" : { "name": "R2-D2", 481 "secretBackstory": null, } }, "errors" : [ { 482 "path": [ "HeroNameQuery", "hero", "secretBackstory"], 483 "message": "secretBackstory is secret" } ]}`; 484 Json exp = parseJson(s); 485 assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s", 486 exp.toPrettyString(), rslt.toPrettyString())); 487 } 488 489 @safe unittest { 490 Json rslt = query(` 491 query HeroNameQuery { 492 hero { 493 name 494 friends { 495 name 496 secretBackstory 497 } 498 } 499 }`); 500 501 string s = `{ 502 "errors" : [ 503 { "message": "secretBackstory is secret" 504 , "path": [ "HeroNameQuery", "hero", "friends", 0, "secretBackstory"] }, 505 { "message": "secretBackstory is secret" 506 , "path": [ "HeroNameQuery", "hero", "friends", 1, "secretBackstory"] }, 507 { "message": "secretBackstory is secret" 508 , "path": [ "HeroNameQuery", "hero", "friends", 2, "secretBackstory"] } 509 ], 510 "data": { "hero": { "name": "R2-D2", 511 "friends": [ 512 { "name": "Luke Skywalker", "secretBackstory": null }, 513 { "name": "Han Solo", "secretBackstory": null }, 514 { "name": "Leia Organa", "secretBackstory": null } 515 ] 516 } 517 } 518 }`; 519 Json exp = parseJson(s); 520 assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s", 521 exp.toPrettyString(), rslt.toPrettyString())); 522 } 523 524 @safe unittest { 525 Json rslt = query(` 526 query HeroNameQuery { 527 mainHero: hero { 528 name 529 story: secretBackstory 530 } 531 }`); 532 533 string s = `{ 534 "data": { 535 "mainHero": { 536 "name": "R2-D2", 537 "story": null, 538 }, 539 }, 540 "errors" : [ 541 { "message": "secretBackstory is secret" 542 , "path": [ "HeroNameQuery", "mainHero", "story"] } 543 ] 544 }`; 545 Json exp = parseJson(s); 546 assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s", 547 exp.toPrettyString(), rslt.toPrettyString())); 548 }