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.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 auto args = `{"someId": "1000"}`; 252 Json rslt = query(` 253 query FetchSomeIDQuery($someId: String!) { 254 human(id: $someId) { 255 name 256 } 257 }`, parseJson(args)); 258 259 string s = `{ "data" : { "human" : { "name" : "Luke Skywalker" } } }`; 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 auto args = `{"someId": "1002"}`; 267 Json rslt = query(` 268 query FetchSomeIDQuery($someId: String!) { 269 human(id: $someId) { 270 name 271 } 272 }`, parseJson(args)); 273 274 string s = `{ "data" : { "human" : { "name" : "Han Solo" } } }`; 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 auto args = `{ "id" : "not a valid id" }`; 282 Json rslt = query(` 283 query humanQuery($id: String!) { 284 human(id: $id) { 285 name 286 } 287 }`, parseJson(args)); 288 289 string s = `{ "data" : { "human" : null } }`; 290 Json exp = parseJson(s); 291 assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s", 292 exp.toPrettyString(), rslt.toPrettyString())); 293 } 294 295 @safe unittest { 296 Json rslt = query(` 297 query FetchLukeAliased { 298 luke: human(id: "1000") { 299 name 300 } 301 }`); 302 303 string s = `{ "data" : { "luke" : { "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 Json rslt = query(` 311 query FetchLukeAndLeiaAliased { 312 luke: human(id: "1000") { 313 name 314 } 315 leia: human(id: "1003") { 316 name 317 } 318 }`); 319 320 string s = `{ "data" : { "luke" : { "name" : "Luke Skywalker" }, 321 "leia" : { "name" : "Leia Organa" } } }`; 322 Json exp = parseJson(s); 323 assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s", 324 exp.toPrettyString(), rslt.toPrettyString())); 325 } 326 327 @safe unittest { 328 Json rslt = query(` 329 query DuplicateFields { 330 luke: human(id: "1000") { 331 name 332 homePlanet 333 } 334 leia: human(id: "1003") { 335 name 336 homePlanet 337 } 338 }`); 339 340 string s = `{ "data" : 341 { "luke" : 342 { "name" : "Luke Skywalker", "homePlanet" : "Tatooine" } 343 , "leia" : 344 { "name" : "Leia Organa", "homePlanet" : "Alderaan" } 345 } 346 }`; 347 Json exp = parseJson(s); 348 assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s", 349 exp.toPrettyString(), rslt.toPrettyString())); 350 } 351 352 @safe unittest { 353 Json rslt = query(` 354 query UseFragment { 355 luke: human(id: "1000") { 356 ...HumanFragment 357 } 358 leia: human(id: "1003") { 359 ...HumanFragment 360 } 361 } 362 fragment HumanFragment on Human { 363 name 364 homePlanet 365 }`); 366 367 string s = `{ "data" : 368 { "luke" : 369 { "name" : "Luke Skywalker", "homePlanet" : "Tatooine" } 370 , "leia" : 371 { "name" : "Leia Organa", "homePlanet" : "Alderaan" } 372 } 373 }`; 374 Json exp = parseJson(s); 375 assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s", 376 exp.toPrettyString(), rslt.toPrettyString())); 377 } 378 379 @safe unittest { 380 Json rslt = query(` 381 query CheckTypeOfR2 { 382 hero { 383 __typename 384 name 385 } 386 }`); 387 388 string s = `{"data" : { "hero" : { "__typename": "Droid", "name": "R2-D2" } 389 } }`; 390 Json exp = parseJson(s); 391 assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s", 392 exp.toPrettyString(), rslt.toPrettyString())); 393 } 394 395 @safe unittest { 396 Json rslt = query(` 397 query CheckTypeOfLuke { 398 hero(episode: EMPIRE) { 399 __typename 400 name 401 } 402 }`); 403 404 string s = `{"data" : { "hero" : { "__typename": "Human", 405 "name": "Luke Skywalker" } 406 } }`; 407 Json exp = parseJson(s); 408 assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s", 409 exp.toPrettyString(), rslt.toPrettyString())); 410 } 411 412 @safe unittest { 413 Json rslt = query(` 414 query HeroNameQuery { 415 hero { 416 name 417 secretBackstory 418 } 419 }`); 420 421 string s = `{"data" : { "hero" : { "name": "R2-D2", 422 "secretBackstory": null, } }, "errors" : [ { 423 "path": [ "HeroNameQuery", "hero", "secretBackstory"], 424 "message": "secretBackstory is secret" } ]}`; 425 Json exp = parseJson(s); 426 assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s", 427 exp.toPrettyString(), rslt.toPrettyString())); 428 } 429 430 @safe unittest { 431 Json rslt = query(` 432 query HeroNameQuery { 433 hero { 434 name 435 friends { 436 name 437 secretBackstory 438 } 439 } 440 }`); 441 442 string s = `{ 443 "errors" : [ 444 { "message": "secretBackstory is secret" 445 , "path": [ "HeroNameQuery", "hero", "friends", 0, "secretBackstory"] }, 446 { "message": "secretBackstory is secret" 447 , "path": [ "HeroNameQuery", "hero", "friends", 1, "secretBackstory"] }, 448 { "message": "secretBackstory is secret" 449 , "path": [ "HeroNameQuery", "hero", "friends", 2, "secretBackstory"] } 450 ], 451 "data": { "hero": { "name": "R2-D2", 452 "friends": [ 453 { "name": "Luke Skywalker", "secretBackstory": null }, 454 { "name": "Han Solo", "secretBackstory": null }, 455 { "name": "Leia Organa", "secretBackstory": null } 456 ] 457 } 458 } 459 }`; 460 Json exp = parseJson(s); 461 assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s", 462 exp.toPrettyString(), rslt.toPrettyString())); 463 } 464 465 @safe unittest { 466 Json rslt = query(` 467 query HeroNameQuery { 468 mainHero: hero { 469 name 470 story: secretBackstory 471 } 472 }`); 473 474 string s = `{ 475 "data": { 476 "mainHero": { 477 "name": "R2-D2", 478 "story": null, 479 }, 480 }, 481 "errors" : [ 482 { "message": "secretBackstory is secret" 483 , "path": [ "HeroNameQuery", "mainHero", "story"] } 484 ] 485 }`; 486 Json exp = parseJson(s); 487 assert(rslt == exp, format("\nexp:\n%s\ngot:\n%s", 488 exp.toPrettyString(), rslt.toPrettyString())); 489 }