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 }