1 module graphql.helper; 2 3 import std.array : empty; 4 import std.algorithm.iteration : each, splitter; 5 import std.algorithm.searching : startsWith, endsWith, canFind; 6 import std.conv : to; 7 import std.datetime : DateTime, Date; 8 import std.exception : enforce, assertThrown; 9 import std.experimental.logger; 10 import std.format : format; 11 import std.stdio; 12 import std..string : capitalize, indexOf, strip; 13 import std.typecons : nullable, Nullable; 14 15 import vibe.data.json; 16 17 import graphql.ast; 18 import graphql.uda; 19 import graphql.constants; 20 import graphql.exception; 21 22 @safe: 23 24 /** dmd and ldc have problems with generation all functions 25 This functions call functions that were undefined. 26 */ 27 private void undefinedFunctions() @trusted { 28 static import core.internal.hash; 29 static import graphql.schema.introspectiontypes; 30 31 const(graphql.schema.introspectiontypes.__Type)[] tmp; 32 core.internal.hash.hashOf!(const(graphql.schema.introspectiontypes.__Type)[]) 33 (tmp, 0); 34 } 35 36 enum d = "data"; 37 enum e = Constants.errors; 38 39 string firstCharUpperCase(string input) { 40 import std.conv : to; 41 import std.uni : isUpper, toUpper; 42 import std.array : front, popFront; 43 if(isUpper(input.front)) { 44 return input; 45 } 46 47 const f = input.front; 48 input.popFront(); 49 50 return to!string(toUpper(f)) ~ input; 51 } 52 53 Json returnTemplate() { 54 Json ret = Json.emptyObject(); 55 ret["data"] = Json.emptyObject(); 56 ret[Constants.errors] = Json.emptyArray(); 57 return ret; 58 } 59 60 void insertError(T)(ref Json result, T t) { 61 insertError(result, t, []); 62 } 63 64 void insertError(T)(ref Json result, T t, PathElement[] path) { 65 Json tmp = Json.emptyObject(); 66 tmp["message"] = serializeToJson(t); 67 if(!path.empty) { 68 tmp["path"] = Json.emptyArray(); 69 foreach(it; path) { 70 tmp["path"] ~= it.toJson(); 71 } 72 } 73 if(e !in result) { 74 result[e] = Json.emptyArray(); 75 } 76 enforce(result[e].type == Json.Type.array); 77 result[e] ~= tmp; 78 } 79 80 void insertPayload(ref Json result, string field, Json data) { 81 if(d in data) { 82 if(d !in result) { 83 result[d] = Json.emptyObject(); 84 } 85 enforce(result[d].type == Json.Type.object); 86 Json* df = field in result[d]; 87 if(df) { 88 result[d][field] = joinJson(*df, data[d]); 89 } else { 90 result[d][field] = data[d]; 91 } 92 } 93 if(e in data) { 94 if(e !in result) { 95 result[e] = Json.emptyArray(); 96 } 97 enforce(result[e].type == Json.Type.array); 98 if(!canFind(result[e].byValue(), data[e])) { 99 result[e] ~= data[e]; 100 } 101 } 102 } 103 104 unittest { 105 Json old = returnTemplate(); 106 old["data"]["foo"] = Json.emptyObject(); 107 old["data"]["foo"]["a"] = 1337; 108 109 Json n = returnTemplate(); 110 n["data"] = Json.emptyObject(); 111 n["data"]["b"] = 1338; 112 113 old.insertPayload("foo", n); 114 assert(old["data"]["foo"].length == 2, format("%s %s", 115 old["data"]["foo"].length, old.toPrettyString()) 116 ); 117 assert("a" in old["data"]["foo"]); 118 assert("b" in old["data"]["foo"]); 119 } 120 121 bool isScalar(ref const(Json) data) { 122 return data.type == Json.Type.bigInt 123 || data.type == Json.Type.bool_ 124 || data.type == Json.Type.float_ 125 || data.type == Json.Type.int_ 126 || data.type == Json.Type..string; 127 } 128 129 bool dataIsEmpty(ref const(Json) data) { 130 import std.experimental.logger; 131 if(data.type == Json.Type.object) { 132 foreach(key, value; data.byKeyValue()) { 133 if(key != Constants.errors && !value.dataIsEmpty()) { 134 //if(key != Constants.errors) { // Issue #22 place to look at 135 return false; 136 } 137 } 138 return true; 139 } else if(data.type == Json.Type.null_ 140 || data.type == Json.Type.undefined 141 ) 142 { 143 return true; 144 } else if(data.type == Json.Type.array) { 145 return data.length == 0; 146 } else if(data.type == Json.Type.bigInt 147 || data.type == Json.Type.bool_ 148 || data.type == Json.Type.float_ 149 || data.type == Json.Type.int_ 150 || data.type == Json.Type..string 151 ) 152 { 153 return false; 154 } 155 156 return true; 157 } 158 159 unittest { 160 string t = `{ "errors" : {} }`; 161 Json j = parseJsonString(t); 162 assert(j.dataIsEmpty()); 163 } 164 165 unittest { 166 string t = `{ "kind": {}, "fields": null, "name": {} }`; 167 Json j = parseJsonString(t); 168 //assert(!j.dataIsEmpty()); // Enable if you don't want to trim. Issue #22 169 assert(j.dataIsEmpty()); 170 } 171 172 unittest { 173 string t = 174 `{ 175 "name" : { 176 "foo" : null 177 } 178 }`; 179 Json j = parseJsonString(t); 180 //assert(!j.dataIsEmpty()); // Enable if you don't want to trim. Issue #22 181 assert(j.dataIsEmpty()); 182 } 183 184 bool dataIsNull(ref const(Json) data) { 185 import std.format : format; 186 enforce(data.type == Json.Type.object, format("%s", data)); 187 if(const(Json)* d = "data" in data) { 188 return d.type == Json.Type.null_; 189 } 190 return false; 191 } 192 193 Json getWithPath(Json input, string path) { 194 auto sp = path.splitter("."); 195 foreach(s; sp) { 196 Json* n = s in input; 197 enforce(n !is null, "failed to traverse the input at " ~ s); 198 input = *n; 199 } 200 return input; 201 } 202 203 unittest { 204 string t = 205 `{ 206 "name" : { 207 "foo" : 13 208 } 209 }`; 210 Json j = parseJsonString(t); 211 Json f = j.getWithPath("name"); 212 assert("foo" in f); 213 214 f = j.getWithPath("name.foo"); 215 enforce(f.to!int() == 13); 216 217 assertThrown(j.getWithPath("doesnotexist")); 218 assertThrown(j.getWithPath("name.alsoNotThere")); 219 } 220 221 enum JoinJsonPrecedence { 222 none, 223 a, 224 b 225 } 226 227 /** Merge two Json objects. 228 Values in a take precedence over values in b. 229 */ 230 Json joinJson(JoinJsonPrecedence jjp = JoinJsonPrecedence.none)(Json a, Json b) 231 { 232 // we can not merge null or undefined values 233 if(a.type == Json.Type.null_ || a.type == Json.Type.undefined) { 234 return b; 235 } 236 if(b.type == Json.Type.null_ || b.type == Json.Type.undefined) { 237 return a; 238 } 239 240 // we need objects to merge 241 if(a.type == Json.Type.object && b.type == Json.Type.object) { 242 Json ret = a.clone(); 243 foreach(key, value; b.byKeyValue()) { 244 Json* ap = key in ret; 245 if(ap is null) { 246 ret[key] = value; 247 } else if(ap.type == Json.Type.object 248 && value.type == Json.Type.object) 249 { 250 ret[key] = joinJson(*ap, value); 251 } else { 252 static if(jjp == JoinJsonPrecedence.none) { 253 throw new Exception(format( 254 "Can not join '%s' and '%s' on key '%s'", 255 ap.type, value.type, key)); 256 } else static if(jjp == JoinJsonPrecedence.a) { 257 } else { 258 ret[key] = value; 259 } 260 } 261 } 262 return ret; 263 } 264 return a; 265 } 266 267 unittest { 268 Json a = parseJsonString(`{"overSize":200}`); 269 Json b = parseJsonString(`{}`); 270 const c = joinJson(b, a); 271 assert(c == a); 272 273 b = parseJsonString(`{"underSize":-100}`); 274 const d = joinJson(b, a); 275 immutable Json r = parseJsonString(`{"overSize":200, "underSize":-100}`); 276 assert(d == r); 277 } 278 279 unittest { 280 Json j = joinJson(parseJsonString(`{"underSize": {"a": -100}}`), 281 parseJsonString(`{"underSize": {"b": 100}}`) 282 ); 283 284 Json r = parseJsonString(`{"underSize": {"a": -100, "b": 100}}`); 285 assert(j == r, format("%s\n\n%s", j.toPrettyString(), r.toPrettyString())); 286 } 287 288 unittest { 289 assertThrown(joinJson(parseJsonString(`{"underSize": {"a": -100}}`), 290 parseJsonString(`{"underSize": {"a": 100}}`) 291 )); 292 } 293 294 unittest { 295 assertThrown(joinJson(parseJsonString(`{"underSize": -100}`), 296 parseJsonString(`{"underSize": {"a": 100}}`) 297 )); 298 } 299 300 template toType(T) { 301 import std.bigint : BigInt; 302 import std.traits : isArray, isIntegral, isAggregateType, isFloatingPoint, 303 isSomeString; 304 static if(is(T == bool)) { 305 enum toType = Json.Type.bool_; 306 } else static if(isIntegral!(T)) { 307 enum toType = Json.Type.int_; 308 } else static if(isFloatingPoint!(T)) { 309 enum toType = Json.Type.float_; 310 } else static if(isSomeString!(T)) { 311 enum toType = Json.Type..string; 312 } else static if(isArray!(T)) { 313 enum toType = Json.Type.array; 314 } else static if(isAggregateType!(T)) { 315 enum toType = Json.Type.object; 316 } else static if(is(T == BigInt)) { 317 enum toType = Json.Type.bigint; 318 } else { 319 enum toType = Json.Type.undefined; 320 } 321 } 322 323 bool hasPathTo(T)(Json data, string path, ref T ret) { 324 enum TT = toType!T; 325 auto sp = path.splitter("."); 326 string f; 327 while(!sp.empty) { 328 f = sp.front; 329 sp.popFront(); 330 if(data.type != Json.Type.object || f !in data) { 331 return false; 332 } else { 333 data = data[f]; 334 } 335 } 336 static if(is(T == Json)) { 337 ret = data; 338 return true; 339 } else { 340 if(data.type == TT) { 341 ret = data.to!T(); 342 return true; 343 } 344 return false; 345 } 346 } 347 348 unittest { 349 Json d = parseJsonString(`{ "foo" : { "path" : "foo" } }`); 350 Json ret; 351 assert(hasPathTo!Json(d, "foo", ret)); 352 assert("path" in ret); 353 assert(ret["path"].type == Json.Type..string); 354 assert(ret["path"].get!string() == "foo"); 355 } 356 357 /** 358 params: 359 path = A "." seperated path 360 */ 361 T getWithDefault(T)(Json data, string[] paths...) { 362 enum TT = toType!T; 363 T ret = T.init; 364 foreach(string path; paths) { 365 if(hasPathTo!T(data, path, ret)) { 366 return ret; 367 } 368 } 369 return ret; 370 } 371 372 unittest { 373 Json d = parseJsonString(`{"errors":[],"data":{"commanderId":8, 374 "__typename":"Starship","series":["DeepSpaceNine", 375 "TheOriginalSeries"],"id":43,"name":"Defiant","size":130, 376 "crewIds":[9,10,11,1,12,13,8],"designation":"NX-74205"}}`); 377 const r = d.getWithDefault!string("data.__typename"); 378 assert(r == "Starship", r); 379 } 380 381 unittest { 382 Json d = parseJsonString(`{"commanderId":8, 383 "__typename":"Starship","series":["DeepSpaceNine", 384 "TheOriginalSeries"],"id":43,"name":"Defiant","size":130, 385 "crewIds":[9,10,11,1,12,13,8],"designation":"NX-74205"}`); 386 const r = d.getWithDefault!string("data.__typename", "__typename"); 387 assert(r == "Starship", r); 388 } 389 390 unittest { 391 Json d = parseJsonString(`{"commanderId":8, 392 "__typename":"Starship","series":["DeepSpaceNine", 393 "TheOriginalSeries"],"id":43,"name":"Defiant","size":130, 394 "crewIds":[9,10,11,1,12,13,8],"designation":"NX-74205"}`); 395 const r = d.getWithDefault!string("__typename"); 396 assert(r == "Starship", r); 397 } 398 399 // TODO should return ref 400 auto accessNN(string[] tokens,T)(T tmp0) { 401 import std.array : back; 402 import std.format : format; 403 if(tmp0 !is null) { 404 static foreach(idx, token; tokens) { 405 mixin(format( 406 `if(tmp%d is null) return null; 407 auto tmp%d = tmp%d.%s;`, idx, idx+1, idx, token) 408 ); 409 } 410 return mixin(format("tmp%d", tokens.length)); 411 } 412 return null; 413 } 414 415 unittest { 416 class A { 417 int a; 418 } 419 420 class B { 421 A a; 422 } 423 424 class C { 425 B b; 426 } 427 428 auto c1 = new C; 429 assert(c1.accessNN!(["b", "a"]) is null); 430 431 c1.b = new B; 432 assert(c1.accessNN!(["b"]) !is null); 433 434 assert(c1.accessNN!(["b", "a"]) is null); 435 // TODO not sure why this is not a lvalue 436 //c1.accessNN!(["b", "a"]) = new A; 437 c1.b.a = new A; 438 assert(c1.accessNN!(["b", "a"]) !is null); 439 } 440 441 T jsonTo(T)(Json item) { 442 static import std.conv; 443 static if(is(T == enum)) { 444 enforce!GQLDExecutionException(item.type == Json.Type..string, 445 format("Enum '%s' must be passed as string not '%s'", 446 T.stringof, item.type)); 447 448 string s = item.to!string(); 449 try { 450 return std.conv.to!T(s); 451 } catch(Exception c) { 452 throw new GQLDExecutionException(c.msg); 453 } 454 } else static if(is(T == GQLDCustomLeaf!Fs, Fs...)) { 455 enforce!GQLDExecutionException(item.type == Json.Type..string, 456 format("%1$s '%1$s' must be passed as string not '%2$s'", 457 T.stringof, item.type)); 458 459 string s = item.to!string(); 460 try { 461 return T(Fs[2](s)); 462 } catch(Exception c) { 463 throw new GQLDExecutionException(c.msg); 464 } 465 } else { 466 try { 467 return item.to!T(); 468 } catch(Exception c) { 469 throw new GQLDExecutionException(c.msg); 470 } 471 } 472 } 473 474 T extract(T)(Json data, string name) { 475 enforce!GQLDExecutionException(data.type == Json.Type.object, format( 476 "Trying to get a '%s' by name '%s' but passed Json is not an object" 477 , T.stringof, name) 478 ); 479 480 Json* item = name in data; 481 482 enforce!GQLDExecutionException(item !is null, format( 483 "Trying to get a '%s' by name '%s' which is not present in passed " 484 ~ "object '%s'" 485 , T.stringof, name, data) 486 ); 487 488 return jsonTo!(T)(*item); 489 } 490 491 unittest { 492 import std.exception : assertThrown; 493 Json j = parseJsonString(`null`); 494 assertThrown(j.extract!string("Hello")); 495 } 496 497 unittest { 498 enum E { 499 no, 500 yes 501 } 502 import std.exception : assertThrown; 503 Json j = parseJsonString(`{ "foo": 1337 }`); 504 505 assertThrown(j.extract!E("foo")); 506 507 j = parseJsonString(`{ "foo": "str" }`); 508 assertThrown(j.extract!E("foo")); 509 510 j = parseJsonString(`{ "foo": "yes" }`); 511 assert(j.extract!E("foo") == E.yes); 512 } 513 514 unittest { 515 import std.exception : assertThrown; 516 Json j = parseJsonString(`{ "foo": 1337 }`); 517 immutable auto foo = j.extract!int("foo"); 518 519 assertThrown(Json.emptyObject().extract!float("Hello")); 520 assertThrown(j.extract!string("Hello")); 521 } 522 523 unittest { 524 import std.exception : assertThrown; 525 enum FooEn { 526 a, 527 b 528 } 529 Json j = parseJsonString(`{ "foo": "a" }`); 530 immutable auto foo = j.extract!FooEn("foo"); 531 assert(foo == FooEn.a); 532 533 assertThrown(Json.emptyObject().extract!float("Hello")); 534 assertThrown(j.extract!string("Hello")); 535 assert(j["foo"].jsonTo!FooEn() == FooEn.a); 536 537 Json k = parseJsonString(`{ "foo": "b" }`); 538 assert(k["foo"].jsonTo!FooEn() == FooEn.b); 539 } 540 541 const(Document) lexAndParse(string s) { 542 import graphql.lexer; 543 import graphql.parser; 544 auto l = Lexer(s, QueryParser.no); 545 auto p = Parser(l); 546 const(Document) doc = p.parseDocument(); 547 return doc; 548 } 549 550 struct StringTypeStrip { 551 string input; 552 string str; 553 bool outerNotNull; 554 bool arr; 555 bool innerNotNull; 556 557 string toString() const { 558 import std.format : format; 559 return format("StringTypeStrip(input:'%s', str:'%s', " 560 ~ "arr:'%s', outerNotNull:'%s', innerNotNull:'%s')", 561 this.input, this.str, this.arr, this.outerNotNull, 562 this.innerNotNull); 563 } 564 } 565 566 StringTypeStrip stringTypeStrip(string str) { 567 Nullable!StringTypeStrip gqld = gqldStringTypeStrip(str); 568 return gqld.get(); 569 //return gqld.isNull() 570 // ? dlangStringTypeStrip(str) 571 // : gqld.get(); 572 } 573 574 private Nullable!StringTypeStrip gqldStringTypeStrip(string str) { 575 StringTypeStrip ret; 576 ret.input = str; 577 immutable string old = str; 578 bool firstBang; 579 if(str.endsWith('!')) { 580 firstBang = true; 581 str = str[0 .. $ - 1]; 582 } 583 584 bool arr; 585 if(str.startsWith('[') && str.endsWith(']')) { 586 arr = true; 587 str = str[1 .. $ - 1]; 588 } 589 590 bool secondBang; 591 if(str.endsWith('!')) { 592 secondBang = true; 593 str = str[0 .. $ - 1]; 594 } 595 596 if(arr) { 597 ret.innerNotNull = secondBang; 598 ret.outerNotNull = firstBang; 599 } else { 600 ret.innerNotNull = firstBang; 601 } 602 603 str = canFind(["ubyte", "byte", "ushort", "short", "long", "ulong"], str) 604 ? "Int" 605 : str; 606 607 str = canFind(["string", "int", "float", "bool"], str) 608 ? capitalize(str) 609 : str; 610 611 str = str == "__type" ? "__Type" : str; 612 str = str == "__schema" ? "__Schema" : str; 613 str = str == "__inputvalue" ? "__InputValue" : str; 614 str = str == "__directive" ? "__Directive" : str; 615 str = str == "__field" ? "__Field" : str; 616 617 ret.arr = arr; 618 619 ret.str = str; 620 //writefln("%s %s", __LINE__, ret); 621 622 //return old == str ? Nullable!(StringTypeStrip).init : nullable(ret); 623 return nullable(ret); 624 } 625 626 unittest { 627 auto a = gqldStringTypeStrip("String"); 628 assert(!a.isNull()); 629 630 a = gqldStringTypeStrip("String!"); 631 assert(!a.isNull()); 632 assert(a.get().str == "String"); 633 assert(a.get().innerNotNull, format("%s", a.get())); 634 635 a = gqldStringTypeStrip("[String!]"); 636 assert(!a.isNull()); 637 assert(a.get().str == "String"); 638 assert(a.get().arr, format("%s", a.get())); 639 assert(a.get().innerNotNull, format("%s", a.get())); 640 641 a = gqldStringTypeStrip("[String]!"); 642 assert(!a.isNull()); 643 assert(a.get().str == "String"); 644 assert(a.get().arr, format("%s", a.get())); 645 assert(!a.get().innerNotNull, format("%s", a.get())); 646 assert(a.get().outerNotNull, format("%s", a.get())); 647 648 a = gqldStringTypeStrip("[String!]!"); 649 assert(!a.isNull()); 650 assert(a.get().str == "String"); 651 assert(a.get().arr, format("%s", a.get())); 652 assert(a.get().innerNotNull, format("%s", a.get())); 653 assert(a.get().outerNotNull, format("%s", a.get())); 654 } 655 656 private StringTypeStrip dlangStringTypeStrip(string str) { 657 StringTypeStrip ret; 658 ret.outerNotNull = true; 659 ret.innerNotNull = true; 660 ret.input = str; 661 662 immutable ns = "NullableStore!"; 663 immutable ns1 = "NullableStore!("; 664 immutable leaf = "GQLDCustomLeaf!"; 665 immutable leaf1 = "GQLDCustomLeaf!("; 666 immutable nll = "Nullable!"; 667 immutable nll1 = "Nullable!("; 668 669 // NullableStore!( .... ) 670 if(str.startsWith(ns1) && str.endsWith(")")) { 671 str = str[ns1.length .. $ - 1]; 672 } 673 674 // NullableStore!.... 675 if(str.startsWith(ns)) { 676 str = str[ns.length .. $]; 677 } 678 679 // GQLDCustomLeaf!( .... ) 680 if(str.startsWith(leaf1) && str.endsWith(")")) { 681 str = str[leaf1.length .. $ - 1]; 682 } 683 684 bool firstNull; 685 686 // Nullable!( .... ) 687 if(str.startsWith(nll1) && str.endsWith(")")) { 688 firstNull = true; 689 str = str[nll1.length .. $ - 1]; 690 } 691 692 // NullableStore!( .... ) 693 if(str.startsWith(ns1) && str.endsWith(")")) { 694 str = str[ns1.length .. $ - 1]; 695 } 696 697 // NullableStore!.... 698 if(str.startsWith(ns)) { 699 str = str[ns.length .. $]; 700 } 701 702 if(str.endsWith("!")) { 703 str = str[0 .. $ - 1]; 704 } 705 706 // xxxxxxx[] 707 if(str.endsWith("[]")) { 708 ret.arr = true; 709 str = str[0 .. $ - 2]; 710 } 711 712 bool secondNull; 713 714 // Nullable!( .... ) 715 if(str.startsWith(nll1) && str.endsWith(")")) { 716 secondNull = true; 717 str = str[nll1.length .. $ - 1]; 718 } 719 720 if(str.endsWith("!")) { 721 str = str[0 .. $ - 1]; 722 } 723 724 // Nullable! .... 725 if(str.startsWith(nll)) { 726 secondNull = true; 727 str = str[nll.length .. $]; 728 } 729 730 // NullableStore!( .... ) 731 if(str.startsWith(ns1) && str.endsWith(")")) { 732 str = str[ns1.length .. $ - 1]; 733 } 734 735 // NullableStore!.... 736 if(str.startsWith(ns)) { 737 str = str[ns.length .. $]; 738 } 739 740 str = canFind(["ubyte", "byte", "ushort", "short", "long", "ulong"], str) 741 ? "Int" 742 : str; 743 744 str = canFind(["string", "int", "float", "bool"], str) 745 ? capitalize(str) 746 : str; 747 748 str = str == "__type" ? "__Type" : str; 749 str = str == "__schema" ? "__Schema" : str; 750 str = str == "__inputvalue" ? "__InputValue" : str; 751 str = str == "__directive" ? "__Directive" : str; 752 str = str == "__field" ? "__Field" : str; 753 754 //writefln("firstNull %s, secondNull %s, arr %s", firstNull, secondNull, 755 // ret.arr); 756 757 if(ret.arr) { 758 ret.innerNotNull = !secondNull; 759 ret.outerNotNull = !firstNull; 760 } else { 761 ret.innerNotNull = !secondNull; 762 } 763 764 ret.str = str; 765 return ret; 766 } 767 768 unittest { 769 string t = "Nullable!string"; 770 StringTypeStrip r = t.dlangStringTypeStrip(); 771 assert(r.str == "String", to!string(r)); 772 assert(!r.arr, to!string(r)); 773 assert(!r.innerNotNull, to!string(r)); 774 assert(r.outerNotNull, to!string(r)); 775 776 t = "Nullable!(string[])"; 777 r = t.dlangStringTypeStrip(); 778 assert(r.str == "String", to!string(r)); 779 assert(r.arr, to!string(r)); 780 assert(r.innerNotNull, to!string(r)); 781 assert(!r.outerNotNull, to!string(r)); 782 } 783 784 unittest { 785 string t = "Nullable!__type"; 786 StringTypeStrip r = t.dlangStringTypeStrip(); 787 assert(r.str == "__Type", to!string(r)); 788 assert(!r.innerNotNull, to!string(r)); 789 assert(r.outerNotNull, to!string(r)); 790 assert(!r.arr, to!string(r)); 791 792 t = "Nullable!(__type[])"; 793 r = t.dlangStringTypeStrip(); 794 assert(r.str == "__Type", to!string(r)); 795 assert(r.innerNotNull, to!string(r)); 796 assert(!r.outerNotNull, to!string(r)); 797 assert(r.arr, to!string(r)); 798 } 799 800 template isClass(T) { 801 enum isClass = is(T == class); 802 } 803 804 unittest { 805 static assert(!isClass!int); 806 static assert( isClass!Object); 807 } 808 809 template isNotInTypeSet(T, R...) { 810 import std.meta : staticIndexOf; 811 enum isNotInTypeSet = staticIndexOf!(T, R) == -1; 812 } 813 814 string getTypename(Schema,T)(auto ref T input) @trusted { 815 //pragma(msg, T); 816 //writefln("To %s", T.stringof); 817 static if(!isClass!(T)) { 818 return T.stringof; 819 } else { 820 // fetch the typeinfo of the item, and compare it down until we get to a 821 // class we have. If none found, return the name of the type itself. 822 import graphql.reflection; 823 auto tinfo = typeid(input); 824 const auto reflect = SchemaReflection!Schema.instance; 825 while(tinfo !is null) { 826 if(auto cname = tinfo in reflect.classes) { 827 return *cname; 828 } 829 tinfo = tinfo.base; 830 } 831 return T.stringof; 832 } 833 } 834 835 Json toGraphqlJson(Schema,T)(auto ref T input) { 836 import std.array : empty; 837 import std.conv : to; 838 import std.typecons : Nullable; 839 import std.traits : isArray, isAggregateType, isBasicType, isSomeString, 840 isScalarType, isSomeString, FieldNameTuple, FieldTypeTuple; 841 842 import nullablestore; 843 844 static if(isArray!T && !isSomeString!T) { 845 Json ret = Json.emptyArray(); 846 foreach(ref it; input) { 847 ret ~= toGraphqlJson!Schema(it); 848 } 849 return ret; 850 } else static if(is(T : GQLDCustomLeaf!Type, Type...)) { 851 return Json(Type[1](input)); 852 } else static if(is(T : Nullable!Type, Type)) { 853 return input.isNull() ? Json(null) : toGraphqlJson!Schema(input.get()); 854 } else static if(is(T == enum)) { 855 return Json(to!string(input)); 856 } else static if(isBasicType!T || isScalarType!T || isSomeString!T) { 857 return serializeToJson(input); 858 } else static if(isAggregateType!T) { 859 Json ret = Json.emptyObject(); 860 861 // the important bit is the setting of the __typename field 862 ret["__typename"] = getTypename!(Schema)(input); 863 //writefln("Got %s", ret["__typename"].to!string()); 864 865 alias names = FieldNameTuple!(T); 866 alias types = FieldTypeTuple!(T); 867 static foreach(idx; 0 .. names.length) {{ 868 static if(!names[idx].empty) { 869 static if(is(types[idx] : NullableStore!Type, Type)) { 870 } else static if(is(types[idx] == enum)) { 871 ret[names[idx]] = 872 to!string(__traits(getMember, input, names[idx])); 873 } else { 874 ret[names[idx]] = toGraphqlJson!Schema( 875 __traits(getMember, input, names[idx]) 876 ); 877 } 878 } 879 }} 880 return ret; 881 } else { 882 static assert(false, T.stringof ~ " not supported"); 883 } 884 } 885 886 string dtToString(DateTime dt) { 887 return dt.toISOExtString(); 888 } 889 890 DateTime stringToDT(string s) { 891 return DateTime.fromISOExtString(s); 892 } 893 894 string dToString(Date dt) { 895 return dt.toISOExtString(); 896 } 897 898 unittest { 899 import std.typecons : nullable, Nullable; 900 import nullablestore; 901 902 struct Foo { 903 int a; 904 Nullable!int b; 905 NullableStore!float c; 906 GQLDCustomLeaf!(DateTime, dtToString, stringToDT) dt2; 907 Nullable!(GQLDCustomLeaf!(DateTime, dtToString, stringToDT)) dt; 908 } 909 910 DateTime dt = DateTime(1337, 7, 1, 1, 1, 1); 911 DateTime dt2 = DateTime(2337, 7, 1, 1, 1, 3); 912 913 alias DT = GQLDCustomLeaf!(DateTime, dtToString, stringToDT); 914 915 Foo foo; 916 foo.dt2 = DT(dt2); 917 foo.dt = nullable(DT(dt)); 918 Json j = toGraphqlJson!int(foo); 919 assert(j["a"].to!int() == 0); 920 assert(j["b"].type == Json.Type.null_); 921 assert(j["dt"].type == Json.Type..string, format("%s\n%s", j["dt"].type, 922 j.toPrettyString() 923 ) 924 ); 925 immutable string exp = j["dt"].to!string(); 926 assert(exp == "1337-07-01T01:01:01", exp); 927 immutable string exp2 = j["dt2"].to!string(); 928 assert(exp2 == "2337-07-01T01:01:03", exp2); 929 930 immutable DT back = extract!DT(j, "dt"); 931 assert(back.value == dt); 932 933 immutable DT back2 = extract!DT(j, "dt2"); 934 assert(back2.value == dt2); 935 } 936 937 struct PathElement { 938 string str; 939 size_t idx; 940 941 static PathElement opCall(string s) { 942 PathElement ret; 943 ret.str = s; 944 return ret; 945 } 946 947 static PathElement opCall(size_t s) { 948 PathElement ret; 949 ret.idx = s; 950 return ret; 951 } 952 953 Json toJson() { 954 return this.str.empty ? Json(this.idx) : Json(this.str); 955 } 956 }