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