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