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 }