1 /// \file HiBONBase.d 2 3 module tagion.betterC.hibon.HiBONBase; 4 5 @nogc: 6 7 import std.meta : AliasSeq; 8 import std.range.primitives : isInputRange; 9 import std.traits : FieldNameTuple, Unqual, getUDAs, hasUDA, isBasicType, isIntegral, isNumeric, isSomeString, isType; 10 11 version (WebAssembly) { 12 pragma(msg, "WebAssembler"); 13 } 14 else { 15 import core.stdc.stdio; 16 } 17 18 import tagion.betterC.hibon.BigNumber; 19 import tagion.betterC.hibon.Document; 20 import tagion.betterC.hibon.HiBON; 21 import tagion.betterC.utils.Bailout; 22 import tagion.betterC.utils.Basic; 23 import tagion.betterC.utils.BinBuffer; 24 import tagion.betterC.utils.Memory; 25 import tagion.betterC.utils.Text; 26 import tagion.betterC.utils.sdt; 27 import LEB128 = tagion.betterC.utils.LEB128; 28 29 enum HIBON_VERSION = 0; 30 31 /** 32 * HiBON Type codes 33 */ 34 enum Type : ubyte { 35 NONE = 0x00, /// End Of Document 36 FLOAT64 = 0x01, /// Floating point 37 STRING = 0x02, /// UTF8 STRING 38 DOCUMENT = 0x03, /// Embedded document (Both Object and Documents) 39 BINARY = 0x05, /// Binary data 40 41 BOOLEAN = 0x08, /// Boolean - true or false 42 TIME = 0x09, /// Standard Time counted as the total 100nsecs from midnight, January 1st, 1 A.D. UTC. 43 INT32 = 0x10, /// 32-bit integer 44 INT64 = 0x12, /// 64-bit integer, 45 // FLOAT128 = 0x13, /// Decimal 128bits 46 BIGINT = 0x1B, /// Signed Bigint 47 48 UINT32 = 0x20, /// 32 bit unsigend integer 49 FLOAT32 = 0x21, /// 32 bit Float 50 UINT64 = 0x22, /// 64 bit unsigned integer 51 HASHDOC = 0x23, /// Hash point to documement, public key or signature 52 VER = 0x3F, /// Version field 53 DEFINED_NATIVE = 0x40, /// Reserved as a definition tag it's for Native types 54 NATIVE_DOCUMENT = DEFINED_NATIVE | 0x3e, /// This type is only used as an internal represention (Document type) 55 56 DEFINED_ARRAY = 0x80, /// Indicated an Intrinsic array types 57 /// Native types is only used inside the BSON object 58 NATIVE_HIBON_ARRAY = DEFINED_ARRAY | DEFINED_NATIVE | DOCUMENT, 59 /// Represetents (HISON[]) is convert to an ARRAY of DOCUMENT's 60 NATIVE_DOCUMENT_ARRAY = DEFINED_ARRAY | DEFINED_NATIVE | NATIVE_DOCUMENT, 61 /// Represetents (Document[]) is convert to an ARRAY of DOCUMENT's 62 NATIVE_STRING_ARRAY = DEFINED_ARRAY | DEFINED_NATIVE | STRING, /// Represetents (string[]) is convert to an ARRAY of string's 63 } 64 65 struct DataBlock { 66 @nogc: 67 protected { 68 uint _type; 69 immutable(ubyte)[] _data; 70 } 71 @property uint type() const pure nothrow { 72 return _type; 73 } 74 75 @property immutable(ubyte[]) data() const pure nothrow { 76 return _data; 77 } 78 79 this(const DataBlock x) { 80 _type = x._type; 81 _data = x._data; 82 } 83 84 this(const uint type, immutable(ubyte[]) data) { 85 _type = type; 86 _data = data; 87 } 88 89 this(immutable(ubyte[]) data) { 90 const leb128 = LEB128.decode!uint(data); 91 _type = leb128.value; 92 this._data = data[leb128.size .. $]; 93 } 94 95 void serialize(ref BinBuffer buffer) const { 96 size_t index; 97 LEB128.encode(buffer, _type); 98 buffer.write(_data); 99 } 100 101 @property size_t size() pure const { 102 return LEB128.calc_size(_type) + _data.length; 103 } 104 } 105 106 //alias HashDoc = DataBlock; //!(Type.HASHDOC); 107 108 enum isDataBlock(T) = is(T : const(DataBlock)); 109 110 version (none) struct Key { 111 @nogc: 112 enum KeyType { 113 NONE, 114 DATA, 115 TEXT, 116 INDEX 117 } 118 119 protected { 120 union { 121 const(ubyte[]) data; 122 Text text; 123 uint index; 124 } 125 126 KeyType key_type; 127 } 128 129 this(const(char[]) key) { 130 text = Text(key); 131 key_type = KeyType.TEXT; 132 } 133 134 this(const uint index) { 135 this.index = index; 136 key_type = KeyType.INDEX; 137 } 138 139 this(const(ubyte[]) data) { 140 this.data = data; 141 key_type = KeyType.DATA; 142 } 143 144 ~this() { 145 dispose; 146 } 147 148 size_t size() const pure { 149 with (KeyType) { 150 final switch (key_type) { 151 case NONE: 152 break; 153 case DATA: 154 if (data[0] is 0) { 155 return ubyte.sizeof + LEB128.calc_size(data[1 .. $]); 156 } 157 break; 158 case TEXT: 159 const leb128_len = LEB128.encode!uint(data); 160 return leb128_len.size + leb128_len.value; 161 break; 162 case INDEX: 163 return ubyte.sizeof + LEB128.calc_size(index); 164 } 165 } 166 assert(0); 167 } 168 169 void dispose() { 170 if (key_type is KeyType.DATA) { 171 text.dispose; 172 } 173 } 174 175 void serialize(ref BinBuffer bin) const { 176 with (KeyType) { 177 final switch (key_type) { 178 case NONE: 179 break; 180 case DATA: 181 bin.write(data); 182 break; 183 case TEXT: 184 bin.write(text.serialize); 185 break; 186 case INDEX: 187 LEB128.encode(bin, index); 188 } 189 } 190 assert(0); 191 } 192 193 bool isIndex() const pure { 194 with (KeyType) { 195 final switch (key_type) { 196 case DATA: 197 return (data[0] is 0); 198 case TEXT: 199 size_t dummy; 200 return is_index(text.serialize, dummy); 201 case INDEX: 202 return true; 203 case NONE: 204 return false; 205 } 206 } 207 assert(0); 208 } 209 210 T to(T)() const if (is(T : const(char)[]) || is(T == uint)) { 211 with (KeyType) { 212 final switch (key_type) { 213 case DATA: 214 if (data[0] is 0) { 215 static if (is(T == uint)) { 216 return LEB128.decode!uint(data[1 .. $]).value; 217 } 218 } 219 else { 220 static if (is(T : const(char)[])) { 221 const leb128_len = LEB128.decode!uint(data); 222 return (cast(immutable(char)*) data.ptr)[leb128_len.size .. leb128_len.size + leb128_len 223 .value]; 224 } 225 } 226 break; 227 case TEXT: 228 static if (is(T : const(char)[])) { 229 return text.serialize; 230 } 231 else { 232 uint key_index; 233 if (is_index(text.serialize, key_index)) { 234 return key_index; 235 } 236 } 237 break; 238 case INDEX: 239 static if (is(T == uint)) { 240 return index; 241 } 242 case NONE: 243 244 } 245 assert(0); 246 } 247 } 248 249 int opCmp(const(char[]) b) const pure { 250 return key_compare(data, b); 251 } 252 253 int opCmp(ref const Key b) const pure { 254 return opCmp(b.data); 255 } 256 257 int opCmp(const(Key*) b) const pure { 258 return opCmp(b.data); 259 } 260 261 bool opEquals(T)(T b) const pure { 262 return opCmp(b) == 0; 263 } 264 265 } 266 267 /** 268 * @return true if the type is a internal native HiBON type 269 */ 270 bool isNative(Type type) pure nothrow { 271 with (Type) { 272 return ((type & DEFINED_NATIVE) !is 0) && (type !is DEFINED_NATIVE); 273 } 274 } 275 276 /** 277 Returns: 278 true if the type is a internal native array HiBON type 279 */ 280 // bool isNativeArray(Type type) pure nothrow { 281 // with(Type) { 282 // return ((type & DEFINED_ARRAY) !is 0) && (isNative(type)); 283 // } 284 // } 285 286 /** 287 * @return true if the type is a HiBON data array (This is not the same as HiBON.isArray) 288 */ 289 bool isArray(Type type) pure nothrow { 290 with (Type) { 291 return ((type & DEFINED_ARRAY) !is 0) && (type !is DEFINED_ARRAY) && (!isNative(type)); 292 } 293 } 294 295 mixin(Init_HiBON_Types!("__gshared immutable hibon_types=", 0)); 296 297 /** 298 * @return true if the type is a valid HiBONRecord excluding narive types 299 */ 300 bool isHiBONBaseType(Type type) { 301 return hibon_types[type]; 302 } 303 304 bool isDataBlock(Type type) pure nothrow { 305 with (Type) { 306 return (type is HASHDOC); 307 } 308 } 309 310 template Init_HiBON_Types(string text, uint i) { 311 static if (i is ubyte.max + 1) { 312 enum Init_HiBON_Types = text ~ "];"; 313 } 314 else { 315 enum start_bracket = (i is 0) ? "[" : ""; 316 enum E = cast(Type) i; 317 enum flag = (!isNative(E) && (E !is Type.NONE) && (E !is Type.VER) && ( 318 E !is Type.DEFINED_ARRAY) && (E !is Type 319 .DEFINED_NATIVE)); 320 enum Init_HiBON_Types = Init_HiBON_Types!(text ~ start_bracket ~ flag.stringof ~ ",", i + 1); 321 } 322 } 323 324 version (none) { 325 326 enum isBasicValueType(T) = isBasicType!T || is(T : decimal_t); 327 } 328 /** 329 * HiBON Generic value used by the HiBON class and the Document struct 330 */ 331 //@safe 332 union ValueT(bool NATIVE = false, HiBON, Document) { 333 @nogc: 334 @Type(Type.FLOAT32) float float32; 335 @Type(Type.FLOAT64) double float64; 336 // @Type(Type.FLOAT128) decimal_t float128; 337 @Type(Type.STRING) string text; 338 @Type(Type.BOOLEAN) bool boolean; 339 // @Type(Type.LIST) 340 static if (!is(HiBON == void)) { 341 @Type(Type.DOCUMENT) HiBON document; 342 void dispose() { 343 version (WebAssembly) { 344 } 345 else { 346 printf("VALUE Dispose\n"); 347 } 348 } 349 } 350 else static if (!is(Document == void)) { 351 @Type(Type.DOCUMENT) Document document; 352 } 353 @Type(Type.TIME) sdt_t date; 354 @Type(Type.INT32) int int32; 355 @Type(Type.INT64) long int64; 356 @Type(Type.UINT32) uint uint32; 357 @Type(Type.UINT64) ulong uint64; 358 // @Type(Type.BIGINT) BigNumber bigint; 359 // @Type(Type.HASHDOC) DataBlock hashdoc; 360 @Type(Type.BIGINT) BigNumber bigint; 361 @Type(Type.HASHDOC) DataBlock hashdoc; 362 363 static if (!is(Document == void)) { 364 @Type(Type.NATIVE_DOCUMENT) Document native_document; 365 } 366 @Type(Type.BINARY) immutable(ubyte)[] binary; 367 static if (NATIVE) { 368 @Type(Type.NATIVE_HIBON_ARRAY) HiBON[] native_hibon_array; 369 @Type(Type.NATIVE_DOCUMENT_ARRAY) Document[] native_document_array; 370 @Type(Type.NATIVE_STRING_ARRAY) string[] native_string_array; 371 372 } 373 alias NativeValueDataTypes = AliasSeq!(); 374 /** 375 * @return the value as HiBON type E 376 */ 377 @trusted @nogc auto by(Type type)() pure const { 378 import std.format; 379 380 static foreach (i, name; FieldNameTuple!ValueT) { 381 { 382 enum member_code = format(q{alias member = ValueT.%s;}, name); 383 mixin(member_code); 384 enum MemberType = getUDAs!(member, Type)[0]; 385 alias MemberT = typeof(member); 386 enum valid_value = !((MemberType is Type.NONE) || (!NATIVE 387 && isOneOf!(MemberT, NativeValueDataTypes))); 388 389 static if (type is MemberType) { 390 static assert(valid_value, format("The type %s named ValueT.%s is not valid value", type, name)); 391 return this.tupleof[i]; 392 } 393 } 394 } 395 assert(0); 396 } 397 398 protected template GetType(T, TList...) { 399 static if (TList.length is 0) { 400 enum GetType = Type.NONE; 401 } 402 else { 403 enum name = TList[0]; 404 enum member_code = "alias member=ValueT." ~ name ~ ";"; 405 mixin(member_code); 406 static if (__traits(compiles, typeof(member)) && hasUDA!(member, Type)) { 407 enum MemberType = getUDAs!(member, Type)[0]; 408 alias MemberT = typeof(member); 409 static if ((MemberType is Type.TIME) && is(T == sdt_t)) { 410 enum GetType = MemberType; 411 } 412 else static if (is(T == MemberT)) { 413 enum GetType = MemberType; 414 } 415 else { 416 enum GetType = GetType!(T, TList[1 .. $]); 417 } 418 } 419 else { 420 enum GetType = GetType!(T, TList[1 .. $]); 421 } 422 } 423 } 424 425 /** 426 * convert the T to a HiBON-Type 427 */ 428 enum asType(T) = GetType!(Unqual!T, FieldNameTuple!(ValueT)); 429 430 /** 431 is true if the type T is support by the HiBON 432 */ 433 enum hasType(T) = asType!T !is Type.NONE; 434 435 static if (!is(Document == void) && is(HiBON == void)) { 436 this(Document doc) { 437 document = doc; 438 } 439 } 440 441 static if (!is(Document == void) && !is(HiBON == void)) { 442 this(Document doc) { 443 native_document = doc; 444 } 445 } 446 447 /** 448 * Construct a Value of the type T 449 */ 450 this(T)(T x) if (isOneOf!(Unqual!T, typeof(this.tupleof)) && !is(T == struct)) { 451 alias MutableT = Unqual!T; 452 static foreach (m; __traits(allMembers, ValueT)) { 453 static if (is(typeof(__traits(getMember, this, m)) == MutableT)) { 454 enum code = "alias member=ValueT." ~ m ~ ";"; 455 mixin(code); 456 static if (hasUDA!(member, Type)) { 457 alias MemberT = typeof(member); 458 static if (is(MutableT == MemberT)) { 459 __traits(getMember, this, m) = x; 460 return; 461 } 462 } 463 } 464 } 465 assert(0, T.stringof ~ " is not supported"); 466 } 467 468 /** 469 * Constructs a Value of the type BigNumber 470 */ 471 this(const BigNumber big) pure { 472 bigint = cast(BigNumber) big; 473 } 474 475 this(DataBlock datablock) pure { 476 hashdoc = datablock; 477 } 478 479 @trusted this(const sdt_t x) pure { 480 date = x; 481 } 482 483 /** 484 * Assign the value to x 485 * @param x = value to be assigned 486 */ 487 void opAssign(T)(T x) if (isOneOf!(T, typeof(this.tupleof))) { 488 alias UnqualT = Unqual!T; 489 static foreach (m; __traits(allMembers, ValueT)) { 490 static if (is(typeof(__traits(getMember, this, m)) == T)) { 491 static if ((is(T == struct) || is(T == class)) && !__traits(compiles, __traits(getMember, this, m) = x)) { 492 enum code = "alias member=ValueT." ~ m ~ ";"; 493 mixin(code); 494 enum MemberType = getUDAs!(member, Type)[0]; 495 static assert(MemberType !is Type.NONE, T.stringof ~ " is not supported"); 496 x.copy(__traits(getMember, this, m)); 497 } 498 else { 499 __traits(getMember, this, m) = cast(UnqualT) x; 500 } 501 } 502 } 503 } 504 505 // /** 506 // Assign of none standard HiBON types. 507 // This function will cast to type has the best match to he parameter x 508 // Params: 509 // x = sign value 510 // */ 511 // void opAssign(T)(T x) if (!isOneOf!(T, typeof(this.tupleof))) { 512 // alias UnqualT=Unqual!T; 513 // alias CastT=castTo!(UnqualT, CastTypes); 514 // static assert(is(CastT==void), "Type "~T.stringof~" not supported"); 515 // alias E=asType!UnqualT; 516 // opAssing(cast(CastT)x); 517 // } 518 519 /** 520 * Convert a HiBON Type to a D-type 521 */ 522 alias TypeT(Type aType) = typeof(by!aType()); 523 524 /** 525 * @return the size on bytes of the value as a HiBON type E 526 */ 527 uint size(Type E)() const pure nothrow { 528 static if (isHiBONBaseType(E)) { 529 alias T = TypeT!E; 530 static if (isBasicValueType!T || (E is Type.UTC)) { 531 return T.sizeof; 532 } 533 else static if (is(T : U[], U) && isBasicValueType!U) { 534 return cast(uint)(by!(E).length * U.sizeof); 535 } 536 else { 537 static assert(0, "Type " ~ E.stringof ~ " of " ~ T.stringof ~ " is not defined"); 538 } 539 } 540 else { 541 static assert(0, "Illegal type " ~ E.stringof); 542 } 543 } 544 545 } 546 547 // unittest { 548 // alias Value = ValueT!(false, void, void); 549 // Value test; 550 // with(Type) { 551 // test=Value(int(-42)); assert(test.by!INT32 == -42); 552 // test=Value(long(-42)); assert(test.by!INT64 == -42); 553 // test=Value(uint(42)); assert(test.by!UINT32 == 42); 554 // test=Value(ulong(42)); assert(test.by!UINT64 == 42); 555 // test=Value(float(42.42)); assert(test.by!FLOAT32 == float(42.42)); 556 // test=Value(double(17.42)); assert(test.by!FLOAT64 == double(17.42)); 557 // sdt_t time=1001; 558 // test=Value(time); assert(test.by!TIME == time); 559 // test=Value("Hello"); assert(test.by!STRING == "Hello"); 560 // } 561 // } 562 563 // unittest { 564 // import std.typecons; 565 // alias Value = ValueT!(false, void, void); 566 567 // { // Check invalid type 568 // Value value; 569 // static assert(!__traits(compiles, value='x')); 570 // } 571 572 // { // Simple data type 573 // auto test_tabel=tuple( 574 // float(-1.23), double(2.34), "Text", true, ulong(0x1234_5678_9ABC_DEF0), 575 // int(-42), uint(42), long(-0x1234_5678_9ABC_DEF0) 576 // ); 577 // foreach(i, t; test_tabel) { 578 // Value v; 579 // v=test_tabel[i]; 580 // alias U = test_tabel.Types[i]; 581 // enum E = Value.asType!U; 582 // assert(test_tabel[i] == v.by!E); 583 // } 584 // } 585 586 // version(none) 587 // { // utc test, 588 // static assert(Value.asType!sdt_t is Type.TIME); 589 // sdt_t time = 1234; 590 // Value v; 591 // v = time; 592 // assert(v.by!(Type.TIME) == 1234); 593 // alias U = Value.TypeT!(Type.TIME); 594 // static assert(is(U == const sdt_t)); 595 // static assert(!is(U == const ulong)); 596 // } 597 598 // } 599 600 /** 601 * Converts from a text to a index 602 * @param a = the string to be converted to an index 603 * @param result = index value 604 * @return true if a is an index 605 */ 606 // memcpy(return void* s1, scope const void* s2, size_t n); 607 bool is_index(const(char[]) a, out uint result) pure { 608 enum MAX_UINT_SIZE = uint.max.stringof.length; 609 if (a.length <= MAX_UINT_SIZE) { 610 if ((a[0] is '0') && (a.length > 1)) { 611 return false; 612 } 613 foreach (c; a) { 614 if ((c < '0') || (c > '9')) { 615 return false; 616 } 617 } 618 immutable number = a.to_ulong; 619 if (number <= uint.max) { 620 result = cast(uint) number; 621 return true; 622 } 623 } 624 return false; 625 } 626 627 ulong to_ulong(const(char[]) num) pure { 628 ulong result; 629 foreach (a; num) { 630 result *= 10; 631 result += (a - '0'); 632 } 633 return result; 634 } 635 636 uint to_uint(string num) pure { 637 ulong result = to_ulong(num); 638 // .check(result <= uint.max, "Bad uint overflow"); 639 return cast(uint) result; 640 } 641 642 /** 643 * Check if all the keys in range is indices and are consecutive 644 * @return true if keys is the indices of an HiBON array 645 */ 646 version (none) bool isArray(R)(R keys) { 647 bool check_array_index(const uint previous_index) { 648 if (!keys.empty) { 649 uint current_index; 650 if (is_index(keys.front, current_index)) { 651 if (previous_index + 1 is current_index) { 652 keys.popFront; 653 return check_array_index(current_index); 654 } 655 } 656 return false; 657 } 658 return true; 659 } 660 661 if (!keys.empty) { 662 uint previous_index = uint.max; 663 if (is_index(keys.front, previous_index) && (previous_index is 0)) { 664 keys.popFront; 665 return check_array_index(previous_index); 666 } 667 } 668 return false; 669 } 670 671 // /// 672 // unittest { // check is_index 673 // uint index; 674 // assert(is_index("0", index)); 675 // assert(index is 0); 676 // assert(!is_index("-1", index)); 677 678 // assert(is_index(uint.max.stringof[0..$-1], index)); 679 // assert(index is uint.max); 680 681 // enum overflow=((cast(ulong)uint.max)+1); 682 // assert(!is_index(overflow.stringof, index)); 683 684 // assert(is_index("42", index)); 685 // assert(index is 42); 686 687 // assert(!is_index("0x0", index)); 688 // assert(!is_index("00", index)); 689 // assert(!is_index("01", index)); 690 // } 691 692 /** 693 * This function decides the order of the HiBON keys 694 */ 695 int key_compare(const(char[]) a, const(char[]) b) pure 696 in { 697 assert(a.length > 0); 698 assert(b.length > 0); 699 } 700 do { 701 int res = 1; 702 uint a_index; 703 uint b_index; 704 if (is_index(a, a_index) && is_index(b, b_index)) { 705 if (a_index < b_index) { 706 res = -1; 707 } 708 else if (a_index == b_index) { 709 res = 0; 710 } 711 res = 1; 712 } 713 if (a.length == b.length) { 714 res = 0; 715 foreach (i, elem; a) { 716 if (elem != b[i]) { 717 res = 1; 718 break; 719 } 720 } 721 } 722 else if (a < b) { 723 res = -1; 724 } 725 return res; 726 } 727 728 /** 729 * Checks if the keys in the range is ordred 730 * @return true if all keys in the range is ordered 731 */ 732 bool is_key_ordered(R)(R range) if (isInputRange!R) { 733 string prev_key; 734 while (!range.empty) { 735 if ((prev_key.length == 0) || (key_compare(prev_key, range.front) < 0)) { 736 prev_key = range.front; 737 range.popFront; 738 } 739 else { 740 return false; 741 } 742 } 743 return true; 744 } 745 746 // /// 747 // unittest { // Check less_than 748 // assert(key_compare("a", "b") < 0); 749 // assert(key_compare("0", "1") < 0); 750 // assert(key_compare("00", "0") > 0); 751 // assert(key_compare("0", "abe") < 0); 752 // assert(key_compare("42", "abe") < 0); 753 // assert(key_compare("42", "17") > 0); 754 // assert(key_compare("42", "42") == 0); 755 // assert(key_compare("abc", "abc") == 0); 756 // } 757 758 /** 759 * @return true if the key is a valid HiBON key 760 */ 761 @safe bool is_key_valid(const(char[]) a) pure nothrow { 762 enum : char { 763 SPACE = 0x20, 764 DEL = 0x7F, 765 DOUBLE_QUOTE = 34, 766 QUOTE = 39, 767 BACK_QUOTE = 0x60 768 } 769 if (a.length > 0) { 770 foreach (c; a) { 771 // Chars between SPACE and DEL is valid 772 // except for " ' ` is not valid 773 if ((c <= SPACE) || (c >= DEL) || 774 (c == DOUBLE_QUOTE) || (c == QUOTE) || 775 (c == BACK_QUOTE)) { 776 return false; 777 } 778 } 779 return true; 780 } 781 return false; 782 } 783 784 /// 785 // unittest { // Check is_key_valid 786 // assert(!is_key_valid("")); 787 // string text=" "; // SPACE 788 // assert(!is_key_valid(text)); 789 // text="\x80"; // Only simple ASCII 790 // assert(!is_key_valid(text)); 791 // text=`"`; // Double quote 792 // assert(!is_key_valid(text)); 793 // text="'"; // Sigle quote 794 // assert(!is_key_valid(text)); 795 // text="`"; // Back quote 796 // assert(!is_key_valid(text)); 797 // text="\0"; 798 // assert(!is_key_valid(text)); 799 800 // assert(is_key_valid("abc")); 801 // assert(is_key_valid("42")); 802 803 // text=""; 804 // char[ubyte.max+1] max_key_size; 805 // foreach(ref a; max_key_size) { 806 // a='a'; 807 // } 808 // assert(is_key_valid(max_key_size[0..$-1])); 809 // assert(is_key_valid(max_key_size)); 810 // } 811 812 template isOneOf(T, TList...) { 813 static if (TList.length == 0) { 814 enum isOneOf = false; 815 } 816 else static if (is(T == TList[0])) { 817 enum isOneOf = true; 818 } 819 else { 820 alias isOneOf = isOneOf!(T, TList[1 .. $]); 821 } 822 }