1 module graphql.builder; 2 3 import std.experimental.allocator; 4 import std.experimental.logger : logf; 5 import std.experimental.allocator.mallocator : Mallocator; 6 import std.exception : enforce; 7 import std.format : format; 8 9 import vibe.data.json; 10 11 import fixedsizearray; 12 13 import graphql.argumentextractor; 14 import graphql.helper; 15 import graphql.ast; 16 import graphql.parser; 17 import graphql.lexer; 18 import graphql.directives; 19 20 @safe: 21 22 const(FragmentDefinition) findFragment(const(Document) doc, string name) { 23 import std.algorithm.searching : canFind; 24 import std.experimental.logger : logf; 25 enforce(doc !is null); 26 const(Definitions) cur = doc.defs; 27 return findFragmentImpl(cur, name); 28 } 29 30 const(FragmentDefinition) findFragmentImpl(const(Definitions) cur, 31 string name) 32 { 33 if(cur is null) { 34 return null; 35 } else { 36 enforce(cur.def !is null); 37 if(cur.def.ruleSelection == DefinitionEnum.F) { 38 enforce(cur.def.frag !is null); 39 if(cur.def.frag.name.value == name) { 40 return cur.def.frag; 41 } 42 } 43 return findFragmentImpl(cur.follow, name); 44 } 45 } 46 47 Selections findFragment(Document doc, string name, string[] typenames) { 48 import std.algorithm.searching : canFind; 49 import std.experimental.logger : logf; 50 if(doc is null) { 51 return null; 52 } 53 Definitions cur = doc.defs; 54 while(cur !is null) { 55 enforce(cur.def !is null); 56 if(cur.def.ruleSelection == DefinitionEnum.F) { 57 enforce(cur.def.frag !is null); 58 //logf("%s == %s && %s in %s", cur.def.frag.name.value, name, 59 // cur.def.frag.tc.value, typenames 60 // ); 61 if(cur.def.frag.name.value == name 62 && canFind(typenames, cur.def.frag.tc.value)) 63 { 64 //logf("found it"); 65 return cur.def.frag.ss.sel; 66 } else { 67 //logf("not found"); 68 } 69 } 70 cur = cur.follow; 71 } 72 73 //logf("search failed"); 74 return null; 75 } 76 77 unittest { 78 string s = `{ 79 user(id: 1) { 80 friends { 81 name 82 } 83 name 84 age 85 } 86 }`; 87 auto l = Lexer(s); 88 auto p = Parser(l); 89 auto d = p.parseDocument(); 90 91 const f = findFragment(d, "fooo", ["user"]); 92 assert(f is null); 93 } 94 95 unittest { 96 string s = ` 97 fragment fooo on Hero { 98 name 99 }`; 100 auto l = Lexer(s); 101 auto p = Parser(l); 102 auto d = p.parseDocument(); 103 104 auto f = findFragment(d, "fooo", ["Hero"]); 105 assert(f !is null); 106 assert(f.sel.field.name.name.value == "name"); 107 108 f = findFragment(d, "fooo", ["Villian"]); 109 assert(f is null); 110 } 111 112 struct FieldRangeItem { 113 import std.array : empty; 114 Field f; 115 Document doc; 116 117 @property string name() { 118 return f.name.name.value; 119 //return f.name.aka.value.empty ? f.name.name.value : f.name.aka.value; 120 } 121 122 @property string aka() { 123 return f.name.aka.value; 124 } 125 } 126 127 struct FieldRange { 128 FixedSizeArray!(Selections,64) cur; 129 Document doc; 130 string[] typenames; 131 Json vars; 132 133 this(Selections sels, Document doc, string[] typenames, Json vars) { 134 this.doc = doc; 135 this.typenames = typenames; 136 this.vars = vars; 137 this.cur.insertBack(sels); 138 this.build(); 139 //this.test(); 140 } 141 142 @property bool empty() const pure { 143 return this.cur.length == 0; 144 } 145 146 @property FieldRangeItem front() { 147 enforce(!this.cur.empty); 148 enforce(this.cur.back !is null); 149 enforce(this.cur.back.sel !is null); 150 enforce(this.cur.back.sel.field !is null); 151 return FieldRangeItem(this.cur.back.sel.field, this.doc); 152 } 153 154 bool directivesAllowContinue(Selection sel, Json vars) { 155 Directives dirs; 156 final switch(sel.ruleSelection) { 157 case SelectionEnum.Field: 158 dirs = sel.field.dirs; 159 break; 160 case SelectionEnum.Spread: 161 dirs = sel.frag.dirs; 162 break; 163 case SelectionEnum.IFrag: 164 dirs = sel.ifrag.dirs; 165 break; 166 } 167 return continueAfterDirectives(dirs, vars); 168 } 169 170 void popFront() { 171 this.cur.back = this.cur.back.follow; 172 this.build(); 173 } 174 175 void build() { 176 if(this.cur.empty) { 177 return; 178 } 179 if(this.cur.back !is null 180 && directivesAllowContinue(this.cur.back.sel, vars)) 181 { 182 const SelectionEnum se = this.cur.back.sel.ruleSelection; 183 if(se == SelectionEnum.Field) { 184 return; 185 } else { 186 Selections follow = this.cur.back.follow; 187 Selections f = se == SelectionEnum.Spread 188 ? findFragment(doc, this.cur.back.sel.frag.name.value, 189 this.typenames 190 ) 191 : resolveInlineFragment(this.cur.back.sel.ifrag, 192 this.typenames 193 ); 194 195 this.cur.removeBack(); 196 197 if(follow !is null) { 198 this.cur.insertBack(follow); 199 this.build(); 200 //this.test(); 201 } 202 if(f !is null) { 203 this.cur.insertBack(f); 204 this.build(); 205 //this.test(); 206 } 207 } 208 } else if(this.cur.back is null) { 209 this.cur.removeBack(); 210 this.build(); 211 } else { 212 this.cur.back = this.cur.back.follow; 213 this.build(); 214 } 215 } 216 217 /*void popFront() { 218 this.cur.back = this.cur.back.follow; 219 this.build(); 220 this.test(); 221 } 222 223 void test() { 224 import std.format : format; 225 import std.conv : to; 226 import std.algorithm.iteration : map; 227 //logf("%s", this.cur[].map!(i => format("nn %s, ft %s", i !is null, 228 // i !is null ? to!string(i.sel.ruleSelection) : "null")) 229 // ); 230 foreach(it; this.cur[]) { 231 assert(it !is null); 232 assert(it.sel.ruleSelection == SelectionEnum.Field); 233 } 234 } 235 236 void build() { 237 if(this.cur.empty) { 238 return; 239 } 240 if(this.cur.back is null) { 241 this.cur.removeBack(); 242 this.build(); 243 this.test(); 244 return; 245 } 246 if(this.cur.back.sel.ruleSelection == SelectionEnum.Field) { 247 return; 248 } else if(this.cur.back.sel.ruleSelection == SelectionEnum.Spread 249 || this.cur.back.sel.ruleSelection == SelectionEnum.IFrag) 250 { 251 Selections follow = this.cur.back.follow; 252 Selections f = 253 this.cur.back.sel.ruleSelection == SelectionEnum.Spread 254 ? findFragment(doc, this.cur.back.sel.frag.name.value, 255 this.typenames 256 ) 257 : resolveInlineFragment(this.cur.back.sel.ifrag, 258 this.typenames 259 ); 260 261 this.cur.removeBack(); 262 263 if(follow !is null) { 264 this.cur.insertBack(follow); 265 this.build(); 266 this.test(); 267 } 268 if(f !is null) { 269 this.cur.insertBack(f); 270 this.build(); 271 this.test(); 272 } 273 } 274 }*/ 275 } 276 277 Selections resolveInlineFragment(InlineFragment ilf, string[] typenames) { 278 import std.algorithm.searching : canFind; 279 final switch(ilf.ruleSelection) { 280 case InlineFragmentEnum.TDS: 281 goto case InlineFragmentEnum.TS; 282 case InlineFragmentEnum.TS: 283 return canFind(typenames, ilf.tc.value) ? ilf.ss.sel : null; 284 case InlineFragmentEnum.DS: 285 return ilf.ss.sel; 286 case InlineFragmentEnum.S: 287 return ilf.ss.sel; 288 } 289 } 290 291 FieldRange fieldRange(OperationDefinition od, Document doc, 292 string[] typenames) 293 { 294 return FieldRange(od.accessNN!(["ss", "sel"]), doc, typenames, 295 Json.emptyObject()); 296 } 297 298 FieldRange fieldRange(SelectionSet ss, Document doc, string[] typenames) { 299 return FieldRange(ss.sel, doc, typenames, Json.emptyObject()); 300 } 301 302 FieldRangeItem[] fieldRangeArr(Selections sel, Document doc, 303 string[] typenames, Json vars) 304 { 305 import std.array : array; 306 return FieldRange(sel, doc, typenames, vars).array; 307 } 308 309 FieldRangeItem[] fieldRangeArr(Selections sel, Document doc, 310 string[] typenames) 311 { 312 import std.array : array; 313 return fieldRangeArr(sel, doc, typenames, Json.emptyObject()); 314 } 315 316 struct OpDefRangeItem { 317 Document doc; 318 Definition def; 319 320 FieldRange fieldRange(string[] typenames) { 321 return .fieldRange(accessNN!(["op", "ss"])(this.def), this.doc, 322 typenames 323 ); 324 } 325 } 326 327 struct OpDefRange { 328 Document doc; 329 Definitions defs; 330 331 this(Document doc) { 332 this.doc = doc; 333 this.defs = doc.defs; 334 this.advance(); 335 } 336 337 private void advance() { 338 while(this.defs !is null 339 && this.defs.def.ruleSelection != DefinitionEnum.O) 340 { 341 this.defs = this.defs.follow; 342 } 343 } 344 345 @property bool empty() const { 346 return this.defs is null; 347 } 348 349 @property OpDefRangeItem front() { 350 return OpDefRangeItem(this.doc, this.defs.def); 351 } 352 353 void popFront() { 354 this.defs = this.defs.follow; 355 this.advance(); 356 } 357 358 @property typeof(this) save() { 359 OpDefRange ret; 360 ret.doc = this.doc; 361 ret.defs = this.defs; 362 return ret; 363 } 364 } 365 366 OpDefRange opDefRange(Document doc) { 367 return OpDefRange(doc); 368 } 369 370 unittest { 371 string s = `{ 372 user(id: 1) { 373 friends { 374 name 375 } 376 name 377 age 378 } 379 }`; 380 auto l = Lexer(s); 381 auto p = Parser(l); 382 auto d = p.parseDocument(); 383 384 FieldRange r = fieldRange(d.defs.def.op, d, ["User"]); 385 assert(!r.empty); 386 assert(r.front.name == "user"); 387 } 388 389 unittest { 390 string s = `{ 391 user(id: 1) { 392 ...foo 393 } 394 } 395 396 fragment foo on User { 397 name 398 age 399 } 400 `; 401 auto l = Lexer(s); 402 auto p = Parser(l); 403 auto d = p.parseDocument(); 404 405 const f = findFragment(d, "foo", ["User"]); 406 assert(f !is null); 407 408 FieldRange r = fieldRange(d.defs.def.op, d, ["User"]); 409 assert(!r.empty); 410 assert(r.front.name == "user"); 411 } 412 413 unittest { 414 string s = `{ 415 user(id: 1) { 416 ...foo 417 ...bar 418 } 419 } 420 421 fragment foo on User { 422 name 423 } 424 425 fragment bar on User { 426 age 427 } 428 `; 429 auto l = Lexer(s); 430 auto p = Parser(l); 431 auto d = p.parseDocument(); 432 433 const f = findFragment(d, "foo", ["User"]); 434 assert(f !is null); 435 436 const f2 = findFragment(d, "bar", ["User"]); 437 assert(f2 !is null); 438 439 FieldRange r = fieldRange(d.defs.def.op, d, ["User"]); 440 assert(!r.empty); 441 assert(r.front.name == "user"); 442 } 443 444 unittest { 445 string s = `{ 446 user(id: 1) { 447 ...foo 448 ...bar 449 hello 450 } 451 } 452 453 fragment foo on User { 454 name 455 } 456 457 fragment bar on User { 458 age 459 } 460 `; 461 auto l = Lexer(s); 462 auto p = Parser(l); 463 auto d = p.parseDocument(); 464 465 const f = findFragment(d, "foo", ["User"]); 466 assert(f !is null); 467 468 const f2 = findFragment(d, "bar", ["User"]); 469 assert(f2 !is null); 470 471 FieldRange r = fieldRange(d.defs.def.op, d, ["User"]); 472 assert(!r.empty); 473 assert(r.front.name == "user"); 474 } 475 476 unittest { 477 string s = `{ 478 user(id: 1) { 479 hello 480 ...foo 481 ...bar 482 } 483 } 484 485 fragment foo on User { 486 name 487 } 488 489 fragment bar on User { 490 age 491 } 492 `; 493 auto l = Lexer(s); 494 auto p = Parser(l); 495 auto d = p.parseDocument(); 496 497 const f = findFragment(d, "foo", ["User"]); 498 assert(f !is null); 499 500 const f2 = findFragment(d, "bar", ["User"]); 501 assert(f2 !is null); 502 503 FieldRange r = fieldRange(d.defs.def.op, d, ["User"]); 504 assert(!r.empty); 505 assert(r.front.name == "user"); 506 } 507 508 unittest { 509 string s = `{ 510 user(id: 1) { 511 hello 512 ...foo 513 ...bar 514 } 515 } 516 517 fragment foo on User { 518 name 519 } 520 521 fragment bar on User { 522 age 523 ...baz 524 } 525 526 fragment baz on User { 527 args 528 } 529 `; 530 auto l = Lexer(s); 531 auto p = Parser(l); 532 auto d = p.parseDocument(); 533 534 const f = findFragment(d, "foo", ["User"]); 535 assert(f !is null); 536 537 const f2 = findFragment(d, "bar", ["User"]); 538 assert(f2 !is null); 539 540 FieldRange r = fieldRange(d.defs.def.op, d, ["User"]); 541 assert(!r.empty); 542 assert(r.front.name == "user"); 543 } 544 545 unittest { 546 string s = `{ 547 user(id: 1) { 548 hello 549 ...foo 550 zzzz 551 ...bar 552 } 553 } 554 555 fragment foo on User { 556 name 557 } 558 559 fragment bar on User { 560 age 561 ...baz 562 } 563 564 fragment baz on User { 565 args 566 } 567 `; 568 auto l = Lexer(s); 569 auto p = Parser(l); 570 auto d = p.parseDocument(); 571 572 const f = findFragment(d, "foo", ["User"]); 573 assert(f !is null); 574 575 const f2 = findFragment(d, "bar", ["User"]); 576 assert(f2 !is null); 577 578 FieldRange r = fieldRange(d.defs.def.op, d, ["User"]); 579 assert(!r.empty); 580 assert(r.front.name == "user"); 581 } 582 583 unittest { 584 import std.format : format; 585 import std.stdio; 586 587 string s = `{ 588 user(id: 1) { 589 hello 590 ...foo 591 zzzz 592 ...bar 593 } 594 } 595 596 fragment foo on User { 597 name 598 } 599 600 fragment bar on User { 601 age 602 ...baz 603 } 604 605 fragment baz on User { 606 args 607 } 608 `; 609 auto l = Lexer(s); 610 auto p = Parser(l); 611 auto d = p.parseDocument(); 612 613 immutable auto nn = ["hello", "name", "zzzz", "age", "args"]; 614 size_t cnt = 0; 615 foreach(it; opDefRange(d)) { 616 ++cnt; 617 foreach(jt; it.fieldRange(["User"])) { 618 } 619 } 620 assert(cnt == 1); 621 }