1 module tagion.hibon.HiBONRecord; 2 3 import std.exception : assumeWontThrow; 4 import std.stdio; 5 import std.traits; 6 import std.typecons : No, Tuple, Yes; 7 import tagion.basic.basic : EnumContinuousSequency, basename; 8 import tagion.hibon.Document : Document; 9 import tagion.hibon.HiBON : HiBON; 10 import tagion.hibon.HiBONBase : ValueT; 11 import tagion.hibon.HiBONException : HiBONRecordException; 12 import tagion.hibon.HiBONJSON; 13 14 alias DocResult = Tuple!(Document.Element.ErrorCode, "error", string, "key"); 15 16 /// Returns: true if struct or class supports toHiBON 17 enum isHiBON(T) = (is(T == struct) || is(T == class)) && hasMember!(T, 18 "toHiBON") && (is(ReturnType!(T.toHiBON) : const(HiBON))); 19 20 /// Returns: true if struct or class supports toDoc 21 enum isHiBONRecord(T) = (is(T == struct) || is(T == class)) && hasMember!(T, 22 "toDoc") && (is(ReturnType!(T.toDoc) : const(Document))); 23 24 enum isHiBONTypeArray(T) = isArray!T && isHiBONRecord!(ForeachType!T); 25 26 /** 27 Used for HiBONRecords which have a recorder type 28 Param: T is the type of the recorder to be checked 29 Param: doc 30 Returns: true if the doc has the correct Recorder type 31 */ 32 @safe 33 bool isRecord(T)(const Document doc) nothrow pure { 34 static if (hasUDA!(T, recordType)) { 35 enum record_type = getUDAs!(T, recordType)[0].name; 36 return doc.hasMember(TYPENAME) && assumeWontThrow(doc[TYPENAME] == record_type); 37 } 38 else { 39 return false; 40 } 41 } 42 43 /** 44 * Gets the doc[TYPENAME] from the document. 45 * Params: 46 * doc = Document containing typename 47 * Returns: TYPENAME or string.init 48 */ 49 @safe 50 string getType(const Document doc) pure { 51 if (doc.hasMember(TYPENAME)) { 52 return doc[TYPENAME].get!string; 53 } 54 return string.init; 55 } 56 57 enum STUB = HiBONPrefix.HASH ~ ""; 58 @safe bool isStub(const Document doc) pure { 59 return !doc.empty && doc.keys.front == STUB; 60 } 61 62 enum HiBONPrefix { 63 HASH = '#', 64 PARAM = '$', 65 } 66 67 @safe 68 bool hasHashKey(T)(T doc) if (is(T : const(HiBON)) || is(T : const(Document))) { 69 return !doc.empty && doc.keys.front[0] is HiBONPrefix.HASH && doc.keys.front != STUB; 70 } 71 72 @safe 73 unittest { 74 import std.array : array; 75 import std.range : iota; 76 77 Document doc; 78 { // Define stub 79 auto h = new HiBON; 80 h[STUB] = iota(ubyte(5)).array.idup; 81 doc = Document(h); 82 } 83 84 assert(isStub(doc)); 85 assert(!hasHashKey(doc)); 86 87 { // Define document with hash key 88 auto h = new HiBON; 89 h["#key"] = "some data"; 90 doc = Document(h); 91 } 92 93 assert(!isStub(doc)); 94 assert(hasHashKey(doc)); 95 } 96 97 template isSpecialKeyType(T) { 98 import std.traits : KeyType, isAssociativeArray, isUnsigned; 99 100 static if (isAssociativeArray!T) { 101 alias KeyT = KeyType!T; 102 enum isSpecialKeyType = !ValueT!(false, void, void).hasType!KeyT; 103 } 104 else { 105 enum isSpecialKeyType = false; 106 } 107 } 108 109 /++ 110 Label use to set the HiBON member name 111 +/ 112 struct label { 113 string name; /// Name of the HiBON member 114 } 115 116 struct optional; /// This flag is set to true if this paramer is optional 117 118 struct exclude; // Exclude the member from the HiBONRecord 119 /++ 120 filter attribute for toHiBON 121 +/ 122 struct filter { 123 string code; /// filter function 124 enum Initialized = filter(q{a !is a.init}); 125 } 126 127 /++ 128 Validates the Document type on construction 129 +/ 130 struct inspect { 131 string code; /// 132 enum Initialized = inspect(q{a !is a.init}); 133 } 134 135 /++ 136 Used to set a member default value of the member is not defined in the Document 137 +/ 138 struct fixed { 139 string code; 140 } 141 142 /++ 143 Sets the HiBONRecord type 144 +/ 145 struct recordType { 146 string name; 147 string code; // This is is mixed after the Document constructor 148 } 149 /++ 150 Gets the label for HiBON member 151 Params: 152 member = is the member alias 153 +/ 154 template GetLabel(alias member) { 155 import std.traits : getUDAs, hasUDA; 156 157 static if (hasUDA!(member, label)) { 158 enum _label = getUDAs!(member, label)[0]; 159 static if (_label.name == VOID) { 160 enum GetLabel = label(basename!(member)); 161 } 162 else { 163 enum GetLabel = _label; 164 } 165 } 166 else { 167 enum GetLabel = label(basename!(member)); 168 } 169 } 170 171 enum TYPENAME = HiBONPrefix.PARAM ~ "@"; 172 enum VOID = "*"; 173 174 mixin template HiBONRecordType() { 175 import std.traits : getUDAs, hasUDA, isIntegral, isUnsigned; 176 import tagion.hibon.Document : Document; 177 import tagion.hibon.HiBONRecord : TYPENAME, recordType; 178 179 alias ThisType = typeof(this); 180 181 static if (hasUDA!(ThisType, recordType)) { 182 alias record_types = getUDAs!(ThisType, recordType); 183 static assert(record_types.length is 1, "Only one recordType UDA allowed"); 184 static if (record_types[0].name.length) { 185 enum type_name = record_types[0].name; 186 import tagion.hibon.HiBONRecord : isRecordT = isRecord; 187 188 alias isRecord = isRecordT!ThisType; 189 version (none) static bool isRecord(const Document doc) nothrow { 190 if (doc.hasMember(TYPENAME)) { 191 return doc[TYPENAME].get!string == type_name; 192 } 193 return false; 194 } 195 } 196 } 197 } 198 199 /++ 200 HiBON Helper template to implement constructor and toHiBON member functions 201 Params: 202 TYPE = is used to set a HiBON record type (TYPENAME) 203 Examples: 204 -------------------- 205 @recodeType("TEST") // Set the HiBONRecord type name 206 struct Test { 207 @label("$X") uint x; // The member in HiBON is "$X" 208 string name; // The member in HiBON is "name" 209 @label("num") int num; // The member in HiBON is "num" and is optional 210 @optional string text; // optional hibon member 211 @exclude bool dummy; // This parameter is not included in the HiBON 212 } 213 -------------------- 214 CTOR = is used for constructor 215 Example: 216 -------------------- 217 struct TestCtor { 218 uint x; 219 HiBONRecord!("TEST2", 220 q{ 221 this(uint x) { 222 this.x=x; 223 } 224 } 225 ); 226 } 227 -------------------- 228 229 +/ 230 231 pragma(msg, "fixme(cbr): The less_than function in this mixin is used for none string key (Should be added to the HiBON spec)"); 232 233 mixin template HiBONRecord(string CTOR = "") { 234 235 import std.traits : getUDAs, hasUDA, getSymbolsByUDA, OriginalType, 236 Unqual, hasMember, isCallable, 237 EnumMembers, ForeachType, isArray, isAssociativeArray, KeyType, ValueType; 238 import std.algorithm.iteration : map; 239 import std.array : array, assocArray, join; 240 import std.format; 241 import std.functional : unaryFun; 242 import std.meta : AliasSeq, staticMap; 243 import std.range : enumerate, iota, lockstep; 244 import std.range.primitives : isInputRange; 245 import std.typecons : Tuple; 246 import tagion.basic.basic : EnumContinuousSequency, basename; 247 248 // import tagion.hibon.HiBONException : check; 249 import tagion.basic.Message : message; 250 import tagion.basic.basic : CastTo, basename; 251 import tagion.basic.tagionexceptions : Check; 252 import tagion.hibon.HiBONException : HiBONRecordException; 253 import tagion.hibon.HiBONRecord : isHiBON, isHiBONRecord, HiBONRecordType, isSpecialKeyType, 254 label, exclude, optional, GetLabel, filter, fixed, inspect, VOID; 255 import tagion.hibon.HiBONBase : TypedefBase; 256 import HiBONRecord = tagion.hibon.HiBONRecord; 257 258 protected alias check = Check!(HiBONRecordException); 259 260 import tagion.hibon.HiBON : HiBON; 261 import tagion.hibon.HiBONJSON : JSONString; 262 263 mixin JSONString; 264 265 mixin HiBONRecordType; 266 alias isRecord = HiBONRecord.isRecord!ThisType; 267 268 enum HAS_TYPE = hasMember!(ThisType, "type_name"); 269 static bool less_than(Key)(Key a, Key b) if (!is(Key : string)) { 270 alias BaseKey = TypedefBase!Key; 271 static if (HiBON.Value.hasType!BaseKey || is(BaseKey == enum)) { 272 return Key(a) < Key(b); 273 } 274 else static if (isHiBONRecord!BaseKey) { 275 return a.toDoc.serialize < b.toDoc.serialize; 276 } 277 else { 278 assert(0, format("Index %s is not supported", Index.stringof)); 279 } 280 } 281 282 @trusted final inout(HiBON) toHiBON() inout { 283 auto hibon = new HiBON; 284 static HiBON toList(L)(L list) { 285 import std.algorithm : sort; 286 import std.algorithm.iteration : map; 287 import std.array : array, byPair; 288 import std.range : refRange; 289 import std.typecons : tuple; 290 291 auto result = new HiBON; 292 alias UnqualL = Unqual!L; 293 alias ElementT = Unqual!(TypedefBase!(ForeachType!L)); 294 static if (isArray!L || isAssociativeArray!L) { 295 static if (isSpecialKeyType!L) { 296 uint list_index; 297 alias Pair = ForeachType!(typeof(list.byPair)); 298 static struct SwapAble { 299 Pair elm; 300 } 301 302 auto range = list.byPair 303 .map!(pair => new SwapAble(pair)) 304 .array 305 .sort!((a, b) => less_than(a.elm.key, b.elm.key)) 306 .map!(a => tuple(a.elm.key, a.elm.value)); 307 } 308 else { 309 auto range = list; 310 } 311 } 312 else { 313 auto range = list.enumerate; 314 } 315 foreach (index, e; range) { 316 void set(Index, Value)(Index key, Value value) { 317 static if (isSpecialKeyType!L) { 318 auto element = new HiBON; 319 alias BaseIndex = TypedefBase!Index; 320 static if (HiBON.Value.hasType!BaseIndex || is(BaseIndex == enum)) { 321 element[0] = Index(key); 322 } 323 else static if (isHiBONRecord!BaseIndex) { 324 element[0] = BaseIndex(key.toDoc); 325 } 326 else { 327 assert(0, format("Index %s is not supported", Index.stringof)); 328 } 329 element[1] = value; 330 result[list_index++] = element; 331 } 332 else { 333 result[index] = value; 334 } 335 } 336 337 static if (HiBON.Value.hasType!ElementT || is(ElementT == enum)) { 338 set(index, e); 339 } 340 else static if (isHiBON!ElementT) { 341 set(index, e.toHiBON); 342 } 343 else static if (isInputRange!ElementT) { 344 set(index, toList(e)); 345 } 346 else { 347 static assert(0, format("Can not convert %s to HiBON", L.stringof)); 348 } 349 } 350 return result; 351 } 352 353 MemberLoop: foreach (i, m; this.tupleof) { 354 static if (__traits(compiles, typeof(m))) { 355 enum default_name = basename!(this.tupleof[i]); 356 enum optional_flag = hasUDA!(this.tupleof[i], optional); 357 enum exclude_flag = hasUDA!(this.tupleof[i], exclude); 358 alias label = GetLabel!(this.tupleof[i]); 359 enum name = label.name; 360 // } 361 // else { 362 // enum name=basename!(this.tupleof[i]); 363 // } 364 static if (hasUDA!(this.tupleof[i], filter)) { 365 alias filters = getUDAs!(this.tupleof[i], filter); 366 static foreach (F; filters) { 367 { 368 alias filterFun = unaryFun!(F.code); 369 if (!filterFun(this.tupleof[i])) { 370 continue MemberLoop; 371 } 372 } 373 } 374 } 375 static assert(name.length > 0, 376 format("Label for %s can not be empty", default_name)); 377 static if (!exclude_flag) { 378 alias MemberT = typeof(m); 379 alias BaseT = TypedefBase!MemberT; 380 alias UnqualT = Unqual!BaseT; 381 // writefln("name=%s BaseT=%s isInputRange!BaseT=%s isInputRange!UnqualT=%s", 382 // name, BaseT.stringof, isInputRange!BaseT, isInputRange!UnqualT); 383 static if (HiBON.Value.hasType!UnqualT) { 384 hibon[name] = cast(BaseT) m; 385 } 386 else static if (isHiBON!BaseT) { 387 hibon[name] = m.toHiBON; 388 } 389 else static if (isHiBONRecord!BaseT) { 390 hibon[name] = m.toDoc; 391 } 392 else static if (is(MemberT == enum)) { 393 hibon[name] = cast(OriginalType!MemberT) m; 394 } 395 else static if (is(BaseT == class) || is(BaseT == struct)) { 396 static if (isInputRange!UnqualT) { 397 alias ElementT = Unqual!(ForeachType!UnqualT); 398 static assert((HiBON.Value.hasType!ElementT) || isHiBON!ElementT, 399 format("The sub element '%s' of type %s is not supported", 400 name, BaseT.stringof)); 401 402 hibon[name] = toList(cast(UnqualT) m); 403 } 404 else { 405 static assert(is(BaseT == HiBON) || is(BaseT : const(Document)), 406 format(`A sub class/struct '%s' of type %s must have a toHiBON or must be ingnored with @exclude UDA tag`, 407 name, BaseT.stringof)); 408 hibon[name] = cast(BaseT) m; 409 } 410 } 411 else static if (isInputRange!UnqualT || isAssociativeArray!UnqualT) { 412 alias BaseU = TypedefBase!(ForeachType!(UnqualT)); 413 hibon[name] = toList(m); 414 } 415 else static if (isIntegral!UnqualT || UnqualT.sizeof <= short.sizeof) { 416 static if (isUnsigned!UnqualT) { 417 hibon[name] = cast(uint) m; 418 } 419 else { 420 hibon[name] = cast(int) m; 421 } 422 } 423 else { 424 static assert(0, format("Convering for member '%s' of type %s is not supported by default", 425 name, MemberT.stringof)); 426 } 427 } 428 } 429 } 430 static if (HAS_TYPE) { 431 hibon[TYPENAME] = type_name; 432 } 433 @nogc @trusted inout(HiBON) result() inout pure nothrow { 434 return cast(inout) hibon; 435 } 436 437 return result; 438 } 439 440 enum NO_DEFAULT_CTOR = (CTOR == "{}"); 441 /++ 442 Constructors must be mixed in or else the default construction is will be removed 443 +/ 444 static if (CTOR.length && !NO_DEFAULT_CTOR) { 445 mixin(CTOR); 446 } 447 448 // import std.traits : FieldNameTuple, Fields; 449 450 template GetKeyName(uint i) { 451 enum default_name = basename!(this.tupleof[i]); 452 static if (hasUDA!(this.tupleof[i], label)) { 453 alias label = GetLabel!(this.tupleof[i]); 454 enum GetKeyName = (label.name == VOID) ? default_name : label.name; 455 } 456 else { 457 enum GetKeyName = default_name; 458 } 459 } 460 461 /++ 462 Returns: 463 The a list of Record keys 464 +/ 465 protected static string[] _keys() pure nothrow { 466 string[] result; 467 alias ThisTuple = typeof(ThisType.tupleof); 468 static foreach (i; 0 .. ThisTuple.length) { 469 result ~= GetKeyName!i; 470 } 471 return result; 472 } 473 474 enum keys = _keys; 475 476 // version(none) { 477 // alias KeyType = Tuple!(string, "key", string, "type"); 478 479 // static auto getKeyType() { 480 // alias ThisTuple = typeof(ThisType.tupleof); 481 482 // } 483 484 // static bool isValid() pure nothrow { 485 486 // return 487 // } 488 // } 489 490 static if (!NO_DEFAULT_CTOR) { 491 @safe this(const HiBON hibon) { 492 this(Document(hibon.serialize)); 493 } 494 495 @safe this(const Document doc) { 496 static if (HAS_TYPE) { 497 string _type = doc[TYPENAME].get!string; 498 check(_type == type_name, format("Wrong %s type %s should be %s", 499 TYPENAME, _type, type_name)); 500 } 501 static if (hasUDA!(ThisType, recordType)) { 502 enum record = getUDAs!(ThisType, recordType)[0]; 503 static if (record.code) { 504 scope (exit) { 505 mixin(record.code); 506 } 507 } 508 } 509 static R toList(R)(const Document doc) { 510 alias MemberU = ForeachType!(R); 511 alias BaseU = TypedefBase!MemberU; 512 static if (isArray!R) { 513 alias UnqualU = Unqual!MemberU; 514 check(doc.isArray, format("Document is expected to be an array")); 515 MemberU[] result; 516 result.length = doc.length; 517 result = doc[].map!(e => e.get!MemberU).array; 518 enum do_foreach = false; 519 } 520 else static if (isSpecialKeyType!R) { 521 R result; 522 enum do_foreach = true; 523 } 524 else static if (isAssociativeArray!R) { 525 alias ValueT = ForeachType!R; 526 alias KeyT = KeyType!R; 527 R result = assocArray( 528 doc.keys.map!(key => key.to!KeyT), 529 doc[].map!(e => e.get!ValueT)); 530 enum do_foreach = false; 531 532 } 533 else { 534 return R(doc); 535 enum do_foreach = false; 536 } 537 static if (do_foreach) { 538 foreach (elm; doc[]) { 539 static if (isSpecialKeyType!R) { 540 const value_doc = elm.get!Document; 541 alias KeyT = KeyType!R; 542 alias BaseKeyT = TypedefBase!KeyT; 543 static if (Document.Value.hasType!BaseKeyT || is(BaseKeyT == enum)) { 544 const key = KeyT(value_doc[0].get!BaseKeyT); 545 } 546 else { 547 auto key = KeyT(value_doc[0].get!BaseKeyT); 548 } 549 const e = value_doc[1]; 550 } 551 else { 552 const e = elm; 553 } 554 static if (Document.Value.hasType!MemberU || is(BaseU == enum)) { 555 auto value = e.get!BaseU; 556 } 557 else static if (Document.Value.hasType!BaseU) { 558 // Special case for Typedef 559 auto value = MemberU(e.get!BaseU); 560 } 561 else { 562 const sub_doc = e.get!Document; 563 static if (is(BaseU == struct)) { 564 auto value = BaseU(sub_doc); 565 } 566 else static if (is(BaseU == class)) { 567 auto value = new BaseU(sub_doc); 568 } 569 else static if (isInputRange!BaseU) { 570 auto value = toList!BaseU(sub_doc); 571 } 572 else { 573 static assert(0, 574 format("Can not convert %s to Document", R.stringof)); 575 } 576 } 577 static if (isAssociativeArray!R) { 578 static if (isSpecialKeyType!R) { 579 result[key] = value; 580 } 581 else { 582 alias ResultKeyType = KeyType!(typeof(result)); 583 result[e.key.to!ResultKeyType] = value; 584 } 585 } 586 else { 587 result[e.index] = value; 588 } 589 } 590 } 591 return result; 592 } 593 594 enum do_valid = hasMember!(ThisType, "valid") 595 && isCallable!(valid) && __traits(compiles, valid(doc)); 596 static if (do_valid) { 597 check(valid(doc), 598 format("Document verification faild for HiBONRecord %s", 599 ThisType.stringof)); 600 } 601 602 enum do_verify = hasMember!(ThisType, "verify") 603 && isCallable!(verify) && __traits(compiles, this.verify()); 604 605 static if (do_verify) { 606 scope (exit) { 607 check(this.verify(), 608 format("Document verification faild for HiBONRecord %s", 609 ThisType.stringof)); 610 } 611 } 612 613 alias ThisTuple = typeof(ThisType.tupleof); 614 ForeachTuple: foreach (i, ref m; this.tupleof) { 615 static if (__traits(compiles, typeof(m))) { 616 enum default_name = basename!(this.tupleof[i]); 617 enum optional_flag = hasUDA!(this.tupleof[i], optional); 618 enum exclude_flag = hasUDA!(this.tupleof[i], exclude); 619 static if (hasUDA!(this.tupleof[i], label)) { 620 alias label = GetLabel!(this.tupleof[i]); 621 enum name = (label.name == VOID) ? default_name : label.name; 622 //enum optional_flag = label.optional || hasUDA!(this.tupleof[i], optional); 623 static if (optional_flag) { 624 if (!doc.hasMember(name)) { 625 continue ForeachTuple; 626 } 627 } 628 static if (HAS_TYPE) { 629 static assert(TYPENAME != label.name, 630 format("Fixed %s is already definded to %s but is redefined for %s.%s", 631 TYPENAME, TYPE, ThisType.stringof, 632 basename!(this.tupleof[i]))); 633 } 634 } 635 else { 636 enum name = default_name; 637 } 638 static assert(name.length > 0, 639 format("Label for %s can not be empty", default_name)); 640 static if (!exclude_flag) { 641 static if (hasUDA!(this.tupleof[i], fixed)) { 642 alias assigns = getUDAs!(this.tupleof[i], fixed); 643 static assert(assigns.length is 1, 644 "Only one fixed UDA allowed per member"); 645 static assert(!optional_flag, "The optional parameter in label can not be used in connection with the fixed attribute"); 646 enum code = format(q{this.tupleof[i]=%s;}, assigns[0].code); 647 if (!doc.hasMember(name)) { 648 mixin(code); 649 continue ForeachTuple; 650 } 651 } 652 enum member_name = this.tupleof[i].stringof; 653 alias MemberT = typeof(m); 654 //alias BaseT = TypedefBase!MemberT; 655 alias BaseT = MemberT; 656 alias UnqualT = Unqual!BaseT; 657 static if (optional_flag) { 658 if (!doc.hasMember(name)) { 659 continue ForeachTuple; 660 } 661 } 662 static if (hasUDA!(this.tupleof[i], inspect)) { 663 alias Inspects = getUDAs!(this.tupleof[i], inspect); 664 scope (exit) { 665 static foreach (F; Inspects) { 666 { 667 alias inspectFun = unaryFun!(F.code); 668 check(inspectFun(m), 669 message("Member %s failed on inspection %s with %s", 670 name, F.code, m)); 671 } 672 } 673 } 674 675 } 676 static if (is(BaseT == enum)) { 677 m = doc[name].get!BaseT; 678 } 679 else static if (Document.isDocTypedef!BaseT) { 680 m = doc[name].get!BaseT; 681 } 682 else static if (Document.Value.hasType!BaseT) { 683 m = doc[name].get!BaseT; 684 } 685 else static if (is(BaseT == struct)) { 686 auto sub_doc = doc[name].get!Document; 687 m = BaseT(sub_doc); 688 } 689 else static if (is(BaseT == class)) { 690 const sub_doc = Document(doc[name].get!Document); 691 m = new BaseT(sub_doc); 692 } 693 else static if (isInputRange!BaseT || isAssociativeArray!BaseT) { 694 Document sub_doc; 695 if (doc.hasMember(name)) { 696 sub_doc = Document(doc[name].get!Document); 697 } 698 m = toList!BaseT(sub_doc); 699 } 700 else static if (isIntegral!BaseT && BaseT.sizeof <= short.sizeof) { 701 static if (isUnsigned!BaseT) { 702 m = cast(BaseT) doc[name].get!uint; 703 } 704 else { 705 m = cast(BaseT) doc[name].get!int; 706 } 707 } 708 else { 709 static assert(0, 710 format("Convering for member '%s' of type %s is not supported by default", 711 name, MemberT.stringof)); 712 713 } 714 } 715 } 716 else { 717 static assert(0, format("Type %s for member %s is not supported", 718 BaseT.stringof, name)); 719 } 720 } 721 } 722 } 723 724 @safe final immutable(ubyte[]) serialize() const { 725 return toHiBON.serialize; 726 } 727 728 @safe final const(Document) toDoc() const { 729 return Document(toHiBON.serialize); 730 } 731 } 732 733 @safe unittest { 734 import std.algorithm.comparison : equal; 735 import std.exception : assertNotThrown, assertThrown; 736 import std.format; 737 import std.meta : AliasSeq; 738 import std.range : lockstep; 739 import std.stdio; 740 import std.traits : OriginalType, Unqual, staticMap; 741 import tagion.hibon.HiBONException : HiBONException, HiBONRecordException; 742 743 @recordType("SIMPEL") static struct Simpel { 744 int s; 745 string text; 746 mixin HiBONRecord!(q{ 747 this(int s, string text) { 748 this.s=s; this.text=text; 749 } 750 }); 751 752 } 753 754 @recordType("SIMPELLABEL") static struct SimpelLabel { 755 @label("$S") int s; 756 @label("TEXT") string text; 757 mixin HiBONRecord!(q{ 758 this(int s, string text) { 759 this.s=s; this.text=text; 760 } 761 }); 762 } 763 764 @recordType("BASIC") static struct BasicData { 765 int i32; 766 uint u32; 767 long i64; 768 ulong u64; 769 float f32; 770 double f64; 771 string text; 772 bool flag; 773 mixin HiBONRecord!(q{this(int i32, 774 uint u32, 775 long i64, 776 ulong u64, 777 float f32, 778 double f64, 779 string text, 780 bool flag) { 781 this.i32=i32; 782 this.u32=u32; 783 this.i64=i64; 784 this.u64=u64; 785 this.f32=f32; 786 this.f64=f64; 787 this.text=text; 788 this.flag=flag; 789 } 790 }); 791 } 792 793 template SimpelOption(string LABEL = "") { 794 @recordType(LABEL) 795 static struct SimpelOption { 796 int not_an_option; 797 @label("s") @optional int s; 798 @optional string text; 799 mixin HiBONRecord!(); 800 } 801 } 802 803 { // Simpel basic type check 804 { 805 const s = Simpel(-42, "some text"); 806 const docS = s.toDoc; 807 // writefln("keys=%s", docS.keys); 808 assert(docS["s"].get!int == -42); 809 assert(docS["text"].get!string == "some text"); 810 assert(docS[TYPENAME].get!string == Simpel.type_name); 811 assert(isRecord!Simpel(docS)); 812 const s_check = Simpel(docS); 813 // const s_check=Simpel(s); 814 assert(s == s_check); 815 assert(s_check.toJSON.toString == format("%j", s_check)); 816 817 assert(isRecord!Simpel(docS)); 818 assert(!isRecord!SimpelLabel(docS)); 819 } 820 821 { 822 const s = SimpelLabel(42, "other text"); 823 const docS = s.toDoc; 824 assert(docS["$S"].get!int == 42); 825 assert(docS["TEXT"].get!string == "other text"); 826 assert(docS[TYPENAME].get!string == SimpelLabel.type_name); 827 assert(isRecord!SimpelLabel(docS)); 828 const s_check = SimpelLabel(docS); 829 830 assert(s == s_check); 831 832 immutable s_imut = SimpelLabel(docS); 833 assert(s_imut == s_check); 834 } 835 836 { 837 const s = BasicData(-42, 42, -42_000_000_000UL, 42_000_000_000L, 838 42.42e-9, -42.42e-300, "text", true); 839 const docS = s.toDoc; 840 841 const s_check = BasicData(docS); 842 843 assert(s == s_check); 844 immutable s_imut = BasicData(docS); 845 assert(s_imut == s_check); 846 } 847 } 848 849 { // Check option 850 alias NoLabel = SimpelOption!(""); 851 alias WithLabel = SimpelOption!("LBL"); 852 853 { // Empty document 854 auto h = new HiBON; 855 const doc = Document(h.serialize); 856 // writefln("docS=\n%s", doc.toJSON(true).toPrettyString); 857 assertThrown!HiBONException(NoLabel(doc)); 858 assertThrown!HiBONException(WithLabel(doc)); 859 } 860 861 { 862 auto h = new HiBON; 863 h["not_an_option"] = 42; 864 const doc = Document(h.serialize); 865 // writefln("docS=\n%s", doc.toJSON(true).toPrettyString); 866 assertNotThrown!Exception(NoLabel(doc)); 867 assertThrown!HiBONException(WithLabel(doc)); 868 } 869 870 { 871 auto h = new HiBON; 872 h["not_an_option"] = 42; 873 h[TYPENAME] = "LBL"; 874 const doc = Document(h.serialize); 875 // writefln("docS=\n%s", doc.toJSON(true).toPrettyString); 876 assertNotThrown!Exception(NoLabel(doc)); 877 assertNotThrown!Exception(WithLabel(doc)); 878 } 879 880 { 881 NoLabel s; 882 s.not_an_option = 42; 883 s.s = 17; 884 s.text = "text!"; 885 const doc = s.toDoc; 886 // writefln("docS=\n%s", doc.toJSON(true).toPrettyString); 887 assertNotThrown!Exception(NoLabel(doc)); 888 assertThrown!HiBONException(WithLabel(doc)); 889 890 auto h = s.toHiBON; 891 h[TYPENAME] = WithLabel.type_name; 892 const doc_label = Document(h.serialize); 893 // writefln("docS=\n%s", doc_label.toJSON(true).toPrettyString); 894 895 const s_label = WithLabel(doc_label); 896 // writefln("docS=\n%s", s_label.toDoc.toJSON(true).toPrettyString); 897 898 const s_new = NoLabel(s_label.toDoc); 899 900 } 901 } 902 903 { // Check verify member 904 template NotBoth(bool FILTER) { 905 @recordType("NotBoth") static struct NotBoth { 906 static if (FILTER) { 907 @optional @(filter.Initialized) int x; 908 @optional @(filter.Initialized) @filter(q{a < 42}) int y; 909 } 910 else { 911 @optional int x; 912 @optional int y; 913 } 914 bool valid(const Document doc) { 915 return doc.hasMember("x") ^ doc.hasMember("y"); 916 } 917 918 mixin HiBONRecord!(q{ 919 this(int x, int y) { 920 this.x=x; this.y=y; 921 } 922 }); 923 } 924 } 925 926 alias NotBothFilter = NotBoth!true; 927 alias NotBothNoFilter = NotBoth!false; 928 929 const s_filter_x = NotBothFilter(11, int.init); 930 const s_filter_y = NotBothFilter(int.init, 13); 931 const s_dont_filter = NotBothFilter(); 932 const s_dont_filter_xy = NotBothFilter(11, 13); 933 934 const s_filter_x_doc = s_filter_x.toDoc; 935 const s_filter_y_doc = s_filter_y.toDoc; 936 const s_dont_filter_doc = s_dont_filter.toDoc; 937 const s_dont_filter_xy_doc = s_dont_filter_xy.toDoc; 938 939 // writefln("docS=\n%s", s_filter_x.toDoc.toJSON(true).toPrettyString); 940 // writefln("docS=\n%s", s_filter_y.toDoc.toJSON(true).toPrettyString); 941 { 942 const check_s_filter_x = NotBothFilter(s_filter_x_doc); 943 assert(check_s_filter_x == s_filter_x); 944 const check_s_filter_y = NotBothFilter(s_filter_y_doc); 945 assert(check_s_filter_y == s_filter_y); 946 } 947 948 { // Test that the .verify throws an HiBONRecordException 949 950 assertThrown!HiBONRecordException(NotBothNoFilter(s_dont_filter_doc)); 951 assertThrown!HiBONRecordException(NotBothNoFilter(s_dont_filter_xy_doc)); 952 assertThrown!HiBONRecordException(NotBothFilter(s_dont_filter_doc)); 953 assertThrown!HiBONRecordException(NotBothFilter(s_dont_filter_xy_doc)); 954 // const x=NotBothFilter(s_dont_filter_xy_doc); 955 // const x=NotBothNoFilter(s_dont_filter_xy_doc) 956 } 957 958 { 959 const s_filter_42 = NotBothFilter(12, 42); 960 const s_filter_42_doc = s_filter_42.toDoc; 961 const s_filter_not_42 = NotBothFilter(s_filter_42.toDoc); 962 assert(s_filter_not_42 == NotBothFilter(12, int.init)); 963 } 964 // const s_no_filter_x=NotBothNoFilter(11, int.init); 965 // const s_no_filter_y=NotBothNoFilter(int.init, 13); 966 // const s_dont_no_filter=NotBothNoFilter(); 967 // const s_dont_no_filter_xy=NotBothNoFilter(11, 13); 968 969 // writefln("docS=\n%s", s_no_filter_x.toDoc.toJSON(true).toPrettyString); 970 // writefln("docS=\n%s", s_no_filter_y.toDoc.toJSON(true).toPrettyString); 971 // writefln("docS=\n%s", s_dont_no_filter.toDoc.toJSON(true).toPrettyString); 972 // writefln("docS=\n%s", s_dont_no_filter_xy.toDoc.toJSON(true).toPrettyString); 973 974 } 975 976 { 977 @safe static struct SuperStruct { 978 Simpel sub; 979 string some_text; 980 mixin HiBONRecord!(q{ 981 this(string some_text, int s, string text) { 982 this.some_text=some_text; 983 sub=Simpel(s, text); 984 } 985 }); 986 } 987 988 const s = SuperStruct("some_text", 42, "text"); 989 const doc = s.toDoc; 990 const s_converted = SuperStruct(doc); 991 assert(s == s_converted); 992 assert(doc.toJSON.toString == format("%j", s_converted)); 993 assert(doc.toJSON.toPrettyString == format("%J", s_converted)); 994 } 995 996 { 997 @safe static class SuperClass { 998 Simpel sub; 999 string class_some_text; 1000 mixin HiBONRecord!(q{ 1001 this(string some_text, int s, string text) @safe { 1002 this.class_some_text=some_text; 1003 sub=Simpel(s, text); 1004 } 1005 }); 1006 } 1007 1008 const s = new SuperClass("some_text", 42, "text"); 1009 const doc = s.toDoc; 1010 const s_converted = new SuperClass(doc); 1011 assert(doc == s_converted.toDoc); 1012 1013 // For some reason SuperClass because is a class format is not @safe 1014 (() @trusted { 1015 assert(doc.toJSON.toString == format("%j", s_converted)); 1016 assert(doc.toJSON.toPrettyString == format("%J", s_converted)); 1017 })(); 1018 } 1019 1020 { 1021 static struct Test { 1022 @inspect(q{a < 42}) @inspect(q{a > 3}) int x; 1023 mixin HiBONRecord; 1024 } 1025 1026 Test s; 1027 s.x = 17; 1028 assertNotThrown!Exception(Test(s.toDoc)); 1029 s.x = 42; 1030 assertThrown!HiBONRecordException(Test(s.toDoc)); 1031 s.x = 1; 1032 assertThrown!HiBONRecordException(Test(s.toDoc)); 1033 1034 } 1035 1036 { // Base type array 1037 static struct Array { 1038 int[] a; 1039 mixin HiBONRecord; 1040 } 1041 1042 Array s; 1043 s.a = [17, 42, 17]; 1044 1045 const doc = s.toDoc; 1046 const result = Array(doc); 1047 assert(s == result); 1048 assert(doc.toJSON.toString == format("%j", result)); 1049 1050 } 1051 1052 { // String array 1053 static struct StringArray { 1054 string[] texts; 1055 mixin HiBONRecord; 1056 } 1057 1058 StringArray s; 1059 s.texts = ["one", "two", "three"]; 1060 1061 const doc = s.toDoc; 1062 const result = StringArray(doc); 1063 assert(s == result); 1064 assert(doc.toJSON.toString == format("%j", result)); 1065 } 1066 1067 { // Element as range 1068 @safe static struct Range(T) { 1069 alias UnqualT = Unqual!T; 1070 protected T[] array; 1071 @nogc this(T[] array) { 1072 this.array = array; 1073 } 1074 1075 @nogc @property nothrow { 1076 const(T) front() const pure { 1077 return array[0]; 1078 } 1079 1080 bool empty() const pure { 1081 return array.length is 0; 1082 } 1083 1084 void popFront() { 1085 if (array.length) { 1086 array = array[1 .. $]; 1087 } 1088 } 1089 } 1090 1091 @trusted this(const Document doc) { 1092 auto result = new UnqualT[doc.length]; 1093 foreach (ref a, e; lockstep(result, doc[])) { 1094 a = e.get!T; 1095 } 1096 array = result; 1097 } 1098 } 1099 1100 @safe auto StructWithRangeTest(T)(T[] array) { 1101 alias R = Range!T; 1102 @safe static struct StructWithRange { 1103 R range; 1104 static assert(isInputRange!R); 1105 mixin HiBONRecord!(q{ 1106 this(T[] array) { 1107 this.range=R(array); 1108 } 1109 }); 1110 } 1111 1112 return StructWithRange(array); 1113 } 1114 1115 { // Simple Range 1116 const(int)[] array = [-42, 3, 17]; 1117 const s = StructWithRangeTest(array); 1118 1119 const doc = s.toDoc; 1120 alias ResultT = typeof(s); 1121 1122 const s_doc = ResultT(doc); 1123 1124 assert(s_doc == s); 1125 assert(doc.toJSON.toString == format("%j", s)); 1126 } 1127 1128 { // Range of structs 1129 Simpel[] simpels; 1130 simpels ~= Simpel(1, "one"); 1131 simpels ~= Simpel(2, "two"); 1132 simpels ~= Simpel(3, "three"); 1133 { 1134 auto s = StructWithRangeTest(simpels); 1135 alias StructWithRange = typeof(s); 1136 { 1137 auto h = new HiBON; 1138 h["s"] = s; 1139 1140 const s_get = h["s"].get!StructWithRange; 1141 assert(s == s_get); 1142 } 1143 } 1144 1145 { 1146 static struct SimpelArray { 1147 Simpel[] array; 1148 mixin HiBONRecord; 1149 } 1150 1151 { // SimpelArray with empty array 1152 SimpelArray s; 1153 auto h = new HiBON; 1154 h["s"] = s; 1155 const s_get = h["s"].get!SimpelArray; 1156 assert(s_get == s); 1157 } 1158 { 1159 SimpelArray s; 1160 s.array = simpels; 1161 auto h = new HiBON; 1162 h["s"] = s; 1163 1164 const s_get = h["s"].get!SimpelArray; 1165 1166 assert(s_get == s); 1167 const s_doc = s_get.toDoc; 1168 1169 const s_array = s_doc["array"].get!(Simpel[]); 1170 assert(equal(s_array, s.array)); 1171 1172 const s_result = SimpelArray(s_doc); 1173 assert(s_result == s); 1174 } 1175 } 1176 } 1177 1178 { // Jagged Array 1179 @safe static struct Jagged { 1180 Simpel[][] y; 1181 mixin HiBONRecord; 1182 } 1183 1184 Simpel[][] ragged = [ 1185 [Simpel(1, "one"), Simpel(2, "one")], 1186 [Simpel(1, "two"), Simpel(2, "two"), Simpel(3, "two")], 1187 [Simpel(1, "three")] 1188 ]; 1189 1190 Jagged jagged; 1191 jagged.y = ragged; 1192 1193 const jagged_doc = jagged.toDoc; 1194 1195 const result = Jagged(jagged_doc); 1196 1197 assert(jagged == result); 1198 1199 assert(jagged_doc.toJSON.toString == format("%j", jagged)); 1200 1201 } 1202 1203 { 1204 @safe static struct Associative { 1205 Simpel[string] a; 1206 mixin HiBONRecord; 1207 } 1208 1209 Associative associative; 1210 associative.a["$one"] = Simpel(1, "one"); 1211 associative.a["$two"] = Simpel(1, "two"); 1212 associative.a["$three"] = Simpel(1, "three"); 1213 1214 // writefln("%J", associative); 1215 1216 const associative_doc = associative.toDoc; 1217 1218 const result = Associative(associative_doc); 1219 (() @trusted { 1220 assert(equal(result.a.keys, associative.a.keys)); 1221 assert(equal(result.a.byValue, associative.a.byValue)); 1222 })(); 1223 1224 assert(associative_doc.toJSON.toString == format("%j", associative)); 1225 1226 } 1227 1228 { // Test of enum 1229 enum Count : uint { 1230 one = 1, 1231 two, 1232 three 1233 } 1234 1235 { // Single enum 1236 static struct CountStruct { 1237 Count count; 1238 mixin HiBONRecord; 1239 } 1240 1241 CountStruct s; 1242 s.count = Count.two; 1243 1244 const s_doc = s.toDoc; 1245 const result = CountStruct(s_doc); 1246 1247 assert(s == result); 1248 assert(s_doc.toJSON.toString == format("%j", result)); 1249 } 1250 1251 { // Array of enum 1252 static struct CountArray { 1253 Count[] count; 1254 mixin HiBONRecord; 1255 } 1256 1257 CountArray s; 1258 s.count = [Count.one, Count.two, Count.three]; 1259 1260 const s_doc = s.toDoc; 1261 const result = CountArray(s_doc); 1262 1263 assert(s == result); 1264 assert(s_doc.toJSON.toString == format("%j", result)); 1265 } 1266 } 1267 1268 { // Test of Typedef array 1269 import std.typecons : Typedef; 1270 1271 alias Text = Typedef!(string, null, "Text"); 1272 1273 // Pubkey is a Typedef 1274 import tagion.crypto.Types : Pubkey; 1275 1276 static struct TextArray { 1277 Text[] texts; 1278 mixin HiBONRecord; 1279 } 1280 1281 TextArray s; 1282 s.texts = [Text("one"), Text("two"), Text("three")]; 1283 1284 const s_doc = s.toDoc; 1285 const result = TextArray(s_doc); 1286 1287 assert(s == result); 1288 assert(s_doc.toJSON.toString == format("%j", result)); 1289 } 1290 1291 } 1292 1293 { // None standard Keys 1294 import std.algorithm : each, map; 1295 import std.algorithm : sort; 1296 import std.algorithm.sorting : isStrictlyMonotonic; 1297 import std.array : array; 1298 import std.range : tee; 1299 import std.stdio; 1300 import std.typecons : Typedef; 1301 import std.typecons : tuple; 1302 import tagion.basic.Types : Buffer; 1303 1304 static void binwrite(Args...)(ubyte[] buf, Args args) @trusted { 1305 import std.bitmanip : write; 1306 1307 write(buf, args); 1308 } 1309 // alias binwrite=assumeTrusted!(bitmanip.write!Buffer); 1310 { // Typedef on HiBON.type is used as key in an associative-array 1311 pragma(msg, "fixme(cbr): make sure that the assoicated array is hash invariant"); 1312 alias Bytes = Typedef!(immutable(ubyte)[], null, "Bytes"); 1313 alias Tabel = int[Bytes]; 1314 static struct StructBytes { 1315 Tabel tabel; 1316 mixin HiBONRecord; 1317 } 1318 1319 static assert(isSpecialKeyType!Tabel); 1320 1321 import std.outbuffer; 1322 1323 Tabel tabel; 1324 auto list = [-17, 117, 3, 17, 42]; 1325 auto buffer = new ubyte[int.sizeof]; 1326 foreach (i; list) { 1327 binwrite(buffer, i, 0); 1328 tabel[Bytes(buffer.idup)] = i; 1329 } 1330 1331 StructBytes s; 1332 s.tabel = tabel; 1333 const s_doc = s.toDoc; 1334 const result = StructBytes(s_doc); 1335 1336 assert( 1337 equal( 1338 list 1339 .map!((i) { binwrite(buffer, i, 0); return tuple(buffer.idup, i); }) 1340 .array 1341 .sort, 1342 s_doc["tabel"] 1343 .get!Document[] 1344 .map!(e => tuple(e.get!Document[0].get!Buffer, e.get!Document[1].get!int)) 1345 )); 1346 assert(s_doc == result.toDoc); 1347 } 1348 1349 { // Typedef of a HiBONRecord is used as key in an associative-array 1350 static struct KeyStruct { 1351 int x; 1352 string text; 1353 mixin HiBONRecord!(q{ 1354 this(int x, string text) { 1355 this.x=x; this.text=text; 1356 } 1357 }); 1358 } 1359 1360 alias Key = Typedef!(KeyStruct, KeyStruct.init, "Key"); 1361 1362 alias Tabel = int[Key]; 1363 1364 static struct StructKeys { 1365 Tabel tabel; 1366 mixin HiBONRecord; 1367 } 1368 1369 Tabel list = [ 1370 Key(KeyStruct(2, "two")): 2, Key(KeyStruct(4, "four")): 4, 1371 Key(KeyStruct(1, "one")): 1, Key(KeyStruct(3, "three")): 3 1372 ]; 1373 1374 StructKeys s; 1375 s.tabel = list; 1376 1377 const s_doc = s.toDoc; 1378 1379 // Checks that the key is ordered in the tabel 1380 assert(s_doc["tabel"].get!Document[].map!( 1381 a => a.get!Document[0].get!Document.serialize).array.isStrictlyMonotonic); 1382 1383 const result = StructKeys(s_doc); 1384 //assert(result == s); 1385 assert(result.toDoc == s.toDoc); 1386 1387 } 1388 } 1389 1390 { // Fixed Attribute 1391 // The fixed atttibute is used set a default i value in case the member was not defined in the Document 1392 static struct FixedStruct { 1393 @label("$x") @filter(q{a != 17}) @fixed(q{-1}) int x; 1394 mixin HiBONRecord; 1395 } 1396 1397 { // No effect 1398 FixedStruct s; 1399 s.x = 42; 1400 const s_doc = s.toDoc; 1401 const result = FixedStruct(s_doc); 1402 assert(result.x is 42); 1403 } 1404 1405 { // Because x=17 is filtered out the fixed -1 value will be set 1406 FixedStruct s; 1407 s.x = 17; 1408 const s_doc = s.toDoc; 1409 const result = FixedStruct(s_doc); 1410 assert(result.x is -1); 1411 } 1412 } 1413 1414 { 1415 static struct ImpliciteTypes { 1416 ushort u_s; 1417 short i_s; 1418 ubyte u_b; 1419 byte i_b; 1420 mixin HiBONRecord; 1421 } 1422 1423 { // 1424 ImpliciteTypes s; 1425 s.u_s = 42_000; 1426 s.i_s = -22_000; 1427 s.u_b = 142; 1428 s.i_b = -42; 1429 1430 const s_doc = s.toDoc; 1431 const result = ImpliciteTypes(s_doc); 1432 assert(result.u_s == 42_000); 1433 assert(result.i_s == -22_000); 1434 assert(result.u_b == 142); 1435 assert(result.i_b == -42); 1436 } 1437 1438 } 1439 1440 /// Associative Array with integral key 1441 { 1442 static struct ArrayKey(Key) { 1443 string[Key] a; 1444 mixin HiBONRecord; 1445 } 1446 1447 { 1448 import std.algorithm.sorting : sort; 1449 import std.array : array, byPair; 1450 1451 ArrayKey!int a_int; 1452 string[int] days = [1: "Monday", 2: "Tuesday", 3: "Wednesday"]; 1453 a_int.a = days; 1454 1455 const a_toDoc = a_int.toDoc; 1456 auto result = ArrayKey!int(a_toDoc); 1457 1458 enum key_sort = q{a.key < b.key}; 1459 1460 assert(equal( 1461 result.a.byPair.array.sort!key_sort, 1462 a_int.a.byPair.array.sort!key_sort) 1463 ); 1464 } 1465 } 1466 } 1467 1468 @safe 1469 unittest { 1470 import tagion.utils.StdTime; 1471 1472 { /// Single time element 1473 static struct Time { 1474 @label("$t") sdt_t time; 1475 mixin HiBONRecord; 1476 } 1477 1478 Time expected_time; 1479 expected_time.time = 12345678; 1480 const doc = expected_time.toDoc; 1481 const result = Time(doc); 1482 assert(expected_time == result); 1483 } 1484 1485 { 1486 static struct Times { 1487 sdt_t[] times; 1488 mixin HiBONRecord; 1489 } 1490 1491 Times expected_times; 1492 expected_times.times = [sdt_t(12345), sdt_t(23456), sdt_t(1345)]; 1493 const doc = expected_times.toDoc; 1494 const result = Times(doc); 1495 assert(expected_times == result); 1496 } 1497 } 1498 1499 /// 1500 @safe 1501 unittest { /// Reseved keys and types 1502 1503 { /// Check for reseved HiBON types 1504 @recordType("$@") 1505 static struct S { 1506 int x; 1507 mixin HiBONRecord; 1508 } 1509 1510 S s; 1511 const doc = s.toDoc; 1512 assert(doc.valid is Document.Element.ErrorCode.RESERVED_HIBON_TYPE); 1513 } 1514 { /// Check for reseved keys 1515 static struct S { 1516 @label("$@x") int x; 1517 mixin HiBONRecord; 1518 } 1519 1520 S s; 1521 const doc = s.toDoc; 1522 assert(doc.valid is Document.Element.ErrorCode.RESERVED_KEY); 1523 } 1524 1525 }