1 /++ 2 FGHJ Representation 3 4 Copyright: Tamedia Digital, 2016 5 6 Authors: Ilya Yaroshenko 7 8 License: MIT 9 10 Macros: 11 SUBMODULE = $(LINK2 fghj_$1.html, fghj.$1) 12 SUBREF = $(LINK2 fghj_$1.html#.$2, $(TT $2))$(NBSP) 13 T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) 14 T4=$(TR $(TDNW $(LREF $1)) $(TD $2) $(TD $3) $(TD $4)) 15 +/ 16 module fghj.fghj; 17 18 import std.exception; 19 import std.range.primitives; 20 import std.typecons; 21 import std.traits; 22 23 import fghj.jsonbuffer; 24 import fghj.jsonparser: assumePure; 25 26 version(X86_64) 27 version = X86_Any; 28 else 29 version(X86) 30 version = X86_Any; 31 32 version (D_Exceptions) 33 { 34 import mir.serde: SerdeException; 35 /++ 36 Serde Exception 37 +/ 38 class FghjSerdeException : SerdeException 39 { 40 /// zero based faulty location 41 size_t location; 42 43 /// 44 this( 45 string msg, 46 size_t location, 47 string file = __FILE__, 48 size_t line = __LINE__, 49 ) pure nothrow @nogc @safe 50 { 51 this.location = location; 52 super(msg, file, line); 53 } 54 55 /// 56 this( 57 string msg, 58 string file = __FILE__, 59 size_t line = __LINE__, 60 Throwable next = null) pure nothrow @nogc @safe 61 { 62 super(msg, file, line, next); 63 } 64 65 /// 66 this( 67 string msg, 68 Throwable next, 69 string file = __FILE__, 70 size_t line = __LINE__, 71 ) pure nothrow @nogc @safe 72 { 73 this(msg, file, line, next); 74 } 75 76 override FghjSerdeException toMutable() @trusted pure nothrow @nogc const 77 { 78 return cast() this; 79 } 80 81 alias toMutable this; 82 } 83 } 84 85 deprecated("use mir.serde: SerdeException instead") 86 alias FghjException = SerdeException; 87 88 /// 89 class InvalidFghjException: SerdeException 90 { 91 /// 92 this( 93 uint kind, 94 string file = __FILE__, 95 size_t line = __LINE__, 96 Throwable next = null) pure nothrow @safe 97 { 98 import mir.format: text; 99 super(text("FGHJ values is invalid for kind = ", kind), file, line, next); 100 } 101 102 /// 103 this( 104 uint kind, 105 Throwable next, 106 string file = __FILE__, 107 size_t line = __LINE__, 108 ) pure nothrow @safe 109 { 110 this(kind, file, line, next); 111 } 112 } 113 114 private void enforceValidFghj( 115 bool condition, 116 uint kind, 117 string file = __FILE__, 118 size_t line = __LINE__) @safe pure 119 { 120 if(!condition) 121 throw new InvalidFghjException(kind, file, line); 122 } 123 124 /// 125 class EmptyFghjException: SerdeException 126 { 127 /// 128 this( 129 string msg = "FGHJ value is empty", 130 string file = __FILE__, 131 size_t line = __LINE__, 132 Throwable next = null) pure nothrow @nogc @safe 133 { 134 super(msg, file, line, next); 135 } 136 } 137 138 /++ 139 The structure for FGHJ manipulation. 140 +/ 141 struct Fghj 142 { 143 /// 144 enum Kind : ubyte 145 { 146 /// 147 null_ = 0x00, 148 /// 149 true_ = 0x01, 150 /// 151 false_ = 0x02, 152 /// 153 number = 0x03, 154 /// 155 string = 0x05, 156 /// 157 array = 0x09, 158 /// 159 object = 0x0A, 160 } 161 162 /// Returns FGHJ Kind 163 ubyte kind() const pure @safe @nogc 164 { 165 if (!data.length) 166 { 167 static immutable exc = new EmptyFghjException; 168 throw exc; 169 } 170 return data[0]; 171 } 172 173 /++ 174 Plain FGHJ data. 175 +/ 176 ubyte[] data; 177 178 /// Creates FGHJ using already allocated data 179 this(ubyte[] data) pure @safe nothrow @nogc 180 { 181 this.data = data; 182 } 183 184 /// Creates FGHJ from a string 185 this(in char[] str) pure @safe 186 { 187 data = new ubyte[str.length + 5]; 188 data[0] = Kind..string; 189 length4 = str.length; 190 data[5 .. $] = cast(const(ubyte)[])str; 191 } 192 193 /// 194 unittest 195 { 196 assert(Fghj("string") == "string"); 197 assert(Fghj("string") != "String"); 198 } 199 200 // \uXXXX character support 201 unittest 202 { 203 import mir.conv: to; 204 import fghj.jsonparser; 205 assert(Fghj("begin\u000bend").to!string == `"begin\u000Bend"`); 206 assert("begin\u000bend" == cast(string) `"begin\u000Bend"`.parseJson, to!string(cast(ubyte[]) cast(string)( `"begin\u000Bend"`.parseJson))); 207 } 208 209 /// Sets deleted bit on 210 void remove() pure @safe nothrow @nogc 211 { 212 if(data.length) 213 data[0] |= 0x80; 214 } 215 216 /// 217 unittest 218 { 219 import mir.conv: to; 220 import fghj.jsonparser; 221 auto fghjData = `{"foo":"bar","inner":{"a":true,"b":false,"c":"32323","d":null,"e":{}}}`.parseJson; 222 fghjData["inner", "d"].remove; 223 assert(fghjData.to!string == `{"foo":"bar","inner":{"a":true,"b":false,"c":"32323","e":{}}}`); 224 } 225 226 /// 227 void toString(Dg)(scope Dg sink) const 228 { 229 scope buffer = JsonBuffer!Dg(sink); 230 toStringImpl(buffer); 231 buffer.flush; 232 } 233 234 /+ 235 Internal recursive toString implementation. 236 Params: 237 sink = output range that accepts `char`, `in char[]` and compile time string `(string str)()` 238 +/ 239 private void toStringImpl(Dg)(ref JsonBuffer!Dg sink) const 240 { 241 if (!data.length) 242 { 243 static immutable exc = new EmptyFghjException("Data buffer is empty"); 244 throw exc; 245 } 246 auto t = data[0]; 247 switch(t) 248 { 249 case Kind.null_: 250 enforceValidFghj(data.length == 1, t); 251 sink.put!"null"; 252 break; 253 case Kind.true_: 254 enforceValidFghj(data.length == 1, t); 255 sink.put!"true"; 256 break; 257 case Kind.false_: 258 enforceValidFghj(data.length == 1, t); 259 sink.put!"false"; 260 break; 261 case Kind.number: 262 enforceValidFghj(data.length > 1, t); 263 size_t length = data[1]; 264 enforceValidFghj(data.length == length + 2, t); 265 sink.putSmallEscaped(cast(const(char)[]) data[2 .. $]); 266 break; 267 case Kind..string: 268 enforceValidFghj(data.length >= 5, Kind.object); 269 enforceValidFghj(data.length == length4 + 5, t); 270 sink.put('"'); 271 sink.put(cast(const(char)[]) data[5 .. $]); 272 sink.put('"'); 273 break; 274 case Kind.array: 275 auto elems = Fghj(cast(ubyte[])data).byElement; 276 if(elems.empty) 277 { 278 sink.put!"[]"; 279 break; 280 } 281 sink.put('['); 282 elems.front.toStringImpl(sink); 283 elems.popFront; 284 foreach(e; elems) 285 { 286 sink.put(','); 287 e.toStringImpl(sink); 288 } 289 sink.put(']'); 290 break; 291 case Kind.object: 292 auto pairs = Fghj(cast(ubyte[])data).byKeyValue; 293 if(pairs.empty) 294 { 295 sink.put!"{}"; 296 break; 297 } 298 sink.put!"{\""; 299 sink.put(pairs.front.key); 300 sink.put!"\":"; 301 pairs.front.value.toStringImpl(sink); 302 pairs.popFront; 303 foreach(e; pairs) 304 { 305 sink.put!",\""; 306 sink.put(e.key); 307 sink.put!"\":"; 308 e.value.toStringImpl(sink); 309 } 310 sink.put('}'); 311 break; 312 default: 313 enforceValidFghj(0, t); 314 } 315 } 316 317 /// 318 unittest 319 { 320 import mir.conv: to; 321 import fghj.jsonparser; 322 auto text = `{"foo":"bar","inner":{"a":true,"b":false,"c":"32323","d":null,"e":{}}}`; 323 const fghjData = text.parseJson; 324 assert(fghjData.to!string == text); 325 } 326 327 /++ 328 `==` operator overloads for `null` 329 +/ 330 bool opEquals(in Fghj rhs) const @safe pure nothrow @nogc 331 { 332 return data == rhs.data; 333 } 334 335 /// 336 unittest 337 { 338 import fghj.jsonparser; 339 auto fghjData = `null`.parseJson; 340 assert(fghjData == fghjData); 341 } 342 343 /++ 344 `==` operator overloads for `null` 345 +/ 346 bool opEquals(typeof(null)) const pure @safe nothrow 347 { 348 return data.length == 1 && data[0] == 0; 349 } 350 351 /// 352 unittest 353 { 354 import fghj.jsonparser; 355 auto fghjData = `null`.parseJson; 356 assert(fghjData == null); 357 } 358 359 /++ 360 `==` operator overloads for `bool` 361 +/ 362 bool opEquals(bool boolean) const pure @safe nothrow 363 { 364 return data.length == 1 && (data[0] == Kind.true_ && boolean || data[0] == Kind.false_ && !boolean); 365 } 366 367 /// 368 unittest 369 { 370 import fghj.jsonparser; 371 auto fghjData = `true`.parseJson; 372 assert(fghjData == true); 373 assert(fghjData != false); 374 } 375 376 /++ 377 `==` operator overloads for `string` 378 +/ 379 bool opEquals(in char[] str) const pure @trusted nothrow 380 { 381 return data.length >= 5 && data[0] == Kind..string && data[5 .. 5 + length4] == cast(const(ubyte)[]) str; 382 } 383 384 /// 385 unittest 386 { 387 import fghj.jsonparser; 388 auto fghjData = `"str"`.parseJson; 389 assert(fghjData == "str"); 390 assert(fghjData != "stR"); 391 } 392 393 /++ 394 Returns: 395 input range composed of elements of an array. 396 +/ 397 auto byElement() pure 398 { 399 static struct Range 400 { 401 private ubyte[] _data; 402 private Fghj _front; 403 404 auto save()() pure @property 405 { 406 return this; 407 } 408 409 void popFront() pure 410 { 411 while(!_data.empty) 412 { 413 uint t = cast(ubyte) _data.front; 414 switch(t) 415 { 416 case Kind.null_: 417 case Kind.true_: 418 case Kind.false_: 419 _front = Fghj(_data[0 .. 1]); 420 _data.popFront; 421 return; 422 case Kind.number: 423 enforceValidFghj(_data.length >= 2, t); 424 size_t len = _data[1] + 2; 425 enforceValidFghj(_data.length >= len, t); 426 _front = Fghj(_data[0 .. len]); 427 _data = _data[len .. $]; 428 return; 429 case Kind..string: 430 case Kind.array: 431 case Kind.object: 432 enforceValidFghj(_data.length >= 5, t); 433 size_t len = Fghj(_data).length4 + 5; 434 enforceValidFghj(_data.length >= len, t); 435 _front = Fghj(_data[0 .. len]); 436 _data = _data[len .. $]; 437 return; 438 case 0x80 | Kind.null_: 439 case 0x80 | Kind.true_: 440 case 0x80 | Kind.false_: 441 _data.popFront; 442 continue; 443 case 0x80 | Kind.number: 444 enforceValidFghj(_data.length >= 2, t); 445 _data.popFrontExactly(_data[1] + 2); 446 continue; 447 case 0x80 | Kind..string: 448 case 0x80 | Kind.array: 449 case 0x80 | Kind.object: 450 enforceValidFghj(_data.length >= 5, t); 451 size_t len = Fghj(_data).length4 + 5; 452 _data.popFrontExactly(len); 453 continue; 454 default: 455 enforceValidFghj(0, t); 456 } 457 } 458 _front = Fghj.init; 459 } 460 461 auto front() pure @property 462 { 463 assert(!empty); 464 return _front; 465 } 466 467 bool empty() pure @property 468 { 469 return _front.data.length == 0; 470 } 471 } 472 if(data.empty || data[0] != Kind.array) 473 return Range.init; 474 enforceValidFghj(data.length >= 5, Kind.array); 475 enforceValidFghj(length4 == data.length - 5, Kind.array); 476 auto ret = Range(data[5 .. $]); 477 if(ret._data.length) 478 ret.popFront; 479 return ret; 480 } 481 482 /++ 483 Returns: 484 Input range composed of key-value pairs of an object. 485 Elements are type of `Tuple!(const(char)[], "key", Fghj, "value")`. 486 +/ 487 auto byKeyValue() pure 488 { 489 static struct Range 490 { 491 private ubyte[] _data; 492 private Tuple!(const(char)[], "key", Fghj, "value") _front; 493 494 auto save() pure @property 495 { 496 return this; 497 } 498 499 void popFront() pure 500 { 501 while(!_data.empty) 502 { 503 enforceValidFghj(_data.length > 1, Kind.object); 504 size_t l = cast(ubyte) _data[0]; 505 _data.popFront; 506 enforceValidFghj(_data.length >= l, Kind.object); 507 _front.key = cast(const(char)[])_data[0 .. l]; 508 _data.popFrontExactly(l); 509 uint t = cast(ubyte) _data.front; 510 switch(t) 511 { 512 case Kind.null_: 513 case Kind.true_: 514 case Kind.false_: 515 _front.value = Fghj(_data[0 .. 1]); 516 _data.popFront; 517 return; 518 case Kind.number: 519 enforceValidFghj(_data.length >= 2, t); 520 size_t len = _data[1] + 2; 521 enforceValidFghj(_data.length >= len, t); 522 _front.value = Fghj(_data[0 .. len]); 523 _data = _data[len .. $]; 524 return; 525 case Kind..string: 526 case Kind.array: 527 case Kind.object: 528 enforceValidFghj(_data.length >= 5, t); 529 size_t len = Fghj(_data).length4 + 5; 530 enforceValidFghj(_data.length >= len, t); 531 _front.value = Fghj(_data[0 .. len]); 532 _data = _data[len .. $]; 533 return; 534 case 0x80 | Kind.null_: 535 case 0x80 | Kind.true_: 536 case 0x80 | Kind.false_: 537 _data.popFront; 538 continue; 539 case 0x80 | Kind.number: 540 enforceValidFghj(_data.length >= 2, t); 541 _data.popFrontExactly(_data[1] + 2); 542 continue; 543 case 0x80 | Kind..string: 544 case 0x80 | Kind.array: 545 case 0x80 | Kind.object: 546 enforceValidFghj(_data.length >= 5, t); 547 size_t len = Fghj(_data).length4 + 5; 548 _data.popFrontExactly(len); 549 continue; 550 default: 551 enforceValidFghj(0, t); 552 } 553 } 554 _front = _front.init; 555 } 556 557 auto front() pure @property 558 { 559 assert(!empty); 560 return _front; 561 } 562 563 bool empty() pure @property 564 { 565 return _front.value.data.length == 0; 566 } 567 } 568 if(data.empty || data[0] != Kind.object) 569 return Range.init; 570 enforceValidFghj(data.length >= 5, Kind.object); 571 enforceValidFghj(length4 == data.length - 5, Kind.object); 572 auto ret = Range(data[5 .. $]); 573 if(ret._data.length) 574 ret.popFront; 575 return ret; 576 } 577 578 /// returns 4-byte length 579 private size_t length4() const @property pure nothrow @nogc @trusted 580 { 581 assert(data.length >= 5); 582 version(X86_Any) 583 { 584 return (cast(uint*)(data.ptr + 1))[0]; 585 } 586 else 587 { 588 align(4) auto ret = *cast(ubyte[4]*)(data.ptr + 1); 589 return (cast(uint[1])ret)[0]; 590 } 591 } 592 593 /// ditto 594 private void length4(size_t len) const @property pure nothrow @nogc @trusted 595 { 596 assert(data.length >= 5); 597 assert(len <= uint.max); 598 version(X86_Any) 599 { 600 *(cast(uint*)(data.ptr + 1)) = cast(uint) len; 601 } 602 else 603 { 604 *(cast(ubyte[4]*)(data.ptr + 1)) = cast(ubyte[4]) cast(uint[1]) [cast(uint) len]; 605 } 606 } 607 608 /++ 609 Searches for a value recursively in an FGHJ object. 610 611 Params: 612 keys = list of keys keys 613 Returns 614 FGHJ value if it was found (first win) or FGHJ with empty plain data. 615 +/ 616 Fghj opIndex(in char[][] keys...) pure 617 { 618 auto fghj = this; 619 if(fghj.data.empty) 620 return Fghj.init; 621 L: foreach(key; keys) 622 { 623 if(fghj.data[0] != Fghj.Kind.object) 624 return Fghj.init; 625 foreach(e; fghj.byKeyValue) 626 { 627 if(e.key == key) 628 { 629 fghj = e.value; 630 continue L; 631 } 632 } 633 return Fghj.init; 634 } 635 return fghj; 636 } 637 638 /// 639 unittest 640 { 641 import fghj.jsonparser; 642 auto fghjData = `{"foo":"bar","inner":{"a":true,"b":false,"c":"32323","d":null,"e":{}}}`.parseJson; 643 assert(fghjData["inner", "a"] == true); 644 assert(fghjData["inner", "b"] == false); 645 assert(fghjData["inner", "c"] == "32323"); 646 assert(fghjData["inner", "d"] == null); 647 assert(fghjData["no", "such", "keys"] == Fghj.init); 648 } 649 650 /++ 651 Params: 652 def = default value. It is used when FGHJ value equals `Fghj.init`. 653 Returns: 654 `cast(T) this` if `this != Fghj.init` and `def` otherwise. 655 +/ 656 T get(T)(T def) 657 { 658 if(data.length) 659 { 660 return cast(T) this; 661 } 662 return def; 663 } 664 665 /// 666 unittest 667 { 668 import fghj.jsonparser; 669 auto fghjData = `{"foo":"bar","inner":{"a":true,"b":false,"c":"32323","d":null,"e":{}}}`.parseJson; 670 assert(fghjData["inner", "a"].get(false) == true); 671 assert(fghjData["inner", "b"].get(true) == false); 672 assert(fghjData["inner", "c"].get(100) == 32323); 673 assert(fghjData["no", "such", "keys"].get(100) == 100); 674 } 675 676 /++ 677 `cast` operator overloading. 678 +/ 679 T opCast(T)() 680 { 681 import std.datetime: SysTime, DateTime, usecs, UTC; 682 import std.traits: isNumeric; 683 import mir.conv: to; 684 import std.conv: ConvException; 685 import std.format: format; 686 import std.math: trunc; 687 import fghj.serialization; 688 auto k = kind; 689 with(Kind) switch(kind) 690 { 691 case null_ : 692 static if (isNumeric!T 693 || is(T == interface) 694 || is(T == class) 695 || is(T == E[], E) 696 || is(T == E[K], E, K) 697 || is(T == bool)) 698 return T.init; 699 else goto default; 700 case true_ : 701 static if(__traits(compiles, true.to!T)) 702 return true.to!T; 703 else goto default; 704 case false_: 705 static if(__traits(compiles, false.to!T)) 706 return false.to!T; 707 else goto default; 708 case number: 709 { 710 auto str = cast(const(char)[]) data[2 .. $]; 711 static if(is(T == bool)) 712 return assumePure(() => str.to!double)() != 0; 713 else 714 static if(is(T == SysTime) || is(T == DateTime)) 715 { 716 auto unixTime = assumePure(() => str.to!real)(); 717 auto secsR = assumePure(() => unixTime.trunc)(); 718 auto rem = unixTime - secsR; 719 auto st = SysTime.fromUnixTime(cast(long)(secsR), UTC()); 720 assumePure((ref SysTime st) => st.fracSecs = usecs(cast(long)(rem * 1_000_000)))(st); 721 return assumePure(() => st.to!T)(); 722 } 723 else 724 static if(__traits(compiles, assumePure(() => str.to!T)())) 725 return assumePure(() => str.to!T)(); 726 else goto default; 727 } 728 case string: 729 { 730 auto str = cast(const(char)[]) data[5 .. $]; 731 static if(is(T == bool)) 732 return str != "0" && str != "false" && str != ""; 733 else 734 static if(__traits(compiles, str.to!T)) 735 return str.to!T; 736 else goto default; 737 } 738 static if (isAggregateType!T || isArray!T) 739 { 740 case array : 741 case object: 742 static if(__traits(compiles, {T t = deserialize!T(this);})) 743 return deserialize!T(this); 744 else goto default; 745 } 746 default: 747 throw new ConvException(format("Cannot convert kind %s(\\x%02X) to %s", cast(Kind) k, k, T.stringof)); 748 } 749 } 750 751 /// null 752 unittest 753 { 754 import std.math; 755 import fghj.serialization; 756 auto null_ = serializeToFghj(null); 757 interface I {} 758 class C {} 759 assert(cast(uint[]) null_ is null); 760 assert(cast(uint[uint]) null_ is null); 761 assert(cast(I) null_ is null); 762 assert(cast(C) null_ is null); 763 assert(isNaN(cast(double) null_)); 764 assert(! cast(bool) null_); 765 } 766 767 /// boolean 768 unittest 769 { 770 import std.math; 771 import fghj.serialization; 772 auto true_ = serializeToFghj(true); 773 auto false_ = serializeToFghj(false); 774 static struct C { 775 this(bool){} 776 } 777 auto a = cast(C) true_; 778 auto b = cast(C) false_; 779 assert(cast(bool) true_ == true); 780 assert(cast(bool) false_ == false); 781 assert(cast(uint) true_ == 1); 782 assert(cast(uint) false_ == 0); 783 assert(cast(double) true_ == 1); 784 assert(cast(double) false_ == 0); 785 } 786 787 /// numbers 788 unittest 789 { 790 import std.bigint; 791 import fghj.serialization; 792 auto number = serializeToFghj(1234); 793 auto zero = serializeToFghj(0); 794 static struct C 795 { 796 this(in char[] numberString) 797 { 798 assert(numberString == "1234"); 799 } 800 } 801 auto a = cast(C) number; 802 assert(cast(bool) number == true); 803 assert(cast(bool) zero == false); 804 assert(cast(uint) number == 1234); 805 assert(cast(double) number == 1234); 806 assert(cast(BigInt) number == 1234); 807 assert(cast(uint) zero == 0); 808 assert(cast(double) zero == 0); 809 assert(cast(BigInt) zero == 0); 810 } 811 812 /// string 813 unittest 814 { 815 import std.bigint; 816 import fghj.serialization; 817 auto number = serializeToFghj("1234"); 818 auto false_ = serializeToFghj("false"); 819 auto bar = serializeToFghj("bar"); 820 auto zero = serializeToFghj("0"); 821 static struct C 822 { 823 this(in char[] str) 824 { 825 assert(str == "1234"); 826 } 827 } 828 auto a = cast(C) number; 829 assert(cast(string) number == "1234"); 830 assert(cast(bool) number == true); 831 assert(cast(bool) bar == true); 832 assert(cast(bool) zero == false); 833 assert(cast(bool) false_ == false); 834 assert(cast(uint) number == 1234); 835 assert(cast(double) number == 1234); 836 assert(cast(BigInt) number == 1234); 837 assert(cast(uint) zero == 0); 838 assert(cast(double) zero == 0); 839 assert(cast(BigInt) zero == 0); 840 } 841 842 /++ 843 For FGHJ arrays and objects `cast(T)` just returns `this.deserialize!T`. 844 +/ 845 unittest 846 { 847 import std.bigint; 848 import fghj.serialization; 849 assert(cast(int[]) serializeToFghj([100, 20]) == [100, 20]); 850 } 851 852 /// UNIX Time 853 unittest 854 { 855 import std.datetime; 856 import fghj.serialization; 857 858 auto num = serializeToFghj(0.123456789); // rounding up to usecs 859 assert(cast(DateTime) num == DateTime(1970, 1, 1)); 860 assert(cast(SysTime) num == SysTime(DateTime(1970, 1, 1), usecs(123456), UTC())); // UTC time zone is used. 861 } 862 }